@thispointon/kondi-chat 0.1.2
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.
- package/LICENSE +21 -0
- package/README.md +556 -0
- package/bin/kondi-chat +56 -0
- package/bin/kondi-chat.js +72 -0
- package/package.json +55 -0
- package/scripts/demo.tape +49 -0
- package/scripts/postinstall.cjs +103 -0
- package/src/audit/analytics.ts +261 -0
- package/src/audit/ledger.ts +253 -0
- package/src/audit/telemetry.ts +165 -0
- package/src/cli/backend.ts +675 -0
- package/src/cli/commands.ts +419 -0
- package/src/cli/help.ts +182 -0
- package/src/cli/submit-helpers.ts +159 -0
- package/src/cli/submit.ts +539 -0
- package/src/cli/wizard.ts +121 -0
- package/src/context/bootstrap.ts +138 -0
- package/src/context/budget.ts +100 -0
- package/src/context/manager.ts +666 -0
- package/src/context/memory.ts +160 -0
- package/src/context/preflight.ts +176 -0
- package/src/context/project-brain.ts +101 -0
- package/src/context/receipts.ts +108 -0
- package/src/context/skills.ts +154 -0
- package/src/context/symbol-index.ts +240 -0
- package/src/council/profiles.ts +137 -0
- package/src/council/tool.ts +138 -0
- package/src/council-engine/cli/council-artifacts.ts +230 -0
- package/src/council-engine/cli/council-config.ts +178 -0
- package/src/council-engine/cli/council-session-export.ts +116 -0
- package/src/council-engine/cli/kondi.ts +98 -0
- package/src/council-engine/cli/llm-caller.ts +229 -0
- package/src/council-engine/cli/localStorage-shim.ts +119 -0
- package/src/council-engine/cli/node-platform.ts +68 -0
- package/src/council-engine/cli/run-council.ts +481 -0
- package/src/council-engine/cli/run-pipeline.ts +772 -0
- package/src/council-engine/cli/session-export.ts +153 -0
- package/src/council-engine/configs/councils/analysis.json +101 -0
- package/src/council-engine/configs/councils/code-planning.json +86 -0
- package/src/council-engine/configs/councils/coding.json +89 -0
- package/src/council-engine/configs/councils/debate.json +97 -0
- package/src/council-engine/configs/councils/solo-claude.json +34 -0
- package/src/council-engine/configs/councils/solo-gpt.json +34 -0
- package/src/council-engine/council/coding-orchestrator.ts +1205 -0
- package/src/council-engine/council/context-bootstrap.ts +147 -0
- package/src/council-engine/council/context-inspection.ts +42 -0
- package/src/council-engine/council/context-store.ts +763 -0
- package/src/council-engine/council/deliberation-orchestrator.ts +2762 -0
- package/src/council-engine/council/factory.ts +164 -0
- package/src/council-engine/council/index.ts +201 -0
- package/src/council-engine/council/ledger-store.ts +438 -0
- package/src/council-engine/council/prompts.ts +1689 -0
- package/src/council-engine/council/storage-cleanup.ts +164 -0
- package/src/council-engine/council/store.ts +1110 -0
- package/src/council-engine/council/synthesis.ts +291 -0
- package/src/council-engine/council/types.ts +845 -0
- package/src/council-engine/council/validation.ts +613 -0
- package/src/council-engine/pipeline/build-detect.ts +73 -0
- package/src/council-engine/pipeline/executor.ts +1048 -0
- package/src/council-engine/pipeline/index.ts +9 -0
- package/src/council-engine/pipeline/install-detect.ts +84 -0
- package/src/council-engine/pipeline/memory-store.ts +182 -0
- package/src/council-engine/pipeline/output-parsers.ts +146 -0
- package/src/council-engine/pipeline/run-output.ts +149 -0
- package/src/council-engine/pipeline/session-import.ts +177 -0
- package/src/council-engine/pipeline/store.ts +753 -0
- package/src/council-engine/pipeline/test-detect.ts +82 -0
- package/src/council-engine/pipeline/types.ts +401 -0
- package/src/council-engine/services/deliberationSummary.ts +114 -0
- package/src/council-engine/tsconfig.json +16 -0
- package/src/council-engine/types/mcp.ts +122 -0
- package/src/council-engine/utils/filterTools.ts +73 -0
- package/src/engine/apply.ts +238 -0
- package/src/engine/checkpoints.ts +237 -0
- package/src/engine/consultants.ts +347 -0
- package/src/engine/diff.ts +171 -0
- package/src/engine/errors.ts +102 -0
- package/src/engine/git-tools.ts +246 -0
- package/src/engine/hooks.ts +181 -0
- package/src/engine/loop-guard.ts +155 -0
- package/src/engine/permissions.ts +293 -0
- package/src/engine/pipeline.ts +376 -0
- package/src/engine/sub-agents.ts +133 -0
- package/src/engine/task-card.ts +185 -0
- package/src/engine/task-router.ts +256 -0
- package/src/engine/task-store.ts +86 -0
- package/src/engine/tools.ts +783 -0
- package/src/engine/verify.ts +111 -0
- package/src/mcp/client.ts +225 -0
- package/src/mcp/config.ts +120 -0
- package/src/mcp/tool-manager.ts +192 -0
- package/src/mcp/types.ts +61 -0
- package/src/providers/llm-caller.ts +943 -0
- package/src/providers/rate-limiter.ts +238 -0
- package/src/router/NOTES.md +28 -0
- package/src/router/collector.ts +474 -0
- package/src/router/embeddings.ts +286 -0
- package/src/router/index.ts +299 -0
- package/src/router/intent-router.ts +225 -0
- package/src/router/nn-router.ts +205 -0
- package/src/router/profiles.ts +309 -0
- package/src/router/registry.ts +565 -0
- package/src/router/rules.ts +274 -0
- package/src/router/train.py +408 -0
- package/src/session/store.ts +211 -0
- package/src/test-utils/mock-llm.ts +39 -0
- package/src/types.ts +322 -0
- package/src/web/manager.ts +311 -0
|
@@ -0,0 +1,1110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Council: Persistence Store
|
|
3
|
+
* CRUD operations for councils with localStorage persistence
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
Council,
|
|
8
|
+
Persona,
|
|
9
|
+
CouncilMessage,
|
|
10
|
+
OrchestrationConfig,
|
|
11
|
+
SharedContext,
|
|
12
|
+
Resolution,
|
|
13
|
+
DeliberationConfig,
|
|
14
|
+
DeliberationState,
|
|
15
|
+
DeliberationRoleAssignment,
|
|
16
|
+
DeliberationPhase,
|
|
17
|
+
DeliberationRole,
|
|
18
|
+
} from './types';
|
|
19
|
+
import { validateCouncil } from './validation';
|
|
20
|
+
import { deleteLedger } from './ledger-store';
|
|
21
|
+
import { deleteAllArtifacts } from './context-store';
|
|
22
|
+
import { councilDataStore } from './storage-cleanup';
|
|
23
|
+
|
|
24
|
+
const STORAGE_KEY = 'mcp-councils';
|
|
25
|
+
const STORAGE_VERSION = 2;
|
|
26
|
+
|
|
27
|
+
interface StorageData {
|
|
28
|
+
version: number;
|
|
29
|
+
councils: Council[];
|
|
30
|
+
lastUpdated: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Storage Helpers
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
function loadFromStorage(): StorageData {
|
|
38
|
+
try {
|
|
39
|
+
const raw = councilDataStore.getItem(STORAGE_KEY);
|
|
40
|
+
if (!raw) {
|
|
41
|
+
return { version: STORAGE_VERSION, councils: [], lastUpdated: new Date().toISOString() };
|
|
42
|
+
}
|
|
43
|
+
const data = JSON.parse(raw) as StorageData;
|
|
44
|
+
// Handle version migrations
|
|
45
|
+
if (data.version < STORAGE_VERSION) {
|
|
46
|
+
console.log('[CouncilStore] Migrating from version', data.version, 'to', STORAGE_VERSION);
|
|
47
|
+
data.councils = migrateCouncils(data.councils, data.version);
|
|
48
|
+
data.version = STORAGE_VERSION;
|
|
49
|
+
saveToStorage(data);
|
|
50
|
+
}
|
|
51
|
+
return data;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('[CouncilStore] Failed to load from storage:', error);
|
|
54
|
+
return { version: STORAGE_VERSION, councils: [], lastUpdated: new Date().toISOString() };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Migrate councils from older versions
|
|
60
|
+
*/
|
|
61
|
+
function migrateCouncils(councils: Council[], fromVersion: number): Council[] {
|
|
62
|
+
let migrated = councils;
|
|
63
|
+
|
|
64
|
+
// Migration from v1 to v2: Add deliberation fields
|
|
65
|
+
if (fromVersion < 2) {
|
|
66
|
+
migrated = migrated.map((council) => ({
|
|
67
|
+
...council,
|
|
68
|
+
// Existing councils get undefined deliberation (backward compatible)
|
|
69
|
+
deliberation: undefined,
|
|
70
|
+
deliberationState: undefined,
|
|
71
|
+
}));
|
|
72
|
+
console.log('[CouncilStore] Migrated', migrated.length, 'councils from v1 to v2');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Always sanitize status — old duplicates may have invalid values like 'created'
|
|
76
|
+
migrated = migrated.map((council) => ({
|
|
77
|
+
...council,
|
|
78
|
+
status: ['active', 'paused', 'resolved'].includes(council.status) ? council.status : 'active',
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
return migrated;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function saveToStorage(data: StorageData): void {
|
|
85
|
+
data.lastUpdated = new Date().toISOString();
|
|
86
|
+
const json = JSON.stringify(data);
|
|
87
|
+
councilDataStore.setItem(STORAGE_KEY, json);
|
|
88
|
+
console.log('[CouncilStore] Saved', data.councils.length, 'councils');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Council CRUD
|
|
93
|
+
// ============================================================================
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get all councils
|
|
97
|
+
*/
|
|
98
|
+
export function getAllCouncils(): Council[] {
|
|
99
|
+
const data = loadFromStorage();
|
|
100
|
+
return data.councils.sort(
|
|
101
|
+
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get a council by ID
|
|
107
|
+
*/
|
|
108
|
+
export function getCouncil(id: string): Council | null {
|
|
109
|
+
const data = loadFromStorage();
|
|
110
|
+
return data.councils.find((c) => c.id === id) || null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create a new council
|
|
115
|
+
*/
|
|
116
|
+
export function createCouncil(params: {
|
|
117
|
+
name: string;
|
|
118
|
+
topic: string;
|
|
119
|
+
sharedContext?: Partial<SharedContext>;
|
|
120
|
+
personas?: Persona[];
|
|
121
|
+
orchestration?: Partial<OrchestrationConfig>;
|
|
122
|
+
deliberation?: Partial<DeliberationConfig>;
|
|
123
|
+
pipelineId?: string;
|
|
124
|
+
}): Council {
|
|
125
|
+
const now = new Date().toISOString();
|
|
126
|
+
const isDeliberationMode = params.orchestration?.mode === 'deliberation';
|
|
127
|
+
|
|
128
|
+
const council: Council = {
|
|
129
|
+
id: crypto.randomUUID(),
|
|
130
|
+
name: params.name,
|
|
131
|
+
createdAt: now,
|
|
132
|
+
updatedAt: now,
|
|
133
|
+
topic: params.topic,
|
|
134
|
+
sharedContext: {
|
|
135
|
+
description: params.sharedContext?.description || params.topic,
|
|
136
|
+
documents: params.sharedContext?.documents || [],
|
|
137
|
+
data: params.sharedContext?.data,
|
|
138
|
+
constraints: params.sharedContext?.constraints,
|
|
139
|
+
},
|
|
140
|
+
personas: params.personas || [],
|
|
141
|
+
orchestration: {
|
|
142
|
+
mode: params.orchestration?.mode || 'debate',
|
|
143
|
+
turnStrategy: params.orchestration?.turnStrategy || 'round-robin',
|
|
144
|
+
maxTurnsPerRound: params.orchestration?.maxTurnsPerRound || 5,
|
|
145
|
+
maxTotalTurns: params.orchestration?.maxTotalTurns,
|
|
146
|
+
autoSynthesize: params.orchestration?.autoSynthesize ?? true,
|
|
147
|
+
synthesizerId: params.orchestration?.synthesizerId,
|
|
148
|
+
convergenceCriteria: params.orchestration?.convergenceCriteria,
|
|
149
|
+
requiresResolution: params.orchestration?.requiresResolution ?? false,
|
|
150
|
+
},
|
|
151
|
+
messages: [],
|
|
152
|
+
status: 'active',
|
|
153
|
+
totalTokensUsed: 0,
|
|
154
|
+
estimatedCost: 0,
|
|
155
|
+
// Deliberation config (only for deliberation mode)
|
|
156
|
+
deliberation: isDeliberationMode ? {
|
|
157
|
+
enabled: true,
|
|
158
|
+
roleAssignments: params.deliberation?.roleAssignments || [],
|
|
159
|
+
minRounds: params.deliberation?.minRounds ?? 1,
|
|
160
|
+
maxRounds: params.deliberation?.maxRounds ?? 4,
|
|
161
|
+
maxRevisions: params.deliberation?.maxRevisions ?? 3,
|
|
162
|
+
expectedOutput: params.deliberation?.expectedOutput,
|
|
163
|
+
decisionCriteria: params.deliberation?.decisionCriteria,
|
|
164
|
+
summaryMode: params.deliberation?.summaryMode ?? 'hybrid',
|
|
165
|
+
summarizeAfterRound: params.deliberation?.summarizeAfterRound ?? 1,
|
|
166
|
+
contextTokenBudget: params.deliberation?.contextTokenBudget ?? 40000,
|
|
167
|
+
consultantErrorPolicy: params.deliberation?.consultantErrorPolicy ?? 'retry',
|
|
168
|
+
maxRetries: params.deliberation?.maxRetries ?? 2,
|
|
169
|
+
requirePlan: params.deliberation?.requirePlan ?? false,
|
|
170
|
+
consultantExecution: params.deliberation?.consultantExecution ?? 'sequential',
|
|
171
|
+
workingDirectory: params.deliberation?.workingDirectory,
|
|
172
|
+
directoryConstrained: params.deliberation?.directoryConstrained ?? true,
|
|
173
|
+
saveDeliberation: params.deliberation?.saveDeliberation ?? false,
|
|
174
|
+
saveDeliberationMode: params.deliberation?.saveDeliberationMode ?? 'full',
|
|
175
|
+
maxWordsPerResponse: params.deliberation?.maxWordsPerResponse,
|
|
176
|
+
bootstrapContext: params.deliberation?.bootstrapContext,
|
|
177
|
+
stepType: params.deliberation?.stepType,
|
|
178
|
+
testCommand: params.deliberation?.testCommand,
|
|
179
|
+
maxDebugCycles: params.deliberation?.maxDebugCycles,
|
|
180
|
+
maxReviewCycles: params.deliberation?.maxReviewCycles,
|
|
181
|
+
allowedServerIds: params.deliberation?.allowedServerIds,
|
|
182
|
+
} : undefined,
|
|
183
|
+
// Deliberation state (initialized when deliberation starts)
|
|
184
|
+
deliberationState: undefined,
|
|
185
|
+
// Pipeline linkage
|
|
186
|
+
pipelineId: params.pipelineId,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Validate before saving
|
|
190
|
+
const validation = validateCouncil(council);
|
|
191
|
+
if (!validation.success) {
|
|
192
|
+
const issues = validation.error.issues
|
|
193
|
+
.map((issue) => {
|
|
194
|
+
const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
|
|
195
|
+
return `${path}: ${issue.message}`;
|
|
196
|
+
})
|
|
197
|
+
.join('; ');
|
|
198
|
+
console.error('[CouncilStore] Invalid council on create:', issues, validation.error);
|
|
199
|
+
throw new Error(`Invalid council data: ${issues}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const data = loadFromStorage();
|
|
203
|
+
data.councils.push(council);
|
|
204
|
+
saveToStorage(data);
|
|
205
|
+
|
|
206
|
+
console.log('[CouncilStore] Created council:', council.id, council.name, isDeliberationMode ? '(deliberation mode)' : '');
|
|
207
|
+
return council;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Update a council
|
|
212
|
+
*/
|
|
213
|
+
export function updateCouncil(
|
|
214
|
+
id: string,
|
|
215
|
+
updates: Partial<Omit<Council, 'id' | 'createdAt'>>
|
|
216
|
+
): Council | null {
|
|
217
|
+
const data = loadFromStorage();
|
|
218
|
+
const index = data.councils.findIndex((c) => c.id === id);
|
|
219
|
+
|
|
220
|
+
if (index === -1) {
|
|
221
|
+
console.warn('[CouncilStore] Council not found:', id);
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const council = data.councils[index];
|
|
226
|
+
const updated: Council = {
|
|
227
|
+
...council,
|
|
228
|
+
...updates,
|
|
229
|
+
updatedAt: new Date().toISOString(),
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Coerce invalid status values to 'active' (e.g. 'created' from old duplicates)
|
|
233
|
+
if (!['active', 'paused', 'resolved'].includes(updated.status)) {
|
|
234
|
+
updated.status = 'active';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Validate the updated council
|
|
238
|
+
const validation = validateCouncil(updated);
|
|
239
|
+
if (!validation.success) {
|
|
240
|
+
const issues = validation.error.issues
|
|
241
|
+
.map((issue) => {
|
|
242
|
+
const path = issue.path.length > 0 ? issue.path.join('.') : '(root)';
|
|
243
|
+
return `${path}: ${issue.message}`;
|
|
244
|
+
})
|
|
245
|
+
.join('; ');
|
|
246
|
+
console.error('[CouncilStore] Invalid council update:', issues, validation.error);
|
|
247
|
+
throw new Error(`Invalid council data: ${issues}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
data.councils[index] = updated;
|
|
251
|
+
saveToStorage(data);
|
|
252
|
+
|
|
253
|
+
console.log('[CouncilStore] Updated council:', id);
|
|
254
|
+
return updated;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Delete a council
|
|
259
|
+
*/
|
|
260
|
+
export function deleteCouncil(id: string): boolean {
|
|
261
|
+
const data = loadFromStorage();
|
|
262
|
+
const index = data.councils.findIndex((c) => c.id === id);
|
|
263
|
+
|
|
264
|
+
if (index === -1) {
|
|
265
|
+
console.warn('[CouncilStore] Council not found:', id);
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
data.councils.splice(index, 1);
|
|
270
|
+
saveToStorage(data);
|
|
271
|
+
|
|
272
|
+
console.log('[CouncilStore] Deleted council:', id);
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ============================================================================
|
|
277
|
+
// Persona Operations
|
|
278
|
+
// ============================================================================
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Add a persona to a council
|
|
282
|
+
*/
|
|
283
|
+
export function addPersona(councilId: string, persona: Persona): Council | null {
|
|
284
|
+
const council = getCouncil(councilId);
|
|
285
|
+
if (!council) return null;
|
|
286
|
+
|
|
287
|
+
// Check for duplicate names
|
|
288
|
+
if (council.personas.some((p) => p.name === persona.name)) {
|
|
289
|
+
throw new Error(`Persona "${persona.name}" already exists in this council`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Also add a role assignment if deliberation config exists
|
|
293
|
+
const updates: Partial<Council> = {
|
|
294
|
+
personas: [...council.personas, persona],
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (council.deliberation) {
|
|
298
|
+
const existingAssignment = council.deliberation.roleAssignments.find(
|
|
299
|
+
(r) => r.personaId === persona.id
|
|
300
|
+
);
|
|
301
|
+
if (!existingAssignment) {
|
|
302
|
+
// Default new personas to 'consultant' role
|
|
303
|
+
const role = persona.preferredDeliberationRole || 'consultant';
|
|
304
|
+
updates.deliberation = {
|
|
305
|
+
...council.deliberation,
|
|
306
|
+
roleAssignments: [
|
|
307
|
+
...council.deliberation.roleAssignments,
|
|
308
|
+
{ personaId: persona.id, role },
|
|
309
|
+
],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return updateCouncil(councilId, updates);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Update a persona in a council
|
|
319
|
+
*/
|
|
320
|
+
export function updatePersona(
|
|
321
|
+
councilId: string,
|
|
322
|
+
personaId: string,
|
|
323
|
+
updates: Partial<Omit<Persona, 'id'>>
|
|
324
|
+
): Council | null {
|
|
325
|
+
const council = getCouncil(councilId);
|
|
326
|
+
if (!council) return null;
|
|
327
|
+
|
|
328
|
+
const personaIndex = council.personas.findIndex((p) => p.id === personaId);
|
|
329
|
+
if (personaIndex === -1) {
|
|
330
|
+
throw new Error(`Persona not found: ${personaId}`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const updatedPersonas = [...council.personas];
|
|
334
|
+
updatedPersonas[personaIndex] = {
|
|
335
|
+
...updatedPersonas[personaIndex],
|
|
336
|
+
...updates,
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
return updateCouncil(councilId, { personas: updatedPersonas });
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Remove a persona from a council
|
|
344
|
+
*/
|
|
345
|
+
export function removePersona(councilId: string, personaId: string): Council | null {
|
|
346
|
+
const council = getCouncil(councilId);
|
|
347
|
+
if (!council) return null;
|
|
348
|
+
|
|
349
|
+
const updates: Partial<Council> = {
|
|
350
|
+
personas: council.personas.filter((p) => p.id !== personaId),
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// Also remove the role assignment if deliberation config exists
|
|
354
|
+
if (council.deliberation) {
|
|
355
|
+
updates.deliberation = {
|
|
356
|
+
...council.deliberation,
|
|
357
|
+
roleAssignments: council.deliberation.roleAssignments.filter(
|
|
358
|
+
(r) => r.personaId !== personaId
|
|
359
|
+
),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return updateCouncil(councilId, updates);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Mute/unmute a persona
|
|
368
|
+
*/
|
|
369
|
+
export function setPersonaMuted(
|
|
370
|
+
councilId: string,
|
|
371
|
+
personaId: string,
|
|
372
|
+
muted: boolean
|
|
373
|
+
): Council | null {
|
|
374
|
+
return updatePersona(councilId, personaId, { muted });
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ============================================================================
|
|
378
|
+
// Message Operations
|
|
379
|
+
// ============================================================================
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Add a message to a council
|
|
383
|
+
*/
|
|
384
|
+
export function addMessage(councilId: string, message: CouncilMessage): Council | null {
|
|
385
|
+
const council = getCouncil(councilId);
|
|
386
|
+
if (!council) return null;
|
|
387
|
+
|
|
388
|
+
return updateCouncil(councilId, {
|
|
389
|
+
messages: [...council.messages, message],
|
|
390
|
+
totalTokensUsed: council.totalTokensUsed + message.tokensUsed,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Get messages for a council, optionally filtered
|
|
396
|
+
*/
|
|
397
|
+
export function getMessages(
|
|
398
|
+
councilId: string,
|
|
399
|
+
options?: {
|
|
400
|
+
speakerId?: string;
|
|
401
|
+
speakerType?: 'persona' | 'user' | 'system';
|
|
402
|
+
limit?: number;
|
|
403
|
+
offset?: number;
|
|
404
|
+
}
|
|
405
|
+
): CouncilMessage[] {
|
|
406
|
+
const council = getCouncil(councilId);
|
|
407
|
+
if (!council) return [];
|
|
408
|
+
|
|
409
|
+
let messages = council.messages;
|
|
410
|
+
|
|
411
|
+
if (options?.speakerId) {
|
|
412
|
+
messages = messages.filter((m) => m.speakerId === options.speakerId);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (options?.speakerType) {
|
|
416
|
+
messages = messages.filter((m) => m.speakerType === options.speakerType);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (options?.offset) {
|
|
420
|
+
messages = messages.slice(options.offset);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (options?.limit) {
|
|
424
|
+
messages = messages.slice(0, options.limit);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return messages;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ============================================================================
|
|
431
|
+
// Status & Resolution Operations
|
|
432
|
+
// ============================================================================
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Update council status
|
|
436
|
+
*/
|
|
437
|
+
export function setCouncilStatus(
|
|
438
|
+
councilId: string,
|
|
439
|
+
status: Council['status']
|
|
440
|
+
): Council | null {
|
|
441
|
+
return updateCouncil(councilId, { status });
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Set council resolution
|
|
446
|
+
*/
|
|
447
|
+
export function setResolution(
|
|
448
|
+
councilId: string,
|
|
449
|
+
resolution: Resolution
|
|
450
|
+
): Council | null {
|
|
451
|
+
return updateCouncil(councilId, {
|
|
452
|
+
resolution,
|
|
453
|
+
status: 'resolved',
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Update cost tracking
|
|
459
|
+
*/
|
|
460
|
+
export function updateCost(
|
|
461
|
+
councilId: string,
|
|
462
|
+
additionalTokens: number,
|
|
463
|
+
additionalCost: number
|
|
464
|
+
): Council | null {
|
|
465
|
+
const council = getCouncil(councilId);
|
|
466
|
+
if (!council) return null;
|
|
467
|
+
|
|
468
|
+
return updateCouncil(councilId, {
|
|
469
|
+
totalTokensUsed: council.totalTokensUsed + additionalTokens,
|
|
470
|
+
estimatedCost: council.estimatedCost + additionalCost,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// ============================================================================
|
|
475
|
+
// Query Operations
|
|
476
|
+
// ============================================================================
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Search councils by name or topic
|
|
480
|
+
*/
|
|
481
|
+
export function searchCouncils(query: string): Council[] {
|
|
482
|
+
const councils = getAllCouncils();
|
|
483
|
+
const lowerQuery = query.toLowerCase();
|
|
484
|
+
|
|
485
|
+
return councils.filter(
|
|
486
|
+
(c) =>
|
|
487
|
+
c.name.toLowerCase().includes(lowerQuery) ||
|
|
488
|
+
c.topic.toLowerCase().includes(lowerQuery)
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Get councils by status
|
|
494
|
+
*/
|
|
495
|
+
export function getCouncilsByStatus(status: Council['status']): Council[] {
|
|
496
|
+
const councils = getAllCouncils();
|
|
497
|
+
return councils.filter((c) => c.status === status);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Get active councils (not resolved)
|
|
502
|
+
*/
|
|
503
|
+
export function getActiveCouncils(): Council[] {
|
|
504
|
+
return getCouncilsByStatus('active');
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Get recent councils
|
|
509
|
+
*/
|
|
510
|
+
export function getRecentCouncils(limit = 10): Council[] {
|
|
511
|
+
return getAllCouncils().slice(0, limit);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ============================================================================
|
|
515
|
+
// Export/Import
|
|
516
|
+
// ============================================================================
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Export a council to JSON
|
|
520
|
+
*/
|
|
521
|
+
export function exportCouncil(councilId: string): string | null {
|
|
522
|
+
const council = getCouncil(councilId);
|
|
523
|
+
if (!council) return null;
|
|
524
|
+
return JSON.stringify(council, null, 2);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Import a council from JSON
|
|
529
|
+
*/
|
|
530
|
+
export function importCouncil(json: string): Council {
|
|
531
|
+
const data = JSON.parse(json);
|
|
532
|
+
|
|
533
|
+
// Validate the imported data
|
|
534
|
+
const validation = validateCouncil(data);
|
|
535
|
+
if (!validation.success) {
|
|
536
|
+
throw new Error(`Invalid council data: ${validation.error.message}`);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Generate new ID to avoid conflicts
|
|
540
|
+
const council: Council = {
|
|
541
|
+
...validation.data,
|
|
542
|
+
id: crypto.randomUUID(),
|
|
543
|
+
createdAt: new Date().toISOString(),
|
|
544
|
+
updatedAt: new Date().toISOString(),
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
const storageData = loadFromStorage();
|
|
548
|
+
storageData.councils.push(council);
|
|
549
|
+
saveToStorage(storageData);
|
|
550
|
+
|
|
551
|
+
return council;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Duplicate a council
|
|
556
|
+
*/
|
|
557
|
+
export function duplicateCouncil(councilId: string, newName?: string): Council | null {
|
|
558
|
+
const original = getCouncil(councilId);
|
|
559
|
+
if (!original) return null;
|
|
560
|
+
|
|
561
|
+
const now = new Date().toISOString();
|
|
562
|
+
|
|
563
|
+
// Build persona ID mapping (old → new) so role assignments stay linked
|
|
564
|
+
const personaIdMap = new Map<string, string>();
|
|
565
|
+
const newPersonas = original.personas.map((p) => {
|
|
566
|
+
const newId = crypto.randomUUID();
|
|
567
|
+
personaIdMap.set(p.id, newId);
|
|
568
|
+
return { ...p, id: newId };
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// Remap role assignments to new persona IDs
|
|
572
|
+
const newRoleAssignments = original.deliberation?.roleAssignments?.map((ra) => ({
|
|
573
|
+
...ra,
|
|
574
|
+
personaId: personaIdMap.get(ra.personaId) || ra.personaId,
|
|
575
|
+
}));
|
|
576
|
+
|
|
577
|
+
const duplicate: Council = {
|
|
578
|
+
...original,
|
|
579
|
+
id: crypto.randomUUID(),
|
|
580
|
+
name: newName || `${original.name} (Copy)`,
|
|
581
|
+
createdAt: now,
|
|
582
|
+
updatedAt: now,
|
|
583
|
+
messages: [],
|
|
584
|
+
status: 'active',
|
|
585
|
+
resolution: undefined,
|
|
586
|
+
totalTokensUsed: 0,
|
|
587
|
+
estimatedCost: 0,
|
|
588
|
+
personas: newPersonas,
|
|
589
|
+
deliberation: original.deliberation ? {
|
|
590
|
+
...original.deliberation,
|
|
591
|
+
roleAssignments: newRoleAssignments || [],
|
|
592
|
+
} : undefined,
|
|
593
|
+
deliberationState: undefined,
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
const data = loadFromStorage();
|
|
597
|
+
data.councils.push(duplicate);
|
|
598
|
+
saveToStorage(data);
|
|
599
|
+
|
|
600
|
+
return duplicate;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// ============================================================================
|
|
604
|
+
// Deliberation State Operations
|
|
605
|
+
// ============================================================================
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Initialize deliberation state for a council
|
|
609
|
+
*/
|
|
610
|
+
export function initializeDeliberationState(councilId: string): Council | null {
|
|
611
|
+
const council = getCouncil(councilId);
|
|
612
|
+
if (!council || !council.deliberation) return null;
|
|
613
|
+
|
|
614
|
+
const initialState: DeliberationState = {
|
|
615
|
+
currentPhase: 'created',
|
|
616
|
+
currentRound: 0,
|
|
617
|
+
roundRunId: crypto.randomUUID(),
|
|
618
|
+
maxRounds: council.deliberation.maxRounds,
|
|
619
|
+
revisionCount: 0,
|
|
620
|
+
maxRevisions: council.deliberation.maxRevisions,
|
|
621
|
+
roundSubmissions: {},
|
|
622
|
+
roundSummaries: {},
|
|
623
|
+
activeContextId: '',
|
|
624
|
+
activeContextVersion: 0,
|
|
625
|
+
pendingPatches: [],
|
|
626
|
+
reDeliberationCount: 0,
|
|
627
|
+
errorLog: [],
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
return updateCouncil(councilId, { deliberationState: initialState });
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Update deliberation state
|
|
635
|
+
*/
|
|
636
|
+
export function updateDeliberationState(
|
|
637
|
+
councilId: string,
|
|
638
|
+
updates: Partial<DeliberationState>
|
|
639
|
+
): Council | null {
|
|
640
|
+
const council = getCouncil(councilId);
|
|
641
|
+
if (!council || !council.deliberationState) return null;
|
|
642
|
+
|
|
643
|
+
const updatedState: DeliberationState = {
|
|
644
|
+
...council.deliberationState,
|
|
645
|
+
...updates,
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
return updateCouncil(councilId, { deliberationState: updatedState });
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Update deliberation phase
|
|
653
|
+
*/
|
|
654
|
+
export function setDeliberationPhase(
|
|
655
|
+
councilId: string,
|
|
656
|
+
phase: DeliberationPhase,
|
|
657
|
+
previousPhase?: DeliberationPhase
|
|
658
|
+
): Council | null {
|
|
659
|
+
let council = getCouncil(councilId);
|
|
660
|
+
if (!council) return null;
|
|
661
|
+
|
|
662
|
+
// Initialize deliberationState if it doesn't exist
|
|
663
|
+
if (!council.deliberationState) {
|
|
664
|
+
council = initializeDeliberationState(councilId);
|
|
665
|
+
if (!council) return null;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const updates: Partial<DeliberationState> = { currentPhase: phase };
|
|
669
|
+
if (previousPhase !== undefined) {
|
|
670
|
+
updates.previousPhase = previousPhase;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return updateDeliberationState(councilId, updates);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Advance to next round
|
|
678
|
+
*/
|
|
679
|
+
export function advanceDeliberationRound(councilId: string): Council | null {
|
|
680
|
+
const council = getCouncil(councilId);
|
|
681
|
+
if (!council || !council.deliberationState) return null;
|
|
682
|
+
|
|
683
|
+
const newRound = council.deliberationState.currentRound + 1;
|
|
684
|
+
|
|
685
|
+
return updateDeliberationState(councilId, {
|
|
686
|
+
currentRound: newRound,
|
|
687
|
+
roundRunId: crypto.randomUUID(),
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Record a consultant submission for the current round
|
|
693
|
+
*/
|
|
694
|
+
export function recordRoundSubmission(
|
|
695
|
+
councilId: string,
|
|
696
|
+
personaId: string
|
|
697
|
+
): Council | null {
|
|
698
|
+
const council = getCouncil(councilId);
|
|
699
|
+
if (!council || !council.deliberationState) return null;
|
|
700
|
+
|
|
701
|
+
const currentRound = council.deliberationState.currentRound;
|
|
702
|
+
const submissions = { ...council.deliberationState.roundSubmissions };
|
|
703
|
+
|
|
704
|
+
if (!submissions[currentRound]) {
|
|
705
|
+
submissions[currentRound] = [];
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if (!submissions[currentRound].includes(personaId)) {
|
|
709
|
+
submissions[currentRound].push(personaId);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return updateDeliberationState(councilId, { roundSubmissions: submissions });
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Check if all consultants have submitted for the current round
|
|
717
|
+
*/
|
|
718
|
+
export function isRoundComplete(councilId: string): boolean {
|
|
719
|
+
const council = getCouncil(councilId);
|
|
720
|
+
if (!council || !council.deliberation || !council.deliberationState) return false;
|
|
721
|
+
|
|
722
|
+
const currentRound = council.deliberationState.currentRound;
|
|
723
|
+
const submissions = council.deliberationState.roundSubmissions[currentRound] || [];
|
|
724
|
+
|
|
725
|
+
// Get consultant persona IDs from role assignments
|
|
726
|
+
const consultantIds = council.deliberation.roleAssignments
|
|
727
|
+
.filter((r) => r.role === 'consultant')
|
|
728
|
+
.map((r) => r.personaId);
|
|
729
|
+
|
|
730
|
+
return consultantIds.every((id) => submissions.includes(id));
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Set role assignments for a council
|
|
735
|
+
*/
|
|
736
|
+
export function setRoleAssignments(
|
|
737
|
+
councilId: string,
|
|
738
|
+
assignments: DeliberationRoleAssignment[]
|
|
739
|
+
): Council | null {
|
|
740
|
+
const council = getCouncil(councilId);
|
|
741
|
+
if (!council || !council.deliberation) return null;
|
|
742
|
+
|
|
743
|
+
const updatedDeliberation: DeliberationConfig = {
|
|
744
|
+
...council.deliberation,
|
|
745
|
+
roleAssignments: assignments,
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
return updateCouncil(councilId, { deliberation: updatedDeliberation });
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Add a pending patch to deliberation state
|
|
753
|
+
*/
|
|
754
|
+
export function addPendingPatch(councilId: string, patchId: string): Council | null {
|
|
755
|
+
const council = getCouncil(councilId);
|
|
756
|
+
if (!council || !council.deliberationState) return null;
|
|
757
|
+
|
|
758
|
+
const pendingPatches = [...council.deliberationState.pendingPatches, patchId];
|
|
759
|
+
|
|
760
|
+
return updateDeliberationState(councilId, { pendingPatches });
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Remove a pending patch from deliberation state
|
|
765
|
+
*/
|
|
766
|
+
export function removePendingPatch(councilId: string, patchId: string): Council | null {
|
|
767
|
+
const council = getCouncil(councilId);
|
|
768
|
+
if (!council || !council.deliberationState) return null;
|
|
769
|
+
|
|
770
|
+
const pendingPatches = council.deliberationState.pendingPatches.filter(
|
|
771
|
+
(id) => id !== patchId
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
return updateDeliberationState(councilId, { pendingPatches });
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Set round summary
|
|
779
|
+
*/
|
|
780
|
+
export function setRoundSummary(
|
|
781
|
+
councilId: string,
|
|
782
|
+
round: number,
|
|
783
|
+
summary: string
|
|
784
|
+
): Council | null {
|
|
785
|
+
const council = getCouncil(councilId);
|
|
786
|
+
if (!council || !council.deliberationState) return null;
|
|
787
|
+
|
|
788
|
+
const roundSummaries = {
|
|
789
|
+
...council.deliberationState.roundSummaries,
|
|
790
|
+
[round]: summary,
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
return updateDeliberationState(councilId, { roundSummaries });
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Set active context
|
|
798
|
+
*/
|
|
799
|
+
export function setActiveContext(
|
|
800
|
+
councilId: string,
|
|
801
|
+
contextId: string,
|
|
802
|
+
version: number
|
|
803
|
+
): Council | null {
|
|
804
|
+
return updateDeliberationState(councilId, {
|
|
805
|
+
activeContextId: contextId,
|
|
806
|
+
activeContextVersion: version,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Set manager's last evaluation
|
|
812
|
+
*/
|
|
813
|
+
export function setManagerEvaluation(
|
|
814
|
+
councilId: string,
|
|
815
|
+
evaluation: DeliberationState['managerLastEvaluation']
|
|
816
|
+
): Council | null {
|
|
817
|
+
return updateDeliberationState(councilId, { managerLastEvaluation: evaluation });
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Set final decision ID
|
|
822
|
+
*/
|
|
823
|
+
export function setFinalDecision(councilId: string, decisionId: string): Council | null {
|
|
824
|
+
return updateDeliberationState(councilId, { finalDecisionId: decisionId });
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Set work directive ID
|
|
829
|
+
*/
|
|
830
|
+
export function setWorkDirective(councilId: string, directiveId: string): Council | null {
|
|
831
|
+
return updateDeliberationState(councilId, { workDirectiveId: directiveId });
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Set current output ID
|
|
836
|
+
*/
|
|
837
|
+
export function setCurrentOutput(councilId: string, outputId: string): Council | null {
|
|
838
|
+
return updateDeliberationState(councilId, { currentOutputId: outputId });
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Increment revision count
|
|
843
|
+
*/
|
|
844
|
+
export function incrementRevisionCount(councilId: string): Council | null {
|
|
845
|
+
const council = getCouncil(councilId);
|
|
846
|
+
if (!council || !council.deliberationState) return null;
|
|
847
|
+
|
|
848
|
+
return updateDeliberationState(councilId, {
|
|
849
|
+
revisionCount: council.deliberationState.revisionCount + 1,
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Add error to log
|
|
855
|
+
*/
|
|
856
|
+
export function addErrorToLog(councilId: string, error: string): Council | null {
|
|
857
|
+
const council = getCouncil(councilId);
|
|
858
|
+
if (!council || !council.deliberationState) return null;
|
|
859
|
+
|
|
860
|
+
const errorLog = [...council.deliberationState.errorLog, error];
|
|
861
|
+
|
|
862
|
+
return updateDeliberationState(councilId, { errorLog });
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Get persona by role
|
|
867
|
+
*/
|
|
868
|
+
export function getPersonaByRole(
|
|
869
|
+
council: Council,
|
|
870
|
+
role: DeliberationRole
|
|
871
|
+
): Persona[] {
|
|
872
|
+
if (!council.deliberation) return [];
|
|
873
|
+
|
|
874
|
+
const roleAssignments = council.deliberation.roleAssignments.filter(
|
|
875
|
+
(r) => r.role === role
|
|
876
|
+
);
|
|
877
|
+
|
|
878
|
+
return roleAssignments
|
|
879
|
+
.map((r) => council.personas.find((p) => p.id === r.personaId))
|
|
880
|
+
.filter((p): p is Persona => p !== undefined);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Get role assignment for a persona
|
|
885
|
+
*/
|
|
886
|
+
export function getRoleAssignment(
|
|
887
|
+
council: Council,
|
|
888
|
+
personaId: string
|
|
889
|
+
): DeliberationRoleAssignment | undefined {
|
|
890
|
+
return council.deliberation?.roleAssignments.find((r) => r.personaId === personaId);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Check if council is in deliberation mode
|
|
895
|
+
*/
|
|
896
|
+
export function isDeliberationMode(council: Council): boolean {
|
|
897
|
+
return council.orchestration.mode === 'deliberation' && council.deliberation?.enabled === true;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Delete council and all associated deliberation data
|
|
902
|
+
*/
|
|
903
|
+
export function deleteCouncilWithData(id: string): boolean {
|
|
904
|
+
// Delete ledger
|
|
905
|
+
deleteLedger(id);
|
|
906
|
+
|
|
907
|
+
// Delete artifacts
|
|
908
|
+
deleteAllArtifacts(id);
|
|
909
|
+
|
|
910
|
+
// Delete council
|
|
911
|
+
return deleteCouncil(id);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// ============================================================================
|
|
915
|
+
// Store Class (for React integration)
|
|
916
|
+
// ============================================================================
|
|
917
|
+
|
|
918
|
+
export class CouncilStore {
|
|
919
|
+
private listeners: Set<() => void> = new Set();
|
|
920
|
+
|
|
921
|
+
subscribe(listener: () => void): () => void {
|
|
922
|
+
this.listeners.add(listener);
|
|
923
|
+
return () => this.listeners.delete(listener);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
private notify(): void {
|
|
927
|
+
this.listeners.forEach((listener) => listener());
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
getAll = getAllCouncils;
|
|
931
|
+
get = getCouncil;
|
|
932
|
+
|
|
933
|
+
create(params: Parameters<typeof createCouncil>[0]): Council {
|
|
934
|
+
const council = createCouncil(params);
|
|
935
|
+
this.notify();
|
|
936
|
+
return council;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
update(id: string, updates: Parameters<typeof updateCouncil>[1]): Council | null {
|
|
940
|
+
const council = updateCouncil(id, updates);
|
|
941
|
+
if (council) this.notify();
|
|
942
|
+
return council;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
delete(id: string): boolean {
|
|
946
|
+
const success = deleteCouncilWithData(id);
|
|
947
|
+
if (success) this.notify();
|
|
948
|
+
return success;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
addPersona(councilId: string, persona: Persona): Council | null {
|
|
952
|
+
const council = addPersona(councilId, persona);
|
|
953
|
+
if (council) this.notify();
|
|
954
|
+
return council;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
removePersona(councilId: string, personaId: string): Council | null {
|
|
958
|
+
const council = removePersona(councilId, personaId);
|
|
959
|
+
if (council) this.notify();
|
|
960
|
+
return council;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
updatePersona(councilId: string, personaId: string, updates: Partial<Omit<Persona, 'id'>>): Council | null {
|
|
964
|
+
const council = updatePersona(councilId, personaId, updates);
|
|
965
|
+
if (council) this.notify();
|
|
966
|
+
return council;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
addMessage(councilId: string, message: CouncilMessage): Council | null {
|
|
970
|
+
const council = addMessage(councilId, message);
|
|
971
|
+
if (council) this.notify();
|
|
972
|
+
return council;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
setStatus(councilId: string, status: Council['status']): Council | null {
|
|
976
|
+
const council = setCouncilStatus(councilId, status);
|
|
977
|
+
if (council) this.notify();
|
|
978
|
+
return council;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
resolve(councilId: string, resolution: Resolution): Council | null {
|
|
982
|
+
const council = setResolution(councilId, resolution);
|
|
983
|
+
if (council) this.notify();
|
|
984
|
+
return council;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// ============================================================================
|
|
988
|
+
// Deliberation Methods
|
|
989
|
+
// ============================================================================
|
|
990
|
+
|
|
991
|
+
initializeDeliberation(councilId: string): Council | null {
|
|
992
|
+
const council = initializeDeliberationState(councilId);
|
|
993
|
+
if (council) this.notify();
|
|
994
|
+
return council;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
updateDeliberationState(
|
|
998
|
+
councilId: string,
|
|
999
|
+
updates: Partial<DeliberationState>
|
|
1000
|
+
): Council | null {
|
|
1001
|
+
const council = updateDeliberationState(councilId, updates);
|
|
1002
|
+
if (council) this.notify();
|
|
1003
|
+
return council;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
setDeliberationPhase(
|
|
1007
|
+
councilId: string,
|
|
1008
|
+
phase: DeliberationPhase,
|
|
1009
|
+
previousPhase?: DeliberationPhase
|
|
1010
|
+
): Council | null {
|
|
1011
|
+
const council = setDeliberationPhase(councilId, phase, previousPhase);
|
|
1012
|
+
if (council) this.notify();
|
|
1013
|
+
return council;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
advanceRound(councilId: string): Council | null {
|
|
1017
|
+
const council = advanceDeliberationRound(councilId);
|
|
1018
|
+
if (council) this.notify();
|
|
1019
|
+
return council;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
recordSubmission(councilId: string, personaId: string): Council | null {
|
|
1023
|
+
const council = recordRoundSubmission(councilId, personaId);
|
|
1024
|
+
if (council) this.notify();
|
|
1025
|
+
return council;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
isRoundComplete(councilId: string): boolean {
|
|
1029
|
+
return isRoundComplete(councilId);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
setRoleAssignments(
|
|
1033
|
+
councilId: string,
|
|
1034
|
+
assignments: DeliberationRoleAssignment[]
|
|
1035
|
+
): Council | null {
|
|
1036
|
+
const council = setRoleAssignments(councilId, assignments);
|
|
1037
|
+
if (council) this.notify();
|
|
1038
|
+
return council;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
addPendingPatch(councilId: string, patchId: string): Council | null {
|
|
1042
|
+
const council = addPendingPatch(councilId, patchId);
|
|
1043
|
+
if (council) this.notify();
|
|
1044
|
+
return council;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
removePendingPatch(councilId: string, patchId: string): Council | null {
|
|
1048
|
+
const council = removePendingPatch(councilId, patchId);
|
|
1049
|
+
if (council) this.notify();
|
|
1050
|
+
return council;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
setRoundSummary(councilId: string, round: number, summary: string): Council | null {
|
|
1054
|
+
const council = setRoundSummary(councilId, round, summary);
|
|
1055
|
+
if (council) this.notify();
|
|
1056
|
+
return council;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
setActiveContext(councilId: string, contextId: string, version: number): Council | null {
|
|
1060
|
+
const council = setActiveContext(councilId, contextId, version);
|
|
1061
|
+
if (council) this.notify();
|
|
1062
|
+
return council;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
setManagerEvaluation(
|
|
1066
|
+
councilId: string,
|
|
1067
|
+
evaluation: DeliberationState['managerLastEvaluation']
|
|
1068
|
+
): Council | null {
|
|
1069
|
+
const council = setManagerEvaluation(councilId, evaluation);
|
|
1070
|
+
if (council) this.notify();
|
|
1071
|
+
return council;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
setFinalDecision(councilId: string, decisionId: string): Council | null {
|
|
1075
|
+
const council = setFinalDecision(councilId, decisionId);
|
|
1076
|
+
if (council) this.notify();
|
|
1077
|
+
return council;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
setWorkDirective(councilId: string, directiveId: string): Council | null {
|
|
1081
|
+
const council = setWorkDirective(councilId, directiveId);
|
|
1082
|
+
if (council) this.notify();
|
|
1083
|
+
return council;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
setCurrentOutput(councilId: string, outputId: string): Council | null {
|
|
1087
|
+
const council = setCurrentOutput(councilId, outputId);
|
|
1088
|
+
if (council) this.notify();
|
|
1089
|
+
return council;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
incrementRevisionCount(councilId: string): Council | null {
|
|
1093
|
+
const council = incrementRevisionCount(councilId);
|
|
1094
|
+
if (council) this.notify();
|
|
1095
|
+
return council;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
addError(councilId: string, error: string): Council | null {
|
|
1099
|
+
const council = addErrorToLog(councilId, error);
|
|
1100
|
+
if (council) this.notify();
|
|
1101
|
+
return council;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
getPersonaByRole = getPersonaByRole;
|
|
1105
|
+
getRoleAssignment = getRoleAssignment;
|
|
1106
|
+
isDeliberationMode = isDeliberationMode;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Singleton instance for app-wide use
|
|
1110
|
+
export const councilStore = new CouncilStore();
|