micro-models-agent 0.40.1 → 0.41.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mma.mjs +41 -41
- package/dist/cli/commands.js +9 -19
- package/dist/cli/completer.js +36 -37
- package/dist/cli/index.js +2 -2
- package/dist/cli/main.js +48 -23
- package/dist/cli/plugin-commands.js +36 -0
- package/dist/cli/repl-commands.js +40 -12
- package/dist/cli/repl.js +217 -87
- package/dist/cli/run-result.js +22 -0
- package/dist/cli/security-commands.js +5 -7
- package/dist/cli/setup.js +8 -26
- package/dist/config/config.js +52 -5
- package/dist/config/defaults.js +29 -5
- package/dist/config/experts.js +1 -1
- package/dist/config/index.js +3 -3
- package/dist/config/security.js +3 -10
- package/dist/core/agent-moe.js +2 -10
- package/dist/core/agent.js +273 -82
- package/dist/core/bootstrap.js +80 -13
- package/dist/core/index.js +2 -2
- package/dist/core/prompt-builder.js +23 -2
- package/dist/core/session-logger.js +46 -4
- package/dist/core/version.js +24 -0
- package/dist/i18n/en.json +75 -2
- package/dist/i18n/ru.json +74 -1
- package/dist/index.js +1 -1
- package/dist/llm/image-utils.js +4 -5
- package/dist/llm/index.js +4 -4
- package/dist/llm/model-loader.js +6 -6
- package/dist/llm/openai-compat.js +40 -34
- package/dist/llm/orchestrator.js +33 -29
- package/dist/llm/response.js +9 -9
- package/dist/logger/app-logger.js +1 -1
- package/dist/logger/index.js +1 -1
- package/dist/main.js +2489 -2186
- package/dist/migration/backup.js +13 -13
- package/dist/migration/detect.js +11 -11
- package/dist/migration/index.js +2 -2
- package/dist/modules/artifacts/store.js +61 -0
- package/dist/modules/browser/actions.js +34 -4
- package/dist/modules/browser/bridge-client.js +199 -0
- package/dist/modules/browser/bridge-path.js +10 -0
- package/dist/modules/browser/bridge-server.mjs +202 -202
- package/dist/modules/browser/cookie-store.js +6 -6
- package/dist/modules/browser/driver.js +136 -0
- package/dist/modules/browser/index.js +7 -5
- package/dist/modules/browser/module.js +8 -7
- package/dist/modules/browser/session.js +87 -84
- package/dist/modules/browser/snapshot.js +92 -58
- package/dist/modules/browser/types.js +4 -1
- package/dist/modules/certification/cli.js +2 -4
- package/dist/modules/certification/fact-checker.js +1 -3
- package/dist/modules/certification/loader.js +3 -9
- package/dist/modules/certification/runner.js +1 -4
- package/dist/modules/context/chunk-query.js +100 -0
- package/dist/modules/context/fact-extractor.js +162 -0
- package/dist/modules/context/history.js +15 -0
- package/dist/modules/context/index.js +1 -1
- package/dist/modules/context/manager.js +160 -86
- package/dist/modules/execution/audit-runners.js +152 -0
- package/dist/modules/execution/auditor.js +177 -25
- package/dist/modules/execution/execution-plugin.js +272 -0
- package/dist/modules/execution/module.js +201 -544
- package/dist/modules/execution/moe-executor.js +25 -0
- package/dist/modules/execution/plan-store.js +1 -3
- package/dist/modules/execution/plan-tool.js +508 -0
- package/dist/modules/execution/plan-validator.js +10 -10
- package/dist/modules/execution/planner.js +6 -1
- package/dist/modules/execution/stuck-detector.js +173 -10
- package/dist/modules/execution/verifier.js +86 -42
- package/dist/modules/execution/windows-commands.js +41 -0
- package/dist/modules/hallucination/confidence.js +8 -1
- package/dist/modules/hallucination/detector.js +2 -5
- package/dist/modules/hallucination/factual.js +3 -64
- package/dist/modules/hallucination/index.js +1 -1
- package/dist/modules/hallucination/js-identifiers.js +190 -0
- package/dist/modules/hallucination/llm-judge.js +1 -3
- package/dist/modules/indexer/cache.js +9 -7
- package/dist/modules/indexer/index.js +3 -3
- package/dist/modules/indexer/module.js +95 -42
- package/dist/modules/indexer/project-profile.js +183 -0
- package/dist/modules/indexer/walker.js +17 -17
- package/dist/modules/lsp/check-tool.js +58 -0
- package/dist/modules/lsp/client.js +74 -31
- package/dist/modules/lsp/command.js +60 -0
- package/dist/modules/lsp/config.js +87 -33
- package/dist/modules/lsp/index.js +3 -3
- package/dist/modules/lsp/module.js +185 -21
- package/dist/modules/lsp/probe.js +76 -0
- package/dist/modules/lsp/project-root.js +32 -0
- package/dist/modules/lsp/startup-check.js +141 -0
- package/dist/modules/mcp/module.js +2 -6
- package/dist/modules/memory/index.js +1 -1
- package/dist/modules/memory/module.js +71 -23
- package/dist/modules/memory/search.js +11 -9
- package/dist/modules/memory/store.js +13 -13
- package/dist/modules/pipelines/engine.js +10 -10
- package/dist/modules/pipelines/index.js +3 -3
- package/dist/modules/pipelines/parser.js +17 -14
- package/dist/modules/pipelines/template.js +1 -1
- package/dist/modules/plugins/builtin/lint-on-write.js +21 -16
- package/dist/modules/plugins/builtin/notify.js +3 -2
- package/dist/modules/plugins/index.js +1 -1
- package/dist/modules/plugins/loader.js +59 -17
- package/dist/modules/plugins/manager.js +73 -17
- package/dist/modules/processes/detect.js +34 -0
- package/dist/modules/processes/index.js +1 -1
- package/dist/modules/processes/registry.js +135 -46
- package/dist/modules/registry.js +4 -2
- package/dist/modules/security/audit-notifier.js +39 -39
- package/dist/modules/security/command-validator.js +2 -8
- package/dist/modules/security/data-sanitizer.js +1 -9
- package/dist/modules/security/encryption.js +58 -56
- package/dist/modules/security/network-validator.js +1 -9
- package/dist/modules/security/path-validator.js +1 -3
- package/dist/modules/security/security-policies.js +3 -19
- package/dist/modules/security/session-encryption.js +1 -1
- package/dist/modules/security/session-isolation.js +8 -8
- package/dist/modules/session/index.js +3 -3
- package/dist/modules/session/module.js +5 -5
- package/dist/modules/session/store.js +3 -9
- package/dist/modules/skills/matcher.js +27 -0
- package/dist/modules/skills/module.js +1 -2
- package/dist/modules/updater/checker.js +70 -6
- package/dist/modules/updater/index.js +2 -1
- package/dist/modules/updater/module.js +116 -0
- package/dist/modules/user-profile/compressor.js +2 -2
- package/dist/modules/user-profile/index.js +1 -1
- package/dist/modules/user-profile/profile.js +9 -9
- package/dist/tools/attach-image.js +1 -1
- package/dist/tools/bash.js +178 -19
- package/dist/tools/browser.js +46 -29
- package/dist/tools/chunk-query.js +99 -0
- package/dist/tools/download-file.js +116 -0
- package/dist/tools/enable-tools.js +58 -0
- package/dist/tools/executor.js +4 -5
- package/dist/tools/file-info.js +13 -12
- package/dist/tools/filter-tools.js +9 -2
- package/dist/tools/glob-tool.js +11 -11
- package/dist/tools/grep-tool.js +1 -3
- package/dist/tools/hidden-tools-block.js +37 -0
- package/dist/tools/index.js +13 -2
- package/dist/tools/list-dir.js +18 -17
- package/dist/tools/load-skill.js +1 -3
- package/dist/tools/path-utils.js +4 -4
- package/dist/tools/pipeline-run.js +25 -25
- package/dist/tools/process-kill.js +11 -11
- package/dist/tools/process-list.js +20 -22
- package/dist/tools/process-log.js +22 -18
- package/dist/tools/question.js +1 -3
- package/dist/tools/read-file.js +10 -2
- package/dist/tools/recall.js +44 -37
- package/dist/tools/registry.js +15 -4
- package/dist/tools/remember.js +29 -29
- package/dist/tools/scope-check.js +9 -9
- package/dist/tools/subagent.js +54 -9
- package/dist/tools/user-input.js +1 -1
- package/dist/tools/web-browse.js +3 -3
- package/dist/tools/web-fetch.js +3 -3
- package/dist/tools/web-search.js +3 -3
- package/dist/tools/write-file.js +1 -3
- package/dist/ui/box.js +1 -5
- package/dist/ui/index.js +6 -6
- package/dist/ui/line-editor.js +703 -0
- package/dist/ui/line-math.js +69 -0
- package/dist/ui/md-formatter.js +33 -33
- package/dist/ui/output.js +5 -5
- package/dist/ui/plan-view.js +103 -0
- package/dist/ui/renderer.js +15 -10
- package/dist/ui/table.js +1 -1
- package/package.json +48 -48
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { t } from
|
|
2
|
-
import { PipelineEngine } from
|
|
3
|
-
import { PipelineParser } from
|
|
4
|
-
import { TemplateEngine } from
|
|
5
|
-
import { Agent } from
|
|
6
|
-
import { ContextManager } from
|
|
7
|
-
import { PluginManager } from
|
|
8
|
-
import { HallucinationDetector } from
|
|
9
|
-
import { logSecurityBlock } from
|
|
10
|
-
import { getSessionSecurityConfig } from
|
|
1
|
+
import { t } from "../i18n/index";
|
|
2
|
+
import { PipelineEngine } from "../modules/pipelines/engine";
|
|
3
|
+
import { PipelineParser } from "../modules/pipelines/parser";
|
|
4
|
+
import { TemplateEngine } from "../modules/pipelines/template";
|
|
5
|
+
import { Agent } from "../core/agent";
|
|
6
|
+
import { ContextManager } from "../modules/context/manager";
|
|
7
|
+
import { PluginManager } from "../modules/plugins/manager";
|
|
8
|
+
import { HallucinationDetector } from "../modules/hallucination/detector";
|
|
9
|
+
import { logSecurityBlock } from "../modules/security/audit-log";
|
|
10
|
+
import { getSessionSecurityConfig } from "../modules/security/session-isolation";
|
|
11
11
|
const engine = new PipelineEngine();
|
|
12
12
|
const MAX_CONCURRENT = 3;
|
|
13
13
|
const MAX_ATTEMPTS = 3;
|
|
@@ -52,29 +52,29 @@ async function runStep(ctx, step, params, outputs) {
|
|
|
52
52
|
return { ok: false, output: "", error: lastError };
|
|
53
53
|
}
|
|
54
54
|
export const pipelineRunTool = {
|
|
55
|
-
name:
|
|
56
|
-
description:
|
|
57
|
-
tags: [
|
|
55
|
+
name: "pipeline_run",
|
|
56
|
+
description: "Run a named pipeline with YAML definition. Creates a DAG of sub-agents that execute in dependency order.",
|
|
57
|
+
tags: ["shell", "code"],
|
|
58
58
|
parameters: {
|
|
59
|
-
type:
|
|
59
|
+
type: "object",
|
|
60
60
|
properties: {
|
|
61
|
-
name: { type:
|
|
62
|
-
yaml: { type:
|
|
63
|
-
params: { type:
|
|
61
|
+
name: { type: "string", description: "Pipeline name" },
|
|
62
|
+
yaml: { type: "string", description: "Pipeline YAML definition with steps" },
|
|
63
|
+
params: { type: "object", description: "Template params for {key} placeholders" },
|
|
64
64
|
},
|
|
65
|
-
required: [
|
|
65
|
+
required: ["name", "yaml"],
|
|
66
66
|
},
|
|
67
67
|
handler: async (ctx, args) => {
|
|
68
|
-
const name = String(args.name ||
|
|
69
|
-
const yaml = String(args.yaml ||
|
|
68
|
+
const name = String(args.name || "");
|
|
69
|
+
const yaml = String(args.yaml || "");
|
|
70
70
|
const params = args.params || {};
|
|
71
71
|
if (!yaml) {
|
|
72
|
-
return { success: false, output: t(
|
|
72
|
+
return { success: false, output: t("pipeline.invalid") };
|
|
73
73
|
}
|
|
74
74
|
if (!ctx.llmProvider || !ctx.toolExecutor) {
|
|
75
75
|
return {
|
|
76
76
|
success: false,
|
|
77
|
-
output:
|
|
77
|
+
output: "Pipeline cannot run: missing llmProvider or toolExecutor in context",
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
const securityConfig = ctx.sessionContext
|
|
@@ -104,7 +104,7 @@ export const pipelineRunTool = {
|
|
|
104
104
|
if (ready.length === 0) {
|
|
105
105
|
if (failed.size > 0)
|
|
106
106
|
break;
|
|
107
|
-
throw new Error(t(
|
|
107
|
+
throw new Error(t("pipeline.circular", { stepId: order.join(", ") }));
|
|
108
108
|
}
|
|
109
109
|
const batch = ready.slice(0, MAX_CONCURRENT);
|
|
110
110
|
const results = await Promise.all(batch.map((step) => runStep(ctx, step, params, outputs)));
|
|
@@ -114,12 +114,12 @@ export const pipelineRunTool = {
|
|
|
114
114
|
if (result.ok) {
|
|
115
115
|
completed.add(step.id);
|
|
116
116
|
outputs[step.id] = { output: result.output };
|
|
117
|
-
engine.setStepStatus(step.id,
|
|
117
|
+
engine.setStepStatus(step.id, "done");
|
|
118
118
|
logs.push(` ✓ ${step.id}: ${result.output.split("\n")[0]}`);
|
|
119
119
|
}
|
|
120
120
|
else {
|
|
121
121
|
failed.add(step.id);
|
|
122
|
-
engine.setStepStatus(step.id,
|
|
122
|
+
engine.setStepStatus(step.id, "failed");
|
|
123
123
|
logs.push(` ✗ ${step.id}: ${result.error}`);
|
|
124
124
|
}
|
|
125
125
|
}
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { processRegistry } from
|
|
2
|
-
import { t } from
|
|
1
|
+
import { processRegistry } from "../modules/processes";
|
|
2
|
+
import { t } from "../i18n/index";
|
|
3
3
|
export const processKillTool = {
|
|
4
|
-
name:
|
|
5
|
-
description:
|
|
6
|
-
tags: [
|
|
4
|
+
name: "process_kill",
|
|
5
|
+
description: "Stop a background process started via bash (dev server, watcher). Kills the whole process tree (children included). Use the id returned by bash or process_list.",
|
|
6
|
+
tags: ["shell"],
|
|
7
7
|
parameters: {
|
|
8
|
-
type:
|
|
8
|
+
type: "object",
|
|
9
9
|
properties: {
|
|
10
|
-
id: { type:
|
|
10
|
+
id: { type: "string", description: "Process id from bash output or process_list" },
|
|
11
11
|
},
|
|
12
|
-
required: [
|
|
12
|
+
required: ["id"],
|
|
13
13
|
},
|
|
14
14
|
handler: async (ctx, args) => {
|
|
15
15
|
const id = String(args.id);
|
|
16
16
|
const entry = processRegistry.get(id);
|
|
17
17
|
if (!entry) {
|
|
18
|
-
return { success: false, output: t(
|
|
18
|
+
return { success: false, output: t("proc.not_found", { id }) };
|
|
19
19
|
}
|
|
20
20
|
const killed = processRegistry.kill(id);
|
|
21
21
|
if (!killed) {
|
|
22
|
-
return { success: false, output: t(
|
|
22
|
+
return { success: false, output: t("proc.kill_failed", { id }) };
|
|
23
23
|
}
|
|
24
24
|
return {
|
|
25
25
|
success: true,
|
|
26
|
-
output: t(
|
|
26
|
+
output: t("proc.killed", { id, pid: entry.pid }),
|
|
27
27
|
};
|
|
28
28
|
},
|
|
29
29
|
};
|
|
@@ -1,38 +1,36 @@
|
|
|
1
|
-
import { processRegistry } from
|
|
2
|
-
import { t } from
|
|
1
|
+
import { processRegistry } from "../modules/processes";
|
|
2
|
+
import { t } from "../i18n/index";
|
|
3
3
|
export const processListTool = {
|
|
4
|
-
name:
|
|
5
|
-
description:
|
|
6
|
-
tags: [
|
|
4
|
+
name: "process_list",
|
|
5
|
+
description: "List background processes started via the bash tool (dev servers, watchers, long-running commands). Shows id, pid, command, status, and recent output. Use with process_log and process_kill to inspect or stop them.",
|
|
6
|
+
tags: ["shell"],
|
|
7
7
|
parameters: {
|
|
8
|
-
type:
|
|
8
|
+
type: "object",
|
|
9
9
|
properties: {},
|
|
10
10
|
},
|
|
11
11
|
handler: async (ctx) => {
|
|
12
12
|
const list = processRegistry.list(ctx.sessionId);
|
|
13
13
|
if (list.length === 0) {
|
|
14
|
-
return { success: true, output: t(
|
|
14
|
+
return { success: true, output: t("proc.none") };
|
|
15
15
|
}
|
|
16
16
|
const statusLabel = (status) => {
|
|
17
|
-
if (status ===
|
|
18
|
-
return t(
|
|
19
|
-
if (status ===
|
|
20
|
-
return t(
|
|
21
|
-
return t(
|
|
17
|
+
if (status === "running")
|
|
18
|
+
return t("proc.status_running");
|
|
19
|
+
if (status === "exited")
|
|
20
|
+
return t("proc.status_exited");
|
|
21
|
+
return t("proc.status_killed");
|
|
22
22
|
};
|
|
23
|
-
const lines = [`${t(
|
|
23
|
+
const lines = [`${t("proc.list_header")} (${list.length}):`];
|
|
24
24
|
for (const entry of list) {
|
|
25
|
-
const tail = entry.log.length > 0 ? entry.log[entry.log.length - 1] :
|
|
26
|
-
const detail = tail
|
|
27
|
-
? ` — ${tail.slice(0, 80)}${tail.length > 80 ? '…' : ''}`
|
|
28
|
-
: '';
|
|
25
|
+
const tail = entry.log.length > 0 ? entry.log[entry.log.length - 1] : "";
|
|
26
|
+
const detail = tail ? ` — ${tail.slice(0, 80)}${tail.length > 80 ? "…" : ""}` : "";
|
|
29
27
|
lines.push(` ${entry.id} PID ${entry.pid} ${statusLabel(entry.status)} ${entry.command}${detail}`);
|
|
30
28
|
}
|
|
31
|
-
lines.push(`\n${t(
|
|
32
|
-
list:
|
|
33
|
-
log:
|
|
34
|
-
kill:
|
|
29
|
+
lines.push(`\n${t("proc.hint", {
|
|
30
|
+
list: "process_list",
|
|
31
|
+
log: "process_log",
|
|
32
|
+
kill: "process_kill",
|
|
35
33
|
})}`);
|
|
36
|
-
return { success: true, output: lines.join(
|
|
34
|
+
return { success: true, output: lines.join("\n") };
|
|
37
35
|
},
|
|
38
36
|
};
|
|
@@ -1,41 +1,45 @@
|
|
|
1
|
-
import { processRegistry } from
|
|
2
|
-
import { t } from
|
|
3
|
-
import { MAX_PREVIEW_LINES } from
|
|
1
|
+
import { processRegistry } from "../modules/processes";
|
|
2
|
+
import { t } from "../i18n/index";
|
|
3
|
+
import { MAX_PREVIEW_LINES } from "./preview";
|
|
4
4
|
const DEFAULT_TAIL = MAX_PREVIEW_LINES;
|
|
5
5
|
export const processLogTool = {
|
|
6
|
-
name:
|
|
6
|
+
name: "process_log",
|
|
7
7
|
description: `Show the buffered output of a background process started via bash. Shows the last ${DEFAULT_TAIL} lines by default. Use after starting a dev server to verify it came up without errors, and while it runs to check its state.`,
|
|
8
|
-
tags: [
|
|
8
|
+
tags: ["shell"],
|
|
9
|
+
boundedOutput: true,
|
|
9
10
|
parameters: {
|
|
10
|
-
type:
|
|
11
|
+
type: "object",
|
|
11
12
|
properties: {
|
|
12
|
-
id: { type:
|
|
13
|
-
tail: {
|
|
13
|
+
id: { type: "string", description: "Process id from bash output or process_list" },
|
|
14
|
+
tail: {
|
|
15
|
+
type: "number",
|
|
16
|
+
description: `Number of trailing lines to show (default: ${DEFAULT_TAIL}, max 300)`,
|
|
17
|
+
},
|
|
14
18
|
},
|
|
15
|
-
required: [
|
|
19
|
+
required: ["id"],
|
|
16
20
|
},
|
|
17
21
|
handler: async (ctx, args) => {
|
|
18
22
|
const id = String(args.id);
|
|
19
23
|
const entry = processRegistry.get(id);
|
|
20
24
|
if (!entry) {
|
|
21
|
-
return { success: false, output: t(
|
|
25
|
+
return { success: false, output: t("proc.not_found", { id }) };
|
|
22
26
|
}
|
|
23
|
-
const status = entry.status ===
|
|
24
|
-
? t(
|
|
25
|
-
: entry.status ===
|
|
26
|
-
? t(
|
|
27
|
-
: t(
|
|
28
|
-
const tail = typeof args.tail ===
|
|
27
|
+
const status = entry.status === "running"
|
|
28
|
+
? t("proc.status_running")
|
|
29
|
+
: entry.status === "exited"
|
|
30
|
+
? t("proc.status_exited")
|
|
31
|
+
: t("proc.status_killed");
|
|
32
|
+
const tail = typeof args.tail === "number" ? args.tail : DEFAULT_TAIL;
|
|
29
33
|
const log = processRegistry.getLog(id, tail);
|
|
30
34
|
if (!log) {
|
|
31
35
|
return {
|
|
32
36
|
success: true,
|
|
33
|
-
output: `${t(
|
|
37
|
+
output: `${t("proc.log_header", { id, status })} ${t("proc.log_empty")}`,
|
|
34
38
|
};
|
|
35
39
|
}
|
|
36
40
|
return {
|
|
37
41
|
success: true,
|
|
38
|
-
output: `${t(
|
|
42
|
+
output: `${t("proc.log_header", { id, status })}\n${log}`,
|
|
39
43
|
};
|
|
40
44
|
},
|
|
41
45
|
};
|
package/dist/tools/question.js
CHANGED
|
@@ -28,9 +28,7 @@ function normalizeQuestions(args) {
|
|
|
28
28
|
const q = item;
|
|
29
29
|
if (typeof q.question !== "string" || !q.question.trim())
|
|
30
30
|
continue;
|
|
31
|
-
const options = Array.isArray(q.options)
|
|
32
|
-
? q.options.filter(isOption)
|
|
33
|
-
: undefined;
|
|
31
|
+
const options = Array.isArray(q.options) ? q.options.filter(isOption) : undefined;
|
|
34
32
|
// An explicitly provided options array with no valid entries is a
|
|
35
33
|
// malformed call — skip the question instead of blocking on stdin.
|
|
36
34
|
if (Array.isArray(q.options) && options.length === 0)
|
package/dist/tools/read-file.js
CHANGED
|
@@ -5,12 +5,20 @@ import { isPathInScope } from "../modules/security/path-validator";
|
|
|
5
5
|
import { DEFAULT_SECURITY_CONFIG } from "../config/security";
|
|
6
6
|
import { logSecurityBlock } from "../modules/security/audit-log";
|
|
7
7
|
import { safeResolvePath } from "./path-utils";
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Default lines per read_file call. Deliberately much larger than
|
|
10
|
+
* MAX_PREVIEW_LINES (15): a 15-line default forces the model to issue 10-17
|
|
11
|
+
* calls per file, which floods the context and drives compaction every 15
|
|
12
|
+
* iterations (observed: 96 read_file calls, App.tsx read 23x, then 11
|
|
13
|
+
* compactions in ~20 min that deleted the user's task mid-turn). 300 lines
|
|
14
|
+
* covers typical component files in one call while still capping output.
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_LIMIT = 300;
|
|
10
17
|
export const readFileTool = {
|
|
11
18
|
name: "read_file",
|
|
12
19
|
description: `Read a file from the filesystem. Reads up to ${DEFAULT_LIMIT} lines at a time by default; use offset to page through large files.`,
|
|
13
20
|
tags: ["file", "code"],
|
|
21
|
+
boundedOutput: true,
|
|
14
22
|
parameters: {
|
|
15
23
|
type: "object",
|
|
16
24
|
properties: {
|
package/dist/tools/recall.js
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import { homedir } from
|
|
2
|
-
import { join } from
|
|
3
|
-
import { t } from
|
|
4
|
-
import { MemoryStore } from
|
|
5
|
-
const CATEGORIES = [
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { t } from "../i18n/index";
|
|
4
|
+
import { MemoryStore } from "../modules/memory/store";
|
|
5
|
+
const CATEGORIES = ["preferences", "conventions", "decisions", "errors", "facts"];
|
|
6
6
|
export const recallTool = {
|
|
7
|
-
name:
|
|
8
|
-
description:
|
|
9
|
-
tags: [
|
|
7
|
+
name: "recall",
|
|
8
|
+
description: "Recall stored information from memory. Search across preferences, facts, conventions, decisions, errors.",
|
|
9
|
+
tags: ["memory"],
|
|
10
10
|
parameters: {
|
|
11
|
-
type:
|
|
11
|
+
type: "object",
|
|
12
12
|
properties: {
|
|
13
13
|
query: {
|
|
14
|
-
type:
|
|
15
|
-
description:
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Search query (full-text search across all memory)",
|
|
16
16
|
},
|
|
17
17
|
category: {
|
|
18
|
-
type:
|
|
19
|
-
description:
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "Limit to specific category: preferences, conventions, decisions, errors, facts",
|
|
20
20
|
enum: CATEGORIES,
|
|
21
21
|
},
|
|
22
22
|
},
|
|
23
23
|
},
|
|
24
24
|
handler: async (_ctx, args) => {
|
|
25
|
-
const query = args.query ? String(args.query) :
|
|
26
|
-
const category = args.category ? String(args.category) :
|
|
27
|
-
const memoryDir = join(homedir(),
|
|
25
|
+
const query = args.query ? String(args.query) : "";
|
|
26
|
+
const category = args.category ? String(args.category) : "";
|
|
27
|
+
const memoryDir = join(homedir(), ".mma", "memory");
|
|
28
28
|
const store = new MemoryStore(memoryDir);
|
|
29
29
|
try {
|
|
30
30
|
// No query, no category → show everything
|
|
@@ -39,20 +39,26 @@ export const recallTool = {
|
|
|
39
39
|
if (category) {
|
|
40
40
|
const results = searchCategory(store, category, query);
|
|
41
41
|
if (results.length === 0) {
|
|
42
|
-
return { success: true, output: t(
|
|
42
|
+
return { success: true, output: t("tool.recall.empty", { query }) };
|
|
43
43
|
}
|
|
44
|
-
return {
|
|
44
|
+
return {
|
|
45
|
+
success: true,
|
|
46
|
+
output: t("tool.recall.search_results", { category, results: results.join("\n") }),
|
|
47
|
+
};
|
|
45
48
|
}
|
|
46
49
|
// Query across all
|
|
47
50
|
const results = store.search(query);
|
|
48
51
|
if (results.length === 0) {
|
|
49
|
-
return { success: true, output: t(
|
|
52
|
+
return { success: true, output: t("tool.recall.empty", { query }) };
|
|
50
53
|
}
|
|
51
|
-
const formatted = results.map(r => `[${r.file}] ${r.match}`).join(
|
|
52
|
-
return {
|
|
54
|
+
const formatted = results.map((r) => `[${r.file}] ${r.match}`).join("\n");
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
output: t("tool.recall.search_results", { category: "all", results: formatted }),
|
|
58
|
+
};
|
|
53
59
|
}
|
|
54
60
|
catch (err) {
|
|
55
|
-
return { success: true, output: t(
|
|
61
|
+
return { success: true, output: t("tool.memory_error", { error: String(err) }) };
|
|
56
62
|
}
|
|
57
63
|
},
|
|
58
64
|
};
|
|
@@ -60,14 +66,14 @@ function formatAll(store) {
|
|
|
60
66
|
const parts = [];
|
|
61
67
|
const prefs = store.getPreferences();
|
|
62
68
|
if (Object.keys(prefs).length > 0) {
|
|
63
|
-
parts.push(
|
|
69
|
+
parts.push("Preferences:");
|
|
64
70
|
for (const [k, v] of Object.entries(prefs)) {
|
|
65
71
|
parts.push(` ${k} = ${v}`);
|
|
66
72
|
}
|
|
67
73
|
}
|
|
68
|
-
for (const cat of [
|
|
74
|
+
for (const cat of ["facts", "conventions", "decisions", "errors"]) {
|
|
69
75
|
const content = store.read(cat);
|
|
70
|
-
const entries = content.split(
|
|
76
|
+
const entries = content.split("\n").filter((l) => l.startsWith("- "));
|
|
71
77
|
if (entries.length > 0) {
|
|
72
78
|
parts.push(`\n${cat.charAt(0).toUpperCase() + cat.slice(1)} (last 5):`);
|
|
73
79
|
for (const e of entries.slice(-5)) {
|
|
@@ -75,27 +81,27 @@ function formatAll(store) {
|
|
|
75
81
|
}
|
|
76
82
|
}
|
|
77
83
|
}
|
|
78
|
-
return parts.length > 0 ? parts.join(
|
|
84
|
+
return parts.length > 0 ? parts.join("\n") : t("tool.recall.no_memory");
|
|
79
85
|
}
|
|
80
86
|
function formatCategory(store, category) {
|
|
81
|
-
if (category ===
|
|
87
|
+
if (category === "preferences") {
|
|
82
88
|
const prefs = store.getPreferences();
|
|
83
89
|
if (Object.keys(prefs).length === 0)
|
|
84
|
-
return t(
|
|
85
|
-
const lines = [
|
|
90
|
+
return t("tool.recall.no_memory");
|
|
91
|
+
const lines = ["Preferences:"];
|
|
86
92
|
for (const [k, v] of Object.entries(prefs)) {
|
|
87
93
|
lines.push(` ${k} = ${v}`);
|
|
88
94
|
}
|
|
89
|
-
return lines.join(
|
|
95
|
+
return lines.join("\n");
|
|
90
96
|
}
|
|
91
97
|
const content = store.read(category);
|
|
92
|
-
const entries = content.split(
|
|
98
|
+
const entries = content.split("\n").filter((l) => l.startsWith("- "));
|
|
93
99
|
if (entries.length === 0)
|
|
94
|
-
return t(
|
|
95
|
-
return `${category.charAt(0).toUpperCase() + category.slice(1)} (${entries.length} entries):\n${entries.join(
|
|
100
|
+
return t("tool.recall.no_memory");
|
|
101
|
+
return `${category.charAt(0).toUpperCase() + category.slice(1)} (${entries.length} entries):\n${entries.join("\n")}`;
|
|
96
102
|
}
|
|
97
103
|
function searchCategory(store, category, query) {
|
|
98
|
-
if (category ===
|
|
104
|
+
if (category === "preferences") {
|
|
99
105
|
const prefs = store.getPreferences();
|
|
100
106
|
const lower = query.toLowerCase();
|
|
101
107
|
return Object.entries(prefs)
|
|
@@ -104,7 +110,8 @@ function searchCategory(store, category, query) {
|
|
|
104
110
|
}
|
|
105
111
|
const content = store.read(category);
|
|
106
112
|
const lower = query.toLowerCase();
|
|
107
|
-
return content
|
|
108
|
-
.
|
|
109
|
-
.
|
|
113
|
+
return content
|
|
114
|
+
.split("\n")
|
|
115
|
+
.filter((l) => l.startsWith("- ") && l.toLowerCase().includes(lower))
|
|
116
|
+
.map((l) => ` ${l}`);
|
|
110
117
|
}
|
package/dist/tools/registry.js
CHANGED
|
@@ -19,18 +19,29 @@ export class ToolRegistry {
|
|
|
19
19
|
return Array.from(this.tools.values());
|
|
20
20
|
}
|
|
21
21
|
getByTags(tags) {
|
|
22
|
-
return this.getAll().filter(t => {
|
|
22
|
+
return this.getAll().filter((t) => {
|
|
23
23
|
if (!t.tags || t.tags.length === 0)
|
|
24
24
|
return false;
|
|
25
|
-
return tags.some(tag => t.tags.includes(tag));
|
|
25
|
+
return tags.some((tag) => t.tags.includes(tag));
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Tools to expose to the LLM. Always includes `alwaysOn` tools, then tools
|
|
30
|
+
* matching the given tags. When no tags are given, returns every registered
|
|
31
|
+
* tool (backward-compatible). Duplicates are impossible because the
|
|
32
|
+
* alwaysOn + tagged sets are unioned by name.
|
|
33
|
+
*/
|
|
28
34
|
getAllForLLM(tags) {
|
|
29
|
-
const
|
|
30
|
-
|
|
35
|
+
const all = this.getAll();
|
|
36
|
+
const tagSet = new Set(tags ?? []);
|
|
37
|
+
const selected = tagSet.size === 0
|
|
38
|
+
? all
|
|
39
|
+
: all.filter((t) => t.alwaysOn || (t.tags && t.tags.some((tag) => tagSet.has(tag))));
|
|
40
|
+
return selected.map((t) => ({
|
|
31
41
|
name: t.name,
|
|
32
42
|
description: t.description,
|
|
33
43
|
parameters: t.parameters,
|
|
44
|
+
boundedOutput: t.boundedOutput,
|
|
34
45
|
}));
|
|
35
46
|
}
|
|
36
47
|
}
|
package/dist/tools/remember.js
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
import { homedir } from
|
|
2
|
-
import { join } from
|
|
3
|
-
import { t } from
|
|
4
|
-
import { MemoryStore } from
|
|
5
|
-
const CATEGORIES = [
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { t } from "../i18n/index";
|
|
4
|
+
import { MemoryStore } from "../modules/memory/store";
|
|
5
|
+
const CATEGORIES = ["preferences", "conventions", "decisions", "errors", "facts"];
|
|
6
6
|
export const rememberTool = {
|
|
7
|
-
name:
|
|
8
|
-
description:
|
|
9
|
-
tags: [
|
|
7
|
+
name: "remember",
|
|
8
|
+
description: "Remember information across sessions. Use for user preferences, facts, conventions, decisions, or errors.",
|
|
9
|
+
tags: ["memory"],
|
|
10
10
|
parameters: {
|
|
11
|
-
type:
|
|
11
|
+
type: "object",
|
|
12
12
|
properties: {
|
|
13
13
|
category: {
|
|
14
|
-
type:
|
|
15
|
-
description:
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Memory category: preferences, conventions, decisions, errors, facts",
|
|
16
16
|
enum: CATEGORIES,
|
|
17
17
|
},
|
|
18
18
|
key: {
|
|
19
|
-
type:
|
|
19
|
+
type: "string",
|
|
20
20
|
description: 'Key name (required for preferences, e.g. "color", "language")',
|
|
21
21
|
},
|
|
22
22
|
value: {
|
|
23
|
-
type:
|
|
24
|
-
description:
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Value to store (required for preferences)",
|
|
25
25
|
},
|
|
26
26
|
entry: {
|
|
27
|
-
type:
|
|
28
|
-
description:
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "Text entry to append (for conventions, decisions, errors, facts)",
|
|
29
29
|
},
|
|
30
30
|
},
|
|
31
|
-
required: [
|
|
31
|
+
required: ["category"],
|
|
32
32
|
},
|
|
33
33
|
handler: async (_ctx, args) => {
|
|
34
|
-
const category = String(args.category ||
|
|
34
|
+
const category = String(args.category || "");
|
|
35
35
|
if (!CATEGORIES.includes(category)) {
|
|
36
|
-
return { success: false, output: t(
|
|
36
|
+
return { success: false, output: t("tool.invalid_params") };
|
|
37
37
|
}
|
|
38
|
-
const memoryDir = join(homedir(),
|
|
38
|
+
const memoryDir = join(homedir(), ".mma", "memory");
|
|
39
39
|
const store = new MemoryStore(memoryDir);
|
|
40
40
|
try {
|
|
41
|
-
if (category ===
|
|
42
|
-
const key = String(args.key ||
|
|
43
|
-
const value = String(args.value ||
|
|
41
|
+
if (category === "preferences") {
|
|
42
|
+
const key = String(args.key || "");
|
|
43
|
+
const value = String(args.value || "");
|
|
44
44
|
if (!key) {
|
|
45
|
-
return { success: false, output: t(
|
|
45
|
+
return { success: false, output: t("tool.remember.key_required") };
|
|
46
46
|
}
|
|
47
47
|
store.setPreference(key, value);
|
|
48
48
|
return {
|
|
49
49
|
success: true,
|
|
50
|
-
output: t(
|
|
50
|
+
output: t("tool.remember.preference", { key, value }),
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
const entry = String(args.entry ||
|
|
53
|
+
const entry = String(args.entry || "");
|
|
54
54
|
if (!entry) {
|
|
55
|
-
return { success: false, output: t(
|
|
55
|
+
return { success: false, output: t("tool.remember.entry_required") };
|
|
56
56
|
}
|
|
57
57
|
store.append(category, entry);
|
|
58
58
|
return {
|
|
59
59
|
success: true,
|
|
60
|
-
output: t(
|
|
60
|
+
output: t("tool.remember.entry", { category, entry }),
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
catch (err) {
|
|
64
|
-
return { success: true, output: t(
|
|
64
|
+
return { success: true, output: t("tool.memory_error", { error: String(err) }) };
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
};
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import { resolve, normalize } from
|
|
1
|
+
import { resolve, normalize } from "path";
|
|
2
2
|
export function isPathInScope(baseDir, targetPath, scope) {
|
|
3
3
|
const baseResolved = resolve(baseDir);
|
|
4
4
|
const targetResolved = resolve(baseDir, normalize(targetPath));
|
|
5
5
|
if (!targetResolved.startsWith(baseResolved)) {
|
|
6
|
-
return { allowed: false, reason:
|
|
6
|
+
return { allowed: false, reason: "Path is outside the base working directory" };
|
|
7
7
|
}
|
|
8
8
|
if (!scope)
|
|
9
9
|
return { allowed: true };
|
|
10
|
-
const isRead = scope.read_only_files.some(f => targetResolved.startsWith(resolve(baseDir, f)));
|
|
11
|
-
const isWrite = scope.allowed_files.some(f => targetResolved.startsWith(resolve(baseDir, f)));
|
|
10
|
+
const isRead = scope.read_only_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
|
|
11
|
+
const isWrite = scope.allowed_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
|
|
12
12
|
if (isWrite)
|
|
13
13
|
return { allowed: true };
|
|
14
14
|
if (isRead)
|
|
15
|
-
return { allowed: true, reason:
|
|
16
|
-
return { allowed: false, reason:
|
|
15
|
+
return { allowed: true, reason: "Read-only file" };
|
|
16
|
+
return { allowed: false, reason: "Path is not within the allowed scope for this sub-agent" };
|
|
17
17
|
}
|
|
18
18
|
export function isPathWritable(baseDir, targetPath, scope) {
|
|
19
19
|
const baseResolved = resolve(baseDir);
|
|
20
20
|
const targetResolved = resolve(baseDir, normalize(targetPath));
|
|
21
21
|
if (!targetResolved.startsWith(baseResolved)) {
|
|
22
|
-
return { allowed: false, reason:
|
|
22
|
+
return { allowed: false, reason: "Path is outside the base working directory" };
|
|
23
23
|
}
|
|
24
24
|
if (!scope)
|
|
25
25
|
return { allowed: true };
|
|
26
|
-
const isWrite = scope.allowed_files.some(f => targetResolved.startsWith(resolve(baseDir, f)));
|
|
26
|
+
const isWrite = scope.allowed_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
|
|
27
27
|
if (isWrite)
|
|
28
28
|
return { allowed: true };
|
|
29
|
-
return { allowed: false, reason:
|
|
29
|
+
return { allowed: false, reason: "Path is not within the writable scope for this sub-agent" };
|
|
30
30
|
}
|