chainlesschain 0.37.12 → 0.40.1
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/package.json +3 -2
- package/src/commands/agent.js +7 -1
- package/src/commands/ask.js +24 -9
- package/src/commands/chat.js +7 -1
- package/src/commands/cli-anything.js +266 -0
- package/src/commands/compliance.js +216 -0
- package/src/commands/dao.js +312 -0
- package/src/commands/dlp.js +278 -0
- package/src/commands/evomap.js +558 -0
- package/src/commands/hardening.js +230 -0
- package/src/commands/matrix.js +168 -0
- package/src/commands/nostr.js +185 -0
- package/src/commands/pqc.js +162 -0
- package/src/commands/scim.js +218 -0
- package/src/commands/serve.js +109 -0
- package/src/commands/siem.js +156 -0
- package/src/commands/social.js +480 -0
- package/src/commands/terraform.js +148 -0
- package/src/constants.js +1 -0
- package/src/index.js +60 -0
- package/src/lib/autonomous-agent.js +487 -0
- package/src/lib/cli-anything-bridge.js +379 -0
- package/src/lib/cli-context-engineering.js +472 -0
- package/src/lib/compliance-manager.js +290 -0
- package/src/lib/content-recommender.js +205 -0
- package/src/lib/dao-governance.js +296 -0
- package/src/lib/dlp-engine.js +304 -0
- package/src/lib/evomap-client.js +135 -0
- package/src/lib/evomap-federation.js +240 -0
- package/src/lib/evomap-governance.js +250 -0
- package/src/lib/evomap-manager.js +227 -0
- package/src/lib/git-integration.js +1 -1
- package/src/lib/hardening-manager.js +275 -0
- package/src/lib/llm-providers.js +14 -1
- package/src/lib/matrix-bridge.js +196 -0
- package/src/lib/nostr-bridge.js +195 -0
- package/src/lib/permanent-memory.js +370 -0
- package/src/lib/plan-mode.js +211 -0
- package/src/lib/pqc-manager.js +196 -0
- package/src/lib/scim-manager.js +212 -0
- package/src/lib/session-manager.js +38 -0
- package/src/lib/siem-exporter.js +137 -0
- package/src/lib/social-manager.js +283 -0
- package/src/lib/task-model-selector.js +232 -0
- package/src/lib/terraform-manager.js +201 -0
- package/src/lib/ws-server.js +474 -0
- package/src/repl/agent-repl.js +796 -41
- package/src/repl/chat-repl.js +14 -6
package/src/repl/agent-repl.js
CHANGED
|
@@ -26,6 +26,22 @@ import { fileURLToPath } from "url";
|
|
|
26
26
|
import { logger } from "../lib/logger.js";
|
|
27
27
|
import { getPlanModeManager, PlanState } from "../lib/plan-mode.js";
|
|
28
28
|
import { CLISkillLoader } from "../lib/skill-loader.js";
|
|
29
|
+
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
30
|
+
import {
|
|
31
|
+
createSession,
|
|
32
|
+
saveMessages,
|
|
33
|
+
getSession,
|
|
34
|
+
} from "../lib/session-manager.js";
|
|
35
|
+
import { storeMemory, consolidateMemory } from "../lib/hierarchical-memory.js";
|
|
36
|
+
import { CLIContextEngineering } from "../lib/cli-context-engineering.js";
|
|
37
|
+
import { createChatFn } from "../lib/cowork-adapter.js";
|
|
38
|
+
import {
|
|
39
|
+
detectTaskType,
|
|
40
|
+
selectModelForTask,
|
|
41
|
+
} from "../lib/task-model-selector.js";
|
|
42
|
+
import { executeHooks, HookEvents } from "../lib/hook-manager.js";
|
|
43
|
+
import { CLIPermanentMemory } from "../lib/permanent-memory.js";
|
|
44
|
+
import { CLIAutonomousAgent, GoalStatus } from "../lib/autonomous-agent.js";
|
|
29
45
|
|
|
30
46
|
/**
|
|
31
47
|
* Tool definitions for function calling
|
|
@@ -195,7 +211,12 @@ const TOOLS = [
|
|
|
195
211
|
const skillLoader = new CLISkillLoader();
|
|
196
212
|
|
|
197
213
|
/**
|
|
198
|
-
*
|
|
214
|
+
* Reference to the runtime DB for hook execution (set during startAgentRepl)
|
|
215
|
+
*/
|
|
216
|
+
let _hookDb = null;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Execute a tool call (with plan mode filtering and hook pipeline)
|
|
199
220
|
*/
|
|
200
221
|
async function executeTool(name, args) {
|
|
201
222
|
// Plan mode: check if tool is allowed
|
|
@@ -218,6 +239,61 @@ async function executeTool(name, args) {
|
|
|
218
239
|
};
|
|
219
240
|
}
|
|
220
241
|
|
|
242
|
+
// PreToolUse hook
|
|
243
|
+
if (_hookDb) {
|
|
244
|
+
try {
|
|
245
|
+
await executeHooks(_hookDb, HookEvents.PreToolUse, {
|
|
246
|
+
tool: name,
|
|
247
|
+
args,
|
|
248
|
+
timestamp: new Date().toISOString(),
|
|
249
|
+
});
|
|
250
|
+
} catch (_err) {
|
|
251
|
+
// Hook failure should not block tool execution
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let toolResult;
|
|
256
|
+
try {
|
|
257
|
+
toolResult = await _executeToolInner(name, args);
|
|
258
|
+
} catch (err) {
|
|
259
|
+
// ToolError hook
|
|
260
|
+
if (_hookDb) {
|
|
261
|
+
try {
|
|
262
|
+
await executeHooks(_hookDb, HookEvents.ToolError, {
|
|
263
|
+
tool: name,
|
|
264
|
+
args,
|
|
265
|
+
error: err.message,
|
|
266
|
+
});
|
|
267
|
+
} catch (_err) {
|
|
268
|
+
// Non-critical
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
throw err;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// PostToolUse hook
|
|
275
|
+
if (_hookDb) {
|
|
276
|
+
try {
|
|
277
|
+
await executeHooks(_hookDb, HookEvents.PostToolUse, {
|
|
278
|
+
tool: name,
|
|
279
|
+
args,
|
|
280
|
+
result:
|
|
281
|
+
typeof toolResult === "object"
|
|
282
|
+
? JSON.stringify(toolResult).substring(0, 500)
|
|
283
|
+
: String(toolResult).substring(0, 500),
|
|
284
|
+
});
|
|
285
|
+
} catch (_err) {
|
|
286
|
+
// Non-critical
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return toolResult;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Inner tool execution logic (separated for hook wrapping)
|
|
295
|
+
*/
|
|
296
|
+
async function _executeToolInner(name, args) {
|
|
221
297
|
switch (name) {
|
|
222
298
|
case "read_file": {
|
|
223
299
|
const filePath = path.resolve(args.path);
|
|
@@ -402,13 +478,23 @@ async function executeTool(name, args) {
|
|
|
402
478
|
}
|
|
403
479
|
|
|
404
480
|
/**
|
|
405
|
-
* Send a chat completion request with tools
|
|
481
|
+
* Send a chat completion request with tools.
|
|
482
|
+
* Supports all 7 providers via cowork-adapter: ollama, anthropic, openai, deepseek, dashscope, gemini, mistral
|
|
406
483
|
*/
|
|
407
|
-
async function chatWithTools(
|
|
408
|
-
const { provider, model, baseUrl, apiKey } = options;
|
|
484
|
+
async function chatWithTools(rawMessages, options) {
|
|
485
|
+
const { provider, model, baseUrl, apiKey, contextEngine: ce } = options;
|
|
486
|
+
|
|
487
|
+
// Build optimized messages via context engine (or use raw)
|
|
488
|
+
// Find last user message for relevance matching (not tool/assistant)
|
|
489
|
+
const lastUserMsg = [...rawMessages].reverse().find((m) => m.role === "user");
|
|
490
|
+
const messages = ce
|
|
491
|
+
? ce.buildOptimizedMessages(rawMessages, {
|
|
492
|
+
userQuery: lastUserMsg?.content,
|
|
493
|
+
})
|
|
494
|
+
: rawMessages;
|
|
409
495
|
|
|
410
496
|
if (provider === "ollama") {
|
|
411
|
-
// Ollama supports tool calling
|
|
497
|
+
// Ollama supports tool calling natively
|
|
412
498
|
const response = await fetch(`${baseUrl}/api/chat`, {
|
|
413
499
|
method: "POST",
|
|
414
500
|
headers: { "Content-Type": "application/json" },
|
|
@@ -425,43 +511,153 @@ async function chatWithTools(messages, options) {
|
|
|
425
511
|
}
|
|
426
512
|
|
|
427
513
|
return await response.json();
|
|
428
|
-
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (provider === "anthropic") {
|
|
517
|
+
// Anthropic: extract system messages, use tools format
|
|
518
|
+
const key = apiKey || process.env.ANTHROPIC_API_KEY;
|
|
519
|
+
if (!key) throw new Error("ANTHROPIC_API_KEY required");
|
|
520
|
+
|
|
521
|
+
const systemMsgs = messages.filter((m) => m.role === "system");
|
|
522
|
+
const otherMsgs = messages.filter((m) => m.role !== "system");
|
|
523
|
+
|
|
524
|
+
// Convert TOOLS to Anthropic format
|
|
525
|
+
const anthropicTools = TOOLS.map((t) => ({
|
|
526
|
+
name: t.function.name,
|
|
527
|
+
description: t.function.description,
|
|
528
|
+
input_schema: t.function.parameters,
|
|
529
|
+
}));
|
|
530
|
+
|
|
531
|
+
const body = {
|
|
532
|
+
model: model || "claude-sonnet-4-20250514",
|
|
533
|
+
max_tokens: 4096,
|
|
534
|
+
messages: otherMsgs,
|
|
535
|
+
tools: anthropicTools,
|
|
536
|
+
};
|
|
537
|
+
if (systemMsgs.length > 0) {
|
|
538
|
+
body.system = systemMsgs.map((m) => m.content).join("\n");
|
|
539
|
+
}
|
|
540
|
+
|
|
429
541
|
const url =
|
|
430
542
|
baseUrl && baseUrl !== "http://localhost:11434"
|
|
431
543
|
? baseUrl
|
|
432
|
-
: "https://api.
|
|
433
|
-
const key = apiKey || process.env.OPENAI_API_KEY;
|
|
434
|
-
if (!key) throw new Error("API key required");
|
|
544
|
+
: "https://api.anthropic.com/v1";
|
|
435
545
|
|
|
436
|
-
const response = await fetch(`${url}/
|
|
546
|
+
const response = await fetch(`${url}/messages`, {
|
|
437
547
|
method: "POST",
|
|
438
548
|
headers: {
|
|
439
549
|
"Content-Type": "application/json",
|
|
440
|
-
|
|
550
|
+
"x-api-key": key,
|
|
551
|
+
"anthropic-version": "2023-06-01",
|
|
441
552
|
},
|
|
442
|
-
body: JSON.stringify(
|
|
443
|
-
model: model || "gpt-4o-mini",
|
|
444
|
-
messages,
|
|
445
|
-
tools: TOOLS,
|
|
446
|
-
}),
|
|
553
|
+
body: JSON.stringify(body),
|
|
447
554
|
});
|
|
448
555
|
|
|
449
556
|
if (!response.ok) {
|
|
450
|
-
throw new Error(`
|
|
557
|
+
throw new Error(`Anthropic error: ${response.status}`);
|
|
451
558
|
}
|
|
452
559
|
|
|
453
560
|
const data = await response.json();
|
|
454
|
-
// Normalize to Ollama-like format
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
561
|
+
// Normalize Anthropic response to Ollama-like format
|
|
562
|
+
return _normalizeAnthropicResponse(data);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// OpenAI-compatible providers (openai, deepseek, dashscope, mistral, gemini)
|
|
566
|
+
const providerUrls = {
|
|
567
|
+
openai: "https://api.openai.com/v1",
|
|
568
|
+
deepseek: "https://api.deepseek.com/v1",
|
|
569
|
+
dashscope: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
570
|
+
mistral: "https://api.mistral.ai/v1",
|
|
571
|
+
gemini: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
572
|
+
volcengine: "https://ark.cn-beijing.volces.com/api/v3",
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const providerApiKeyEnvs = {
|
|
576
|
+
openai: "OPENAI_API_KEY",
|
|
577
|
+
deepseek: "DEEPSEEK_API_KEY",
|
|
578
|
+
dashscope: "DASHSCOPE_API_KEY",
|
|
579
|
+
mistral: "MISTRAL_API_KEY",
|
|
580
|
+
gemini: "GEMINI_API_KEY",
|
|
581
|
+
volcengine: "VOLCENGINE_API_KEY",
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const url =
|
|
585
|
+
baseUrl && baseUrl !== "http://localhost:11434"
|
|
586
|
+
? baseUrl
|
|
587
|
+
: providerUrls[provider];
|
|
588
|
+
|
|
589
|
+
if (!url) {
|
|
590
|
+
throw new Error(
|
|
591
|
+
`Unsupported provider: ${provider}. Supported: ollama, anthropic, openai, deepseek, dashscope, mistral, gemini, volcengine`,
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const envKey = providerApiKeyEnvs[provider] || "OPENAI_API_KEY";
|
|
596
|
+
const key = apiKey || process.env[envKey];
|
|
597
|
+
if (!key) throw new Error(`${envKey} required for provider ${provider}`);
|
|
598
|
+
|
|
599
|
+
const defaultModels = {
|
|
600
|
+
openai: "gpt-4o-mini",
|
|
601
|
+
deepseek: "deepseek-chat",
|
|
602
|
+
dashscope: "qwen-turbo",
|
|
603
|
+
mistral: "mistral-large-latest",
|
|
604
|
+
gemini: "gemini-2.0-flash",
|
|
605
|
+
volcengine: "doubao-seed-1-6-251015",
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
const response = await fetch(`${url}/chat/completions`, {
|
|
609
|
+
method: "POST",
|
|
610
|
+
headers: {
|
|
611
|
+
"Content-Type": "application/json",
|
|
612
|
+
Authorization: `Bearer ${key}`,
|
|
613
|
+
},
|
|
614
|
+
body: JSON.stringify({
|
|
615
|
+
model: model || defaultModels[provider] || "gpt-4o-mini",
|
|
616
|
+
messages,
|
|
617
|
+
tools: TOOLS,
|
|
618
|
+
}),
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
if (!response.ok) {
|
|
622
|
+
throw new Error(`${provider} API error: ${response.status}`);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const data = await response.json();
|
|
626
|
+
// Normalize to Ollama-like format
|
|
627
|
+
if (!data.choices || !data.choices[0]) {
|
|
628
|
+
throw new Error("Invalid API response: no choices returned");
|
|
629
|
+
}
|
|
630
|
+
const choice = data.choices[0];
|
|
631
|
+
return {
|
|
632
|
+
message: choice.message,
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Normalize Anthropic API response to Ollama-like format for uniform handling
|
|
638
|
+
*/
|
|
639
|
+
function _normalizeAnthropicResponse(data) {
|
|
640
|
+
const content = data.content || [];
|
|
641
|
+
const textBlocks = content.filter((b) => b.type === "text");
|
|
642
|
+
const toolBlocks = content.filter((b) => b.type === "tool_use");
|
|
643
|
+
|
|
644
|
+
const message = {
|
|
645
|
+
role: "assistant",
|
|
646
|
+
content: textBlocks.map((b) => b.text).join("\n") || "",
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
if (toolBlocks.length > 0) {
|
|
650
|
+
message.tool_calls = toolBlocks.map((b) => ({
|
|
651
|
+
id: b.id,
|
|
652
|
+
type: "function",
|
|
653
|
+
function: {
|
|
654
|
+
name: b.name,
|
|
655
|
+
arguments: JSON.stringify(b.input),
|
|
656
|
+
},
|
|
657
|
+
}));
|
|
462
658
|
}
|
|
463
659
|
|
|
464
|
-
|
|
660
|
+
return { message };
|
|
465
661
|
}
|
|
466
662
|
|
|
467
663
|
/**
|
|
@@ -556,7 +752,8 @@ function formatToolArgs(name, args) {
|
|
|
556
752
|
}
|
|
557
753
|
}
|
|
558
754
|
|
|
559
|
-
|
|
755
|
+
function getBaseSystemPrompt() {
|
|
756
|
+
return `You are ChainlessChain AI Assistant, a powerful agentic coding assistant running in the terminal.
|
|
560
757
|
|
|
561
758
|
You have access to tools that let you read files, write files, edit files, run shell commands, and search the codebase. When the user asks you to do something, USE THE TOOLS to actually do it — don't just describe what should be done.
|
|
562
759
|
|
|
@@ -570,6 +767,7 @@ Key behaviors:
|
|
|
570
767
|
- Be concise but thorough
|
|
571
768
|
|
|
572
769
|
Current working directory: ${process.cwd()}`;
|
|
770
|
+
}
|
|
573
771
|
|
|
574
772
|
/**
|
|
575
773
|
* Start the agentic REPL
|
|
@@ -578,9 +776,82 @@ export async function startAgentRepl(options = {}) {
|
|
|
578
776
|
let model = options.model || "qwen2:7b";
|
|
579
777
|
let provider = options.provider || "ollama";
|
|
580
778
|
const baseUrl = options.baseUrl || "http://localhost:11434";
|
|
581
|
-
const apiKey = options.apiKey ||
|
|
779
|
+
const apiKey = options.apiKey || null;
|
|
780
|
+
|
|
781
|
+
// Bootstrap runtime (best-effort, DB not required)
|
|
782
|
+
let db = null;
|
|
783
|
+
let contextEngine = null;
|
|
784
|
+
let sessionId = null;
|
|
785
|
+
|
|
786
|
+
try {
|
|
787
|
+
const ctx = await bootstrap({ verbose: false });
|
|
788
|
+
db = ctx.db || null;
|
|
789
|
+
} catch (_err) {
|
|
790
|
+
// Continue without DB — static prompt fallback
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Initialize permanent memory
|
|
794
|
+
let permanentMemory = null;
|
|
795
|
+
try {
|
|
796
|
+
const dataDir = process.env.CHAINLESSCHAIN_DATA_DIR || process.cwd();
|
|
797
|
+
const memoryDir = path.join(dataDir, "memory");
|
|
798
|
+
permanentMemory = new CLIPermanentMemory({ db, memoryDir });
|
|
799
|
+
permanentMemory.initialize();
|
|
800
|
+
} catch (_err) {
|
|
801
|
+
// Non-critical
|
|
802
|
+
}
|
|
582
803
|
|
|
583
|
-
|
|
804
|
+
contextEngine = new CLIContextEngineering({ db, permanentMemory });
|
|
805
|
+
|
|
806
|
+
// Initialize autonomous agent
|
|
807
|
+
const autonomousAgent = new CLIAutonomousAgent();
|
|
808
|
+
|
|
809
|
+
// Set hook DB reference for tool pipeline
|
|
810
|
+
_hookDb = db;
|
|
811
|
+
|
|
812
|
+
// Resume existing session or create new one
|
|
813
|
+
if (db && options.sessionId) {
|
|
814
|
+
try {
|
|
815
|
+
const existing = getSession(db, options.sessionId);
|
|
816
|
+
if (existing && existing.messages) {
|
|
817
|
+
sessionId = existing.id;
|
|
818
|
+
}
|
|
819
|
+
} catch (_err) {
|
|
820
|
+
// Non-critical — will create new session
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (db && !sessionId) {
|
|
825
|
+
try {
|
|
826
|
+
const session = createSession(db, {
|
|
827
|
+
title: `Agent ${new Date().toISOString().slice(0, 10)}`,
|
|
828
|
+
provider,
|
|
829
|
+
model,
|
|
830
|
+
});
|
|
831
|
+
sessionId = session.id;
|
|
832
|
+
} catch (_err) {
|
|
833
|
+
// Non-critical
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const messages = [{ role: "system", content: getBaseSystemPrompt() }];
|
|
838
|
+
|
|
839
|
+
// Load resumed session messages
|
|
840
|
+
if (db && options.sessionId && sessionId) {
|
|
841
|
+
try {
|
|
842
|
+
const existing = getSession(db, sessionId);
|
|
843
|
+
if (existing && existing.messages) {
|
|
844
|
+
const parsed =
|
|
845
|
+
typeof existing.messages === "string"
|
|
846
|
+
? JSON.parse(existing.messages)
|
|
847
|
+
: existing.messages;
|
|
848
|
+
messages.push(...parsed.filter((m) => m.role !== "system"));
|
|
849
|
+
logger.info(`Resumed session ${sessionId} (${parsed.length} messages)`);
|
|
850
|
+
}
|
|
851
|
+
} catch (_err) {
|
|
852
|
+
// Non-critical
|
|
853
|
+
}
|
|
854
|
+
}
|
|
584
855
|
|
|
585
856
|
const getPrompt = () => {
|
|
586
857
|
const planManager = getPlanModeManager();
|
|
@@ -605,6 +876,12 @@ export async function startAgentRepl(options = {}) {
|
|
|
605
876
|
logger.log(
|
|
606
877
|
chalk.gray(`Model: ${model} Provider: ${provider} CWD: ${process.cwd()}`),
|
|
607
878
|
);
|
|
879
|
+
if (sessionId) {
|
|
880
|
+
logger.log(chalk.gray(`Session: ${sessionId}`));
|
|
881
|
+
}
|
|
882
|
+
if (db) {
|
|
883
|
+
logger.log(chalk.gray("Context: instinct + memory + notes (DB connected)"));
|
|
884
|
+
}
|
|
608
885
|
logger.log(
|
|
609
886
|
chalk.gray(
|
|
610
887
|
"Describe what you want to do. I can read/write files, run commands, and more.",
|
|
@@ -640,7 +917,23 @@ export async function startAgentRepl(options = {}) {
|
|
|
640
917
|
);
|
|
641
918
|
logger.log(` ${chalk.cyan("/provider")} Show/change provider`);
|
|
642
919
|
logger.log(` ${chalk.cyan("/clear")} Clear conversation`);
|
|
643
|
-
logger.log(
|
|
920
|
+
logger.log(
|
|
921
|
+
` ${chalk.cyan("/compact")} Smart compact (importance-based)`,
|
|
922
|
+
);
|
|
923
|
+
logger.log(
|
|
924
|
+
` ${chalk.cyan("/task")} Set task objective (/task <objective>)`,
|
|
925
|
+
);
|
|
926
|
+
logger.log(` ${chalk.cyan("/task clear")} Clear current task`);
|
|
927
|
+
logger.log(` ${chalk.cyan("/session")} Show current session info`);
|
|
928
|
+
logger.log(
|
|
929
|
+
` ${chalk.cyan("/reindex")} Reindex notes for BM25 search`,
|
|
930
|
+
);
|
|
931
|
+
logger.log(
|
|
932
|
+
` ${chalk.cyan("/stats")} Show context engine statistics`,
|
|
933
|
+
);
|
|
934
|
+
logger.log(
|
|
935
|
+
` ${chalk.cyan("/auto")} Autonomous goal execution (ReAct loop)`,
|
|
936
|
+
);
|
|
644
937
|
logger.log(
|
|
645
938
|
` ${chalk.cyan("/cowork")} Multi-agent collaboration (debate, compare)`,
|
|
646
939
|
);
|
|
@@ -657,7 +950,10 @@ export async function startAgentRepl(options = {}) {
|
|
|
657
950
|
logger.log(" Run shell commands (git, npm, etc.)");
|
|
658
951
|
logger.log(" Search codebase by filename or content");
|
|
659
952
|
logger.log(" Run 138 built-in skills (code-review, summarize, etc.)");
|
|
660
|
-
logger.log(" Plan mode: analyze first, execute after approval
|
|
953
|
+
logger.log(" Plan mode: analyze first, execute after approval");
|
|
954
|
+
logger.log(
|
|
955
|
+
" Context engineering: instinct + memory + notes injection\n",
|
|
956
|
+
);
|
|
661
957
|
prompt();
|
|
662
958
|
return;
|
|
663
959
|
}
|
|
@@ -677,10 +973,31 @@ export async function startAgentRepl(options = {}) {
|
|
|
677
973
|
if (trimmed.startsWith("/provider")) {
|
|
678
974
|
const arg = trimmed.slice(9).trim();
|
|
679
975
|
if (arg) {
|
|
680
|
-
|
|
681
|
-
|
|
976
|
+
const supported = [
|
|
977
|
+
"ollama",
|
|
978
|
+
"anthropic",
|
|
979
|
+
"openai",
|
|
980
|
+
"deepseek",
|
|
981
|
+
"dashscope",
|
|
982
|
+
"mistral",
|
|
983
|
+
"gemini",
|
|
984
|
+
"volcengine",
|
|
985
|
+
];
|
|
986
|
+
if (supported.includes(arg)) {
|
|
987
|
+
provider = arg;
|
|
988
|
+
logger.info(`Provider: ${chalk.cyan(provider)}`);
|
|
989
|
+
} else {
|
|
990
|
+
logger.info(
|
|
991
|
+
`Unsupported provider. Available: ${supported.join(", ")}`,
|
|
992
|
+
);
|
|
993
|
+
}
|
|
682
994
|
} else {
|
|
683
995
|
logger.info(`Current provider: ${chalk.cyan(provider)}`);
|
|
996
|
+
logger.info(
|
|
997
|
+
chalk.gray(
|
|
998
|
+
"Available: ollama, anthropic, openai, deepseek, dashscope, mistral, gemini, volcengine",
|
|
999
|
+
),
|
|
1000
|
+
);
|
|
684
1001
|
}
|
|
685
1002
|
prompt();
|
|
686
1003
|
return;
|
|
@@ -694,13 +1011,107 @@ export async function startAgentRepl(options = {}) {
|
|
|
694
1011
|
}
|
|
695
1012
|
|
|
696
1013
|
if (trimmed === "/compact") {
|
|
697
|
-
|
|
698
|
-
|
|
1014
|
+
if (contextEngine && messages.length > 5) {
|
|
1015
|
+
const compacted = contextEngine.smartCompact(messages);
|
|
1016
|
+
messages.length = 0;
|
|
1017
|
+
messages.push(...compacted);
|
|
1018
|
+
logger.info(
|
|
1019
|
+
`Compacted to ${messages.length} messages (importance-based)`,
|
|
1020
|
+
);
|
|
1021
|
+
} else if (messages.length > 5) {
|
|
1022
|
+
// Fallback: original logic
|
|
699
1023
|
const systemMsg = messages[0];
|
|
700
1024
|
const recent = messages.slice(-4);
|
|
701
1025
|
messages.length = 0;
|
|
702
1026
|
messages.push(systemMsg, ...recent);
|
|
703
|
-
logger.info("
|
|
1027
|
+
logger.info("Compacted to last 4 messages");
|
|
1028
|
+
}
|
|
1029
|
+
prompt();
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// Task commands
|
|
1034
|
+
if (trimmed.startsWith("/task")) {
|
|
1035
|
+
const taskArg = trimmed.slice(5).trim();
|
|
1036
|
+
if (taskArg === "clear") {
|
|
1037
|
+
contextEngine.clearTask();
|
|
1038
|
+
logger.info("Task cleared");
|
|
1039
|
+
} else if (taskArg) {
|
|
1040
|
+
contextEngine.setTask(taskArg);
|
|
1041
|
+
logger.info(`Task set: ${chalk.cyan(taskArg)}`);
|
|
1042
|
+
} else {
|
|
1043
|
+
if (contextEngine.taskContext) {
|
|
1044
|
+
logger.info(
|
|
1045
|
+
`Current task: ${chalk.cyan(contextEngine.taskContext.objective)}`,
|
|
1046
|
+
);
|
|
1047
|
+
} else {
|
|
1048
|
+
logger.info("No task set. Usage: /task <objective>");
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
prompt();
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Session info
|
|
1056
|
+
if (trimmed.startsWith("/session")) {
|
|
1057
|
+
const sessionArg = trimmed.slice(8).trim();
|
|
1058
|
+
if (sessionArg.startsWith("resume ")) {
|
|
1059
|
+
const resumeId = sessionArg.slice(7).trim();
|
|
1060
|
+
if (!db) {
|
|
1061
|
+
logger.info("No database available for session resume");
|
|
1062
|
+
} else {
|
|
1063
|
+
try {
|
|
1064
|
+
const existing = getSession(db, resumeId);
|
|
1065
|
+
if (existing && existing.messages) {
|
|
1066
|
+
const parsed =
|
|
1067
|
+
typeof existing.messages === "string"
|
|
1068
|
+
? JSON.parse(existing.messages)
|
|
1069
|
+
: existing.messages;
|
|
1070
|
+
messages.length = 1; // keep system prompt
|
|
1071
|
+
messages.push(...parsed.filter((m) => m.role !== "system"));
|
|
1072
|
+
sessionId = existing.id;
|
|
1073
|
+
logger.info(
|
|
1074
|
+
`Resumed session ${sessionId} (${parsed.length} messages)`,
|
|
1075
|
+
);
|
|
1076
|
+
} else {
|
|
1077
|
+
logger.info(`Session not found: ${resumeId}`);
|
|
1078
|
+
}
|
|
1079
|
+
} catch (err) {
|
|
1080
|
+
logger.error(`Resume failed: ${err.message}`);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
} else {
|
|
1084
|
+
logger.info(`Session ID: ${sessionId || "none"}`);
|
|
1085
|
+
logger.info(`Messages: ${messages.length}`);
|
|
1086
|
+
logger.info(`DB: ${db ? "connected" : "not available"}`);
|
|
1087
|
+
}
|
|
1088
|
+
prompt();
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// Reindex notes
|
|
1093
|
+
if (trimmed === "/reindex") {
|
|
1094
|
+
if (contextEngine) {
|
|
1095
|
+
contextEngine.reindexNotes();
|
|
1096
|
+
const stats = contextEngine.getStats();
|
|
1097
|
+
logger.info(`Notes reindexed: ${stats.notesIndexed} documents`);
|
|
1098
|
+
} else {
|
|
1099
|
+
logger.info("Context engine not available");
|
|
1100
|
+
}
|
|
1101
|
+
prompt();
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// Stats
|
|
1106
|
+
if (trimmed === "/stats") {
|
|
1107
|
+
if (contextEngine) {
|
|
1108
|
+
const stats = contextEngine.getStats();
|
|
1109
|
+
logger.info(`DB connected: ${stats.hasDb}`);
|
|
1110
|
+
logger.info(`Notes indexed: ${stats.notesIndexed}`);
|
|
1111
|
+
logger.info(`Error history: ${stats.errorCount}`);
|
|
1112
|
+
logger.info(`Active task: ${stats.hasTask}`);
|
|
1113
|
+
} else {
|
|
1114
|
+
logger.info("Context engine not available");
|
|
704
1115
|
}
|
|
705
1116
|
prompt();
|
|
706
1117
|
return;
|
|
@@ -715,10 +1126,16 @@ export async function startAgentRepl(options = {}) {
|
|
|
715
1126
|
if (!subCmd || subCmd === "help") {
|
|
716
1127
|
logger.log(chalk.bold("\nCowork Commands:"));
|
|
717
1128
|
logger.log(
|
|
718
|
-
` ${chalk.cyan("/cowork debate <file>")}
|
|
1129
|
+
` ${chalk.cyan("/cowork debate <file>")} Multi-perspective code review`,
|
|
1130
|
+
);
|
|
1131
|
+
logger.log(
|
|
1132
|
+
` ${chalk.cyan("/cowork compare <prompt>")} A/B solution comparison`,
|
|
1133
|
+
);
|
|
1134
|
+
logger.log(
|
|
1135
|
+
` ${chalk.cyan("/cowork graph <path>")} Code knowledge graph (ASCII)`,
|
|
719
1136
|
);
|
|
720
1137
|
logger.log(
|
|
721
|
-
` ${chalk.cyan("/cowork
|
|
1138
|
+
` ${chalk.cyan("/cowork decision <topic>")} Architecture decision tracking`,
|
|
722
1139
|
);
|
|
723
1140
|
logger.log("");
|
|
724
1141
|
} else if (subCmd === "debate" && coworkInput) {
|
|
@@ -787,9 +1204,88 @@ export async function startAgentRepl(options = {}) {
|
|
|
787
1204
|
} catch (err) {
|
|
788
1205
|
logger.error(`Compare failed: ${err.message}`);
|
|
789
1206
|
}
|
|
1207
|
+
} else if (subCmd === "graph" && coworkInput) {
|
|
1208
|
+
try {
|
|
1209
|
+
const { analyzeCodeKnowledgeGraph } =
|
|
1210
|
+
await import("../lib/cowork/code-knowledge-graph-cli.js");
|
|
1211
|
+
process.stdout.write(chalk.gray("\n Analyzing code graph...\n"));
|
|
1212
|
+
const result = await analyzeCodeKnowledgeGraph({
|
|
1213
|
+
target: coworkInput,
|
|
1214
|
+
llmOptions: { provider, model, baseUrl, apiKey },
|
|
1215
|
+
});
|
|
1216
|
+
// ASCII dependency graph
|
|
1217
|
+
if (result.entities && result.entities.length > 0) {
|
|
1218
|
+
process.stdout.write(chalk.bold(" Code Knowledge Graph:\n"));
|
|
1219
|
+
for (const entity of result.entities.slice(0, 15)) {
|
|
1220
|
+
const deps = (entity.dependencies || []).slice(0, 3).join(", ");
|
|
1221
|
+
process.stdout.write(
|
|
1222
|
+
` ${chalk.cyan(entity.name)} [${entity.type}]${deps ? ` → ${deps}` : ""}\n`,
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
if (result.relationships && result.relationships.length > 0) {
|
|
1226
|
+
process.stdout.write(chalk.bold("\n Relationships:\n"));
|
|
1227
|
+
for (const rel of result.relationships.slice(0, 10)) {
|
|
1228
|
+
process.stdout.write(
|
|
1229
|
+
` ${rel.source} ${chalk.gray(`—${rel.type}→`)} ${rel.target}\n`,
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
} else {
|
|
1234
|
+
process.stdout.write(
|
|
1235
|
+
` ${JSON.stringify(result).substring(0, 500)}\n`,
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
process.stdout.write("\n");
|
|
1239
|
+
messages.push({
|
|
1240
|
+
role: "assistant",
|
|
1241
|
+
content: `[Cowork Graph] Analyzed ${(result.entities || []).length} entities with ${(result.relationships || []).length} relationships.`,
|
|
1242
|
+
});
|
|
1243
|
+
} catch (err) {
|
|
1244
|
+
logger.error(`Graph analysis failed: ${err.message}`);
|
|
1245
|
+
}
|
|
1246
|
+
} else if (subCmd === "decision" && coworkInput) {
|
|
1247
|
+
try {
|
|
1248
|
+
const { analyzeDecisions } =
|
|
1249
|
+
await import("../lib/cowork/decision-kb-cli.js");
|
|
1250
|
+
process.stdout.write(chalk.gray("\n Analyzing decisions...\n"));
|
|
1251
|
+
const result = await analyzeDecisions({
|
|
1252
|
+
target: coworkInput,
|
|
1253
|
+
llmOptions: { provider, model, baseUrl, apiKey },
|
|
1254
|
+
});
|
|
1255
|
+
if (result.decisions && result.decisions.length > 0) {
|
|
1256
|
+
process.stdout.write(chalk.bold(" Architecture Decisions:\n"));
|
|
1257
|
+
for (const d of result.decisions) {
|
|
1258
|
+
const statusColor =
|
|
1259
|
+
d.status === "accepted"
|
|
1260
|
+
? chalk.green
|
|
1261
|
+
: d.status === "rejected"
|
|
1262
|
+
? chalk.red
|
|
1263
|
+
: chalk.yellow;
|
|
1264
|
+
process.stdout.write(
|
|
1265
|
+
` ${statusColor(`[${d.status || "proposed"}]`)} ${chalk.cyan(d.title || d.id)}\n`,
|
|
1266
|
+
);
|
|
1267
|
+
if (d.rationale) {
|
|
1268
|
+
process.stdout.write(
|
|
1269
|
+
` ${chalk.gray(d.rationale.substring(0, 100))}\n`,
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
} else {
|
|
1274
|
+
process.stdout.write(
|
|
1275
|
+
` ${JSON.stringify(result).substring(0, 500)}\n`,
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
process.stdout.write("\n");
|
|
1279
|
+
messages.push({
|
|
1280
|
+
role: "assistant",
|
|
1281
|
+
content: `[Cowork Decision] Found ${(result.decisions || []).length} architecture decisions.`,
|
|
1282
|
+
});
|
|
1283
|
+
} catch (err) {
|
|
1284
|
+
logger.error(`Decision analysis failed: ${err.message}`);
|
|
1285
|
+
}
|
|
790
1286
|
} else {
|
|
791
1287
|
logger.info(
|
|
792
|
-
"Usage: /cowork debate <file
|
|
1288
|
+
"Usage: /cowork debate <file> | compare <prompt> | graph <path> | decision <topic>",
|
|
793
1289
|
);
|
|
794
1290
|
}
|
|
795
1291
|
|
|
@@ -797,6 +1293,144 @@ export async function startAgentRepl(options = {}) {
|
|
|
797
1293
|
return;
|
|
798
1294
|
}
|
|
799
1295
|
|
|
1296
|
+
// Autonomous agent commands
|
|
1297
|
+
if (trimmed.startsWith("/auto")) {
|
|
1298
|
+
const autoArg = trimmed.slice(5).trim();
|
|
1299
|
+
|
|
1300
|
+
if (!autoArg || autoArg === "help") {
|
|
1301
|
+
logger.log(chalk.bold("\nAutonomous Agent Commands:"));
|
|
1302
|
+
logger.log(
|
|
1303
|
+
` ${chalk.cyan("/auto <goal>")} Submit a goal for autonomous execution`,
|
|
1304
|
+
);
|
|
1305
|
+
logger.log(
|
|
1306
|
+
` ${chalk.cyan("/auto status")} Show current goal status`,
|
|
1307
|
+
);
|
|
1308
|
+
logger.log(
|
|
1309
|
+
` ${chalk.cyan("/auto pause")} Pause the running goal`,
|
|
1310
|
+
);
|
|
1311
|
+
logger.log(` ${chalk.cyan("/auto resume")} Resume a paused goal`);
|
|
1312
|
+
logger.log(
|
|
1313
|
+
` ${chalk.cyan("/auto cancel")} Cancel the running goal`,
|
|
1314
|
+
);
|
|
1315
|
+
logger.log(` ${chalk.cyan("/auto list")} List all goals`);
|
|
1316
|
+
logger.log("");
|
|
1317
|
+
} else if (autoArg === "status") {
|
|
1318
|
+
const goals = autonomousAgent.listGoals();
|
|
1319
|
+
const running = goals.find(
|
|
1320
|
+
(g) =>
|
|
1321
|
+
g.status === GoalStatus.RUNNING || g.status === GoalStatus.PAUSED,
|
|
1322
|
+
);
|
|
1323
|
+
if (running) {
|
|
1324
|
+
const detail = autonomousAgent.getGoalStatus(running.id);
|
|
1325
|
+
logger.info(`Goal: ${chalk.cyan(detail.description)}`);
|
|
1326
|
+
logger.info(
|
|
1327
|
+
`Status: ${detail.status} Steps: ${detail.steps.length} Iterations: ${detail.iterations}`,
|
|
1328
|
+
);
|
|
1329
|
+
for (const step of detail.steps) {
|
|
1330
|
+
const icon =
|
|
1331
|
+
step.status === "completed"
|
|
1332
|
+
? "✓"
|
|
1333
|
+
: step.status === "running"
|
|
1334
|
+
? "→"
|
|
1335
|
+
: step.status === "failed"
|
|
1336
|
+
? "✗"
|
|
1337
|
+
: "○";
|
|
1338
|
+
logger.log(
|
|
1339
|
+
` ${icon} ${step.description} ${step.error ? chalk.red(`(${step.error})`) : ""}`,
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
} else {
|
|
1343
|
+
logger.info("No active goal. Use /auto <goal> to submit one.");
|
|
1344
|
+
}
|
|
1345
|
+
} else if (autoArg === "pause") {
|
|
1346
|
+
const goals = autonomousAgent.listGoals();
|
|
1347
|
+
const running = goals.find((g) => g.status === GoalStatus.RUNNING);
|
|
1348
|
+
if (running) {
|
|
1349
|
+
autonomousAgent.pauseGoal(running.id);
|
|
1350
|
+
logger.info(`Paused goal: ${running.description}`);
|
|
1351
|
+
} else {
|
|
1352
|
+
logger.info("No running goal to pause.");
|
|
1353
|
+
}
|
|
1354
|
+
} else if (autoArg === "resume") {
|
|
1355
|
+
const goals = autonomousAgent.listGoals();
|
|
1356
|
+
const paused = goals.find((g) => g.status === GoalStatus.PAUSED);
|
|
1357
|
+
if (paused) {
|
|
1358
|
+
autonomousAgent.resumeGoal(paused.id);
|
|
1359
|
+
logger.info(`Resumed goal: ${paused.description}`);
|
|
1360
|
+
} else {
|
|
1361
|
+
logger.info("No paused goal to resume.");
|
|
1362
|
+
}
|
|
1363
|
+
} else if (autoArg === "cancel") {
|
|
1364
|
+
const goals = autonomousAgent.listGoals();
|
|
1365
|
+
const active = goals.find(
|
|
1366
|
+
(g) =>
|
|
1367
|
+
g.status === GoalStatus.RUNNING || g.status === GoalStatus.PAUSED,
|
|
1368
|
+
);
|
|
1369
|
+
if (active) {
|
|
1370
|
+
autonomousAgent.cancelGoal(active.id);
|
|
1371
|
+
logger.info(`Cancelled goal: ${active.description}`);
|
|
1372
|
+
} else {
|
|
1373
|
+
logger.info("No active goal to cancel.");
|
|
1374
|
+
}
|
|
1375
|
+
} else if (autoArg === "list") {
|
|
1376
|
+
const goals = autonomousAgent.listGoals();
|
|
1377
|
+
if (goals.length === 0) {
|
|
1378
|
+
logger.info("No goals submitted yet.");
|
|
1379
|
+
} else {
|
|
1380
|
+
for (const g of goals) {
|
|
1381
|
+
logger.log(
|
|
1382
|
+
` [${g.status}] ${g.description} (${g.steps} steps, ${g.iterations} iterations)`,
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
} else {
|
|
1387
|
+
// Submit new goal
|
|
1388
|
+
// Lazy-init autonomous agent with LLM chat function
|
|
1389
|
+
if (!autonomousAgent._initialized) {
|
|
1390
|
+
const chatFn = createChatFn({ provider, model, baseUrl, apiKey });
|
|
1391
|
+
autonomousAgent.initialize({
|
|
1392
|
+
llmChat: chatFn,
|
|
1393
|
+
toolExecutor: executeTool,
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// Set up event listeners for live output
|
|
1398
|
+
const goalListener = (evt) => {
|
|
1399
|
+
if (evt.goalId) {
|
|
1400
|
+
if (evt.result)
|
|
1401
|
+
process.stdout.write(
|
|
1402
|
+
chalk.green(` Goal completed: ${evt.result}\n`),
|
|
1403
|
+
);
|
|
1404
|
+
if (evt.error)
|
|
1405
|
+
process.stdout.write(chalk.red(` Goal failed: ${evt.error}\n`));
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
const stepListener = (evt) => {
|
|
1409
|
+
process.stdout.write(chalk.gray(` [step] ${evt.step}\n`));
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1412
|
+
autonomousAgent.on("goal:completed", goalListener);
|
|
1413
|
+
autonomousAgent.on("goal:failed", goalListener);
|
|
1414
|
+
autonomousAgent.on("step:started", stepListener);
|
|
1415
|
+
autonomousAgent.on("step:completed", (evt) => {
|
|
1416
|
+
process.stdout.write(chalk.green(` [done] ${evt.step}\n`));
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1419
|
+
logger.info(`Submitting goal: ${chalk.cyan(autoArg)}`);
|
|
1420
|
+
try {
|
|
1421
|
+
const { goalId } = await autonomousAgent.submitGoal(autoArg);
|
|
1422
|
+
logger.info(
|
|
1423
|
+
`Goal ${goalId} submitted. Use /auto status to track progress.`,
|
|
1424
|
+
);
|
|
1425
|
+
} catch (err) {
|
|
1426
|
+
logger.error(`Failed to submit goal: ${err.message}`);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
prompt();
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
800
1434
|
// Plan mode commands
|
|
801
1435
|
if (trimmed.startsWith("/plan")) {
|
|
802
1436
|
const planManager = getPlanModeManager();
|
|
@@ -858,6 +1492,58 @@ export async function startAgentRepl(options = {}) {
|
|
|
858
1492
|
planManager.rejectPlan("User rejected");
|
|
859
1493
|
logger.info("Plan rejected. Exited plan mode.");
|
|
860
1494
|
}
|
|
1495
|
+
} else if (subCmd === "risk") {
|
|
1496
|
+
if (!planManager.isActive() || !planManager.currentPlan) {
|
|
1497
|
+
logger.info("No active plan to assess.");
|
|
1498
|
+
} else {
|
|
1499
|
+
const risk = planManager.getRiskAssessment();
|
|
1500
|
+
logger.log(
|
|
1501
|
+
`\nRisk Level: ${chalk.bold(risk.level.toUpperCase())} (total: ${risk.totalScore}, max: ${risk.maxScore}, avg: ${risk.averageScore})`,
|
|
1502
|
+
);
|
|
1503
|
+
for (const item of risk.itemScores) {
|
|
1504
|
+
const color =
|
|
1505
|
+
item.score >= 6
|
|
1506
|
+
? chalk.red
|
|
1507
|
+
: item.score >= 3
|
|
1508
|
+
? chalk.yellow
|
|
1509
|
+
: chalk.green;
|
|
1510
|
+
logger.log(` ${color(`[${item.score}]`)} ${item.title}`);
|
|
1511
|
+
}
|
|
1512
|
+
logger.log("");
|
|
1513
|
+
}
|
|
1514
|
+
} else if (subCmd === "execute") {
|
|
1515
|
+
if (!planManager.isActive()) {
|
|
1516
|
+
logger.info("No active plan.");
|
|
1517
|
+
} else if (planManager.state !== PlanState.APPROVED) {
|
|
1518
|
+
logger.info("Plan must be approved first. Use /plan approve.");
|
|
1519
|
+
} else {
|
|
1520
|
+
logger.info("Executing plan items in DAG order...");
|
|
1521
|
+
try {
|
|
1522
|
+
const { results, success } = await planManager.executePlan(
|
|
1523
|
+
async (item) => {
|
|
1524
|
+
if (item.tool && item.params) {
|
|
1525
|
+
process.stdout.write(
|
|
1526
|
+
chalk.gray(` [${item.tool}] ${item.title}\n`),
|
|
1527
|
+
);
|
|
1528
|
+
return await executeTool(item.tool, item.params);
|
|
1529
|
+
}
|
|
1530
|
+
return { skipped: true };
|
|
1531
|
+
},
|
|
1532
|
+
);
|
|
1533
|
+
for (const r of results) {
|
|
1534
|
+
const icon = r.success ? chalk.green("✓") : chalk.red("✗");
|
|
1535
|
+
logger.log(
|
|
1536
|
+
` ${icon} ${r.item.title}${r.error ? ` — ${r.error}` : ""}`,
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
logger.info(
|
|
1540
|
+
`Plan execution ${success ? "completed" : "finished with errors"}.`,
|
|
1541
|
+
);
|
|
1542
|
+
planManager.exitPlanMode({ savePlan: true });
|
|
1543
|
+
} catch (err) {
|
|
1544
|
+
logger.error(`Plan execution failed: ${err.message}`);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
861
1547
|
} else if (subCmd === "exit") {
|
|
862
1548
|
if (planManager.isActive()) {
|
|
863
1549
|
planManager.exitPlanMode({ savePlan: true });
|
|
@@ -878,13 +1564,27 @@ export async function startAgentRepl(options = {}) {
|
|
|
878
1564
|
// Add user message
|
|
879
1565
|
messages.push({ role: "user", content: trimmed });
|
|
880
1566
|
|
|
1567
|
+
// Auto-select best model based on task type
|
|
1568
|
+
let activeModel = model;
|
|
1569
|
+
const taskDetection = detectTaskType(trimmed);
|
|
1570
|
+
if (taskDetection.confidence > 0.3) {
|
|
1571
|
+
const recommended = selectModelForTask(provider, taskDetection.taskType);
|
|
1572
|
+
if (recommended && recommended !== activeModel) {
|
|
1573
|
+
activeModel = recommended;
|
|
1574
|
+
logger.info(
|
|
1575
|
+
chalk.gray(`[auto] ${taskDetection.name} → ${activeModel}`),
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
881
1580
|
try {
|
|
882
1581
|
process.stdout.write("\n");
|
|
883
1582
|
const response = await agentLoop(messages, {
|
|
884
1583
|
provider,
|
|
885
|
-
model,
|
|
1584
|
+
model: activeModel,
|
|
886
1585
|
baseUrl,
|
|
887
1586
|
apiKey,
|
|
1587
|
+
contextEngine,
|
|
888
1588
|
});
|
|
889
1589
|
|
|
890
1590
|
if (response) {
|
|
@@ -893,9 +1593,34 @@ export async function startAgentRepl(options = {}) {
|
|
|
893
1593
|
} else {
|
|
894
1594
|
process.stdout.write("\n");
|
|
895
1595
|
}
|
|
1596
|
+
|
|
1597
|
+
// Auto-save session
|
|
1598
|
+
if (db && sessionId) {
|
|
1599
|
+
try {
|
|
1600
|
+
saveMessages(db, sessionId, messages);
|
|
1601
|
+
} catch (_e) {
|
|
1602
|
+
// Non-critical
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
// Store as episodic memory
|
|
1606
|
+
if (db) {
|
|
1607
|
+
try {
|
|
1608
|
+
storeMemory(db, trimmed, { importance: 0.3, type: "episodic" });
|
|
1609
|
+
} catch (_e) {
|
|
1610
|
+
// Non-critical
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
896
1613
|
} catch (err) {
|
|
897
1614
|
logger.error(`Error: ${err.message}`);
|
|
898
1615
|
|
|
1616
|
+
// Record error for context injection
|
|
1617
|
+
if (contextEngine) {
|
|
1618
|
+
contextEngine.recordError({
|
|
1619
|
+
step: "agent-loop",
|
|
1620
|
+
message: err.message,
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
|
|
899
1624
|
// If connection error, provide helpful message
|
|
900
1625
|
if (
|
|
901
1626
|
err.message.includes("ECONNREFUSED") ||
|
|
@@ -911,7 +1636,37 @@ export async function startAgentRepl(options = {}) {
|
|
|
911
1636
|
prompt();
|
|
912
1637
|
});
|
|
913
1638
|
|
|
914
|
-
rl.on("close", () => {
|
|
1639
|
+
rl.on("close", async () => {
|
|
1640
|
+
// Save session on exit
|
|
1641
|
+
if (db && sessionId) {
|
|
1642
|
+
try {
|
|
1643
|
+
saveMessages(db, sessionId, messages);
|
|
1644
|
+
} catch (_e) {
|
|
1645
|
+
// Non-critical
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
// Auto-summarize session into permanent memory
|
|
1649
|
+
if (permanentMemory && messages.length > 4) {
|
|
1650
|
+
try {
|
|
1651
|
+
permanentMemory.autoSummarize(messages);
|
|
1652
|
+
} catch (_e) {
|
|
1653
|
+
// Non-critical
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
// Consolidate memory
|
|
1657
|
+
if (db) {
|
|
1658
|
+
try {
|
|
1659
|
+
consolidateMemory(db);
|
|
1660
|
+
} catch (_e) {
|
|
1661
|
+
// Non-critical
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
// Shutdown runtime
|
|
1665
|
+
try {
|
|
1666
|
+
await shutdown();
|
|
1667
|
+
} catch (_e) {
|
|
1668
|
+
// Non-critical
|
|
1669
|
+
}
|
|
915
1670
|
process.exit(0);
|
|
916
1671
|
});
|
|
917
1672
|
}
|