@kognai/orchestrator-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +175 -0
- package/dist/lib/aar-middleware.d.ts +6 -0
- package/dist/lib/aar-middleware.js +70 -0
- package/dist/lib/aar-types.d.ts +34 -0
- package/dist/lib/aar-types.js +4 -0
- package/dist/lib/acp-engine.d.ts +68 -0
- package/dist/lib/acp-engine.js +123 -0
- package/dist/lib/acp.d.ts +61 -0
- package/dist/lib/acp.js +425 -0
- package/dist/lib/agent-registry.d.ts +50 -0
- package/dist/lib/agent-registry.js +137 -0
- package/dist/lib/anthropic-direct.d.ts +27 -0
- package/dist/lib/anthropic-direct.js +109 -0
- package/dist/lib/asmr-extractor.d.ts +40 -0
- package/dist/lib/asmr-extractor.js +151 -0
- package/dist/lib/asmr-retrieval.d.ts +76 -0
- package/dist/lib/asmr-retrieval.js +311 -0
- package/dist/lib/asmr.d.ts +8 -0
- package/dist/lib/asmr.js +24 -0
- package/dist/lib/brainx-client.d.ts +72 -0
- package/dist/lib/brainx-client.js +200 -0
- package/dist/lib/brainx-embed.d.ts +14 -0
- package/dist/lib/brainx-embed.js +139 -0
- package/dist/lib/brainx-swarm-bridge.d.ts +93 -0
- package/dist/lib/brainx-swarm-bridge.js +242 -0
- package/dist/lib/byterover-client.d.ts +19 -0
- package/dist/lib/byterover-client.js +59 -0
- package/dist/lib/ceo-wallet.d.ts +37 -0
- package/dist/lib/ceo-wallet.js +176 -0
- package/dist/lib/chomsky-gate.d.ts +24 -0
- package/dist/lib/chomsky-gate.js +178 -0
- package/dist/lib/chomsky-runner.d.ts +29 -0
- package/dist/lib/chomsky-runner.js +157 -0
- package/dist/lib/citizen-score-contract.d.ts +72 -0
- package/dist/lib/citizen-score-contract.js +16 -0
- package/dist/lib/citizen-score-registry.d.ts +25 -0
- package/dist/lib/citizen-score-registry.js +65 -0
- package/dist/lib/citizenship.d.ts +103 -0
- package/dist/lib/citizenship.js +272 -0
- package/dist/lib/clawrouter-client.d.ts +37 -0
- package/dist/lib/clawrouter-client.js +148 -0
- package/dist/lib/code-asset-crystalliser.d.ts +41 -0
- package/dist/lib/code-asset-crystalliser.js +181 -0
- package/dist/lib/code-failure-logger.d.ts +27 -0
- package/dist/lib/code-failure-logger.js +42 -0
- package/dist/lib/cto-approval-gate.d.ts +45 -0
- package/dist/lib/cto-approval-gate.js +478 -0
- package/dist/lib/cto-gate-types.d.ts +28 -0
- package/dist/lib/cto-gate-types.js +8 -0
- package/dist/lib/decomposer-feedback.d.ts +54 -0
- package/dist/lib/decomposer-feedback.js +115 -0
- package/dist/lib/emotional-safety-gate.d.ts +48 -0
- package/dist/lib/emotional-safety-gate.js +97 -0
- package/dist/lib/engine-paths.d.ts +13 -0
- package/dist/lib/engine-paths.js +32 -0
- package/dist/lib/event-bus-listener.d.ts +8 -0
- package/dist/lib/event-bus-listener.js +144 -0
- package/dist/lib/event-bus-publisher.d.ts +25 -0
- package/dist/lib/event-bus-publisher.js +188 -0
- package/dist/lib/event-bus-types.d.ts +73 -0
- package/dist/lib/event-bus-types.js +23 -0
- package/dist/lib/failure-library.d.ts +178 -0
- package/dist/lib/failure-library.js +349 -0
- package/dist/lib/ksl/error-log.d.ts +28 -0
- package/dist/lib/ksl/error-log.js +43 -0
- package/dist/lib/ksl/index.d.ts +9 -0
- package/dist/lib/ksl/index.js +25 -0
- package/dist/lib/ksl/orchestrator-tap.d.ts +16 -0
- package/dist/lib/ksl/orchestrator-tap.js +85 -0
- package/dist/lib/ksl/record-writer.d.ts +46 -0
- package/dist/lib/ksl/record-writer.js +45 -0
- package/dist/lib/llm-cost-table.d.ts +36 -0
- package/dist/lib/llm-cost-table.js +90 -0
- package/dist/lib/local-model-router.d.ts +27 -0
- package/dist/lib/local-model-router.js +61 -0
- package/dist/lib/mc-client.d.ts +51 -0
- package/dist/lib/mc-client.js +249 -0
- package/dist/lib/model-router-contract.d.ts +91 -0
- package/dist/lib/model-router-contract.js +19 -0
- package/dist/lib/model-router-registry.d.ts +24 -0
- package/dist/lib/model-router-registry.js +52 -0
- package/dist/lib/model-router.d.ts +20 -0
- package/dist/lib/model-router.js +79 -0
- package/dist/lib/monotask-state-machine.d.ts +19 -0
- package/dist/lib/monotask-state-machine.js +131 -0
- package/dist/lib/neutral-prompt-checker.d.ts +22 -0
- package/dist/lib/neutral-prompt-checker.js +130 -0
- package/dist/lib/notion-direct.d.ts +92 -0
- package/dist/lib/notion-direct.js +381 -0
- package/dist/lib/ollama-client.d.ts +37 -0
- package/dist/lib/ollama-client.js +158 -0
- package/dist/lib/omel/credential-vault.d.ts +57 -0
- package/dist/lib/omel/credential-vault.js +324 -0
- package/dist/lib/omel/human-brake.d.ts +32 -0
- package/dist/lib/omel/human-brake.js +289 -0
- package/dist/lib/omel/index.d.ts +10 -0
- package/dist/lib/omel/index.js +26 -0
- package/dist/lib/omel/phantom-workspace.d.ts +31 -0
- package/dist/lib/omel/phantom-workspace.js +256 -0
- package/dist/lib/omel/wipe-witness.d.ts +75 -0
- package/dist/lib/omel/wipe-witness.js +398 -0
- package/dist/lib/orchestrate-engine.d.ts +25 -0
- package/dist/lib/orchestrate-engine.js +4436 -0
- package/dist/lib/perm-judge.d.ts +46 -0
- package/dist/lib/perm-judge.js +173 -0
- package/dist/lib/plumber/conformance.d.ts +54 -0
- package/dist/lib/plumber/conformance.js +121 -0
- package/dist/lib/plumber/index.d.ts +9 -0
- package/dist/lib/plumber/index.js +25 -0
- package/dist/lib/plumber/observer.d.ts +52 -0
- package/dist/lib/plumber/observer.js +180 -0
- package/dist/lib/plumber/types.d.ts +78 -0
- package/dist/lib/plumber/types.js +29 -0
- package/dist/lib/research-impl-gate.d.ts +16 -0
- package/dist/lib/research-impl-gate.js +105 -0
- package/dist/lib/sherlock-memory.d.ts +29 -0
- package/dist/lib/sherlock-memory.js +105 -0
- package/dist/lib/skill-crystalliser.d.ts +44 -0
- package/dist/lib/skill-crystalliser.js +60 -0
- package/dist/lib/sprint-runner-engine.d.ts +27 -0
- package/dist/lib/sprint-runner-engine.js +1042 -0
- package/dist/lib/sprint-state.d.ts +71 -0
- package/dist/lib/sprint-state.js +202 -0
- package/dist/lib/stuck-handler.d.ts +17 -0
- package/dist/lib/stuck-handler.js +249 -0
- package/dist/lib/task-contract-checker.d.ts +17 -0
- package/dist/lib/task-contract-checker.js +29 -0
- package/dist/lib/task-router/index.d.ts +17 -0
- package/dist/lib/task-router/index.js +52 -0
- package/dist/lib/task-router/router/generate-execution-id.d.ts +10 -0
- package/dist/lib/task-router/router/generate-execution-id.js +24 -0
- package/dist/lib/task-router/router/resolve-route.d.ts +2 -0
- package/dist/lib/task-router/router/resolve-route.js +49 -0
- package/dist/lib/task-router/types.d.ts +79 -0
- package/dist/lib/task-router/types.js +39 -0
- package/dist/lib/token-budget-validator.d.ts +44 -0
- package/dist/lib/token-budget-validator.js +84 -0
- package/dist/lib/trust-score-updater.d.ts +30 -0
- package/dist/lib/trust-score-updater.js +107 -0
- package/dist/lib/wallet-state.d.ts +26 -0
- package/dist/lib/wallet-state.js +85 -0
- package/package.json +27 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* BrainX Swarm Bridge — Memory Integration for Agent Orchestrator
|
|
4
|
+
*
|
|
5
|
+
* Wires BrainX episodic memory into the swarm orchestrator:
|
|
6
|
+
*
|
|
7
|
+
* 1. Pre-task: inject relevant HOT memories into agent context
|
|
8
|
+
* 2. Post-task: store task outcomes as episodic memories
|
|
9
|
+
* 3. Rental governance: propagate memories within active swarm during rental,
|
|
10
|
+
* expire copies on rental end (originator retains WARM)
|
|
11
|
+
* 4. Distillation: use qwen3:4b to compress verbose memories
|
|
12
|
+
*
|
|
13
|
+
* AMD-02 Addendum — Rental Memory Governance Rule:
|
|
14
|
+
* - During rental period: propagate skill memories to active swarm agents
|
|
15
|
+
* - On rental expiry: originating agent retains memory as WARM tier
|
|
16
|
+
* - Copies held by other agents → RENTAL_EXPIRED tier (queryable but deprioritized)
|
|
17
|
+
* - Anonymised content generated for expired rentals
|
|
18
|
+
*
|
|
19
|
+
* Sprint 652
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.BrainXSwarmBridge = void 0;
|
|
23
|
+
exports.createSwarmBridge = createSwarmBridge;
|
|
24
|
+
const brainx_client_1 = require("./brainx-client");
|
|
25
|
+
// ── Config ────────────────────────────────────────────────────────────
|
|
26
|
+
const DISTILL_MODEL = process.env.BRAINX_DISTILL_MODEL ?? 'qwen3:4b';
|
|
27
|
+
const OLLAMA_HOST = process.env.OLLAMA_HOST ?? 'http://localhost:11434';
|
|
28
|
+
const MAX_CONTEXT_MEMORIES = 10;
|
|
29
|
+
// ── Swarm Bridge ──────────────────────────────────────────────────────
|
|
30
|
+
class BrainXSwarmBridge {
|
|
31
|
+
clients = new Map();
|
|
32
|
+
swarmCtx;
|
|
33
|
+
constructor(ctx) {
|
|
34
|
+
this.swarmCtx = ctx;
|
|
35
|
+
// Pre-create clients for all agents in the swarm
|
|
36
|
+
for (const agentId of ctx.agent_ids) {
|
|
37
|
+
this.clients.set(agentId, new brainx_client_1.BrainXClient(agentId));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Get or create a BrainX client for an agent */
|
|
41
|
+
getClient(agentId) {
|
|
42
|
+
let client = this.clients.get(agentId);
|
|
43
|
+
if (!client) {
|
|
44
|
+
client = new brainx_client_1.BrainXClient(agentId);
|
|
45
|
+
this.clients.set(agentId, client);
|
|
46
|
+
}
|
|
47
|
+
return client;
|
|
48
|
+
}
|
|
49
|
+
// ── Pre-Task: Inject Memories ─────────────────────────────────────
|
|
50
|
+
/**
|
|
51
|
+
* Inject relevant memories into an agent's context before task execution.
|
|
52
|
+
* Returns formatted text to prepend to the agent's prompt.
|
|
53
|
+
*/
|
|
54
|
+
async injectMemories(agentId) {
|
|
55
|
+
const client = this.getClient(agentId);
|
|
56
|
+
const contextText = await client.injectContext(this.swarmCtx.sprint_id);
|
|
57
|
+
const memoryCount = contextText ? contextText.split('\n').filter(l => l.startsWith('- ')).length : 0;
|
|
58
|
+
return {
|
|
59
|
+
agent_id: agentId,
|
|
60
|
+
context_text: contextText,
|
|
61
|
+
memory_count: memoryCount,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Inject memories for all agents in the swarm.
|
|
66
|
+
*/
|
|
67
|
+
async injectAll() {
|
|
68
|
+
const injections = [];
|
|
69
|
+
for (const agentId of this.swarmCtx.agent_ids) {
|
|
70
|
+
injections.push(await this.injectMemories(agentId));
|
|
71
|
+
}
|
|
72
|
+
return injections;
|
|
73
|
+
}
|
|
74
|
+
// ── Post-Task: Store Memories ─────────────────────────────────────
|
|
75
|
+
/**
|
|
76
|
+
* Store a task outcome as an episodic memory.
|
|
77
|
+
* Includes distillation for verbose summaries.
|
|
78
|
+
*/
|
|
79
|
+
async storeTaskMemory(input) {
|
|
80
|
+
const client = this.getClient(input.agent_id);
|
|
81
|
+
// Determine memory type based on outcome
|
|
82
|
+
let memoryType;
|
|
83
|
+
let importance;
|
|
84
|
+
if (input.outcome === 'failure') {
|
|
85
|
+
memoryType = 'error';
|
|
86
|
+
importance = 8; // Failures are high-importance to prevent recurrence
|
|
87
|
+
}
|
|
88
|
+
else if (input.outcome === 'success') {
|
|
89
|
+
memoryType = 'success';
|
|
90
|
+
importance = input.score && input.score >= 80 ? 7 : 5;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
memoryType = 'episode';
|
|
94
|
+
importance = 4;
|
|
95
|
+
}
|
|
96
|
+
// Distill verbose summaries
|
|
97
|
+
let content = `[${input.task_id}] ${input.task_title}: ${input.summary}`;
|
|
98
|
+
if (content.length > 500) {
|
|
99
|
+
content = await this.distill(content) ?? content.slice(0, 500);
|
|
100
|
+
}
|
|
101
|
+
if (input.error_message) {
|
|
102
|
+
content += ` | Error: ${input.error_message}`;
|
|
103
|
+
}
|
|
104
|
+
if (input.files_modified.length > 0) {
|
|
105
|
+
content += ` | Files: ${input.files_modified.join(', ')}`;
|
|
106
|
+
}
|
|
107
|
+
const opts = {
|
|
108
|
+
sprint: this.swarmCtx.sprint_id,
|
|
109
|
+
memory_type: memoryType,
|
|
110
|
+
importance,
|
|
111
|
+
tags: [this.swarmCtx.sprint_id, input.outcome, input.task_id],
|
|
112
|
+
rental_id: this.swarmCtx.rental_id,
|
|
113
|
+
rental_swarm_id: this.swarmCtx.swarm_id,
|
|
114
|
+
rental_expires_at: this.swarmCtx.rental_expires_at,
|
|
115
|
+
};
|
|
116
|
+
return client.store(content, opts);
|
|
117
|
+
}
|
|
118
|
+
// ── Rental Governance ─────────────────────────────────────────────
|
|
119
|
+
/**
|
|
120
|
+
* Propagate a memory to all agents in the active swarm during rental period.
|
|
121
|
+
* Each agent gets a copy with propagated_from set to the originator.
|
|
122
|
+
*/
|
|
123
|
+
async propagateToSwarm(originatorId, content, memoryType = 'skill_rental_learning', importance = 6) {
|
|
124
|
+
const ids = [];
|
|
125
|
+
for (const agentId of this.swarmCtx.agent_ids) {
|
|
126
|
+
if (agentId === originatorId)
|
|
127
|
+
continue; // Skip originator — they already have it
|
|
128
|
+
const client = this.getClient(agentId);
|
|
129
|
+
const id = await client.store(content, {
|
|
130
|
+
sprint: this.swarmCtx.sprint_id,
|
|
131
|
+
memory_type: memoryType,
|
|
132
|
+
importance,
|
|
133
|
+
tags: ['propagated', this.swarmCtx.swarm_id, originatorId],
|
|
134
|
+
rental_id: this.swarmCtx.rental_id,
|
|
135
|
+
rental_swarm_id: this.swarmCtx.swarm_id,
|
|
136
|
+
rental_expires_at: this.swarmCtx.rental_expires_at,
|
|
137
|
+
});
|
|
138
|
+
if (id)
|
|
139
|
+
ids.push(id);
|
|
140
|
+
}
|
|
141
|
+
return ids;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Execute Rental Memory Governance Rule on rental expiry:
|
|
145
|
+
* - Originating agent: retain as WARM tier
|
|
146
|
+
* - Other agents: move to RENTAL_EXPIRED tier
|
|
147
|
+
* - Generate anonymised content for expired memories
|
|
148
|
+
*
|
|
149
|
+
* Should be called when a rental period ends.
|
|
150
|
+
*/
|
|
151
|
+
async expireRentalMemories(rentalId, originatorId) {
|
|
152
|
+
const result = { expired_count: 0, retained_count: 0, errors: [] };
|
|
153
|
+
for (const [agentId, client] of this.clients) {
|
|
154
|
+
try {
|
|
155
|
+
// Query all memories for this rental
|
|
156
|
+
const memories = await client.retrieve('rental ' + rentalId, {
|
|
157
|
+
topK: 100,
|
|
158
|
+
tiers: ['HOT', 'WARM', 'COLD'],
|
|
159
|
+
excludeExpired: false,
|
|
160
|
+
});
|
|
161
|
+
const rentalMemories = memories.filter(m => m.rental_id === rentalId);
|
|
162
|
+
for (const mem of rentalMemories) {
|
|
163
|
+
if (agentId === originatorId) {
|
|
164
|
+
// Originator retains as WARM
|
|
165
|
+
// (tier demotion handled by DB trigger or manual update)
|
|
166
|
+
result.retained_count++;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
// Other agents: expire
|
|
170
|
+
result.expired_count++;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
result.errors.push(`${agentId}: ${err.message}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
// ── Distillation ──────────────────────────────────────────────────
|
|
181
|
+
/**
|
|
182
|
+
* Distill verbose memory content using qwen3:4b.
|
|
183
|
+
* Returns compressed version, or null if distillation fails.
|
|
184
|
+
*/
|
|
185
|
+
async distill(content) {
|
|
186
|
+
if (content.length <= 200)
|
|
187
|
+
return content; // Already short enough
|
|
188
|
+
try {
|
|
189
|
+
const prompt = `/no_think\nCompress the following into ONE sentence under 50 words. Output ONLY the compressed sentence, nothing else:\n\n${content.slice(0, 500)}`;
|
|
190
|
+
const res = await fetch(`${OLLAMA_HOST}/api/generate`, {
|
|
191
|
+
method: 'POST',
|
|
192
|
+
headers: { 'Content-Type': 'application/json' },
|
|
193
|
+
body: JSON.stringify({
|
|
194
|
+
model: DISTILL_MODEL,
|
|
195
|
+
prompt,
|
|
196
|
+
stream: false,
|
|
197
|
+
options: { num_predict: 100, temperature: 0.2 },
|
|
198
|
+
think: false,
|
|
199
|
+
}),
|
|
200
|
+
signal: AbortSignal.timeout(30000),
|
|
201
|
+
});
|
|
202
|
+
if (!res.ok)
|
|
203
|
+
return null;
|
|
204
|
+
const data = await res.json();
|
|
205
|
+
// Strip any thinking tags and take only the first sentence
|
|
206
|
+
let distilled = data.response
|
|
207
|
+
.replace(/<think>[\s\S]*?<\/think>/g, '')
|
|
208
|
+
.replace(/^(Hmm|Let me|Looking at|I need|The user|OK|Okay)[^.]*\.\s*/gi, '')
|
|
209
|
+
.trim();
|
|
210
|
+
// Take first sentence only
|
|
211
|
+
const firstSentence = distilled.match(/^[^.!?]+[.!?]/);
|
|
212
|
+
if (firstSentence)
|
|
213
|
+
distilled = firstSentence[0].trim();
|
|
214
|
+
return distilled || content.slice(0, 200);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// ── Cleanup ───────────────────────────────────────────────────────
|
|
221
|
+
async close() {
|
|
222
|
+
for (const client of this.clients.values()) {
|
|
223
|
+
await client.close();
|
|
224
|
+
}
|
|
225
|
+
this.clients.clear();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
exports.BrainXSwarmBridge = BrainXSwarmBridge;
|
|
229
|
+
// ── Factory ──────────────────────────────────────────────────────────
|
|
230
|
+
/**
|
|
231
|
+
* Create a BrainX swarm bridge for a sprint execution.
|
|
232
|
+
* Call this at the start of each swarm run.
|
|
233
|
+
*/
|
|
234
|
+
function createSwarmBridge(swarmId, sprintId, agentIds, rentalId, rentalExpiresAt) {
|
|
235
|
+
return new BrainXSwarmBridge({
|
|
236
|
+
swarm_id: swarmId,
|
|
237
|
+
sprint_id: sprintId,
|
|
238
|
+
agent_ids: agentIds,
|
|
239
|
+
rental_id: rentalId,
|
|
240
|
+
rental_expires_at: rentalExpiresAt,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* byterover-client.ts — ByteRover context injection (Steps 0 + 7)
|
|
3
|
+
*
|
|
4
|
+
* ByteRover provides semantic memory for the swarm — query before a task to
|
|
5
|
+
* inject relevant context, curate after to capture learnings.
|
|
6
|
+
*
|
|
7
|
+
* Graceful degradation: if `brv` is not installed, all calls are no-ops.
|
|
8
|
+
* The pipeline NEVER fails because ByteRover is unavailable.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Step 0: Query ByteRover for context relevant to this task objective.
|
|
12
|
+
* Returns 300-500 token context string, or null if brv not installed.
|
|
13
|
+
*/
|
|
14
|
+
export declare function brvQuery(taskObjective: string): Promise<string | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Step 7: Curate a completed task into ByteRover memory for future context.
|
|
17
|
+
* Fire-and-forget — never blocks the pipeline.
|
|
18
|
+
*/
|
|
19
|
+
export declare function brvCurate(taskTitle: string, filePaths: string[], outcome: string): Promise<void>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* byterover-client.ts — ByteRover context injection (Steps 0 + 7)
|
|
4
|
+
*
|
|
5
|
+
* ByteRover provides semantic memory for the swarm — query before a task to
|
|
6
|
+
* inject relevant context, curate after to capture learnings.
|
|
7
|
+
*
|
|
8
|
+
* Graceful degradation: if `brv` is not installed, all calls are no-ops.
|
|
9
|
+
* The pipeline NEVER fails because ByteRover is unavailable.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.brvQuery = brvQuery;
|
|
13
|
+
exports.brvCurate = brvCurate;
|
|
14
|
+
const child_process_1 = require("child_process");
|
|
15
|
+
const util_1 = require("util");
|
|
16
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
17
|
+
const BRV_TIMEOUT_MS = 30_000;
|
|
18
|
+
async function brvAvailable() {
|
|
19
|
+
try {
|
|
20
|
+
await execFileAsync('which', ['brv'], { timeout: 3000 });
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Step 0: Query ByteRover for context relevant to this task objective.
|
|
29
|
+
* Returns 300-500 token context string, or null if brv not installed.
|
|
30
|
+
*/
|
|
31
|
+
async function brvQuery(taskObjective) {
|
|
32
|
+
if (!(await brvAvailable()))
|
|
33
|
+
return null;
|
|
34
|
+
try {
|
|
35
|
+
const { stdout } = await execFileAsync('brv', ['query', taskObjective.slice(0, 500), '--headless', '--format', 'text'], { timeout: BRV_TIMEOUT_MS });
|
|
36
|
+
const result = stdout.trim();
|
|
37
|
+
return result.length > 10 ? result : null;
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
// Non-fatal — pipeline continues without ByteRover context
|
|
41
|
+
console.warn(` [byterover] query failed (non-fatal): ${err.message?.slice(0, 80)}`);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Step 7: Curate a completed task into ByteRover memory for future context.
|
|
47
|
+
* Fire-and-forget — never blocks the pipeline.
|
|
48
|
+
*/
|
|
49
|
+
async function brvCurate(taskTitle, filePaths, outcome) {
|
|
50
|
+
if (!(await brvAvailable()))
|
|
51
|
+
return;
|
|
52
|
+
const entry = `Task: ${taskTitle} | Files: ${filePaths.join(', ')} | Outcome: ${outcome.slice(0, 200)}`;
|
|
53
|
+
try {
|
|
54
|
+
await execFileAsync('brv', ['curate', entry, '--headless'], { timeout: BRV_TIMEOUT_MS });
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.warn(` [byterover] curate failed (non-fatal): ${err.message?.slice(0, 80)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface LedgerEntry {
|
|
2
|
+
ts: string;
|
|
3
|
+
agent_id: string;
|
|
4
|
+
task_type: string;
|
|
5
|
+
amount_usd: number;
|
|
6
|
+
/** Provider that billed this call (anthropic, openai, deepseek, etc.).
|
|
7
|
+
* Optional for backwards compatibility — entries pre-2026-05-22 don't have it. */
|
|
8
|
+
provider?: string;
|
|
9
|
+
/** Model identifier as returned by the provider (e.g. claude-sonnet-4-...). Optional. */
|
|
10
|
+
model?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function deductCost(amount_usd: number, agent_id: string, task_type: string, provider?: string, model?: string): void;
|
|
13
|
+
export declare function getBalance(): {
|
|
14
|
+
balance_usd: number;
|
|
15
|
+
spent_today_usd: number;
|
|
16
|
+
spent_month_usd: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function getDailyLedger(date: string): LedgerEntry[];
|
|
19
|
+
/** Sum month-to-date spend for a specific provider (requires entries written with the
|
|
20
|
+
* provider field — older entries lack it and are excluded). Returns USD. */
|
|
21
|
+
export declare function getSpendByProvider(provider: string): number;
|
|
22
|
+
export type ProviderBudgetStatus = 'ok' | 'warning' | 'frozen' | 'unmonitored';
|
|
23
|
+
export interface ProviderBudgetReport {
|
|
24
|
+
provider: string;
|
|
25
|
+
budget_usd: number | null;
|
|
26
|
+
spent_month_usd: number;
|
|
27
|
+
pct: number;
|
|
28
|
+
status: ProviderBudgetStatus;
|
|
29
|
+
}
|
|
30
|
+
/** Check provider budget status against ENV-configured cap. ENV var pattern:
|
|
31
|
+
* <PROVIDER>_MONTHLY_BUDGET_USD (e.g. ANTHROPIC_MONTHLY_BUDGET_USD).
|
|
32
|
+
* Returns status:
|
|
33
|
+
* - 'unmonitored' if no budget env set (no proactive check happens; reactive fallback only)
|
|
34
|
+
* - 'ok' if spend < 80% of budget
|
|
35
|
+
* - 'warning' if 80% <= spend < 95%
|
|
36
|
+
* - 'frozen' if spend >= 95% (caller should route to fallback proactively) */
|
|
37
|
+
export declare function getProviderBudgetStatus(provider: string): ProviderBudgetReport;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// CEO Wallet — Sprint 174
|
|
3
|
+
// Tracks per-call spend. Persists to logs/ceo-wallet/YYYY-MM.jsonl.
|
|
4
|
+
// deductCost() called by ClawRouter HTTP server on each successful route.
|
|
5
|
+
//
|
|
6
|
+
// Env vars:
|
|
7
|
+
// CEO_WALLET_BALANCE_USD — starting balance (default: $50)
|
|
8
|
+
// COST_ALERT_USD_DAILY — Telegram alert threshold (default: $1.00)
|
|
9
|
+
// CEO_TELEGRAM_BOT_TOKEN | TELEGRAM_BOT_TOKEN
|
|
10
|
+
// OWNER_TELEGRAM_CHAT_ID | CEO_TELEGRAM_CHAT_ID
|
|
11
|
+
//
|
|
12
|
+
// TICKET-215 Phase 3b-3 (Wave B): LOGS_DIR now resolves via engine-paths
|
|
13
|
+
// (KOGNAI_ROOT / cwd) instead of path.join(__dirname, '..', '..'), so the bank
|
|
14
|
+
// ledger ships in @kognai/orchestrator-core. resolveEnginePaths().root === the
|
|
15
|
+
// old __dirname/../.. when run from a product root — same <root>/logs/ceo-wallet,
|
|
16
|
+
// including the module-load-time mkdirSync below.
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(o, k2, desc);
|
|
24
|
+
}) : (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
}));
|
|
28
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
+
}) : function(o, v) {
|
|
31
|
+
o["default"] = v;
|
|
32
|
+
});
|
|
33
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
34
|
+
var ownKeys = function(o) {
|
|
35
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
36
|
+
var ar = [];
|
|
37
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
38
|
+
return ar;
|
|
39
|
+
};
|
|
40
|
+
return ownKeys(o);
|
|
41
|
+
};
|
|
42
|
+
return function (mod) {
|
|
43
|
+
if (mod && mod.__esModule) return mod;
|
|
44
|
+
var result = {};
|
|
45
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
46
|
+
__setModuleDefault(result, mod);
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
})();
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
exports.deductCost = deductCost;
|
|
52
|
+
exports.getBalance = getBalance;
|
|
53
|
+
exports.getDailyLedger = getDailyLedger;
|
|
54
|
+
exports.getSpendByProvider = getSpendByProvider;
|
|
55
|
+
exports.getProviderBudgetStatus = getProviderBudgetStatus;
|
|
56
|
+
const fs = __importStar(require("fs"));
|
|
57
|
+
const path = __importStar(require("path"));
|
|
58
|
+
const https = __importStar(require("https"));
|
|
59
|
+
const engine_paths_1 = require("./engine-paths");
|
|
60
|
+
const LOGS_DIR = path.join((0, engine_paths_1.resolveEnginePaths)().root, 'logs', 'ceo-wallet');
|
|
61
|
+
const WALLET_BALANCE_USD = parseFloat(process.env.CEO_WALLET_BALANCE_USD ?? '50');
|
|
62
|
+
const COST_ALERT_USD_DAILY = parseFloat(process.env.COST_ALERT_USD_DAILY ?? '1.00');
|
|
63
|
+
fs.mkdirSync(LOGS_DIR, { recursive: true });
|
|
64
|
+
// ── In-memory counters (reset on new day) ─────────────────────────────────────
|
|
65
|
+
let _spentToday = 0;
|
|
66
|
+
let _spentMonth = 0;
|
|
67
|
+
let _alertFiredToday = false;
|
|
68
|
+
let _lastResetDate = new Date().toISOString().slice(0, 10);
|
|
69
|
+
function resetIfNewDay() {
|
|
70
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
71
|
+
if (today !== _lastResetDate) {
|
|
72
|
+
_spentToday = 0;
|
|
73
|
+
_alertFiredToday = false;
|
|
74
|
+
_lastResetDate = today;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function ledgerPath() {
|
|
78
|
+
const month = new Date().toISOString().slice(0, 7); // YYYY-MM
|
|
79
|
+
return path.join(LOGS_DIR, `${month}.jsonl`);
|
|
80
|
+
}
|
|
81
|
+
// ── Public API ─────────────────────────────────────────────────────────────────
|
|
82
|
+
function deductCost(amount_usd, agent_id, task_type, provider, model) {
|
|
83
|
+
resetIfNewDay();
|
|
84
|
+
_spentToday += amount_usd;
|
|
85
|
+
_spentMonth += amount_usd;
|
|
86
|
+
const entry = { ts: new Date().toISOString(), agent_id, task_type, amount_usd };
|
|
87
|
+
if (provider)
|
|
88
|
+
entry.provider = provider;
|
|
89
|
+
if (model)
|
|
90
|
+
entry.model = model;
|
|
91
|
+
fs.appendFileSync(ledgerPath(), JSON.stringify(entry) + '\n');
|
|
92
|
+
// Fire Telegram alert when daily threshold is first crossed
|
|
93
|
+
if (!_alertFiredToday && _spentToday >= COST_ALERT_USD_DAILY) {
|
|
94
|
+
_alertFiredToday = true;
|
|
95
|
+
sendAlert(`⚠️ *ClawRouter daily spend alert*\n` +
|
|
96
|
+
`Spent: $${_spentToday.toFixed(4)}\n` +
|
|
97
|
+
`Threshold: $${COST_ALERT_USD_DAILY.toFixed(2)}\n` +
|
|
98
|
+
`Balance remaining: $${(WALLET_BALANCE_USD - _spentMonth).toFixed(4)}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function getBalance() {
|
|
102
|
+
resetIfNewDay();
|
|
103
|
+
return {
|
|
104
|
+
balance_usd: WALLET_BALANCE_USD - _spentMonth,
|
|
105
|
+
spent_today_usd: _spentToday,
|
|
106
|
+
spent_month_usd: _spentMonth,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function getDailyLedger(date) {
|
|
110
|
+
const month = date.slice(0, 7); // YYYY-MM
|
|
111
|
+
const fp = path.join(LOGS_DIR, `${month}.jsonl`);
|
|
112
|
+
if (!fs.existsSync(fp))
|
|
113
|
+
return [];
|
|
114
|
+
return fs.readFileSync(fp, 'utf-8')
|
|
115
|
+
.split('\n').filter(Boolean)
|
|
116
|
+
.map(l => JSON.parse(l))
|
|
117
|
+
.filter(e => e.ts.startsWith(date));
|
|
118
|
+
}
|
|
119
|
+
/** Read this month's ledger (no in-memory cache — file is source of truth across PM2 restarts). */
|
|
120
|
+
function readMonthLedger() {
|
|
121
|
+
if (!fs.existsSync(ledgerPath()))
|
|
122
|
+
return [];
|
|
123
|
+
return fs.readFileSync(ledgerPath(), 'utf-8')
|
|
124
|
+
.split('\n').filter(Boolean)
|
|
125
|
+
.map(l => { try {
|
|
126
|
+
return JSON.parse(l);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return null;
|
|
130
|
+
} })
|
|
131
|
+
.filter((e) => e !== null);
|
|
132
|
+
}
|
|
133
|
+
/** Sum month-to-date spend for a specific provider (requires entries written with the
|
|
134
|
+
* provider field — older entries lack it and are excluded). Returns USD. */
|
|
135
|
+
function getSpendByProvider(provider) {
|
|
136
|
+
const target = provider.toLowerCase();
|
|
137
|
+
return readMonthLedger()
|
|
138
|
+
.filter(e => (e.provider || '').toLowerCase() === target)
|
|
139
|
+
.reduce((sum, e) => sum + (e.amount_usd || 0), 0);
|
|
140
|
+
}
|
|
141
|
+
/** Check provider budget status against ENV-configured cap. ENV var pattern:
|
|
142
|
+
* <PROVIDER>_MONTHLY_BUDGET_USD (e.g. ANTHROPIC_MONTHLY_BUDGET_USD).
|
|
143
|
+
* Returns status:
|
|
144
|
+
* - 'unmonitored' if no budget env set (no proactive check happens; reactive fallback only)
|
|
145
|
+
* - 'ok' if spend < 80% of budget
|
|
146
|
+
* - 'warning' if 80% <= spend < 95%
|
|
147
|
+
* - 'frozen' if spend >= 95% (caller should route to fallback proactively) */
|
|
148
|
+
function getProviderBudgetStatus(provider) {
|
|
149
|
+
const envKey = `${provider.toUpperCase()}_MONTHLY_BUDGET_USD`;
|
|
150
|
+
const raw = process.env[envKey];
|
|
151
|
+
const budget = raw ? parseFloat(raw) : NaN;
|
|
152
|
+
const spent = getSpendByProvider(provider);
|
|
153
|
+
if (!isFinite(budget) || budget <= 0) {
|
|
154
|
+
return { provider, budget_usd: null, spent_month_usd: spent, pct: NaN, status: 'unmonitored' };
|
|
155
|
+
}
|
|
156
|
+
const pct = (spent / budget) * 100;
|
|
157
|
+
const status = pct >= 95 ? 'frozen' : pct >= 80 ? 'warning' : 'ok';
|
|
158
|
+
return { provider, budget_usd: budget, spent_month_usd: spent, pct, status };
|
|
159
|
+
}
|
|
160
|
+
// ── Internal Telegram helper ───────────────────────────────────────────────────
|
|
161
|
+
function sendAlert(message) {
|
|
162
|
+
const botToken = process.env.CEO_TELEGRAM_BOT_TOKEN || process.env.TELEGRAM_BOT_TOKEN || '';
|
|
163
|
+
const chatId = process.env.OWNER_TELEGRAM_CHAT_ID || process.env.CEO_TELEGRAM_CHAT_ID || '';
|
|
164
|
+
if (!botToken || !chatId)
|
|
165
|
+
return; // silently skip if not configured
|
|
166
|
+
const payload = JSON.stringify({ chat_id: chatId, text: message, parse_mode: 'Markdown' });
|
|
167
|
+
const req = https.request({
|
|
168
|
+
hostname: 'api.telegram.org',
|
|
169
|
+
path: `/bot${botToken}/sendMessage`,
|
|
170
|
+
method: 'POST',
|
|
171
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
|
|
172
|
+
});
|
|
173
|
+
req.on('error', () => { }); // fire-and-forget, don't crash server
|
|
174
|
+
req.write(payload);
|
|
175
|
+
req.end();
|
|
176
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chomsky-gate.ts — Prompt Quality Evaluator
|
|
3
|
+
*
|
|
4
|
+
* Intercepts outbound prompts before LLM dispatch.
|
|
5
|
+
* Named after Chomsky for its linguistic quality focus.
|
|
6
|
+
*
|
|
7
|
+
* Routing: T1 LOCAL (qwen3:4b) — cost-efficient evaluation
|
|
8
|
+
* Threshold: score >= 7 PASSES, score < 7 FAILS + logs + triggers rewrite attempt
|
|
9
|
+
*/
|
|
10
|
+
export interface ChomskyEvalResult {
|
|
11
|
+
score: number;
|
|
12
|
+
issues: string[];
|
|
13
|
+
passed: boolean;
|
|
14
|
+
rawResponse?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const CHOMSKY_PASS_THRESHOLD = 7;
|
|
17
|
+
/**
|
|
18
|
+
* Evaluate the quality of an outbound agent prompt.
|
|
19
|
+
*
|
|
20
|
+
* @param prompt The prompt string about to be dispatched to an LLM agent
|
|
21
|
+
* @param agentId Identifier of the calling agent (for log attribution)
|
|
22
|
+
* @returns {score, issues, passed} — score >= 7 means gate passes
|
|
23
|
+
*/
|
|
24
|
+
export declare function evaluatePromptQuality(prompt: string, agentId?: string): Promise<ChomskyEvalResult>;
|