@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,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session persistence — save/load sessions under `.kondi-chat/sessions/`.
|
|
3
|
+
*
|
|
4
|
+
* Single SessionStore. Two constants (AUTO_SAVE_MS, MAX_AGE_DAYS). Atomic
|
|
5
|
+
* writes via temp+rename. The ledger files stay flat in storageDir as they
|
|
6
|
+
* are today — see specs/06-session-resume/SPEC.md for the rationale.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync,
|
|
11
|
+
renameSync, rmSync, statSync, unlinkSync,
|
|
12
|
+
} from 'node:fs';
|
|
13
|
+
import { dirname, join } from 'node:path';
|
|
14
|
+
import type { Session } from '../types.ts';
|
|
15
|
+
|
|
16
|
+
export const AUTO_SAVE_MS = 30_000;
|
|
17
|
+
export const MAX_AGE_DAYS = 30;
|
|
18
|
+
const MAX_AGE_MS = MAX_AGE_DAYS * 24 * 60 * 60 * 1000;
|
|
19
|
+
|
|
20
|
+
export interface PersistedSession {
|
|
21
|
+
version: 1;
|
|
22
|
+
session: Session;
|
|
23
|
+
activeProfile: string;
|
|
24
|
+
overrideModel?: string;
|
|
25
|
+
lastSavedAt: string;
|
|
26
|
+
workingDirectory: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SessionIndexEntry {
|
|
30
|
+
id: string;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
workingDirectory: string;
|
|
34
|
+
messageCount: number;
|
|
35
|
+
totalCostUsd: number;
|
|
36
|
+
activeModel?: string;
|
|
37
|
+
profile: string;
|
|
38
|
+
summary: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function atomicWrite(path: string, data: string): void {
|
|
42
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
43
|
+
const tmp = path + '.tmp';
|
|
44
|
+
writeFileSync(tmp, data);
|
|
45
|
+
try { renameSync(tmp, path); } catch { writeFileSync(path, data); }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class SessionStore {
|
|
49
|
+
private sessionsDir: string;
|
|
50
|
+
private indexPath: string;
|
|
51
|
+
private activePath: string;
|
|
52
|
+
private index: SessionIndexEntry[];
|
|
53
|
+
|
|
54
|
+
constructor(storageDir: string) {
|
|
55
|
+
this.sessionsDir = join(storageDir, 'sessions');
|
|
56
|
+
this.indexPath = join(this.sessionsDir, 'index.json');
|
|
57
|
+
this.activePath = join(this.sessionsDir, 'active.json');
|
|
58
|
+
mkdirSync(this.sessionsDir, { recursive: true });
|
|
59
|
+
this.index = this.loadIndex();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
save(session: Session, profile: string, overrideModel?: string): void {
|
|
63
|
+
const persisted: PersistedSession = {
|
|
64
|
+
version: 1,
|
|
65
|
+
session,
|
|
66
|
+
activeProfile: profile,
|
|
67
|
+
overrideModel,
|
|
68
|
+
lastSavedAt: new Date().toISOString(),
|
|
69
|
+
workingDirectory: session.workingDirectory || '',
|
|
70
|
+
};
|
|
71
|
+
atomicWrite(
|
|
72
|
+
join(this.sessionsDir, `${session.id}.json`),
|
|
73
|
+
JSON.stringify(persisted, null, 2),
|
|
74
|
+
);
|
|
75
|
+
this.updateIndex({
|
|
76
|
+
id: session.id,
|
|
77
|
+
createdAt: session.createdAt,
|
|
78
|
+
updatedAt: new Date().toISOString(),
|
|
79
|
+
workingDirectory: session.workingDirectory || '',
|
|
80
|
+
messageCount: session.messages.length,
|
|
81
|
+
totalCostUsd: session.totalCostUsd,
|
|
82
|
+
activeModel: session.activeModel,
|
|
83
|
+
profile,
|
|
84
|
+
summary: this.computeSummary(session),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
load(idOrPrefix: string): PersistedSession | null {
|
|
89
|
+
const id = this.resolveId(idOrPrefix);
|
|
90
|
+
if (!id) return null;
|
|
91
|
+
const file = join(this.sessionsDir, `${id}.json`);
|
|
92
|
+
if (!existsSync(file)) return null;
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(readFileSync(file, 'utf-8')) as PersistedSession;
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
loadLatest(workingDirectory?: string): PersistedSession | null {
|
|
101
|
+
const entries = this.list(workingDirectory);
|
|
102
|
+
if (entries.length === 0) return null;
|
|
103
|
+
return this.load(entries[0].id);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
list(workingDirectory?: string): SessionIndexEntry[] {
|
|
107
|
+
const entries = workingDirectory
|
|
108
|
+
? this.index.filter(e => e.workingDirectory === workingDirectory)
|
|
109
|
+
: [...this.index];
|
|
110
|
+
return entries.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
delete(id: string): void {
|
|
114
|
+
const resolved = this.resolveId(id) || id;
|
|
115
|
+
const file = join(this.sessionsDir, `${resolved}.json`);
|
|
116
|
+
if (existsSync(file)) { try { unlinkSync(file); } catch { /* ignore */ } }
|
|
117
|
+
this.index = this.index.filter(e => e.id !== resolved);
|
|
118
|
+
this.saveIndex();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Delete sessions older than MAX_AGE_DAYS. Returns count removed. */
|
|
122
|
+
cleanup(): { deleted: number } {
|
|
123
|
+
const cutoff = Date.now() - MAX_AGE_MS;
|
|
124
|
+
let deleted = 0;
|
|
125
|
+
for (const entry of [...this.index]) {
|
|
126
|
+
const updated = new Date(entry.updatedAt).getTime();
|
|
127
|
+
if (!isFinite(updated) || updated < cutoff) {
|
|
128
|
+
this.delete(entry.id);
|
|
129
|
+
deleted++;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { deleted };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
setActive(id: string): void {
|
|
136
|
+
atomicWrite(this.activePath, JSON.stringify({ id }));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
getActive(): string | null {
|
|
140
|
+
if (!existsSync(this.activePath)) return null;
|
|
141
|
+
try { return JSON.parse(readFileSync(this.activePath, 'utf-8')).id || null; }
|
|
142
|
+
catch { return null; }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
format(workingDirectory?: string): string {
|
|
146
|
+
const entries = this.list(workingDirectory);
|
|
147
|
+
if (entries.length === 0) return 'No sessions yet.';
|
|
148
|
+
const lines = ['Sessions (newest first):'];
|
|
149
|
+
for (const e of entries.slice(0, 20)) {
|
|
150
|
+
lines.push(
|
|
151
|
+
` ${e.id.slice(0, 8)} ${e.updatedAt.slice(0, 19)} ${e.messageCount}msg $${e.totalCostUsd.toFixed(4)}`
|
|
152
|
+
+ (e.summary ? `\n ${e.summary}` : ''),
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (entries.length > 20) lines.push(` ... and ${entries.length - 20} more`);
|
|
156
|
+
return lines.join('\n');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Match by exact id, or ≥8-char prefix when unambiguous. */
|
|
160
|
+
private resolveId(idOrPrefix: string): string | null {
|
|
161
|
+
if (this.index.some(e => e.id === idOrPrefix)) return idOrPrefix;
|
|
162
|
+
if (idOrPrefix.length < 8) return null;
|
|
163
|
+
const matches = this.index.filter(e => e.id.startsWith(idOrPrefix));
|
|
164
|
+
return matches.length === 1 ? matches[0].id : null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private updateIndex(entry: SessionIndexEntry): void {
|
|
168
|
+
const existing = this.index.findIndex(e => e.id === entry.id);
|
|
169
|
+
if (existing >= 0) this.index[existing] = entry;
|
|
170
|
+
else this.index.push(entry);
|
|
171
|
+
this.saveIndex();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private loadIndex(): SessionIndexEntry[] {
|
|
175
|
+
if (!existsSync(this.indexPath)) return [];
|
|
176
|
+
try {
|
|
177
|
+
const raw = JSON.parse(readFileSync(this.indexPath, 'utf-8'));
|
|
178
|
+
return Array.isArray(raw?.sessions) ? raw.sessions : [];
|
|
179
|
+
} catch { return []; }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private saveIndex(): void {
|
|
183
|
+
atomicWrite(this.indexPath, JSON.stringify({ sessions: this.index }, null, 2));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Spec 13 — save in-progress assistant content so a crash leaves a recoverable trail. */
|
|
187
|
+
savePartialMessage(sessionId: string, content: string): void {
|
|
188
|
+
const dir = join(this.sessionsDir, '..', 'recovery');
|
|
189
|
+
const path = join(dir, `${sessionId}-partial.json`);
|
|
190
|
+
atomicWrite(path, JSON.stringify({ sessionId, content, savedAt: new Date().toISOString() }));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Spec 13 — read any partial from a prior run; null if none. */
|
|
194
|
+
checkForRecovery(sessionId: string): { content: string; savedAt: string } | null {
|
|
195
|
+
const path = join(this.sessionsDir, '..', 'recovery', `${sessionId}-partial.json`);
|
|
196
|
+
if (!existsSync(path)) return null;
|
|
197
|
+
try { return JSON.parse(readFileSync(path, 'utf-8')); } catch { return null; }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Delete the partial file once the session has integrated it. */
|
|
201
|
+
clearRecovery(sessionId: string): void {
|
|
202
|
+
const path = join(this.sessionsDir, '..', 'recovery', `${sessionId}-partial.json`);
|
|
203
|
+
if (existsSync(path)) { try { unlinkSync(path); } catch { /* ignore */ } }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private computeSummary(session: Session): string {
|
|
207
|
+
if (session.state.goal) return session.state.goal.slice(0, 120);
|
|
208
|
+
const firstUser = session.messages.find(m => m.role === 'user');
|
|
209
|
+
return firstUser ? firstUser.content.slice(0, 120) : '';
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Mock LLM for tests.
|
|
3
|
+
*
|
|
4
|
+
* Pass a pre-canned queue of LLMResponses; each call dequeues the next.
|
|
5
|
+
* Tests that exercise multi-turn tool loops can enqueue the full sequence.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { LLMRequest, LLMResponse } from '../types.ts';
|
|
9
|
+
|
|
10
|
+
export interface MockLLMOptions {
|
|
11
|
+
responses: Array<Partial<LLMResponse>>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface MockLLM {
|
|
15
|
+
call: (req: LLMRequest) => Promise<LLMResponse>;
|
|
16
|
+
calls: LLMRequest[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createMockLLM(opts: MockLLMOptions): MockLLM {
|
|
20
|
+
const queue = [...opts.responses];
|
|
21
|
+
const calls: LLMRequest[] = [];
|
|
22
|
+
return {
|
|
23
|
+
calls,
|
|
24
|
+
async call(req: LLMRequest): Promise<LLMResponse> {
|
|
25
|
+
calls.push(req);
|
|
26
|
+
const next = queue.shift();
|
|
27
|
+
if (!next) throw new Error('Mock LLM: no more responses queued');
|
|
28
|
+
return {
|
|
29
|
+
content: next.content ?? '',
|
|
30
|
+
model: next.model ?? req.model ?? 'mock',
|
|
31
|
+
provider: next.provider ?? req.provider ?? 'anthropic',
|
|
32
|
+
inputTokens: next.inputTokens ?? 10,
|
|
33
|
+
outputTokens: next.outputTokens ?? 20,
|
|
34
|
+
latencyMs: next.latencyMs ?? 1,
|
|
35
|
+
toolCalls: next.toolCalls,
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for kondi-chat
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Provider & Model
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
export type ProviderId =
|
|
10
|
+
| 'anthropic'
|
|
11
|
+
| 'openai'
|
|
12
|
+
| 'deepseek'
|
|
13
|
+
| 'google'
|
|
14
|
+
| 'xai'
|
|
15
|
+
| 'zai'
|
|
16
|
+
| 'ollama'
|
|
17
|
+
| 'nvidia-router';
|
|
18
|
+
|
|
19
|
+
export interface ProviderConfig {
|
|
20
|
+
id: ProviderId;
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
baseUrl?: string;
|
|
23
|
+
defaultModel?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ModelInfo {
|
|
27
|
+
id: string;
|
|
28
|
+
provider: ProviderId;
|
|
29
|
+
contextWindow: number;
|
|
30
|
+
maxOutputTokens: number;
|
|
31
|
+
inputCostPer1M: number;
|
|
32
|
+
outputCostPer1M: number;
|
|
33
|
+
supportsCaching?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Messages
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
export interface Message {
|
|
41
|
+
role: 'user' | 'assistant' | 'system';
|
|
42
|
+
content: string;
|
|
43
|
+
timestamp: string;
|
|
44
|
+
model?: string;
|
|
45
|
+
provider?: ProviderId;
|
|
46
|
+
tokenCount?: number;
|
|
47
|
+
inputTokens?: number;
|
|
48
|
+
outputTokens?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Repo Map — structured codebase summary, created once per repo
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
export interface RepoMap {
|
|
56
|
+
stack: string[];
|
|
57
|
+
entrypoints: string[];
|
|
58
|
+
subsystems: Array<{ name: string; paths: string[]; purpose: string }>;
|
|
59
|
+
commands: { build?: string; test?: string; lint?: string; typecheck?: string };
|
|
60
|
+
conventions: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Session State — durable conversation memory
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
export interface SessionState {
|
|
68
|
+
goal: string;
|
|
69
|
+
decisions: string[];
|
|
70
|
+
constraints: string[];
|
|
71
|
+
currentPlan: string[];
|
|
72
|
+
activeTaskId?: string;
|
|
73
|
+
recentFailures: string[];
|
|
74
|
+
lastUpdatedAtTurn: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Task Card — bounded work packet for execution
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Task kinds are open-ended. Defaults: implementation, fix, refactor, test, analysis.
|
|
83
|
+
* Users/plugins can add domain-specific kinds: robot-control, image-generation, etc.
|
|
84
|
+
* The router learns to route new kinds through training data.
|
|
85
|
+
*/
|
|
86
|
+
export type TaskKind = string;
|
|
87
|
+
|
|
88
|
+
export interface TaskCard {
|
|
89
|
+
id: string;
|
|
90
|
+
kind: TaskKind;
|
|
91
|
+
goal: string;
|
|
92
|
+
relevantFiles: string[];
|
|
93
|
+
constraints: string[];
|
|
94
|
+
acceptanceCriteria: string[];
|
|
95
|
+
outputMode: 'diff' | 'file_replacements' | 'text';
|
|
96
|
+
failures: number;
|
|
97
|
+
createdAt: string;
|
|
98
|
+
completedAt?: string;
|
|
99
|
+
status: 'pending' | 'executing' | 'verifying' | 'passed' | 'failed' | 'promoted';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Creative Generation
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
export interface CreativeGenerationRequest {
|
|
107
|
+
description: string;
|
|
108
|
+
images?: string[]; // Base64 encoded images
|
|
109
|
+
style?: string; // e.g., "technical", "narrative", "visual", etc.
|
|
110
|
+
constraints?: string[]; // Any specific requirements
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface CreativeGenerationResponse {
|
|
114
|
+
content: string; // The generated creative content
|
|
115
|
+
type: 'text' | 'code' | 'structured' | 'mixed';
|
|
116
|
+
metadata?: {
|
|
117
|
+
model: string;
|
|
118
|
+
tokens?: number;
|
|
119
|
+
confidence?: number;
|
|
120
|
+
suggestions?: string[]; // Additional ideas or variations
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Verification
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
export interface VerificationResult {
|
|
129
|
+
passed: boolean;
|
|
130
|
+
testOutput?: string;
|
|
131
|
+
lintOutput?: string;
|
|
132
|
+
typecheckOutput?: string;
|
|
133
|
+
error?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// Audit Ledger — every LLM call recorded
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
export type LedgerPhase =
|
|
141
|
+
| 'consult' // consultant expert — domain-specialized persona
|
|
142
|
+
| 'discuss' // frontier: user conversation
|
|
143
|
+
| 'commit' // system: state update
|
|
144
|
+
| 'dispatch' // frontier: task card creation
|
|
145
|
+
| 'execute' // worker: code generation
|
|
146
|
+
| 'verify' // local: tests/lint/typecheck
|
|
147
|
+
| 'reflect' // frontier: summarize results
|
|
148
|
+
| 'compress' // cheap: context compression
|
|
149
|
+
| 'state_update'; // cheap: working state update
|
|
150
|
+
|
|
151
|
+
export interface LedgerEntry {
|
|
152
|
+
id: string;
|
|
153
|
+
timestamp: string;
|
|
154
|
+
phase: LedgerPhase;
|
|
155
|
+
provider: ProviderId;
|
|
156
|
+
model: string;
|
|
157
|
+
inputTokens: number;
|
|
158
|
+
outputTokens: number;
|
|
159
|
+
latencyMs: number;
|
|
160
|
+
costUsd: number;
|
|
161
|
+
cached: boolean;
|
|
162
|
+
/** Input tokens served from the provider's prompt cache (included in inputTokens). */
|
|
163
|
+
cachedInputTokens?: number;
|
|
164
|
+
|
|
165
|
+
/** What was sent (system + user prompt) */
|
|
166
|
+
promptSummary: string;
|
|
167
|
+
/** What came back */
|
|
168
|
+
responseSummary: string;
|
|
169
|
+
|
|
170
|
+
/** Associated task card ID, if any */
|
|
171
|
+
taskId?: string;
|
|
172
|
+
/** Was this a promotion (retry with better model)? */
|
|
173
|
+
promoted?: boolean;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Session
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
export interface Session {
|
|
181
|
+
id: string;
|
|
182
|
+
createdAt: string;
|
|
183
|
+
workingDirectory?: string;
|
|
184
|
+
|
|
185
|
+
/** Full message history — never truncated, used for export/replay */
|
|
186
|
+
messages: Message[];
|
|
187
|
+
|
|
188
|
+
/** Durable session state */
|
|
189
|
+
state: SessionState;
|
|
190
|
+
|
|
191
|
+
/** Repo map — structured codebase summary */
|
|
192
|
+
repoMap?: RepoMap;
|
|
193
|
+
|
|
194
|
+
/** Raw grounding context for prompt assembly */
|
|
195
|
+
groundingContext?: string;
|
|
196
|
+
|
|
197
|
+
/** All task cards created during this session */
|
|
198
|
+
tasks: TaskCard[];
|
|
199
|
+
|
|
200
|
+
/** Active provider/model (can change mid-session) */
|
|
201
|
+
activeProvider: ProviderId;
|
|
202
|
+
activeModel?: string;
|
|
203
|
+
|
|
204
|
+
/** Cumulative cost tracking */
|
|
205
|
+
totalInputTokens: number;
|
|
206
|
+
totalOutputTokens: number;
|
|
207
|
+
totalCostUsd: number;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
// Context Budget
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
export interface ContextSection {
|
|
215
|
+
key: string;
|
|
216
|
+
content: string;
|
|
217
|
+
priority: number;
|
|
218
|
+
compressible: boolean;
|
|
219
|
+
tokenEstimate: number;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// Tool Use
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
export interface ToolDefinition {
|
|
227
|
+
name: string;
|
|
228
|
+
description: string;
|
|
229
|
+
parameters: Record<string, unknown>; // JSON Schema
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export interface ToolCall {
|
|
233
|
+
id: string;
|
|
234
|
+
name: string;
|
|
235
|
+
arguments: Record<string, unknown>;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface ToolResult {
|
|
239
|
+
toolCallId: string;
|
|
240
|
+
content: string;
|
|
241
|
+
isError?: boolean;
|
|
242
|
+
/** Spec 03 — unified diff, when the tool mutated a file. */
|
|
243
|
+
diff?: string;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
// LLM Call — multi-turn message for agent loops
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
/** Spec 09 — multimodal content part. Text remains the default. */
|
|
251
|
+
export type ContentPart =
|
|
252
|
+
| { type: 'text'; text: string }
|
|
253
|
+
| { type: 'image'; mimeType: string; base64: string };
|
|
254
|
+
|
|
255
|
+
export interface LLMMessage {
|
|
256
|
+
role: 'user' | 'assistant' | 'tool';
|
|
257
|
+
content?: string;
|
|
258
|
+
/** Hidden chain-of-thought from reasoning models. Must be passed back
|
|
259
|
+
* to some providers (DeepSeek) in multi-turn conversations. */
|
|
260
|
+
reasoningContent?: string;
|
|
261
|
+
/** Spec 09 — interleaved text/image parts; providers that support it use this. */
|
|
262
|
+
parts?: ContentPart[];
|
|
263
|
+
toolCalls?: ToolCall[]; // assistant messages with tool use
|
|
264
|
+
toolResults?: ToolResult[]; // tool-result messages
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/** Spec 09 — image attachment descriptor (used by submit command + pipeline). */
|
|
268
|
+
export interface ImageAttachment {
|
|
269
|
+
mimeType: string;
|
|
270
|
+
base64: string;
|
|
271
|
+
originalPath?: string;
|
|
272
|
+
sizeBytes: number;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface LLMRequest {
|
|
276
|
+
provider: ProviderId;
|
|
277
|
+
model?: string;
|
|
278
|
+
systemPrompt: string;
|
|
279
|
+
/** Simple single-turn: set userMessage */
|
|
280
|
+
userMessage?: string;
|
|
281
|
+
/** Multi-turn agent loop: set messages instead */
|
|
282
|
+
messages?: LLMMessage[];
|
|
283
|
+
tools?: ToolDefinition[];
|
|
284
|
+
maxOutputTokens?: number;
|
|
285
|
+
temperature?: number;
|
|
286
|
+
cacheablePrefix?: string;
|
|
287
|
+
/** Stream the response token by token */
|
|
288
|
+
stream?: boolean;
|
|
289
|
+
/** Callback for each streamed token chunk */
|
|
290
|
+
onToken?: (token: string) => void;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export interface LLMResponse {
|
|
294
|
+
content: string;
|
|
295
|
+
model: string;
|
|
296
|
+
provider: ProviderId;
|
|
297
|
+
inputTokens: number;
|
|
298
|
+
outputTokens: number;
|
|
299
|
+
latencyMs: number;
|
|
300
|
+
cached?: boolean;
|
|
301
|
+
toolCalls?: ToolCall[];
|
|
302
|
+
/** True if this response came from a fallback model, not the originally requested one */
|
|
303
|
+
wasFallback?: boolean;
|
|
304
|
+
/** The originally requested model (if different from the responding model) */
|
|
305
|
+
requestedModel?: string;
|
|
306
|
+
/** Spec 14 — raw response headers for rate-limit parsing. */
|
|
307
|
+
responseHeaders?: Record<string, string>;
|
|
308
|
+
/**
|
|
309
|
+
* Hidden chain-of-thought emitted by reasoning models (GLM-5.x, OpenAI o-series,
|
|
310
|
+
* DeepSeek-R1, Anthropic extended thinking). Billed as output tokens but not
|
|
311
|
+
* shown inline; the TUI exposes it via Ctrl+R.
|
|
312
|
+
*/
|
|
313
|
+
reasoningContent?: string;
|
|
314
|
+
/**
|
|
315
|
+
* Portion of inputTokens that the provider served from its prompt cache
|
|
316
|
+
* (OpenAI `prompt_tokens_details.cached_tokens`, Anthropic
|
|
317
|
+
* `cache_read_input_tokens`). Billed at reduced rate — our cost estimate
|
|
318
|
+
* subtracts 50% of the standard input price on the cached portion, which
|
|
319
|
+
* matches the published discount on both OpenAI and Z.AI endpoints.
|
|
320
|
+
*/
|
|
321
|
+
cachedInputTokens?: number;
|
|
322
|
+
}
|