@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,753 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline Store: Persistence & State Management
|
|
3
|
+
* localStorage-backed CRUD with subscribe/notify pattern (follows councilStore)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
Pipeline,
|
|
8
|
+
PipelineStage,
|
|
9
|
+
PipelineStep,
|
|
10
|
+
PipelineStepStatus,
|
|
11
|
+
PipelineStatus,
|
|
12
|
+
StepConfig,
|
|
13
|
+
StepArtifact,
|
|
14
|
+
LlmStepConfig,
|
|
15
|
+
} from './types';
|
|
16
|
+
import { migrateLlmConfig } from './types';
|
|
17
|
+
import { councilDataStore } from '../council/storage-cleanup';
|
|
18
|
+
import { deleteCouncilWithData } from '../council/store';
|
|
19
|
+
|
|
20
|
+
const STORAGE_KEY = 'mcp-pipelines';
|
|
21
|
+
|
|
22
|
+
interface StorageData {
|
|
23
|
+
version: number;
|
|
24
|
+
pipelines: Pipeline[];
|
|
25
|
+
lastUpdated: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Storage Helpers
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
function migrateV1toV2(data: StorageData): StorageData {
|
|
33
|
+
if (data.version >= 2) return data;
|
|
34
|
+
|
|
35
|
+
console.log('[PipelineStore] Migrating v1 \u2192 v2: council\u2192planning, execution stays, gate stays');
|
|
36
|
+
for (const pipeline of data.pipelines) {
|
|
37
|
+
for (const stage of pipeline.stages) {
|
|
38
|
+
for (const step of stage.steps) {
|
|
39
|
+
if ((step.config as { type: string }).type === 'council') {
|
|
40
|
+
(step.config as { type: string }).type = 'planning';
|
|
41
|
+
}
|
|
42
|
+
// 'execution' and 'gate' stay unchanged
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
data.version = 2;
|
|
47
|
+
return data;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* v2 → v3: Convert legacy LlmStepConfig (flat model/provider/systemPrompt)
|
|
52
|
+
* to CouncilStepConfig (with councilSetup). All non-gate step types are now councils.
|
|
53
|
+
*/
|
|
54
|
+
function migrateV2toV3(data: StorageData): StorageData {
|
|
55
|
+
if (data.version >= 3) return data;
|
|
56
|
+
|
|
57
|
+
let migrated = 0;
|
|
58
|
+
for (const pipeline of data.pipelines) {
|
|
59
|
+
for (const stage of pipeline.stages) {
|
|
60
|
+
for (const step of stage.steps) {
|
|
61
|
+
const config = step.config as any;
|
|
62
|
+
// Detect legacy LlmStepConfig: has type decisioning/execution but no councilSetup
|
|
63
|
+
if ((config.type === 'decisioning' || config.type === 'execution') && !config.councilSetup) {
|
|
64
|
+
step.config = migrateLlmConfig(config as LlmStepConfig);
|
|
65
|
+
migrated++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (migrated > 0) {
|
|
72
|
+
console.log(`[PipelineStore] Migrating v2 → v3: converted ${migrated} LLM step(s) to council format`);
|
|
73
|
+
}
|
|
74
|
+
data.version = 3;
|
|
75
|
+
return data;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* v3 → v4: Rename step types to broader names.
|
|
80
|
+
* planning→council, decisioning→analysis, execution→agent, review-docs→review, enrichment→enrich.
|
|
81
|
+
*/
|
|
82
|
+
function migrateV3toV4(data: StorageData): StorageData {
|
|
83
|
+
if (data.version >= 4) return data;
|
|
84
|
+
|
|
85
|
+
const typeMap: Record<string, string> = {
|
|
86
|
+
planning: 'council',
|
|
87
|
+
decisioning: 'analysis',
|
|
88
|
+
execution: 'agent',
|
|
89
|
+
'review-docs': 'review',
|
|
90
|
+
enrichment: 'enrich',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
let migrated = 0;
|
|
94
|
+
for (const pipeline of data.pipelines) {
|
|
95
|
+
for (const stage of pipeline.stages) {
|
|
96
|
+
for (const step of stage.steps) {
|
|
97
|
+
const config = step.config as { type: string };
|
|
98
|
+
const newType = typeMap[config.type];
|
|
99
|
+
if (newType) {
|
|
100
|
+
config.type = newType;
|
|
101
|
+
migrated++;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (migrated > 0) {
|
|
108
|
+
console.log(`[PipelineStore] Migrating v3 → v4: renamed ${migrated} step type(s)`);
|
|
109
|
+
}
|
|
110
|
+
data.version = 4;
|
|
111
|
+
return data;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* v4 → v5: Rename 'council' → 'code_planning'.
|
|
116
|
+
* The old 'council' type was specifically for code planning (PLAN_TOOLS, planning prompts).
|
|
117
|
+
* Now 'council' is a new open-ended deliberation type, and old council steps become 'code_planning'.
|
|
118
|
+
*/
|
|
119
|
+
function migrateV4toV5(data: StorageData): StorageData {
|
|
120
|
+
if (data.version >= 5) return data;
|
|
121
|
+
|
|
122
|
+
let migrated = 0;
|
|
123
|
+
for (const pipeline of data.pipelines) {
|
|
124
|
+
for (const stage of pipeline.stages) {
|
|
125
|
+
for (const step of stage.steps) {
|
|
126
|
+
const config = step.config as { type: string };
|
|
127
|
+
if (config.type === 'council') {
|
|
128
|
+
config.type = 'code_planning';
|
|
129
|
+
migrated++;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (migrated > 0) {
|
|
136
|
+
console.log(`[PipelineStore] Migrating v4 → v5: renamed ${migrated} 'council' step(s) to 'code_planning'`);
|
|
137
|
+
}
|
|
138
|
+
data.version = 5;
|
|
139
|
+
return data;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function loadFromStorage(): StorageData {
|
|
143
|
+
try {
|
|
144
|
+
const raw = councilDataStore.getItem(STORAGE_KEY);
|
|
145
|
+
if (!raw) {
|
|
146
|
+
return { version: 5, pipelines: [], lastUpdated: new Date().toISOString() };
|
|
147
|
+
}
|
|
148
|
+
let data = JSON.parse(raw) as StorageData;
|
|
149
|
+
data = migrateV1toV2(data);
|
|
150
|
+
data = migrateV2toV3(data);
|
|
151
|
+
data = migrateV3toV4(data);
|
|
152
|
+
data = migrateV4toV5(data);
|
|
153
|
+
return data;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error('[PipelineStore] Failed to load from storage:', error);
|
|
156
|
+
return { version: 5, pipelines: [], lastUpdated: new Date().toISOString() };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Reset any pipelines/steps that were left in a transient state (running, waiting)
|
|
162
|
+
* from a previous session. Called once on startup.
|
|
163
|
+
*/
|
|
164
|
+
function resetStaleExecutionStates(): void {
|
|
165
|
+
const data = loadFromStorage();
|
|
166
|
+
let dirty = false;
|
|
167
|
+
|
|
168
|
+
for (const pipeline of data.pipelines) {
|
|
169
|
+
if (pipeline.status === 'running' || pipeline.status === 'paused') {
|
|
170
|
+
pipeline.status = 'failed';
|
|
171
|
+
dirty = true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (const stage of pipeline.stages) {
|
|
175
|
+
for (const step of stage.steps) {
|
|
176
|
+
if (step.status === 'running' || step.status === 'waiting_approval') {
|
|
177
|
+
step.status = 'failed';
|
|
178
|
+
step.error = 'Interrupted: application was restarted';
|
|
179
|
+
step.completedAt = new Date().toISOString();
|
|
180
|
+
dirty = true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (dirty) {
|
|
187
|
+
saveToStorage(data);
|
|
188
|
+
console.log('[PipelineStore] Reset stale running/waiting states from previous session');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Run once on module load
|
|
193
|
+
resetStaleExecutionStates();
|
|
194
|
+
|
|
195
|
+
function saveToStorage(data: StorageData): void {
|
|
196
|
+
data.lastUpdated = new Date().toISOString();
|
|
197
|
+
// Use persistent save — pipeline configs MUST survive app restarts
|
|
198
|
+
councilDataStore.setItemPersistent(STORAGE_KEY, JSON.stringify(data));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ============================================================================
|
|
202
|
+
// Pipeline CRUD
|
|
203
|
+
// ============================================================================
|
|
204
|
+
|
|
205
|
+
export function getAllPipelines(): Pipeline[] {
|
|
206
|
+
const data = loadFromStorage();
|
|
207
|
+
return data.pipelines.sort(
|
|
208
|
+
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function getPipeline(id: string): Pipeline | null {
|
|
213
|
+
const data = loadFromStorage();
|
|
214
|
+
return data.pipelines.find((p) => p.id === id) || null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function createPipeline(params: {
|
|
218
|
+
name: string;
|
|
219
|
+
description?: string;
|
|
220
|
+
initialInput?: string;
|
|
221
|
+
settings?: Partial<Pipeline['settings']>;
|
|
222
|
+
}): Pipeline {
|
|
223
|
+
const now = new Date().toISOString();
|
|
224
|
+
|
|
225
|
+
const pipeline: Pipeline = {
|
|
226
|
+
id: crypto.randomUUID(),
|
|
227
|
+
name: params.name,
|
|
228
|
+
description: params.description,
|
|
229
|
+
initialInput: params.initialInput || '',
|
|
230
|
+
stages: [],
|
|
231
|
+
settings: {
|
|
232
|
+
workingDirectory: params.settings?.workingDirectory,
|
|
233
|
+
failurePolicy: params.settings?.failurePolicy || 'stop',
|
|
234
|
+
directoryConstrained: params.settings?.directoryConstrained ?? true,
|
|
235
|
+
},
|
|
236
|
+
status: 'draft',
|
|
237
|
+
currentStageIndex: 0,
|
|
238
|
+
createdAt: now,
|
|
239
|
+
updatedAt: now,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const data = loadFromStorage();
|
|
243
|
+
data.pipelines.push(pipeline);
|
|
244
|
+
saveToStorage(data);
|
|
245
|
+
|
|
246
|
+
console.log('[PipelineStore] Created pipeline:', pipeline.id, pipeline.name);
|
|
247
|
+
return pipeline;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function updatePipeline(
|
|
251
|
+
id: string,
|
|
252
|
+
updates: Partial<Omit<Pipeline, 'id' | 'createdAt'>>
|
|
253
|
+
): Pipeline | null {
|
|
254
|
+
const data = loadFromStorage();
|
|
255
|
+
const index = data.pipelines.findIndex((p) => p.id === id);
|
|
256
|
+
|
|
257
|
+
if (index === -1) {
|
|
258
|
+
console.warn('[PipelineStore] Pipeline not found:', id);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const updated: Pipeline = {
|
|
263
|
+
...data.pipelines[index],
|
|
264
|
+
...updates,
|
|
265
|
+
updatedAt: new Date().toISOString(),
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
data.pipelines[index] = updated;
|
|
269
|
+
saveToStorage(data);
|
|
270
|
+
|
|
271
|
+
return updated;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function deletePipeline(id: string): boolean {
|
|
275
|
+
const data = loadFromStorage();
|
|
276
|
+
const index = data.pipelines.findIndex((p) => p.id === id);
|
|
277
|
+
|
|
278
|
+
if (index === -1) return false;
|
|
279
|
+
|
|
280
|
+
data.pipelines.splice(index, 1);
|
|
281
|
+
saveToStorage(data);
|
|
282
|
+
|
|
283
|
+
console.log('[PipelineStore] Deleted pipeline:', id);
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function duplicatePipeline(id: string, newName?: string): Pipeline | null {
|
|
288
|
+
const original = getPipeline(id);
|
|
289
|
+
if (!original) return null;
|
|
290
|
+
|
|
291
|
+
const now = new Date().toISOString();
|
|
292
|
+
|
|
293
|
+
// Deep clone stages with new IDs
|
|
294
|
+
const clonedStages: PipelineStage[] = original.stages.map((stage) => ({
|
|
295
|
+
id: crypto.randomUUID(),
|
|
296
|
+
name: stage.name,
|
|
297
|
+
steps: stage.steps.map((step) => ({
|
|
298
|
+
...step,
|
|
299
|
+
id: crypto.randomUUID(),
|
|
300
|
+
status: 'pending' as const,
|
|
301
|
+
artifact: undefined,
|
|
302
|
+
error: undefined,
|
|
303
|
+
startedAt: undefined,
|
|
304
|
+
completedAt: undefined,
|
|
305
|
+
})),
|
|
306
|
+
}));
|
|
307
|
+
|
|
308
|
+
const duplicate: Pipeline = {
|
|
309
|
+
...original,
|
|
310
|
+
id: crypto.randomUUID(),
|
|
311
|
+
name: newName || `${original.name} (Copy)`,
|
|
312
|
+
stages: clonedStages,
|
|
313
|
+
status: 'draft',
|
|
314
|
+
currentStageIndex: 0,
|
|
315
|
+
createdAt: now,
|
|
316
|
+
updatedAt: now,
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const data = loadFromStorage();
|
|
320
|
+
data.pipelines.push(duplicate);
|
|
321
|
+
saveToStorage(data);
|
|
322
|
+
|
|
323
|
+
return duplicate;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ============================================================================
|
|
327
|
+
// Stage Operations
|
|
328
|
+
// ============================================================================
|
|
329
|
+
|
|
330
|
+
export function addStage(
|
|
331
|
+
pipelineId: string,
|
|
332
|
+
name?: string,
|
|
333
|
+
atIndex?: number
|
|
334
|
+
): Pipeline | null {
|
|
335
|
+
const pipeline = getPipeline(pipelineId);
|
|
336
|
+
if (!pipeline) return null;
|
|
337
|
+
|
|
338
|
+
const stage: PipelineStage = {
|
|
339
|
+
id: crypto.randomUUID(),
|
|
340
|
+
name: name || `Stage ${pipeline.stages.length + 1}`,
|
|
341
|
+
steps: [],
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const stages = [...pipeline.stages];
|
|
345
|
+
if (atIndex !== undefined && atIndex >= 0 && atIndex <= stages.length) {
|
|
346
|
+
stages.splice(atIndex, 0, stage);
|
|
347
|
+
} else {
|
|
348
|
+
stages.push(stage);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return updatePipeline(pipelineId, { stages });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export function removeStage(pipelineId: string, stageId: string): Pipeline | null {
|
|
355
|
+
const pipeline = getPipeline(pipelineId);
|
|
356
|
+
if (!pipeline) return null;
|
|
357
|
+
|
|
358
|
+
return updatePipeline(pipelineId, {
|
|
359
|
+
stages: pipeline.stages.filter((s) => s.id !== stageId),
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function updateStage(
|
|
364
|
+
pipelineId: string,
|
|
365
|
+
stageId: string,
|
|
366
|
+
updates: Partial<Omit<PipelineStage, 'id'>>
|
|
367
|
+
): Pipeline | null {
|
|
368
|
+
const pipeline = getPipeline(pipelineId);
|
|
369
|
+
if (!pipeline) return null;
|
|
370
|
+
|
|
371
|
+
const stages = pipeline.stages.map((s) =>
|
|
372
|
+
s.id === stageId ? { ...s, ...updates } : s
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
return updatePipeline(pipelineId, { stages });
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export function reorderStages(
|
|
379
|
+
pipelineId: string,
|
|
380
|
+
stageIds: string[]
|
|
381
|
+
): Pipeline | null {
|
|
382
|
+
const pipeline = getPipeline(pipelineId);
|
|
383
|
+
if (!pipeline) return null;
|
|
384
|
+
|
|
385
|
+
const stageMap = new Map(pipeline.stages.map((s) => [s.id, s]));
|
|
386
|
+
const reordered = stageIds
|
|
387
|
+
.map((id) => stageMap.get(id))
|
|
388
|
+
.filter((s): s is PipelineStage => s !== undefined);
|
|
389
|
+
|
|
390
|
+
if (reordered.length !== pipeline.stages.length) return null;
|
|
391
|
+
|
|
392
|
+
return updatePipeline(pipelineId, { stages: reordered });
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ============================================================================
|
|
396
|
+
// Step Operations
|
|
397
|
+
// ============================================================================
|
|
398
|
+
|
|
399
|
+
export function addStep(
|
|
400
|
+
pipelineId: string,
|
|
401
|
+
stageId: string,
|
|
402
|
+
config: StepConfig,
|
|
403
|
+
name?: string
|
|
404
|
+
): Pipeline | null {
|
|
405
|
+
const pipeline = getPipeline(pipelineId);
|
|
406
|
+
if (!pipeline) return null;
|
|
407
|
+
|
|
408
|
+
const step: PipelineStep = {
|
|
409
|
+
id: crypto.randomUUID(),
|
|
410
|
+
name: name || `Step ${pipeline.stages.find((s) => s.id === stageId)?.steps.length || 0 + 1}`,
|
|
411
|
+
config,
|
|
412
|
+
status: 'pending',
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const stages = pipeline.stages.map((s) =>
|
|
416
|
+
s.id === stageId ? { ...s, steps: [...s.steps, step] } : s
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
return updatePipeline(pipelineId, { stages });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export function removeStep(
|
|
423
|
+
pipelineId: string,
|
|
424
|
+
stageId: string,
|
|
425
|
+
stepId: string
|
|
426
|
+
): Pipeline | null {
|
|
427
|
+
const pipeline = getPipeline(pipelineId);
|
|
428
|
+
if (!pipeline) return null;
|
|
429
|
+
|
|
430
|
+
const stages = pipeline.stages.map((s) =>
|
|
431
|
+
s.id === stageId
|
|
432
|
+
? { ...s, steps: s.steps.filter((st) => st.id !== stepId) }
|
|
433
|
+
: s
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
return updatePipeline(pipelineId, { stages });
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export function updateStep(
|
|
440
|
+
pipelineId: string,
|
|
441
|
+
stepId: string,
|
|
442
|
+
updates: Partial<Omit<PipelineStep, 'id'>>
|
|
443
|
+
): Pipeline | null {
|
|
444
|
+
const pipeline = getPipeline(pipelineId);
|
|
445
|
+
if (!pipeline) return null;
|
|
446
|
+
|
|
447
|
+
const stages = pipeline.stages.map((stage) => ({
|
|
448
|
+
...stage,
|
|
449
|
+
steps: stage.steps.map((step) =>
|
|
450
|
+
step.id === stepId ? { ...step, ...updates } : step
|
|
451
|
+
),
|
|
452
|
+
}));
|
|
453
|
+
|
|
454
|
+
return updatePipeline(pipelineId, { stages });
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function updateStepConfig(
|
|
458
|
+
pipelineId: string,
|
|
459
|
+
stepId: string,
|
|
460
|
+
config: StepConfig
|
|
461
|
+
): Pipeline | null {
|
|
462
|
+
return updateStep(pipelineId, stepId, { config });
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ============================================================================
|
|
466
|
+
// Execution State Operations
|
|
467
|
+
// ============================================================================
|
|
468
|
+
|
|
469
|
+
export function setStepStatus(
|
|
470
|
+
pipelineId: string,
|
|
471
|
+
stepId: string,
|
|
472
|
+
status: PipelineStepStatus,
|
|
473
|
+
error?: string
|
|
474
|
+
): Pipeline | null {
|
|
475
|
+
const updates: Partial<PipelineStep> = { status };
|
|
476
|
+
if (status === 'running') {
|
|
477
|
+
updates.startedAt = new Date().toISOString();
|
|
478
|
+
// Clear previous error and completion when starting fresh
|
|
479
|
+
updates.error = undefined;
|
|
480
|
+
updates.completedAt = undefined;
|
|
481
|
+
}
|
|
482
|
+
if (status === 'completed' || status === 'failed') {
|
|
483
|
+
updates.completedAt = new Date().toISOString();
|
|
484
|
+
}
|
|
485
|
+
if (error) {
|
|
486
|
+
updates.error = error;
|
|
487
|
+
}
|
|
488
|
+
return updateStep(pipelineId, stepId, updates);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export function setStepArtifact(
|
|
492
|
+
pipelineId: string,
|
|
493
|
+
stepId: string,
|
|
494
|
+
artifact: StepArtifact
|
|
495
|
+
): Pipeline | null {
|
|
496
|
+
return updateStep(pipelineId, stepId, { artifact });
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export function setPipelineStatus(
|
|
500
|
+
pipelineId: string,
|
|
501
|
+
status: PipelineStatus
|
|
502
|
+
): Pipeline | null {
|
|
503
|
+
return updatePipeline(pipelineId, { status });
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function advanceStage(pipelineId: string): Pipeline | null {
|
|
507
|
+
const pipeline = getPipeline(pipelineId);
|
|
508
|
+
if (!pipeline) return null;
|
|
509
|
+
|
|
510
|
+
return updatePipeline(pipelineId, {
|
|
511
|
+
currentStageIndex: pipeline.currentStageIndex + 1,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Delete council deliberation data (ledger, context, decisions) for steps
|
|
517
|
+
* that are about to be reset. Without this, the UI shows stale entries
|
|
518
|
+
* from the previous run mixed with the new one.
|
|
519
|
+
*/
|
|
520
|
+
function purgeCouncilsForSteps(steps: PipelineStep[]): void {
|
|
521
|
+
for (const step of steps) {
|
|
522
|
+
const councilId = step.artifact?.metadata?.councilId;
|
|
523
|
+
if (councilId) {
|
|
524
|
+
try {
|
|
525
|
+
deleteCouncilWithData(councilId);
|
|
526
|
+
} catch (err) {
|
|
527
|
+
console.warn('[PipelineStore] Failed to delete council data:', councilId, err);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export function resetExecution(pipelineId: string): Pipeline | null {
|
|
534
|
+
const pipeline = getPipeline(pipelineId);
|
|
535
|
+
if (!pipeline) return null;
|
|
536
|
+
|
|
537
|
+
// Delete all council deliberation data before resetting steps
|
|
538
|
+
const allSteps = pipeline.stages.flatMap((s) => s.steps);
|
|
539
|
+
purgeCouncilsForSteps(allSteps);
|
|
540
|
+
|
|
541
|
+
const stages = pipeline.stages.map((stage) => ({
|
|
542
|
+
...stage,
|
|
543
|
+
steps: stage.steps.map((step) => ({
|
|
544
|
+
...step,
|
|
545
|
+
status: 'pending' as const,
|
|
546
|
+
artifact: undefined,
|
|
547
|
+
error: undefined,
|
|
548
|
+
startedAt: undefined,
|
|
549
|
+
completedAt: undefined,
|
|
550
|
+
})),
|
|
551
|
+
}));
|
|
552
|
+
|
|
553
|
+
return updatePipeline(pipelineId, {
|
|
554
|
+
stages,
|
|
555
|
+
status: 'draft',
|
|
556
|
+
currentStageIndex: 0,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Reset a specific step and all subsequent steps to pending.
|
|
562
|
+
* Sets currentStageIndex to the target step's stage so the executor
|
|
563
|
+
* resumes from the right point. Earlier completed steps are preserved.
|
|
564
|
+
*/
|
|
565
|
+
export function resetStepAndAfter(pipelineId: string, stepId: string): Pipeline | null {
|
|
566
|
+
const pipeline = getPipeline(pipelineId);
|
|
567
|
+
if (!pipeline) return null;
|
|
568
|
+
|
|
569
|
+
// Find the step's stage index and step index
|
|
570
|
+
let targetStageIndex = -1;
|
|
571
|
+
let targetStepIndex = -1;
|
|
572
|
+
for (let si = 0; si < pipeline.stages.length; si++) {
|
|
573
|
+
for (let sti = 0; sti < pipeline.stages[si].steps.length; sti++) {
|
|
574
|
+
if (pipeline.stages[si].steps[sti].id === stepId) {
|
|
575
|
+
targetStageIndex = si;
|
|
576
|
+
targetStepIndex = sti;
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (targetStageIndex >= 0) break;
|
|
581
|
+
}
|
|
582
|
+
if (targetStageIndex < 0) return null;
|
|
583
|
+
|
|
584
|
+
// Delete council deliberation data for steps being reset
|
|
585
|
+
const stepsToReset: PipelineStep[] = [];
|
|
586
|
+
for (let si = targetStageIndex; si < pipeline.stages.length; si++) {
|
|
587
|
+
for (let sti = 0; sti < pipeline.stages[si].steps.length; sti++) {
|
|
588
|
+
if (si > targetStageIndex || sti >= targetStepIndex) {
|
|
589
|
+
stepsToReset.push(pipeline.stages[si].steps[sti]);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
purgeCouncilsForSteps(stepsToReset);
|
|
594
|
+
|
|
595
|
+
const stages = pipeline.stages.map((stage, si) => {
|
|
596
|
+
if (si < targetStageIndex) return stage; // earlier stages untouched
|
|
597
|
+
return {
|
|
598
|
+
...stage,
|
|
599
|
+
steps: stage.steps.map((step, sti) => {
|
|
600
|
+
// In the target stage: reset this step and all after it
|
|
601
|
+
// In later stages: reset everything
|
|
602
|
+
if (si > targetStageIndex || sti >= targetStepIndex) {
|
|
603
|
+
return {
|
|
604
|
+
...step,
|
|
605
|
+
status: 'pending' as const,
|
|
606
|
+
artifact: undefined,
|
|
607
|
+
error: undefined,
|
|
608
|
+
startedAt: undefined,
|
|
609
|
+
completedAt: undefined,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
return step;
|
|
613
|
+
}),
|
|
614
|
+
};
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
return updatePipeline(pipelineId, {
|
|
618
|
+
stages,
|
|
619
|
+
status: 'ready',
|
|
620
|
+
currentStageIndex: targetStageIndex,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// ============================================================================
|
|
625
|
+
// Store Class (for React integration)
|
|
626
|
+
// ============================================================================
|
|
627
|
+
|
|
628
|
+
export class PipelineStore {
|
|
629
|
+
private listeners: Set<() => void> = new Set();
|
|
630
|
+
|
|
631
|
+
subscribe(listener: () => void): () => void {
|
|
632
|
+
this.listeners.add(listener);
|
|
633
|
+
return () => this.listeners.delete(listener);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
private notify(): void {
|
|
637
|
+
this.listeners.forEach((listener) => listener());
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
getAll = getAllPipelines;
|
|
641
|
+
get = getPipeline;
|
|
642
|
+
|
|
643
|
+
create(params: Parameters<typeof createPipeline>[0]): Pipeline {
|
|
644
|
+
const pipeline = createPipeline(params);
|
|
645
|
+
this.notify();
|
|
646
|
+
return pipeline;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
update(id: string, updates: Parameters<typeof updatePipeline>[1]): Pipeline | null {
|
|
650
|
+
const pipeline = updatePipeline(id, updates);
|
|
651
|
+
if (pipeline) this.notify();
|
|
652
|
+
return pipeline;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
delete(id: string): boolean {
|
|
656
|
+
const success = deletePipeline(id);
|
|
657
|
+
if (success) this.notify();
|
|
658
|
+
return success;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
duplicate(id: string, newName?: string): Pipeline | null {
|
|
662
|
+
const pipeline = duplicatePipeline(id, newName);
|
|
663
|
+
if (pipeline) this.notify();
|
|
664
|
+
return pipeline;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
addStage(pipelineId: string, name?: string, atIndex?: number): Pipeline | null {
|
|
668
|
+
const pipeline = addStage(pipelineId, name, atIndex);
|
|
669
|
+
if (pipeline) this.notify();
|
|
670
|
+
return pipeline;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
removeStage(pipelineId: string, stageId: string): Pipeline | null {
|
|
674
|
+
const pipeline = removeStage(pipelineId, stageId);
|
|
675
|
+
if (pipeline) this.notify();
|
|
676
|
+
return pipeline;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
updateStage(pipelineId: string, stageId: string, updates: Partial<Omit<PipelineStage, 'id'>>): Pipeline | null {
|
|
680
|
+
const pipeline = updateStage(pipelineId, stageId, updates);
|
|
681
|
+
if (pipeline) this.notify();
|
|
682
|
+
return pipeline;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
reorderStages(pipelineId: string, stageIds: string[]): Pipeline | null {
|
|
686
|
+
const pipeline = reorderStages(pipelineId, stageIds);
|
|
687
|
+
if (pipeline) this.notify();
|
|
688
|
+
return pipeline;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
addStep(pipelineId: string, stageId: string, config: StepConfig, name?: string): Pipeline | null {
|
|
692
|
+
const pipeline = addStep(pipelineId, stageId, config, name);
|
|
693
|
+
if (pipeline) this.notify();
|
|
694
|
+
return pipeline;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
removeStep(pipelineId: string, stageId: string, stepId: string): Pipeline | null {
|
|
698
|
+
const pipeline = removeStep(pipelineId, stageId, stepId);
|
|
699
|
+
if (pipeline) this.notify();
|
|
700
|
+
return pipeline;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
updateStep(pipelineId: string, stepId: string, updates: Partial<Omit<PipelineStep, 'id'>>): Pipeline | null {
|
|
704
|
+
const pipeline = updateStep(pipelineId, stepId, updates);
|
|
705
|
+
if (pipeline) this.notify();
|
|
706
|
+
return pipeline;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
updateStepConfig(pipelineId: string, stepId: string, config: StepConfig): Pipeline | null {
|
|
710
|
+
const pipeline = updateStepConfig(pipelineId, stepId, config);
|
|
711
|
+
if (pipeline) this.notify();
|
|
712
|
+
return pipeline;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
setStepStatus(pipelineId: string, stepId: string, status: PipelineStepStatus, error?: string): Pipeline | null {
|
|
716
|
+
const pipeline = setStepStatus(pipelineId, stepId, status, error);
|
|
717
|
+
if (pipeline) this.notify();
|
|
718
|
+
return pipeline;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
setStepArtifact(pipelineId: string, stepId: string, artifact: StepArtifact): Pipeline | null {
|
|
722
|
+
const pipeline = setStepArtifact(pipelineId, stepId, artifact);
|
|
723
|
+
if (pipeline) this.notify();
|
|
724
|
+
return pipeline;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
setPipelineStatus(pipelineId: string, status: PipelineStatus): Pipeline | null {
|
|
728
|
+
const pipeline = setPipelineStatus(pipelineId, status);
|
|
729
|
+
if (pipeline) this.notify();
|
|
730
|
+
return pipeline;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
advanceStage(pipelineId: string): Pipeline | null {
|
|
734
|
+
const pipeline = advanceStage(pipelineId);
|
|
735
|
+
if (pipeline) this.notify();
|
|
736
|
+
return pipeline;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
resetExecution(pipelineId: string): Pipeline | null {
|
|
740
|
+
const pipeline = resetExecution(pipelineId);
|
|
741
|
+
if (pipeline) this.notify();
|
|
742
|
+
return pipeline;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
resetStepAndAfter(pipelineId: string, stepId: string): Pipeline | null {
|
|
746
|
+
const pipeline = resetStepAndAfter(pipelineId, stepId);
|
|
747
|
+
if (pipeline) this.notify();
|
|
748
|
+
return pipeline;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Singleton instance
|
|
753
|
+
export const pipelineStore = new PipelineStore();
|