@ch4p/cli 0.1.3 → 0.1.5
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/dist/agent-6WIHK7NM.js +767 -0
- package/dist/agent-ANIZYPPF.js +767 -0
- package/dist/agent-HSAJ5EBN.js +761 -0
- package/dist/agent-N2P2SXGG.js +767 -0
- package/dist/audit-HLOQBMBT.js +12 -0
- package/dist/audit-UIGPH3FK.js +12 -0
- package/dist/canvas-3VTC4XPV.js +313 -0
- package/dist/canvas-4FMNW6FZ.js +313 -0
- package/dist/canvas-HJSLG76B.js +313 -0
- package/dist/canvas-XQHVCY27.js +313 -0
- package/dist/chunk-3XAW4XHG.js +185 -0
- package/dist/chunk-4IRZQCRN.js +1832 -0
- package/dist/chunk-AORLXQHZ.js +304 -0
- package/dist/chunk-BMEBRUYL.js +6995 -0
- package/dist/chunk-G6PJSDEJ.js +4372 -0
- package/dist/chunk-IN2I6XRM.js +185 -0
- package/dist/chunk-PAJOAXLQ.js +4368 -0
- package/dist/chunk-TB4IZ7F7.js +301 -0
- package/dist/chunk-U7S375OS.js +1841 -0
- package/dist/chunk-VJATFD4D.js +7003 -0
- package/dist/dist-37TB6EWP.js +25 -0
- package/dist/dist-CIJPZC2B.js +25 -0
- package/dist/doctor-5M3ZB435.js +274 -0
- package/dist/doctor-IQ3MWQSN.js +274 -0
- package/dist/gateway-DV5OL45G.js +2164 -0
- package/dist/gateway-H4Z2EQK2.js +2165 -0
- package/dist/gateway-LUCG72YX.js +2129 -0
- package/dist/gateway-O3QNSZKF.js +2123 -0
- package/dist/gateway-OJW7RY3H.js +2094 -0
- package/dist/gateway-PBLJEK5I.js +2165 -0
- package/dist/gateway-PHPRQTZP.js +2165 -0
- package/dist/gateway-YKKJ4DZE.js +2115 -0
- package/dist/gateway-Z65DCM2Q.js +2097 -0
- package/dist/gateway-ZSXTAYPF.js +2157 -0
- package/dist/gateway-ZVLF7B4C.js +2165 -0
- package/dist/identity-RHQFPSDS.js +215 -0
- package/dist/identity-VGDDAKBY.js +215 -0
- package/dist/index.js +12 -12
- package/dist/install-6LV7B2SV.js +378 -0
- package/dist/install-NAUPXVCI.js +378 -0
- package/dist/message-PTH4CEOD.js +189 -0
- package/dist/message-QCRZIBTO.js +189 -0
- package/dist/message-TGAPVVI4.js +189 -0
- package/dist/message-YQGIARNE.js +189 -0
- package/dist/onboard-CN56V5P6.js +849 -0
- package/dist/onboard-LJFC6HXD.js +849 -0
- package/dist/pairing-ARWQYATE.js +147 -0
- package/dist/pairing-PXCJMCT2.js +147 -0
- package/dist/skills-4EELFYO2.js +138 -0
- package/dist/skills-KXRTDSF2.js +138 -0
- package/dist/status-2ZJPK3VL.js +94 -0
- package/dist/status-W2OXOSH4.js +94 -0
- package/package.json +24 -24
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeepgramSTT,
|
|
3
|
+
ElevenLabsTTS,
|
|
4
|
+
WakeListener,
|
|
5
|
+
WhisperSTT,
|
|
6
|
+
buildSystemPrompt
|
|
7
|
+
} from "./chunk-WL32AHUY.js";
|
|
8
|
+
import {
|
|
9
|
+
DefaultSecurityPolicy,
|
|
10
|
+
NativeEngine,
|
|
11
|
+
ProviderRegistry,
|
|
12
|
+
SubprocessEngine,
|
|
13
|
+
createClaudeCliEngine,
|
|
14
|
+
createCodexCliEngine,
|
|
15
|
+
createMemoryBackend,
|
|
16
|
+
createObserver
|
|
17
|
+
} from "./chunk-BMEBRUYL.js";
|
|
18
|
+
import {
|
|
19
|
+
LoadSkillTool,
|
|
20
|
+
ToolRegistry
|
|
21
|
+
} from "./chunk-PGZ24EFT.js";
|
|
22
|
+
import {
|
|
23
|
+
SkillRegistry
|
|
24
|
+
} from "./chunk-6BURGD2Y.js";
|
|
25
|
+
import {
|
|
26
|
+
AgentLoop,
|
|
27
|
+
ContextManager,
|
|
28
|
+
FormatVerifier,
|
|
29
|
+
LLMVerifier,
|
|
30
|
+
Session,
|
|
31
|
+
createAutoRecallHook,
|
|
32
|
+
createAutoSummarizeHook
|
|
33
|
+
} from "./chunk-U7S375OS.js";
|
|
34
|
+
import {
|
|
35
|
+
generateId
|
|
36
|
+
} from "./chunk-YSCX2QQQ.js";
|
|
37
|
+
import {
|
|
38
|
+
playBriefSplash
|
|
39
|
+
} from "./chunk-CNLYUY2K.js";
|
|
40
|
+
import {
|
|
41
|
+
getLogsDir,
|
|
42
|
+
loadConfig
|
|
43
|
+
} from "./chunk-TB4IZ7F7.js";
|
|
44
|
+
import {
|
|
45
|
+
BLUE,
|
|
46
|
+
BOLD,
|
|
47
|
+
BOX,
|
|
48
|
+
CHAPPIE_GLYPH,
|
|
49
|
+
CHECK,
|
|
50
|
+
CROSS,
|
|
51
|
+
DIM,
|
|
52
|
+
GREEN,
|
|
53
|
+
PROMPT_CHAR,
|
|
54
|
+
RED,
|
|
55
|
+
RESET,
|
|
56
|
+
TEAL,
|
|
57
|
+
TEAL_DIM,
|
|
58
|
+
WARN,
|
|
59
|
+
YELLOW,
|
|
60
|
+
chatHeader,
|
|
61
|
+
separator,
|
|
62
|
+
sessionBanner,
|
|
63
|
+
tokenFooter
|
|
64
|
+
} from "./chunk-NMGPBPNU.js";
|
|
65
|
+
|
|
66
|
+
// src/commands/agent.ts
|
|
67
|
+
import * as readline from "readline";
|
|
68
|
+
function truncateArgs(args) {
|
|
69
|
+
const str = typeof args === "string" ? args : JSON.stringify(args);
|
|
70
|
+
return truncate(str, 80);
|
|
71
|
+
}
|
|
72
|
+
function truncate(str, max) {
|
|
73
|
+
if (str.length <= max) return str;
|
|
74
|
+
return str.slice(0, max - 3) + "...";
|
|
75
|
+
}
|
|
76
|
+
var GUTTER = ` ${TEAL_DIM}${BOX.vertical}${RESET} `;
|
|
77
|
+
function createChatRenderState() {
|
|
78
|
+
return { headerPrinted: false, inTextStream: false, wasThinking: false };
|
|
79
|
+
}
|
|
80
|
+
function resetChatRenderState(state) {
|
|
81
|
+
state.headerPrinted = false;
|
|
82
|
+
state.inTextStream = false;
|
|
83
|
+
state.wasThinking = false;
|
|
84
|
+
}
|
|
85
|
+
function handleAgentEvent(event, state) {
|
|
86
|
+
const ensureHeader = () => {
|
|
87
|
+
if (!state.headerPrinted) {
|
|
88
|
+
console.log(chatHeader(CHAPPIE_GLYPH, "ch4p"));
|
|
89
|
+
state.headerPrinted = true;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
switch (event.type) {
|
|
93
|
+
case "thinking":
|
|
94
|
+
ensureHeader();
|
|
95
|
+
if (!state.wasThinking) {
|
|
96
|
+
process.stdout.write(" ");
|
|
97
|
+
}
|
|
98
|
+
process.stdout.write(`${DIM}${event.delta}${RESET}`);
|
|
99
|
+
state.wasThinking = true;
|
|
100
|
+
break;
|
|
101
|
+
case "text":
|
|
102
|
+
ensureHeader();
|
|
103
|
+
if (state.wasThinking && !state.inTextStream) {
|
|
104
|
+
process.stdout.write("\n\n");
|
|
105
|
+
state.wasThinking = false;
|
|
106
|
+
}
|
|
107
|
+
if (!state.inTextStream) {
|
|
108
|
+
process.stdout.write(" ");
|
|
109
|
+
state.inTextStream = true;
|
|
110
|
+
}
|
|
111
|
+
process.stdout.write(event.delta);
|
|
112
|
+
break;
|
|
113
|
+
case "tool_start":
|
|
114
|
+
ensureHeader();
|
|
115
|
+
if (state.inTextStream) {
|
|
116
|
+
process.stdout.write("\n");
|
|
117
|
+
state.inTextStream = false;
|
|
118
|
+
}
|
|
119
|
+
if (state.wasThinking) {
|
|
120
|
+
process.stdout.write("\n");
|
|
121
|
+
state.wasThinking = false;
|
|
122
|
+
}
|
|
123
|
+
console.log("");
|
|
124
|
+
console.log(`${GUTTER}${BOLD}${event.tool}${RESET}${DIM}(${truncateArgs(event.args)})${RESET}`);
|
|
125
|
+
break;
|
|
126
|
+
case "tool_progress":
|
|
127
|
+
process.stdout.write(`${GUTTER}${DIM}${event.update}${RESET}
|
|
128
|
+
`);
|
|
129
|
+
break;
|
|
130
|
+
case "tool_end":
|
|
131
|
+
if (event.result.success) {
|
|
132
|
+
const output = truncate(event.result.output, 120);
|
|
133
|
+
if (output) {
|
|
134
|
+
console.log(`${GUTTER}${DIM}${output}${RESET}`);
|
|
135
|
+
}
|
|
136
|
+
console.log(`${GUTTER}${CHECK} ${DIM}Done${RESET}`);
|
|
137
|
+
} else {
|
|
138
|
+
console.log(`${GUTTER}${CROSS} ${event.result.error ?? "Unknown error"}`);
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
case "tool_validation_error":
|
|
142
|
+
console.log(`${GUTTER}${WARN} ${YELLOW}${event.tool}: ${event.errors.join(", ")}${RESET}`);
|
|
143
|
+
break;
|
|
144
|
+
case "verification": {
|
|
145
|
+
const v = event.result;
|
|
146
|
+
const outcomeColor = v.outcome === "success" ? GREEN : v.outcome === "partial" ? YELLOW : RED;
|
|
147
|
+
console.log(`
|
|
148
|
+
${GUTTER}${BLUE}verify${RESET} ${outcomeColor}${v.outcome}${RESET} ${DIM}confidence=${v.confidence.toFixed(2)}${RESET}`);
|
|
149
|
+
if (v.reasoning) {
|
|
150
|
+
console.log(`${GUTTER}${DIM}${v.reasoning}${RESET}`);
|
|
151
|
+
}
|
|
152
|
+
if (v.issues && v.issues.length > 0) {
|
|
153
|
+
for (const issue of v.issues) {
|
|
154
|
+
console.log(`${GUTTER}${WARN} ${issue}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case "complete":
|
|
160
|
+
if (state.inTextStream) {
|
|
161
|
+
process.stdout.write("\n");
|
|
162
|
+
state.inTextStream = false;
|
|
163
|
+
}
|
|
164
|
+
if (event.usage) {
|
|
165
|
+
console.log(tokenFooter(event.usage));
|
|
166
|
+
}
|
|
167
|
+
resetChatRenderState(state);
|
|
168
|
+
break;
|
|
169
|
+
case "error":
|
|
170
|
+
ensureHeader();
|
|
171
|
+
console.error(`
|
|
172
|
+
${RED}Error:${RESET} ${event.error.message}`);
|
|
173
|
+
break;
|
|
174
|
+
case "aborted":
|
|
175
|
+
ensureHeader();
|
|
176
|
+
console.log(`
|
|
177
|
+
${YELLOW}Aborted:${RESET} ${event.reason}`);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function createEngine(config) {
|
|
182
|
+
const engineId = config.engines?.default ?? "native";
|
|
183
|
+
const engineConfig = config.engines?.available?.[engineId];
|
|
184
|
+
if (engineId === "claude-cli") {
|
|
185
|
+
try {
|
|
186
|
+
return createClaudeCliEngine({
|
|
187
|
+
command: engineConfig?.command ?? void 0,
|
|
188
|
+
cwd: engineConfig?.cwd ?? void 0,
|
|
189
|
+
timeout: engineConfig?.timeout ?? void 0
|
|
190
|
+
});
|
|
191
|
+
} catch (err) {
|
|
192
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
193
|
+
console.log(` ${YELLOW}\u26A0 Failed to create Claude CLI engine: ${message}${RESET}`);
|
|
194
|
+
console.log(` ${DIM}Falling back to stub engine.${RESET}
|
|
195
|
+
`);
|
|
196
|
+
return createStubEngine(config);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (engineId === "codex-cli") {
|
|
200
|
+
try {
|
|
201
|
+
return createCodexCliEngine({
|
|
202
|
+
command: engineConfig?.command ?? void 0,
|
|
203
|
+
cwd: engineConfig?.cwd ?? void 0,
|
|
204
|
+
timeout: engineConfig?.timeout ?? void 0
|
|
205
|
+
});
|
|
206
|
+
} catch (err) {
|
|
207
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
208
|
+
console.log(` ${YELLOW}\u26A0 Failed to create Codex CLI engine: ${message}${RESET}`);
|
|
209
|
+
console.log(` ${DIM}Falling back to stub engine.${RESET}
|
|
210
|
+
`);
|
|
211
|
+
return createStubEngine(config);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (engineConfig?.type === "subprocess") {
|
|
215
|
+
try {
|
|
216
|
+
return new SubprocessEngine({
|
|
217
|
+
id: engineId,
|
|
218
|
+
name: engineConfig.name ?? `Subprocess (${engineId})`,
|
|
219
|
+
command: engineConfig.command,
|
|
220
|
+
args: engineConfig.args ?? void 0,
|
|
221
|
+
promptMode: engineConfig.promptMode ?? void 0,
|
|
222
|
+
promptFlag: engineConfig.promptFlag ?? void 0,
|
|
223
|
+
cwd: engineConfig.cwd ?? void 0,
|
|
224
|
+
timeout: engineConfig.timeout ?? void 0
|
|
225
|
+
});
|
|
226
|
+
} catch (err) {
|
|
227
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
228
|
+
console.log(` ${YELLOW}\u26A0 Failed to create subprocess engine "${engineId}": ${message}${RESET}`);
|
|
229
|
+
console.log(` ${DIM}Falling back to stub engine.${RESET}
|
|
230
|
+
`);
|
|
231
|
+
return createStubEngine(config);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const providerName = config.agent.provider;
|
|
235
|
+
const providerConfig = config.providers?.[providerName];
|
|
236
|
+
const apiKey = providerConfig?.apiKey;
|
|
237
|
+
const needsKey = providerName !== "ollama";
|
|
238
|
+
if (needsKey && (!apiKey || apiKey.trim().length === 0)) {
|
|
239
|
+
console.log(` ${YELLOW}\u26A0 No API key for ${providerName}. Running in stub mode.${RESET}`);
|
|
240
|
+
console.log(` ${DIM}Set ${providerName.toUpperCase()}_API_KEY or run 'ch4p onboard' to configure.${RESET}
|
|
241
|
+
`);
|
|
242
|
+
return createStubEngine(config);
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
const provider = ProviderRegistry.createProvider({
|
|
246
|
+
id: providerName,
|
|
247
|
+
type: providerName,
|
|
248
|
+
...providerConfig
|
|
249
|
+
});
|
|
250
|
+
return new NativeEngine({
|
|
251
|
+
provider,
|
|
252
|
+
defaultModel: config.agent.model
|
|
253
|
+
});
|
|
254
|
+
} catch (err) {
|
|
255
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
256
|
+
console.log(` ${YELLOW}\u26A0 Failed to create ${providerName} provider: ${message}${RESET}`);
|
|
257
|
+
console.log(` ${DIM}Falling back to stub engine.${RESET}
|
|
258
|
+
`);
|
|
259
|
+
return createStubEngine(config);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function createStubEngine(config) {
|
|
263
|
+
return {
|
|
264
|
+
id: config.engines?.default ?? "native",
|
|
265
|
+
name: "Native Engine (stub)",
|
|
266
|
+
async startRun(job, opts) {
|
|
267
|
+
const ref = generateId(12);
|
|
268
|
+
async function* events() {
|
|
269
|
+
yield { type: "started" };
|
|
270
|
+
const lastMessage = job.messages[job.messages.length - 1];
|
|
271
|
+
const userText = typeof lastMessage?.content === "string" ? lastMessage.content : "(no message)";
|
|
272
|
+
const response = `I received your message: "${truncate(userText, 100)}"
|
|
273
|
+
|
|
274
|
+
This is a placeholder response from the ch4p stub engine. To use a real LLM, configure your API key for ${config.agent.provider}.
|
|
275
|
+
|
|
276
|
+
Current configuration:
|
|
277
|
+
Provider: ${config.agent.provider}
|
|
278
|
+
Model: ${config.agent.model}
|
|
279
|
+
Autonomy: ${config.autonomy.level}
|
|
280
|
+
`;
|
|
281
|
+
for (const char of response) {
|
|
282
|
+
if (opts?.signal?.aborted) {
|
|
283
|
+
yield { type: "error", error: new Error("Aborted") };
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
yield { type: "text_delta", delta: char };
|
|
287
|
+
}
|
|
288
|
+
yield {
|
|
289
|
+
type: "completed",
|
|
290
|
+
answer: response,
|
|
291
|
+
usage: { inputTokens: Math.ceil(userText.length / 4), outputTokens: Math.ceil(response.length / 4) }
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
ref,
|
|
296
|
+
events: events(),
|
|
297
|
+
async cancel() {
|
|
298
|
+
},
|
|
299
|
+
steer(_message) {
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
},
|
|
303
|
+
async resume(_token, _prompt) {
|
|
304
|
+
throw new Error("Resume not supported in stub engine");
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function createSessionConfig(config, skillRegistry, hasMemory, hasSearch) {
|
|
309
|
+
const systemPrompt = buildSystemPrompt({ hasMemory, hasSearch, skillRegistry });
|
|
310
|
+
return {
|
|
311
|
+
sessionId: generateId(16),
|
|
312
|
+
engineId: config.engines?.default ?? "native",
|
|
313
|
+
model: config.agent.model,
|
|
314
|
+
provider: config.agent.provider,
|
|
315
|
+
autonomyLevel: config.autonomy.level,
|
|
316
|
+
cwd: process.cwd(),
|
|
317
|
+
systemPrompt
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function createSkillRegistry(config) {
|
|
321
|
+
if (!config.skills?.enabled) return new SkillRegistry();
|
|
322
|
+
try {
|
|
323
|
+
return SkillRegistry.createFromPaths(config.skills.paths);
|
|
324
|
+
} catch {
|
|
325
|
+
return new SkillRegistry();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function createToolRegistry(config, skillRegistry) {
|
|
329
|
+
const exclude = [];
|
|
330
|
+
if (config.autonomy.level === "readonly") {
|
|
331
|
+
exclude.push("bash", "file_write", "file_edit", "delegate");
|
|
332
|
+
}
|
|
333
|
+
if (!config.mesh?.enabled) {
|
|
334
|
+
exclude.push("mesh");
|
|
335
|
+
}
|
|
336
|
+
const registry = ToolRegistry.createDefault(
|
|
337
|
+
exclude.length > 0 ? { exclude } : void 0
|
|
338
|
+
);
|
|
339
|
+
if (skillRegistry && skillRegistry.size > 0) {
|
|
340
|
+
registry.register(new LoadSkillTool(skillRegistry));
|
|
341
|
+
}
|
|
342
|
+
return registry;
|
|
343
|
+
}
|
|
344
|
+
function createMemory(config) {
|
|
345
|
+
try {
|
|
346
|
+
const memCfg = {
|
|
347
|
+
backend: config.memory.backend,
|
|
348
|
+
vectorWeight: config.memory.vectorWeight,
|
|
349
|
+
keywordWeight: config.memory.keywordWeight,
|
|
350
|
+
embeddingProvider: config.memory.embeddingProvider,
|
|
351
|
+
openaiApiKey: config.providers?.openai?.apiKey || void 0
|
|
352
|
+
};
|
|
353
|
+
const backend = createMemoryBackend(memCfg);
|
|
354
|
+
return backend;
|
|
355
|
+
} catch (err) {
|
|
356
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
357
|
+
console.log(` ${YELLOW}\u26A0 Memory backend failed to initialise: ${message}${RESET}`);
|
|
358
|
+
console.log(` ${DIM}Memory tools will be unavailable this session.${RESET}
|
|
359
|
+
`);
|
|
360
|
+
return void 0;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function createConfiguredObserver(config) {
|
|
364
|
+
try {
|
|
365
|
+
const obsCfg = {
|
|
366
|
+
observers: config.observability.observers ?? ["console"],
|
|
367
|
+
logLevel: config.observability.logLevel ?? "info",
|
|
368
|
+
logPath: `${getLogsDir()}/ch4p.jsonl`
|
|
369
|
+
};
|
|
370
|
+
return createObserver(obsCfg);
|
|
371
|
+
} catch {
|
|
372
|
+
return createObserver({ observers: ["console"], logLevel: "info" });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function createSecurityPolicy(config, cwd) {
|
|
376
|
+
return new DefaultSecurityPolicy({
|
|
377
|
+
workspace: cwd,
|
|
378
|
+
autonomyLevel: config.autonomy.level,
|
|
379
|
+
allowedCommands: config.autonomy.allowedCommands,
|
|
380
|
+
blockedPaths: config.security.blockedPaths
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
function createVerifier(config, _engine) {
|
|
384
|
+
const vCfg = config.verification;
|
|
385
|
+
if (!vCfg?.enabled) return void 0;
|
|
386
|
+
const formatOpts = {
|
|
387
|
+
maxToolErrorRatio: vCfg.maxToolErrorRatio ?? 0.5
|
|
388
|
+
};
|
|
389
|
+
if (vCfg.semantic) {
|
|
390
|
+
try {
|
|
391
|
+
const providerName = config.agent.provider;
|
|
392
|
+
const providerConfig = config.providers?.[providerName];
|
|
393
|
+
const provider = ProviderRegistry.createProvider({
|
|
394
|
+
id: `${providerName}-verifier`,
|
|
395
|
+
type: providerName,
|
|
396
|
+
...providerConfig
|
|
397
|
+
});
|
|
398
|
+
return new LLMVerifier({
|
|
399
|
+
provider,
|
|
400
|
+
model: config.agent.model,
|
|
401
|
+
formatOpts
|
|
402
|
+
});
|
|
403
|
+
} catch {
|
|
404
|
+
return new FormatVerifier(formatOpts);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return new FormatVerifier(formatOpts);
|
|
408
|
+
}
|
|
409
|
+
function createAgentLoop(config, engine, sessionConfig, memoryBackend, skillRegistry, extras) {
|
|
410
|
+
const session = new Session(sessionConfig, {
|
|
411
|
+
...extras?.sessionOpts,
|
|
412
|
+
maxErrors: config.agent.maxSessionErrors
|
|
413
|
+
});
|
|
414
|
+
const tools = createToolRegistry(config, skillRegistry);
|
|
415
|
+
const observer = createConfiguredObserver(config);
|
|
416
|
+
const securityPolicy = createSecurityPolicy(config, sessionConfig.cwd ?? process.cwd());
|
|
417
|
+
const toolContextExtensions = {};
|
|
418
|
+
if (config.search?.enabled && config.search.apiKey) {
|
|
419
|
+
toolContextExtensions.searchApiKey = config.search.apiKey;
|
|
420
|
+
toolContextExtensions.searchConfig = {
|
|
421
|
+
maxResults: config.search.maxResults,
|
|
422
|
+
country: config.search.country,
|
|
423
|
+
searchLang: config.search.searchLang
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
const verifier = createVerifier(config, engine);
|
|
427
|
+
return new AgentLoop(session, engine, tools.list(), observer, {
|
|
428
|
+
maxIterations: extras?.maxIterations ?? 50,
|
|
429
|
+
maxRetries: 3,
|
|
430
|
+
enableStateSnapshots: true,
|
|
431
|
+
verifier,
|
|
432
|
+
memoryBackend,
|
|
433
|
+
securityPolicy,
|
|
434
|
+
onBeforeFirstRun: extras?.onBeforeFirstRun,
|
|
435
|
+
onAfterComplete: extras?.onAfterComplete,
|
|
436
|
+
toolContextExtensions: Object.keys(toolContextExtensions).length > 0 ? toolContextExtensions : void 0,
|
|
437
|
+
maxToolResults: config.agent.maxToolResults,
|
|
438
|
+
maxToolOutputLen: config.agent.maxToolOutputLen,
|
|
439
|
+
maxStateRecords: config.agent.maxStateRecords
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
async function runAgentMessage(config, engine, sessionConfig, message, memoryBackend, skillRegistry, extras, renderState) {
|
|
443
|
+
const loop = createAgentLoop(config, engine, sessionConfig, memoryBackend, skillRegistry, extras);
|
|
444
|
+
const state = renderState ?? createChatRenderState();
|
|
445
|
+
for await (const event of loop.run(message)) {
|
|
446
|
+
handleAgentEvent(event, state);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function createWakeListener(config) {
|
|
450
|
+
const voiceCfg = config.voice;
|
|
451
|
+
if (!voiceCfg?.enabled || !voiceCfg.wake?.enabled) return null;
|
|
452
|
+
try {
|
|
453
|
+
const stt = voiceCfg.stt.provider === "deepgram" ? new DeepgramSTT({ apiKey: voiceCfg.stt.apiKey ?? "" }) : new WhisperSTT({ apiKey: voiceCfg.stt.apiKey ?? "" });
|
|
454
|
+
const tts = voiceCfg.tts.provider === "elevenlabs" ? new ElevenLabsTTS({ apiKey: voiceCfg.tts.apiKey ?? "", voiceId: voiceCfg.tts.voiceId }) : void 0;
|
|
455
|
+
return new WakeListener({
|
|
456
|
+
stt,
|
|
457
|
+
tts,
|
|
458
|
+
config: {
|
|
459
|
+
enabled: true,
|
|
460
|
+
wakeWord: voiceCfg.wake.wakeWord,
|
|
461
|
+
energyThreshold: voiceCfg.wake.energyThreshold,
|
|
462
|
+
silenceDurationMs: voiceCfg.wake.silenceDurationMs
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
} catch (err) {
|
|
466
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
467
|
+
console.log(` ${YELLOW}\u26A0 Voice wake failed to initialise: ${message}${RESET}`);
|
|
468
|
+
console.log(` ${DIM}Voice wake will be unavailable this session.${RESET}
|
|
469
|
+
`);
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
var REPL_HELP = `
|
|
474
|
+
${BOLD}Special Commands${RESET}
|
|
475
|
+
${separator()}
|
|
476
|
+
${TEAL}/exit${RESET} Exit the session
|
|
477
|
+
${TEAL}/clear${RESET} Clear conversation history
|
|
478
|
+
${TEAL}/audit${RESET} Run security audit
|
|
479
|
+
${TEAL}/memory${RESET} Show memory status
|
|
480
|
+
${TEAL}/tools${RESET} List available tools
|
|
481
|
+
${TEAL}/skills${RESET} List available skills
|
|
482
|
+
${TEAL}/help${RESET} Show this help
|
|
483
|
+
`;
|
|
484
|
+
async function runRepl(config, voiceEnabled = false) {
|
|
485
|
+
const engine = createEngine(config);
|
|
486
|
+
const skillRegistry = createSkillRegistry(config);
|
|
487
|
+
const memoryBackend = createMemory(config);
|
|
488
|
+
const hasMemory = !!memoryBackend;
|
|
489
|
+
const hasSearch = !!(config.search?.enabled && config.search.apiKey);
|
|
490
|
+
const sessionConfig = createSessionConfig(config, skillRegistry, hasMemory, hasSearch);
|
|
491
|
+
const tools = createToolRegistry(config);
|
|
492
|
+
const sharedContext = new ContextManager();
|
|
493
|
+
if (sessionConfig.systemPrompt) {
|
|
494
|
+
sharedContext.setSystemPrompt(sessionConfig.systemPrompt);
|
|
495
|
+
}
|
|
496
|
+
const autoSave = config.memory.autoSave !== false;
|
|
497
|
+
const onBeforeFirstRun = memoryBackend && autoSave ? createAutoRecallHook(memoryBackend) : void 0;
|
|
498
|
+
const onAfterComplete = memoryBackend && autoSave ? createAutoSummarizeHook(memoryBackend) : void 0;
|
|
499
|
+
if (process.stdout.isTTY) {
|
|
500
|
+
await playBriefSplash();
|
|
501
|
+
}
|
|
502
|
+
const bannerInfo = {
|
|
503
|
+
Engine: engine.name,
|
|
504
|
+
Model: config.agent.model,
|
|
505
|
+
Autonomy: config.autonomy.level,
|
|
506
|
+
Tools: `${tools.size} loaded`,
|
|
507
|
+
Memory: memoryBackend ? `${config.memory.backend}${autoSave ? " (auto)" : ""}` : `${DIM}disabled${RESET}`
|
|
508
|
+
};
|
|
509
|
+
if (skillRegistry.size > 0) {
|
|
510
|
+
bannerInfo.Skills = `${skillRegistry.size} loaded`;
|
|
511
|
+
}
|
|
512
|
+
const wakeListener = voiceEnabled ? createWakeListener(config) : null;
|
|
513
|
+
if (wakeListener) {
|
|
514
|
+
const wakeCfg = config.voice?.wake;
|
|
515
|
+
bannerInfo.Voice = `${GREEN}wake${RESET}${wakeCfg?.wakeWord ? ` "${wakeCfg.wakeWord}"` : ""}`;
|
|
516
|
+
}
|
|
517
|
+
console.log("\n" + sessionBanner(bannerInfo));
|
|
518
|
+
console.log(` ${DIM}Type ${TEAL}/help${DIM} for commands, ${TEAL}/exit${DIM} to quit.${RESET}
|
|
519
|
+
`);
|
|
520
|
+
if (engine.name === "Native Engine (stub)") {
|
|
521
|
+
console.log(
|
|
522
|
+
` ${YELLOW}${BOLD}\u26A0 Stub engine active${RESET}
|
|
523
|
+
${YELLOW}No API key is configured for "${config.agent.provider}".${RESET}
|
|
524
|
+
${DIM}Responses are placeholders only. Run ${TEAL}ch4p onboard${DIM} to set up a real provider.${RESET}
|
|
525
|
+
`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
const rl = readline.createInterface({
|
|
529
|
+
input: process.stdin,
|
|
530
|
+
output: process.stdout,
|
|
531
|
+
prompt: `${TEAL}${BOLD}${PROMPT_CHAR} ${RESET}`,
|
|
532
|
+
historySize: 200
|
|
533
|
+
});
|
|
534
|
+
let running = true;
|
|
535
|
+
rl.on("SIGINT", () => {
|
|
536
|
+
console.log(`
|
|
537
|
+
${DIM} Use /exit to quit.${RESET}`);
|
|
538
|
+
rl.prompt();
|
|
539
|
+
});
|
|
540
|
+
rl.on("close", () => {
|
|
541
|
+
if (running) {
|
|
542
|
+
running = false;
|
|
543
|
+
wakeListener?.stop();
|
|
544
|
+
void memoryBackend?.close();
|
|
545
|
+
console.log(`
|
|
546
|
+
${DIM} Goodbye!${RESET}
|
|
547
|
+
`);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
if (wakeListener) {
|
|
551
|
+
let voiceProcessing = false;
|
|
552
|
+
wakeListener.on("listening", () => {
|
|
553
|
+
console.log(`
|
|
554
|
+
${TEAL}\u{1F399} Voice wake active${RESET}${config.voice?.wake?.wakeWord ? ` \u2014 say "${config.voice.wake.wakeWord}" to start` : ""}${RESET}`);
|
|
555
|
+
rl.prompt();
|
|
556
|
+
});
|
|
557
|
+
wakeListener.on("wake", (event) => {
|
|
558
|
+
if (voiceProcessing || !running) return;
|
|
559
|
+
voiceProcessing = true;
|
|
560
|
+
console.log(chatHeader(PROMPT_CHAR, "You") + ` ${DIM}(voice)${RESET}`);
|
|
561
|
+
console.log(` ${event.text}`);
|
|
562
|
+
const renderState = createChatRenderState();
|
|
563
|
+
runAgentMessage(config, engine, sessionConfig, event.text, memoryBackend, skillRegistry, {
|
|
564
|
+
sessionOpts: { sharedContext },
|
|
565
|
+
onBeforeFirstRun,
|
|
566
|
+
onAfterComplete
|
|
567
|
+
}, renderState).then(async () => {
|
|
568
|
+
const messages = sharedContext.getMessages();
|
|
569
|
+
const lastAssistant = messages.filter((m) => m.role === "assistant").pop();
|
|
570
|
+
if (lastAssistant && typeof lastAssistant.content === "string") {
|
|
571
|
+
await wakeListener.speak(lastAssistant.content);
|
|
572
|
+
}
|
|
573
|
+
}).catch((err) => {
|
|
574
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
575
|
+
console.error(`
|
|
576
|
+
${RED}Error:${RESET} ${message}`);
|
|
577
|
+
}).finally(() => {
|
|
578
|
+
voiceProcessing = false;
|
|
579
|
+
console.log("");
|
|
580
|
+
rl.prompt();
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
wakeListener.on("error", (err) => {
|
|
584
|
+
console.error(` ${YELLOW}Voice error:${RESET} ${err.message}`);
|
|
585
|
+
});
|
|
586
|
+
try {
|
|
587
|
+
wakeListener.start();
|
|
588
|
+
} catch (err) {
|
|
589
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
590
|
+
console.log(` ${YELLOW}\u26A0 Voice wake start failed: ${message}${RESET}`);
|
|
591
|
+
console.log(` ${DIM}Continuing without voice wake.${RESET}
|
|
592
|
+
`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
rl.prompt();
|
|
596
|
+
for await (const line of rl) {
|
|
597
|
+
const input = line.trim();
|
|
598
|
+
if (!input) {
|
|
599
|
+
rl.prompt();
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (input.startsWith("/")) {
|
|
603
|
+
const cmd = input.toLowerCase().split(/\s+/)[0];
|
|
604
|
+
switch (cmd) {
|
|
605
|
+
case "/exit":
|
|
606
|
+
case "/quit":
|
|
607
|
+
case "/q":
|
|
608
|
+
running = false;
|
|
609
|
+
wakeListener?.stop();
|
|
610
|
+
console.log(`
|
|
611
|
+
${DIM} Goodbye!${RESET}
|
|
612
|
+
`);
|
|
613
|
+
rl.close();
|
|
614
|
+
await memoryBackend?.close();
|
|
615
|
+
return;
|
|
616
|
+
case "/clear":
|
|
617
|
+
sharedContext.clear();
|
|
618
|
+
if (sessionConfig.systemPrompt) {
|
|
619
|
+
sharedContext.setSystemPrompt(sessionConfig.systemPrompt);
|
|
620
|
+
}
|
|
621
|
+
console.log(` ${GREEN}Conversation cleared.${RESET}
|
|
622
|
+
`);
|
|
623
|
+
rl.prompt();
|
|
624
|
+
continue;
|
|
625
|
+
case "/audit": {
|
|
626
|
+
const { runAudit } = await import("./audit-UIGPH3FK.js");
|
|
627
|
+
console.log("");
|
|
628
|
+
runAudit(config);
|
|
629
|
+
console.log("");
|
|
630
|
+
rl.prompt();
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
case "/memory":
|
|
634
|
+
console.log(`
|
|
635
|
+
${BOLD}Memory Status${RESET}`);
|
|
636
|
+
console.log(` ${DIM}Backend: ${config.memory.backend}${RESET}`);
|
|
637
|
+
console.log(` ${DIM}Active: ${memoryBackend ? `${GREEN}yes${RESET}` : `${YELLOW}no (failed to initialise)${RESET}`}`);
|
|
638
|
+
console.log(` ${DIM}Auto-save: ${config.memory.autoSave}${RESET}`);
|
|
639
|
+
console.log(` ${DIM}Vector weight: ${config.memory.vectorWeight ?? 0.7}${RESET}`);
|
|
640
|
+
console.log(` ${DIM}Keyword weight: ${config.memory.keywordWeight ?? 0.3}${RESET}
|
|
641
|
+
`);
|
|
642
|
+
rl.prompt();
|
|
643
|
+
continue;
|
|
644
|
+
case "/tools":
|
|
645
|
+
console.log(`
|
|
646
|
+
${BOLD}Available Tools${RESET} ${DIM}(${tools.size})${RESET}`);
|
|
647
|
+
for (const tool of tools.list()) {
|
|
648
|
+
console.log(` ${TEAL}${tool.name}${RESET} ${DIM}[${tool.weight}]${RESET} ${tool.description}`);
|
|
649
|
+
}
|
|
650
|
+
console.log("");
|
|
651
|
+
rl.prompt();
|
|
652
|
+
continue;
|
|
653
|
+
case "/skills":
|
|
654
|
+
if (skillRegistry.size === 0) {
|
|
655
|
+
console.log(`
|
|
656
|
+
${DIM}No skills loaded.${RESET}
|
|
657
|
+
`);
|
|
658
|
+
} else {
|
|
659
|
+
console.log(`
|
|
660
|
+
${BOLD}Available Skills${RESET} ${DIM}(${skillRegistry.size})${RESET}`);
|
|
661
|
+
for (const skill of skillRegistry.list()) {
|
|
662
|
+
console.log(` ${TEAL}${skill.manifest.name}${RESET} ${DIM}(${skill.source})${RESET} ${skill.manifest.description}`);
|
|
663
|
+
}
|
|
664
|
+
console.log("");
|
|
665
|
+
}
|
|
666
|
+
rl.prompt();
|
|
667
|
+
continue;
|
|
668
|
+
case "/help":
|
|
669
|
+
console.log(REPL_HELP);
|
|
670
|
+
rl.prompt();
|
|
671
|
+
continue;
|
|
672
|
+
default:
|
|
673
|
+
console.log(` ${YELLOW}Unknown command: ${cmd}${RESET}`);
|
|
674
|
+
console.log(` ${DIM}Type /help for available commands.${RESET}
|
|
675
|
+
`);
|
|
676
|
+
rl.prompt();
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
console.log(chatHeader(PROMPT_CHAR, "You"));
|
|
681
|
+
console.log(` ${input}`);
|
|
682
|
+
const renderState = createChatRenderState();
|
|
683
|
+
try {
|
|
684
|
+
await runAgentMessage(config, engine, sessionConfig, input, memoryBackend, skillRegistry, {
|
|
685
|
+
sessionOpts: { sharedContext },
|
|
686
|
+
onBeforeFirstRun,
|
|
687
|
+
onAfterComplete
|
|
688
|
+
}, renderState);
|
|
689
|
+
} catch (err) {
|
|
690
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
691
|
+
console.error(`
|
|
692
|
+
${RED}Error:${RESET} ${message}`);
|
|
693
|
+
}
|
|
694
|
+
console.log("");
|
|
695
|
+
rl.prompt();
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
async function runSingleMessage(config, message) {
|
|
699
|
+
const engine = createEngine(config);
|
|
700
|
+
const skillRegistry = createSkillRegistry(config);
|
|
701
|
+
const memoryBackend = createMemory(config);
|
|
702
|
+
const hasMemory = !!memoryBackend;
|
|
703
|
+
const hasSearch = !!(config.search?.enabled && config.search.apiKey);
|
|
704
|
+
const sessionConfig = createSessionConfig(config, skillRegistry, hasMemory, hasSearch);
|
|
705
|
+
const autoSave = config.memory.autoSave !== false;
|
|
706
|
+
const onBeforeFirstRun = memoryBackend && autoSave ? createAutoRecallHook(memoryBackend) : void 0;
|
|
707
|
+
const onAfterComplete = memoryBackend && autoSave ? createAutoSummarizeHook(memoryBackend) : void 0;
|
|
708
|
+
try {
|
|
709
|
+
console.log(chatHeader(PROMPT_CHAR, "You"));
|
|
710
|
+
console.log(` ${message}`);
|
|
711
|
+
await runAgentMessage(config, engine, sessionConfig, message, memoryBackend, skillRegistry, {
|
|
712
|
+
onBeforeFirstRun,
|
|
713
|
+
onAfterComplete
|
|
714
|
+
});
|
|
715
|
+
console.log("");
|
|
716
|
+
} catch (err) {
|
|
717
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
718
|
+
console.error(`
|
|
719
|
+
${RED}Error:${RESET} ${errMessage}`);
|
|
720
|
+
process.exitCode = 1;
|
|
721
|
+
} finally {
|
|
722
|
+
await memoryBackend?.close();
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
async function agent(args) {
|
|
726
|
+
let config;
|
|
727
|
+
try {
|
|
728
|
+
config = loadConfig();
|
|
729
|
+
} catch (err) {
|
|
730
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
731
|
+
console.error(`
|
|
732
|
+
${RED}Failed to load config:${RESET} ${message}`);
|
|
733
|
+
console.error(` ${DIM}Run ${TEAL}ch4p onboard${DIM} to set up ch4p.${RESET}
|
|
734
|
+
`);
|
|
735
|
+
process.exitCode = 1;
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
let singleMessage = null;
|
|
739
|
+
let voiceEnabled = false;
|
|
740
|
+
for (let i = 0; i < args.length; i++) {
|
|
741
|
+
const arg = args[i];
|
|
742
|
+
if (arg === "-m" || arg === "--message") {
|
|
743
|
+
singleMessage = args[i + 1] ?? "";
|
|
744
|
+
break;
|
|
745
|
+
}
|
|
746
|
+
if (arg.startsWith("-m") && arg.length > 2) {
|
|
747
|
+
singleMessage = arg.slice(2);
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
if (arg === "--voice") {
|
|
751
|
+
voiceEnabled = true;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (singleMessage !== null) {
|
|
755
|
+
if (!singleMessage) {
|
|
756
|
+
console.error(` ${RED}Error:${RESET} -m flag requires a message argument.`);
|
|
757
|
+
process.exitCode = 1;
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
await runSingleMessage(config, singleMessage);
|
|
761
|
+
} else {
|
|
762
|
+
await runRepl(config, voiceEnabled);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
export {
|
|
766
|
+
agent
|
|
767
|
+
};
|