holosphere 1.1.20 → 2.0.0-alpha1

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 (147) hide show
  1. package/.env.example +36 -0
  2. package/.eslintrc.json +16 -0
  3. package/.prettierrc.json +7 -0
  4. package/LICENSE +162 -38
  5. package/README.md +483 -367
  6. package/bin/holosphere-activitypub.js +158 -0
  7. package/cleanup-test-data.js +204 -0
  8. package/examples/demo.html +1333 -0
  9. package/examples/example-bot.js +197 -0
  10. package/package.json +47 -87
  11. package/scripts/check-bundle-size.js +54 -0
  12. package/scripts/check-quest-ids.js +77 -0
  13. package/scripts/import-holons.js +578 -0
  14. package/scripts/publish-to-relay.js +101 -0
  15. package/scripts/read-example.js +186 -0
  16. package/scripts/relay-diagnostic.js +59 -0
  17. package/scripts/relay-example.js +179 -0
  18. package/scripts/resync-to-relay.js +245 -0
  19. package/scripts/revert-import.js +196 -0
  20. package/scripts/test-hybrid-mode.js +108 -0
  21. package/scripts/test-local-storage.js +63 -0
  22. package/scripts/test-nostr-direct.js +55 -0
  23. package/scripts/test-read-data.js +45 -0
  24. package/scripts/test-write-read.js +63 -0
  25. package/scripts/verify-import.js +95 -0
  26. package/scripts/verify-relay-data.js +139 -0
  27. package/src/ai/aggregation.js +319 -0
  28. package/src/ai/breakdown.js +511 -0
  29. package/src/ai/classifier.js +217 -0
  30. package/src/ai/council.js +228 -0
  31. package/src/ai/embeddings.js +279 -0
  32. package/src/ai/federation-ai.js +324 -0
  33. package/src/ai/h3-ai.js +955 -0
  34. package/src/ai/index.js +112 -0
  35. package/src/ai/json-ops.js +225 -0
  36. package/src/ai/llm-service.js +205 -0
  37. package/src/ai/nl-query.js +223 -0
  38. package/src/ai/relationships.js +353 -0
  39. package/src/ai/schema-extractor.js +218 -0
  40. package/src/ai/spatial.js +293 -0
  41. package/src/ai/tts.js +194 -0
  42. package/src/content/social-protocols.js +168 -0
  43. package/src/core/holosphere.js +273 -0
  44. package/src/crypto/secp256k1.js +259 -0
  45. package/src/federation/discovery.js +334 -0
  46. package/src/federation/hologram.js +1042 -0
  47. package/src/federation/registry.js +386 -0
  48. package/src/hierarchical/upcast.js +110 -0
  49. package/src/index.js +2669 -0
  50. package/src/schema/validator.js +91 -0
  51. package/src/spatial/h3-operations.js +110 -0
  52. package/src/storage/backend-factory.js +125 -0
  53. package/src/storage/backend-interface.js +142 -0
  54. package/src/storage/backends/activitypub/server.js +653 -0
  55. package/src/storage/backends/activitypub-backend.js +272 -0
  56. package/src/storage/backends/gundb-backend.js +233 -0
  57. package/src/storage/backends/nostr-backend.js +136 -0
  58. package/src/storage/filesystem-storage-browser.js +41 -0
  59. package/src/storage/filesystem-storage.js +138 -0
  60. package/src/storage/global-tables.js +81 -0
  61. package/src/storage/gun-async.js +281 -0
  62. package/src/storage/gun-wrapper.js +221 -0
  63. package/src/storage/indexeddb-storage.js +122 -0
  64. package/src/storage/key-storage-simple.js +76 -0
  65. package/src/storage/key-storage.js +136 -0
  66. package/src/storage/memory-storage.js +59 -0
  67. package/src/storage/migration.js +338 -0
  68. package/src/storage/nostr-async.js +811 -0
  69. package/src/storage/nostr-client.js +939 -0
  70. package/src/storage/nostr-wrapper.js +211 -0
  71. package/src/storage/outbox-queue.js +208 -0
  72. package/src/storage/persistent-storage.js +109 -0
  73. package/src/storage/sync-service.js +164 -0
  74. package/src/subscriptions/manager.js +142 -0
  75. package/test-ai-real-api.js +202 -0
  76. package/tests/unit/ai/aggregation.test.js +295 -0
  77. package/tests/unit/ai/breakdown.test.js +446 -0
  78. package/tests/unit/ai/classifier.test.js +294 -0
  79. package/tests/unit/ai/council.test.js +262 -0
  80. package/tests/unit/ai/embeddings.test.js +384 -0
  81. package/tests/unit/ai/federation-ai.test.js +344 -0
  82. package/tests/unit/ai/h3-ai.test.js +458 -0
  83. package/tests/unit/ai/index.test.js +304 -0
  84. package/tests/unit/ai/json-ops.test.js +307 -0
  85. package/tests/unit/ai/llm-service.test.js +390 -0
  86. package/tests/unit/ai/nl-query.test.js +383 -0
  87. package/tests/unit/ai/relationships.test.js +311 -0
  88. package/tests/unit/ai/schema-extractor.test.js +384 -0
  89. package/tests/unit/ai/spatial.test.js +279 -0
  90. package/tests/unit/ai/tts.test.js +279 -0
  91. package/tests/unit/content.test.js +332 -0
  92. package/tests/unit/contract/core.test.js +88 -0
  93. package/tests/unit/contract/crypto.test.js +198 -0
  94. package/tests/unit/contract/data.test.js +223 -0
  95. package/tests/unit/contract/federation.test.js +181 -0
  96. package/tests/unit/contract/hierarchical.test.js +113 -0
  97. package/tests/unit/contract/schema.test.js +114 -0
  98. package/tests/unit/contract/social.test.js +217 -0
  99. package/tests/unit/contract/spatial.test.js +110 -0
  100. package/tests/unit/contract/subscriptions.test.js +128 -0
  101. package/tests/unit/contract/utils.test.js +159 -0
  102. package/tests/unit/core.test.js +152 -0
  103. package/tests/unit/crypto.test.js +328 -0
  104. package/tests/unit/federation.test.js +234 -0
  105. package/tests/unit/gun-async.test.js +252 -0
  106. package/tests/unit/hierarchical.test.js +399 -0
  107. package/tests/unit/integration/scenario-01-geographic-storage.test.js +74 -0
  108. package/tests/unit/integration/scenario-02-federation.test.js +76 -0
  109. package/tests/unit/integration/scenario-03-subscriptions.test.js +102 -0
  110. package/tests/unit/integration/scenario-04-validation.test.js +129 -0
  111. package/tests/unit/integration/scenario-05-hierarchy.test.js +125 -0
  112. package/tests/unit/integration/scenario-06-social.test.js +135 -0
  113. package/tests/unit/integration/scenario-07-persistence.test.js +130 -0
  114. package/tests/unit/integration/scenario-08-authorization.test.js +161 -0
  115. package/tests/unit/integration/scenario-09-cross-dimensional.test.js +139 -0
  116. package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +357 -0
  117. package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +410 -0
  118. package/tests/unit/integration/scenario-12-capability-federated-read.test.js +719 -0
  119. package/tests/unit/performance/benchmark.test.js +85 -0
  120. package/tests/unit/schema.test.js +213 -0
  121. package/tests/unit/spatial.test.js +158 -0
  122. package/tests/unit/storage.test.js +195 -0
  123. package/tests/unit/subscriptions.test.js +328 -0
  124. package/tests/unit/test-data-permanence-debug.js +197 -0
  125. package/tests/unit/test-data-permanence.js +340 -0
  126. package/tests/unit/test-key-persistence-fixed.js +148 -0
  127. package/tests/unit/test-key-persistence.js +172 -0
  128. package/tests/unit/test-relay-permanence.js +376 -0
  129. package/tests/unit/test-second-node.js +95 -0
  130. package/tests/unit/test-simple-write.js +89 -0
  131. package/vite.config.js +49 -0
  132. package/vitest.config.js +20 -0
  133. package/FEDERATION.md +0 -213
  134. package/compute.js +0 -298
  135. package/content.js +0 -980
  136. package/federation.js +0 -1234
  137. package/global.js +0 -736
  138. package/hexlib.js +0 -335
  139. package/hologram.js +0 -183
  140. package/holosphere-bundle.esm.js +0 -33256
  141. package/holosphere-bundle.js +0 -33287
  142. package/holosphere-bundle.min.js +0 -39
  143. package/holosphere.d.ts +0 -601
  144. package/holosphere.js +0 -719
  145. package/node.js +0 -246
  146. package/schema.js +0 -139
  147. package/utils.js +0 -302
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Classifier - Auto-classification of content to appropriate lenses
3
+ */
4
+
5
+ /**
6
+ * Classifier class for content categorization
7
+ */
8
+ export class Classifier {
9
+ /**
10
+ * @param {LLMService} llmService - LLM service instance
11
+ * @param {Object} holosphere - HoloSphere instance (optional)
12
+ */
13
+ constructor(llmService, holosphere = null) {
14
+ this.llm = llmService;
15
+ this.holosphere = holosphere;
16
+ this.lensDescriptions = new Map();
17
+ }
18
+
19
+ /**
20
+ * Set HoloSphere instance
21
+ * @param {Object} holosphere - HoloSphere instance
22
+ */
23
+ setHoloSphere(holosphere) {
24
+ this.holosphere = holosphere;
25
+ }
26
+
27
+ /**
28
+ * Register a lens with its description
29
+ * @param {string} lensName - Lens name
30
+ * @param {string} description - Description of what goes in this lens
31
+ * @param {Object} schema - Optional JSON schema for the lens
32
+ */
33
+ registerLens(lensName, description, schema = null) {
34
+ this.lensDescriptions.set(lensName, { description, schema });
35
+ }
36
+
37
+ /**
38
+ * Register multiple lenses
39
+ * @param {Object<string, {description: string, schema?: Object}>} lenses
40
+ */
41
+ registerLenses(lenses) {
42
+ for (const [name, config] of Object.entries(lenses)) {
43
+ this.registerLens(name, config.description, config.schema);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Classify content into the best lens
49
+ * @param {Object|string} content - Content to classify
50
+ * @returns {Promise<{lens: string, confidence: number, reasoning: string}>}
51
+ */
52
+ async classifyToLens(content) {
53
+ const lenses = Array.from(this.lensDescriptions.entries());
54
+
55
+ if (lenses.length === 0) {
56
+ throw new Error('No lenses registered. Use registerLens() first.');
57
+ }
58
+
59
+ const lensDescriptions = lenses
60
+ .map(([name, config]) => `- ${name}: ${config.description}`)
61
+ .join('\n');
62
+
63
+ const contentStr = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
64
+
65
+ const systemPrompt = `You are a content classifier. Classify the following content into the most appropriate lens.
66
+
67
+ Available lenses:
68
+ ${lensDescriptions}
69
+
70
+ Return JSON: {"lens": "lens_name", "confidence": 0.0-1.0, "reasoning": "brief explanation"}`;
71
+
72
+ return this.llm.getJSON(systemPrompt, contentStr, { temperature: 0.2 });
73
+ }
74
+
75
+ /**
76
+ * Classify content into multiple possible lenses
77
+ * @param {Object|string} content - Content to classify
78
+ * @param {number} maxLenses - Maximum lenses to return
79
+ * @returns {Promise<Array<{lens: string, confidence: number}>>}
80
+ */
81
+ async classifyMultiple(content, maxLenses = 3) {
82
+ const lenses = Array.from(this.lensDescriptions.entries());
83
+
84
+ if (lenses.length === 0) {
85
+ throw new Error('No lenses registered');
86
+ }
87
+
88
+ const lensDescriptions = lenses
89
+ .map(([name, config]) => `- ${name}: ${config.description}`)
90
+ .join('\n');
91
+
92
+ const contentStr = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
93
+
94
+ const systemPrompt = `You are a content classifier. Classify the content into up to ${maxLenses} appropriate lenses, ranked by fit.
95
+
96
+ Available lenses:
97
+ ${lensDescriptions}
98
+
99
+ Return JSON array: [{"lens": "name", "confidence": 0.0-1.0}, ...]
100
+ Order by confidence descending.`;
101
+
102
+ return this.llm.getJSON(systemPrompt, contentStr, { temperature: 0.2 });
103
+ }
104
+
105
+ /**
106
+ * Auto-store content in the best lens
107
+ * @param {string} holon - Holon identifier
108
+ * @param {Object} content - Content to store
109
+ * @returns {Promise<{lens: string, stored: boolean, result: Object}>}
110
+ */
111
+ async autoStore(holon, content) {
112
+ if (!this.holosphere) {
113
+ throw new Error('HoloSphere instance required for storage');
114
+ }
115
+
116
+ const classification = await this.classifyToLens(content);
117
+
118
+ // Store in classified lens
119
+ const result = await this.holosphere.put(holon, classification.lens, content);
120
+
121
+ return {
122
+ lens: classification.lens,
123
+ confidence: classification.confidence,
124
+ reasoning: classification.reasoning,
125
+ stored: true,
126
+ result,
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Suggest lens for new content type
132
+ * @param {string} contentDescription - Description of the new content type
133
+ * @returns {Promise<{name: string, description: string}>}
134
+ */
135
+ async suggestNewLens(contentDescription) {
136
+ const existingLenses = Array.from(this.lensDescriptions.keys());
137
+
138
+ const systemPrompt = `You are helping design a data organization system. Suggest a new lens (category) for this type of content.
139
+
140
+ Existing lenses: ${existingLenses.join(', ')}
141
+
142
+ Return JSON: {"name": "lens_name", "description": "what this lens contains"}
143
+ Use lowercase_snake_case for names.`;
144
+
145
+ return this.llm.getJSON(systemPrompt, contentDescription, { temperature: 0.5 });
146
+ }
147
+
148
+ /**
149
+ * Analyze content and extract fields for schema
150
+ * @param {Object} content - Sample content
151
+ * @returns {Promise<Object>} Suggested JSON schema
152
+ */
153
+ async suggestSchema(content) {
154
+ const contentStr = JSON.stringify(content, null, 2);
155
+
156
+ const systemPrompt = `You are a data modeling expert. Analyze this content and suggest a JSON schema for validating similar content.
157
+
158
+ Return a valid JSON schema with:
159
+ - Type definitions
160
+ - Required fields
161
+ - Field descriptions
162
+ - Reasonable constraints`;
163
+
164
+ return this.llm.getJSON(systemPrompt, contentStr, { temperature: 0.3 });
165
+ }
166
+
167
+ /**
168
+ * Validate content against lens schema
169
+ * @param {Object} content - Content to validate
170
+ * @param {string} lensName - Lens to validate against
171
+ * @returns {Promise<{valid: boolean, issues: string[]}>}
172
+ */
173
+ async validateForLens(content, lensName) {
174
+ const lensConfig = this.lensDescriptions.get(lensName);
175
+
176
+ if (!lensConfig) {
177
+ throw new Error(`Lens not found: ${lensName}`);
178
+ }
179
+
180
+ if (!lensConfig.schema) {
181
+ // If no schema, use AI to validate
182
+ const systemPrompt = `Validate if this content is appropriate for the "${lensName}" lens.
183
+ Lens description: ${lensConfig.description}
184
+
185
+ Return JSON: {"valid": true/false, "issues": ["issue1", "issue2"] or []}`;
186
+
187
+ return this.llm.getJSON(systemPrompt, JSON.stringify(content, null, 2), { temperature: 0.2 });
188
+ }
189
+
190
+ // Use schema validation (would integrate with schema validator)
191
+ return { valid: true, issues: [] };
192
+ }
193
+
194
+ /**
195
+ * Get classification stats for a collection
196
+ * @param {Object[]} items - Items to analyze
197
+ * @returns {Promise<Object>} Classification statistics
198
+ */
199
+ async analyzeCollection(items) {
200
+ const classifications = await Promise.all(
201
+ items.map(item => this.classifyToLens(item))
202
+ );
203
+
204
+ const stats = {};
205
+ for (const c of classifications) {
206
+ stats[c.lens] = (stats[c.lens] || 0) + 1;
207
+ }
208
+
209
+ return {
210
+ total: items.length,
211
+ byLens: stats,
212
+ classifications,
213
+ };
214
+ }
215
+ }
216
+
217
+ export default Classifier;
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Council - Multi-perspective wisdom from different viewpoints
3
+ */
4
+
5
+ // Default 12 perspectives based on HolonsBot
6
+ const DEFAULT_PERSPECTIVES = [
7
+ { name: 'Values & Worldview', prompt: 'Answer from the embodied perspective of Values and Worldview - considering ethics, beliefs, culture, and meaning.' },
8
+ { name: 'Health & Wellbeing', prompt: 'Answer from the embodied perspective of Health & Wellbeing - considering physical, mental, and social health.' },
9
+ { name: 'Food & Agriculture', prompt: 'Answer from the embodied perspective of Food & Agriculture - considering food systems, nutrition, and farming.' },
10
+ { name: 'Business & Trade', prompt: 'Answer from the embodied perspective of Business & Trade - considering commerce, markets, and economic exchange.' },
11
+ { name: 'Energy & Resources', prompt: 'Answer from the embodied perspective of Energy & Resources - considering power, materials, and sustainability.' },
12
+ { name: 'Climate & Environment', prompt: 'Answer from the embodied perspective of Climate Change - considering environmental impact and adaptation.' },
13
+ { name: 'Ecosystems & Biosphere', prompt: 'Answer from the embodied perspective of Ecosystems & Biosphere - considering biodiversity and natural systems.' },
14
+ { name: 'Water Availability', prompt: 'Answer from the embodied perspective of Water Availability - considering water access, quality, and management.' },
15
+ { name: 'Habitat & Infrastructure', prompt: 'Answer from the embodied perspective of Habitat & Infrastructure - considering built environment and housing.' },
16
+ { name: 'Economy & Wealth', prompt: 'Answer from the embodied perspective of Economy & Wealth - considering prosperity, inequality, and resources.' },
17
+ { name: 'Governance & Institutions', prompt: 'Answer from the embodied perspective of Governance & Institutions - considering power, policy, and organization.' },
18
+ { name: 'Community & Resilience', prompt: 'Answer from the embodied perspective of Community & Resilience - considering social bonds and adaptability.' },
19
+ ];
20
+
21
+ /**
22
+ * Council class for multi-perspective answers
23
+ */
24
+ export class Council {
25
+ /**
26
+ * @param {LLMService} llmService - LLM service instance
27
+ * @param {Array} perspectives - Custom perspectives (optional)
28
+ */
29
+ constructor(llmService, perspectives = null) {
30
+ this.llm = llmService;
31
+ this.perspectives = perspectives || DEFAULT_PERSPECTIVES;
32
+ }
33
+
34
+ /**
35
+ * Set custom perspectives
36
+ * @param {Array<{name: string, prompt: string}>} perspectives
37
+ */
38
+ setPerspectives(perspectives) {
39
+ this.perspectives = perspectives;
40
+ }
41
+
42
+ /**
43
+ * Get default perspectives
44
+ * @returns {Array} Default perspectives
45
+ */
46
+ static getDefaultPerspectives() {
47
+ return DEFAULT_PERSPECTIVES;
48
+ }
49
+
50
+ /**
51
+ * Ask a question to the council
52
+ * @param {string} question - Question to ask
53
+ * @param {Object} options - Options
54
+ * @returns {Promise<{perspectives: Array, summary: string}>}
55
+ */
56
+ async ask(question, options = {}) {
57
+ const { parallel = true, includeSummary = true } = options;
58
+ const perspectives = options.perspectives || this.perspectives;
59
+
60
+ // Get answers from all perspectives
61
+ let answers;
62
+
63
+ if (parallel) {
64
+ answers = await Promise.all(
65
+ perspectives.map(async (perspective) => {
66
+ const answer = await this._askPerspective(question, perspective);
67
+ return {
68
+ perspective: perspective.name,
69
+ answer,
70
+ };
71
+ })
72
+ );
73
+ } else {
74
+ answers = [];
75
+ for (const perspective of perspectives) {
76
+ const answer = await this._askPerspective(question, perspective);
77
+ answers.push({
78
+ perspective: perspective.name,
79
+ answer,
80
+ });
81
+ }
82
+ }
83
+
84
+ // Generate summary
85
+ let summary = null;
86
+ if (includeSummary) {
87
+ summary = await this._summarize(question, answers);
88
+ }
89
+
90
+ return {
91
+ question,
92
+ perspectives: answers,
93
+ summary,
94
+ timestamp: Date.now(),
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Ask a question from specific perspective
100
+ * @private
101
+ */
102
+ async _askPerspective(question, perspective) {
103
+ const systemPrompt = `You are a wise council member representing a specific perspective.
104
+
105
+ ${perspective.prompt}
106
+
107
+ Provide thoughtful, nuanced answers that honor your unique perspective while being constructive and helpful. Be concise but insightful.`;
108
+
109
+ return this.llm.sendMessage(systemPrompt, question, { temperature: 0.7 });
110
+ }
111
+
112
+ /**
113
+ * Summarize all perspectives
114
+ * @private
115
+ */
116
+ async _summarize(question, answers) {
117
+ const perspectiveTexts = answers
118
+ .map(a => `**${a.perspective}**: ${a.answer}`)
119
+ .join('\n\n');
120
+
121
+ const systemPrompt = `You are a wise facilitator. Synthesize the following perspectives into a balanced summary that:
122
+ 1. Identifies common themes and agreements
123
+ 2. Notes important tensions or tradeoffs
124
+ 3. Provides actionable wisdom
125
+ 4. Remains neutral and balanced
126
+
127
+ Be concise but comprehensive.`;
128
+
129
+ const userMessage = `Question: ${question}
130
+
131
+ Perspectives:
132
+ ${perspectiveTexts}`;
133
+
134
+ return this.llm.sendMessage(systemPrompt, userMessage, { temperature: 0.5 });
135
+ }
136
+
137
+ /**
138
+ * Create custom perspectives from a list of names
139
+ * @param {string[]} names - Perspective names
140
+ * @returns {Array} Custom perspectives
141
+ */
142
+ static createPerspectives(names) {
143
+ return names.map(name => ({
144
+ name,
145
+ prompt: `Answer from the embodied perspective of ${name} - considering all aspects related to ${name.toLowerCase()}.`,
146
+ }));
147
+ }
148
+
149
+ /**
150
+ * Ask with custom perspectives (convenience method)
151
+ * @param {string} question - Question
152
+ * @param {string[]} perspectiveNames - List of perspective names
153
+ * @param {Object} options - Options
154
+ * @returns {Promise<Object>} Council response
155
+ */
156
+ async askCustom(question, perspectiveNames, options = {}) {
157
+ const perspectives = Council.createPerspectives(perspectiveNames);
158
+ return this.ask(question, { ...options, perspectives });
159
+ }
160
+
161
+ /**
162
+ * Get a single perspective answer
163
+ * @param {string} question - Question
164
+ * @param {string} perspectiveName - Perspective name
165
+ * @returns {Promise<string>} Answer
166
+ */
167
+ async askSingle(question, perspectiveName) {
168
+ const perspective = this.perspectives.find(p => p.name === perspectiveName)
169
+ || { name: perspectiveName, prompt: `Answer from the perspective of ${perspectiveName}.` };
170
+
171
+ return this._askPerspective(question, perspective);
172
+ }
173
+
174
+ /**
175
+ * Debate between perspectives
176
+ * @param {string} topic - Topic to debate
177
+ * @param {string[]} perspectiveNames - Two perspectives to debate
178
+ * @param {number} rounds - Number of exchange rounds
179
+ * @returns {Promise<Object>} Debate result
180
+ */
181
+ async debate(topic, perspectiveNames, rounds = 3) {
182
+ if (perspectiveNames.length !== 2) {
183
+ throw new Error('Debate requires exactly 2 perspectives');
184
+ }
185
+
186
+ const perspectives = perspectiveNames.map(name =>
187
+ this.perspectives.find(p => p.name === name)
188
+ || { name, prompt: `Argue from the perspective of ${name}.` }
189
+ );
190
+
191
+ const exchanges = [];
192
+ let context = `Topic: ${topic}`;
193
+
194
+ for (let i = 0; i < rounds; i++) {
195
+ for (const perspective of perspectives) {
196
+ const systemPrompt = `You are debating from the perspective of ${perspective.name}.
197
+ ${perspective.prompt}
198
+ Respond to the previous arguments constructively, acknowledging good points but advocating for your perspective.`;
199
+
200
+ const response = await this.llm.sendMessage(systemPrompt, context, { temperature: 0.7 });
201
+
202
+ exchanges.push({
203
+ perspective: perspective.name,
204
+ round: i + 1,
205
+ response,
206
+ });
207
+
208
+ context += `\n\n${perspective.name}: ${response}`;
209
+ }
210
+ }
211
+
212
+ // Generate conclusion
213
+ const conclusion = await this.llm.sendMessage(
214
+ 'As a neutral moderator, summarize the key insights from this debate and identify areas of agreement and disagreement.',
215
+ context,
216
+ { temperature: 0.5 }
217
+ );
218
+
219
+ return {
220
+ topic,
221
+ perspectives: perspectiveNames,
222
+ exchanges,
223
+ conclusion,
224
+ };
225
+ }
226
+ }
227
+
228
+ export default Council;