nemoris 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +49 -49
- package/LICENSE +21 -21
- package/README.md +209 -209
- package/SECURITY.md +59 -119
- package/bin/nemoris +46 -46
- package/config/agents/agent.toml.example +28 -28
- package/config/agents/content.toml +23 -0
- package/config/agents/default.toml +22 -22
- package/config/agents/heartbeat.toml +35 -0
- package/config/agents/iris.toml +23 -0
- package/config/agents/lab.toml +23 -0
- package/config/agents/main.toml +45 -0
- package/config/agents/nemo.toml +21 -0
- package/config/agents/ops.toml +38 -0
- package/config/agents/orchestrator.toml +18 -18
- package/config/agents/revenue.toml +23 -0
- package/config/agents/testyboo.toml +19 -0
- package/config/delivery.toml +73 -73
- package/config/embeddings.toml +5 -5
- package/config/identity/content-purpose.md +11 -0
- package/config/identity/content-soul.md +45 -0
- package/config/identity/default-purpose.md +1 -1
- package/config/identity/default-soul.md +3 -3
- package/config/identity/heartbeat-purpose.md +9 -0
- package/config/identity/heartbeat-soul.md +16 -0
- package/config/identity/iris-purpose.md +17 -0
- package/config/identity/iris-soul.md +68 -0
- package/config/identity/lab-purpose.md +10 -0
- package/config/identity/lab-soul.md +38 -0
- package/config/identity/main-purpose.md +17 -0
- package/config/identity/main-soul.md +66 -0
- package/config/identity/main-user.md +22 -0
- package/config/identity/ops-purpose.md +9 -0
- package/config/identity/ops-soul.md +16 -0
- package/config/identity/orchestrator-purpose.md +1 -1
- package/config/identity/orchestrator-soul.md +1 -1
- package/config/identity/revenue-purpose.md +9 -0
- package/config/identity/revenue-soul.md +41 -0
- package/config/identity/testyboo-purpose.md +13 -0
- package/config/identity/testyboo-soul.md +20 -0
- package/config/improvement-targets.toml +15 -15
- package/config/jobs/heartbeat-check.toml +30 -30
- package/config/jobs/memory-rollup.toml +46 -46
- package/config/jobs/workspace-health.toml +63 -63
- package/config/mcp.toml +16 -16
- package/config/output-contracts.toml +17 -17
- package/config/peers.toml +32 -32
- package/config/peers.toml.example +32 -32
- package/config/policies/memory-default.toml +10 -10
- package/config/policies/memory-heartbeat.toml +5 -5
- package/config/policies/memory-ops.toml +10 -10
- package/config/policies/tools-heartbeat-minimal.toml +8 -8
- package/config/policies/tools-interactive-safe.toml +8 -8
- package/config/policies/tools-ops-bounded.toml +8 -8
- package/config/policies/tools-orchestrator.toml +7 -7
- package/config/providers/anthropic.toml +15 -15
- package/config/providers/ollama.toml +5 -5
- package/config/providers/openai-codex.toml +9 -9
- package/config/providers/openrouter.toml +5 -5
- package/config/router.toml +22 -22
- package/config/runtime.toml +114 -114
- package/config/skills/self-improvement.toml +15 -15
- package/config/skills/telegram-onboarding-spec.md +240 -240
- package/config/skills/workspace-monitor.toml +15 -15
- package/config/task-router.toml +42 -42
- package/install.sh +50 -50
- package/package.json +91 -90
- package/src/auth/auth-profiles.js +169 -169
- package/src/auth/openai-codex-oauth.js +285 -285
- package/src/battle.js +449 -449
- package/src/cli/help.js +265 -265
- package/src/cli/output-filter.js +49 -49
- package/src/cli/runtime-control.js +704 -704
- package/src/cli-main.js +2763 -2763
- package/src/cli.js +78 -78
- package/src/config/loader.js +332 -332
- package/src/config/schema-validator.js +214 -214
- package/src/config/toml-lite.js +8 -8
- package/src/daemon/action-handlers.js +71 -71
- package/src/daemon/healing-tick.js +87 -87
- package/src/daemon/health-probes.js +90 -90
- package/src/daemon/notifier.js +57 -57
- package/src/daemon/nurse.js +218 -218
- package/src/daemon/repair-log.js +106 -106
- package/src/daemon/rule-staging.js +90 -90
- package/src/daemon/rules.js +29 -29
- package/src/daemon/telegram-commands.js +54 -54
- package/src/daemon/updater.js +85 -85
- package/src/jobs/job-runner.js +78 -78
- package/src/mcp/consumer.js +129 -129
- package/src/memory/active-recall.js +171 -171
- package/src/memory/backend-manager.js +97 -97
- package/src/memory/backends/file-backend.js +38 -38
- package/src/memory/backends/qmd-backend.js +219 -219
- package/src/memory/embedding-guards.js +24 -24
- package/src/memory/embedding-index.js +118 -118
- package/src/memory/embedding-service.js +179 -179
- package/src/memory/file-index.js +177 -177
- package/src/memory/memory-signature.js +5 -5
- package/src/memory/memory-store.js +648 -648
- package/src/memory/retrieval-planner.js +66 -66
- package/src/memory/scoring.js +145 -145
- package/src/memory/simhash.js +78 -78
- package/src/memory/sqlite-active-store.js +824 -824
- package/src/memory/write-policy.js +36 -36
- package/src/onboarding/aliases.js +33 -33
- package/src/onboarding/auth/api-key.js +224 -224
- package/src/onboarding/auth/ollama-detect.js +42 -42
- package/src/onboarding/clack-prompter.js +77 -77
- package/src/onboarding/doctor.js +530 -530
- package/src/onboarding/lock.js +42 -42
- package/src/onboarding/model-catalog.js +344 -344
- package/src/onboarding/phases/auth.js +576 -589
- package/src/onboarding/phases/build.js +130 -130
- package/src/onboarding/phases/choose.js +82 -82
- package/src/onboarding/phases/detect.js +98 -98
- package/src/onboarding/phases/hatch.js +216 -216
- package/src/onboarding/phases/identity.js +79 -79
- package/src/onboarding/phases/ollama.js +345 -345
- package/src/onboarding/phases/scaffold.js +99 -99
- package/src/onboarding/phases/telegram.js +377 -377
- package/src/onboarding/phases/validate.js +204 -204
- package/src/onboarding/phases/verify.js +206 -206
- package/src/onboarding/platform.js +482 -482
- package/src/onboarding/status-bar.js +95 -95
- package/src/onboarding/templates.js +794 -794
- package/src/onboarding/toml-writer.js +38 -38
- package/src/onboarding/tui.js +250 -250
- package/src/onboarding/uninstall.js +153 -153
- package/src/onboarding/wizard.js +516 -499
- package/src/providers/anthropic.js +168 -168
- package/src/providers/base.js +247 -247
- package/src/providers/circuit-breaker.js +136 -136
- package/src/providers/ollama.js +163 -163
- package/src/providers/openai-codex.js +149 -149
- package/src/providers/openrouter.js +136 -136
- package/src/providers/registry.js +36 -36
- package/src/providers/router.js +16 -16
- package/src/runtime/bootstrap-cache.js +47 -47
- package/src/runtime/capabilities-prompt.js +25 -25
- package/src/runtime/completion-ping.js +99 -99
- package/src/runtime/config-validator.js +121 -121
- package/src/runtime/context-ledger.js +360 -360
- package/src/runtime/cutover-readiness.js +42 -42
- package/src/runtime/daemon.js +729 -729
- package/src/runtime/delivery-ack.js +195 -195
- package/src/runtime/delivery-adapters/local-file.js +41 -41
- package/src/runtime/delivery-adapters/openclaw-cli.js +94 -94
- package/src/runtime/delivery-adapters/openclaw-peer.js +98 -98
- package/src/runtime/delivery-adapters/shadow.js +13 -13
- package/src/runtime/delivery-adapters/standalone-http.js +98 -98
- package/src/runtime/delivery-adapters/telegram.js +104 -104
- package/src/runtime/delivery-adapters/tui.js +128 -128
- package/src/runtime/delivery-manager.js +807 -807
- package/src/runtime/delivery-store.js +168 -168
- package/src/runtime/dependency-health.js +118 -118
- package/src/runtime/envelope.js +114 -114
- package/src/runtime/evaluation.js +1089 -1089
- package/src/runtime/exec-approvals.js +216 -216
- package/src/runtime/executor.js +500 -500
- package/src/runtime/failure-ping.js +67 -67
- package/src/runtime/flows.js +83 -83
- package/src/runtime/guards.js +45 -45
- package/src/runtime/handoff.js +51 -51
- package/src/runtime/identity-cache.js +28 -28
- package/src/runtime/improvement-engine.js +109 -109
- package/src/runtime/improvement-harness.js +581 -581
- package/src/runtime/input-sanitiser.js +72 -72
- package/src/runtime/interaction-contract.js +347 -347
- package/src/runtime/lane-readiness.js +226 -226
- package/src/runtime/migration.js +323 -323
- package/src/runtime/model-resolution.js +78 -78
- package/src/runtime/network.js +64 -64
- package/src/runtime/notification-store.js +97 -97
- package/src/runtime/notifier.js +256 -256
- package/src/runtime/orchestrator.js +53 -53
- package/src/runtime/orphan-reaper.js +41 -41
- package/src/runtime/output-contract-schema.js +139 -139
- package/src/runtime/output-contract-validator.js +439 -439
- package/src/runtime/peer-readiness.js +69 -69
- package/src/runtime/peer-registry.js +133 -133
- package/src/runtime/pilot-status.js +108 -108
- package/src/runtime/prompt-builder.js +261 -261
- package/src/runtime/provider-attempt.js +582 -582
- package/src/runtime/report-fallback.js +71 -71
- package/src/runtime/result-normalizer.js +183 -183
- package/src/runtime/retention.js +74 -74
- package/src/runtime/review.js +244 -244
- package/src/runtime/route-job.js +15 -15
- package/src/runtime/run-store.js +38 -38
- package/src/runtime/schedule.js +88 -88
- package/src/runtime/scheduler-state.js +434 -434
- package/src/runtime/scheduler.js +656 -656
- package/src/runtime/session-compactor.js +182 -182
- package/src/runtime/session-search.js +155 -155
- package/src/runtime/slack-inbound.js +249 -249
- package/src/runtime/ssrf.js +102 -102
- package/src/runtime/status-aggregator.js +330 -330
- package/src/runtime/task-contract.js +140 -140
- package/src/runtime/task-packet.js +107 -107
- package/src/runtime/task-router.js +140 -140
- package/src/runtime/telegram-inbound.js +1565 -1565
- package/src/runtime/token-counter.js +134 -134
- package/src/runtime/token-estimator.js +59 -59
- package/src/runtime/tool-loop.js +200 -200
- package/src/runtime/transport-server.js +311 -311
- package/src/runtime/tui-server.js +411 -411
- package/src/runtime/ulid.js +44 -44
- package/src/security/ssrf-check.js +197 -197
- package/src/setup.js +369 -369
- package/src/shadow/bridge.js +303 -303
- package/src/skills/loader.js +84 -84
- package/src/tools/catalog.json +49 -49
- package/src/tools/cli-delegate.js +44 -44
- package/src/tools/mcp-client.js +106 -106
- package/src/tools/micro/cancel-task.js +6 -6
- package/src/tools/micro/complete-task.js +6 -6
- package/src/tools/micro/fail-task.js +6 -6
- package/src/tools/micro/http-fetch.js +74 -74
- package/src/tools/micro/index.js +36 -36
- package/src/tools/micro/lcm-recall.js +60 -60
- package/src/tools/micro/list-dir.js +17 -17
- package/src/tools/micro/list-skills.js +46 -46
- package/src/tools/micro/load-skill.js +38 -38
- package/src/tools/micro/memory-search.js +45 -45
- package/src/tools/micro/read-file.js +11 -11
- package/src/tools/micro/session-search.js +54 -54
- package/src/tools/micro/shell-exec.js +43 -43
- package/src/tools/micro/trigger-job.js +79 -79
- package/src/tools/micro/web-search.js +58 -58
- package/src/tools/micro/workspace-paths.js +39 -39
- package/src/tools/micro/write-file.js +14 -14
- package/src/tools/micro/write-memory.js +41 -41
- package/src/tools/registry.js +348 -348
- package/src/tools/tool-result-contract.js +36 -36
- package/src/tui/chat.js +835 -835
- package/src/tui/renderer.js +175 -175
- package/src/tui/socket-client.js +217 -217
- package/src/utils/canonical-json.js +29 -29
- package/src/utils/compaction.js +30 -30
- package/src/utils/env-loader.js +5 -5
- package/src/utils/errors.js +80 -80
- package/src/utils/fs.js +101 -101
- package/src/utils/ids.js +5 -5
- package/src/utils/model-context-limits.js +30 -30
- package/src/utils/token-budget.js +74 -74
- package/src/utils/usage-cost.js +25 -25
- package/src/utils/usage-metrics.js +14 -14
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
import { createToolResult } from "../tool-result-contract.js";
|
|
2
|
-
import { inspectOutboundUrl } from "../../security/ssrf-check.js";
|
|
3
|
-
|
|
4
|
-
const BRIEF_CAP = 3000; // chars in brief sent to LLM prompt
|
|
5
|
-
const RAW_CAP = 1048576; // 1MB raw storage cap
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Build a useful brief from HTTP response content.
|
|
9
|
-
* - JSON arrays → first 20 items, serialised cleanly
|
|
10
|
-
* - JSON objects → pretty-printed (capped)
|
|
11
|
-
* - HTML → basic tag strip to text (capped)
|
|
12
|
-
* - Plain text → capped
|
|
13
|
-
*/
|
|
14
|
-
function buildBrief(status, statusText, contentType, text) {
|
|
15
|
-
const header = `HTTP ${status} ${statusText}`;
|
|
16
|
-
const isJson = contentType?.includes("application/json") || text.trimStart().startsWith("{") || text.trimStart().startsWith("[");
|
|
17
|
-
const isHtml = contentType?.includes("text/html") || text.trimStart().startsWith("<!") || text.trimStart().startsWith("<html");
|
|
18
|
-
|
|
19
|
-
if (isJson) {
|
|
20
|
-
try {
|
|
21
|
-
const parsed = JSON.parse(text);
|
|
22
|
-
let compact;
|
|
23
|
-
if (Array.isArray(parsed)) {
|
|
24
|
-
const slice = parsed.slice(0, 20);
|
|
25
|
-
compact = JSON.stringify(slice, null, 2);
|
|
26
|
-
const totalNote = parsed.length > 20 ? `\n[array length: ${parsed.length} — showing first 20]` : "";
|
|
27
|
-
return `${header}\n\n${compact.slice(0, BRIEF_CAP)}${totalNote}`;
|
|
28
|
-
} else {
|
|
29
|
-
compact = JSON.stringify(parsed, null, 2);
|
|
30
|
-
return `${header}\n\n${compact.slice(0, BRIEF_CAP)}${compact.length > BRIEF_CAP ? "\n[truncated]" : ""}`;
|
|
31
|
-
}
|
|
32
|
-
} catch {
|
|
33
|
-
// Fall through to plain text handling
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (isHtml) {
|
|
38
|
-
// Strip tags, collapse whitespace
|
|
39
|
-
const stripped = text
|
|
40
|
-
.replace(/<script[\s\S]*?<\/script>/gi, "")
|
|
41
|
-
.replace(/<style[\s\S]*?<\/style>/gi, "")
|
|
42
|
-
.replace(/<[^>]+>/g, " ")
|
|
43
|
-
.replace(/\s+/g, " ")
|
|
44
|
-
.trim();
|
|
45
|
-
return `${header}\n\n${stripped.slice(0, BRIEF_CAP)}${stripped.length > BRIEF_CAP ? "\n[truncated]" : ""}`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return `${header}\n\n${text.slice(0, BRIEF_CAP)}${text.length > BRIEF_CAP ? "\n[truncated]" : ""}`;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export async function httpFetchTool({ url, method = "GET", body, headers = {} }, { fetchImpl = globalThis.fetch, lookupImpl } = {}) {
|
|
52
|
-
try {
|
|
53
|
-
const parsed = new URL(url);
|
|
54
|
-
const inspection = await inspectOutboundUrl(parsed, { lookupImpl });
|
|
55
|
-
if (!inspection.ok) {
|
|
56
|
-
return "Error: request blocked — target resolves to a private/reserved IP address.";
|
|
57
|
-
}
|
|
58
|
-
const opts = {
|
|
59
|
-
method,
|
|
60
|
-
headers: { "User-Agent": "Nemoris/0.1", ...headers },
|
|
61
|
-
signal: AbortSignal.timeout(30000),
|
|
62
|
-
};
|
|
63
|
-
if (body && method !== "GET") opts.body = body;
|
|
64
|
-
const resp = await fetchImpl(url, opts);
|
|
65
|
-
const contentType = resp.headers.get("content-type") || "";
|
|
66
|
-
const text = await resp.text();
|
|
67
|
-
const raw = text.length > RAW_CAP ? text.slice(0, RAW_CAP) + "\n[truncated]" : text;
|
|
68
|
-
const brief = buildBrief(resp.status, resp.statusText, contentType, text);
|
|
69
|
-
|
|
70
|
-
return createToolResult(brief, raw, [url, `http-${resp.status}`]);
|
|
71
|
-
} catch (err) {
|
|
72
|
-
return `Error: ${err.message}`;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
1
|
+
import { createToolResult } from "../tool-result-contract.js";
|
|
2
|
+
import { inspectOutboundUrl } from "../../security/ssrf-check.js";
|
|
3
|
+
|
|
4
|
+
const BRIEF_CAP = 3000; // chars in brief sent to LLM prompt
|
|
5
|
+
const RAW_CAP = 1048576; // 1MB raw storage cap
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Build a useful brief from HTTP response content.
|
|
9
|
+
* - JSON arrays → first 20 items, serialised cleanly
|
|
10
|
+
* - JSON objects → pretty-printed (capped)
|
|
11
|
+
* - HTML → basic tag strip to text (capped)
|
|
12
|
+
* - Plain text → capped
|
|
13
|
+
*/
|
|
14
|
+
function buildBrief(status, statusText, contentType, text) {
|
|
15
|
+
const header = `HTTP ${status} ${statusText}`;
|
|
16
|
+
const isJson = contentType?.includes("application/json") || text.trimStart().startsWith("{") || text.trimStart().startsWith("[");
|
|
17
|
+
const isHtml = contentType?.includes("text/html") || text.trimStart().startsWith("<!") || text.trimStart().startsWith("<html");
|
|
18
|
+
|
|
19
|
+
if (isJson) {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(text);
|
|
22
|
+
let compact;
|
|
23
|
+
if (Array.isArray(parsed)) {
|
|
24
|
+
const slice = parsed.slice(0, 20);
|
|
25
|
+
compact = JSON.stringify(slice, null, 2);
|
|
26
|
+
const totalNote = parsed.length > 20 ? `\n[array length: ${parsed.length} — showing first 20]` : "";
|
|
27
|
+
return `${header}\n\n${compact.slice(0, BRIEF_CAP)}${totalNote}`;
|
|
28
|
+
} else {
|
|
29
|
+
compact = JSON.stringify(parsed, null, 2);
|
|
30
|
+
return `${header}\n\n${compact.slice(0, BRIEF_CAP)}${compact.length > BRIEF_CAP ? "\n[truncated]" : ""}`;
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// Fall through to plain text handling
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (isHtml) {
|
|
38
|
+
// Strip tags, collapse whitespace
|
|
39
|
+
const stripped = text
|
|
40
|
+
.replace(/<script[\s\S]*?<\/script>/gi, "")
|
|
41
|
+
.replace(/<style[\s\S]*?<\/style>/gi, "")
|
|
42
|
+
.replace(/<[^>]+>/g, " ")
|
|
43
|
+
.replace(/\s+/g, " ")
|
|
44
|
+
.trim();
|
|
45
|
+
return `${header}\n\n${stripped.slice(0, BRIEF_CAP)}${stripped.length > BRIEF_CAP ? "\n[truncated]" : ""}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return `${header}\n\n${text.slice(0, BRIEF_CAP)}${text.length > BRIEF_CAP ? "\n[truncated]" : ""}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function httpFetchTool({ url, method = "GET", body, headers = {} }, { fetchImpl = globalThis.fetch, lookupImpl } = {}) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = new URL(url);
|
|
54
|
+
const inspection = await inspectOutboundUrl(parsed, { lookupImpl });
|
|
55
|
+
if (!inspection.ok) {
|
|
56
|
+
return "Error: request blocked — target resolves to a private/reserved IP address.";
|
|
57
|
+
}
|
|
58
|
+
const opts = {
|
|
59
|
+
method,
|
|
60
|
+
headers: { "User-Agent": "Nemoris/0.1", ...headers },
|
|
61
|
+
signal: AbortSignal.timeout(30000),
|
|
62
|
+
};
|
|
63
|
+
if (body && method !== "GET") opts.body = body;
|
|
64
|
+
const resp = await fetchImpl(url, opts);
|
|
65
|
+
const contentType = resp.headers.get("content-type") || "";
|
|
66
|
+
const text = await resp.text();
|
|
67
|
+
const raw = text.length > RAW_CAP ? text.slice(0, RAW_CAP) + "\n[truncated]" : text;
|
|
68
|
+
const brief = buildBrief(resp.status, resp.statusText, contentType, text);
|
|
69
|
+
|
|
70
|
+
return createToolResult(brief, raw, [url, `http-${resp.status}`]);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
return `Error: ${err.message}`;
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/tools/micro/index.js
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { readFileTool } from "./read-file.js";
|
|
2
|
-
import { writeFileTool } from "./write-file.js";
|
|
3
|
-
import { listDirTool } from "./list-dir.js";
|
|
4
|
-
import { shellExecTool } from "./shell-exec.js";
|
|
5
|
-
import { httpFetchTool } from "./http-fetch.js";
|
|
6
|
-
import { executeTriggerJob } from "./trigger-job.js";
|
|
7
|
-
import { executeCompleteTask } from "./complete-task.js";
|
|
8
|
-
import { executeFailTask } from "./fail-task.js";
|
|
9
|
-
import { executeCancelTask } from "./cancel-task.js";
|
|
10
|
-
import { executeMemorySearch } from "./memory-search.js";
|
|
11
|
-
import { writeMemoryTool } from "./write-memory.js";
|
|
12
|
-
import { executeWebSearch } from "./web-search.js";
|
|
13
|
-
import { lcmRecallHandler } from "./lcm-recall.js";
|
|
14
|
-
import { loadSkillHandler } from "./load-skill.js";
|
|
15
|
-
import { listSkillsHandler } from "./list-skills.js";
|
|
16
|
-
|
|
17
|
-
export function registerMicroToolHandlers(registry, context = {}) {
|
|
18
|
-
registry.registerHandler("read_file", (input) => readFileTool(input, context));
|
|
19
|
-
registry.registerHandler("write_file", (input) => writeFileTool(input, context));
|
|
20
|
-
registry.registerHandler("list_dir", (input) => listDirTool(input, context));
|
|
21
|
-
registry.registerHandler("shell_exec", (input) => shellExecTool(input, context));
|
|
22
|
-
registry.registerHandler("http_fetch", (input) => httpFetchTool(input, context));
|
|
23
|
-
registry.registerHandler("trigger_job", (input) => executeTriggerJob(input, context));
|
|
24
|
-
registry.registerHandler("complete_task", (input) => executeCompleteTask(input, context));
|
|
25
|
-
registry.registerHandler("fail_task", (input) => executeFailTask(input, context));
|
|
26
|
-
registry.registerHandler("cancel_task", (input) => executeCancelTask(input, context));
|
|
27
|
-
registry.registerHandler("memory_search", (input) => executeMemorySearch(input, context));
|
|
28
|
-
registry.registerHandler("write_memory", (input) => writeMemoryTool(input, context));
|
|
29
|
-
registry.registerHandler("lcm_recall", (input) => lcmRecallHandler(input, context));
|
|
30
|
-
registry.registerHandler("load_skill", (input) => loadSkillHandler(input, context));
|
|
31
|
-
registry.registerHandler("list_skills", (input) => listSkillsHandler(input, context));
|
|
32
|
-
registry.registerHandler("web_search", (input) => executeWebSearch(input, {
|
|
33
|
-
fetchImpl: context.fetchImpl || globalThis.fetch,
|
|
34
|
-
apiKey: context.openRouterApiKey || process.env.OPENROUTER_API_KEY,
|
|
35
|
-
}));
|
|
36
|
-
}
|
|
1
|
+
import { readFileTool } from "./read-file.js";
|
|
2
|
+
import { writeFileTool } from "./write-file.js";
|
|
3
|
+
import { listDirTool } from "./list-dir.js";
|
|
4
|
+
import { shellExecTool } from "./shell-exec.js";
|
|
5
|
+
import { httpFetchTool } from "./http-fetch.js";
|
|
6
|
+
import { executeTriggerJob } from "./trigger-job.js";
|
|
7
|
+
import { executeCompleteTask } from "./complete-task.js";
|
|
8
|
+
import { executeFailTask } from "./fail-task.js";
|
|
9
|
+
import { executeCancelTask } from "./cancel-task.js";
|
|
10
|
+
import { executeMemorySearch } from "./memory-search.js";
|
|
11
|
+
import { writeMemoryTool } from "./write-memory.js";
|
|
12
|
+
import { executeWebSearch } from "./web-search.js";
|
|
13
|
+
import { lcmRecallHandler } from "./lcm-recall.js";
|
|
14
|
+
import { loadSkillHandler } from "./load-skill.js";
|
|
15
|
+
import { listSkillsHandler } from "./list-skills.js";
|
|
16
|
+
|
|
17
|
+
export function registerMicroToolHandlers(registry, context = {}) {
|
|
18
|
+
registry.registerHandler("read_file", (input) => readFileTool(input, context));
|
|
19
|
+
registry.registerHandler("write_file", (input) => writeFileTool(input, context));
|
|
20
|
+
registry.registerHandler("list_dir", (input) => listDirTool(input, context));
|
|
21
|
+
registry.registerHandler("shell_exec", (input) => shellExecTool(input, context));
|
|
22
|
+
registry.registerHandler("http_fetch", (input) => httpFetchTool(input, context));
|
|
23
|
+
registry.registerHandler("trigger_job", (input) => executeTriggerJob(input, context));
|
|
24
|
+
registry.registerHandler("complete_task", (input) => executeCompleteTask(input, context));
|
|
25
|
+
registry.registerHandler("fail_task", (input) => executeFailTask(input, context));
|
|
26
|
+
registry.registerHandler("cancel_task", (input) => executeCancelTask(input, context));
|
|
27
|
+
registry.registerHandler("memory_search", (input) => executeMemorySearch(input, context));
|
|
28
|
+
registry.registerHandler("write_memory", (input) => writeMemoryTool(input, context));
|
|
29
|
+
registry.registerHandler("lcm_recall", (input) => lcmRecallHandler(input, context));
|
|
30
|
+
registry.registerHandler("load_skill", (input) => loadSkillHandler(input, context));
|
|
31
|
+
registry.registerHandler("list_skills", (input) => listSkillsHandler(input, context));
|
|
32
|
+
registry.registerHandler("web_search", (input) => executeWebSearch(input, {
|
|
33
|
+
fetchImpl: context.fetchImpl || globalThis.fetch,
|
|
34
|
+
apiKey: context.openRouterApiKey || process.env.OPENROUTER_API_KEY,
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lcm_recall micro tool — search compacted conversation history.
|
|
3
|
-
*
|
|
4
|
-
* Returns a typed tool result contract: { brief, raw, index_terms }.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { createToolResult } from "../tool-result-contract.js";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @param {{ query: string, limit?: number }} input
|
|
11
|
-
* @param {{ contextLedger: object, sessionId: string }} context
|
|
12
|
-
* @returns {Promise<import('../tool-result-contract.js').ToolResultContract>}
|
|
13
|
-
*/
|
|
14
|
-
export async function lcmRecallHandler(input, context) {
|
|
15
|
-
const { query, limit = 5 } = input;
|
|
16
|
-
const { contextLedger, sessionId } = context;
|
|
17
|
-
|
|
18
|
-
if (!query) throw new Error("lcm_recall requires a query parameter.");
|
|
19
|
-
if (!contextLedger) {
|
|
20
|
-
// Fallback if ledger is missing in context (e.g. some tests)
|
|
21
|
-
return createToolResult(
|
|
22
|
-
"ContextLedger not available. Cannot recall compacted context.",
|
|
23
|
-
JSON.stringify([]),
|
|
24
|
-
[query]
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const summaries = contextLedger.getContextSummaries({ session_id: sessionId });
|
|
29
|
-
|
|
30
|
-
const queryWords = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
31
|
-
|
|
32
|
-
const matches = summaries.filter(s => {
|
|
33
|
-
const text = s.summary_text.toLowerCase();
|
|
34
|
-
return queryWords.every(word => text.includes(word));
|
|
35
|
-
}).slice(0, limit);
|
|
36
|
-
|
|
37
|
-
if (matches.length === 0) {
|
|
38
|
-
return createToolResult(
|
|
39
|
-
`No relevant compacted context found for "${query}".`,
|
|
40
|
-
JSON.stringify([]),
|
|
41
|
-
[query]
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const briefLines = matches.map((s, i) => {
|
|
46
|
-
const dateStr = s.created_at || "unknown date";
|
|
47
|
-
return `${i + 1}. [Depth ${s.depth}] (${dateStr}) — ${s.summary_text}`;
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const brief = `Found ${matches.length} matching compacted summary(ies):\n${briefLines.join("\n")}`;
|
|
51
|
-
|
|
52
|
-
const indexTerms = new Set([query]);
|
|
53
|
-
matches.forEach(s => {
|
|
54
|
-
s.summary_text.split(/\W+/).forEach(word => {
|
|
55
|
-
if (word.length > 3) indexTerms.add(word.toLowerCase());
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
return createToolResult(brief, JSON.stringify(matches), Array.from(indexTerms));
|
|
60
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* lcm_recall micro tool — search compacted conversation history.
|
|
3
|
+
*
|
|
4
|
+
* Returns a typed tool result contract: { brief, raw, index_terms }.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createToolResult } from "../tool-result-contract.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {{ query: string, limit?: number }} input
|
|
11
|
+
* @param {{ contextLedger: object, sessionId: string }} context
|
|
12
|
+
* @returns {Promise<import('../tool-result-contract.js').ToolResultContract>}
|
|
13
|
+
*/
|
|
14
|
+
export async function lcmRecallHandler(input, context) {
|
|
15
|
+
const { query, limit = 5 } = input;
|
|
16
|
+
const { contextLedger, sessionId } = context;
|
|
17
|
+
|
|
18
|
+
if (!query) throw new Error("lcm_recall requires a query parameter.");
|
|
19
|
+
if (!contextLedger) {
|
|
20
|
+
// Fallback if ledger is missing in context (e.g. some tests)
|
|
21
|
+
return createToolResult(
|
|
22
|
+
"ContextLedger not available. Cannot recall compacted context.",
|
|
23
|
+
JSON.stringify([]),
|
|
24
|
+
[query]
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const summaries = contextLedger.getContextSummaries({ session_id: sessionId });
|
|
29
|
+
|
|
30
|
+
const queryWords = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
31
|
+
|
|
32
|
+
const matches = summaries.filter(s => {
|
|
33
|
+
const text = s.summary_text.toLowerCase();
|
|
34
|
+
return queryWords.every(word => text.includes(word));
|
|
35
|
+
}).slice(0, limit);
|
|
36
|
+
|
|
37
|
+
if (matches.length === 0) {
|
|
38
|
+
return createToolResult(
|
|
39
|
+
`No relevant compacted context found for "${query}".`,
|
|
40
|
+
JSON.stringify([]),
|
|
41
|
+
[query]
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const briefLines = matches.map((s, i) => {
|
|
46
|
+
const dateStr = s.created_at || "unknown date";
|
|
47
|
+
return `${i + 1}. [Depth ${s.depth}] (${dateStr}) — ${s.summary_text}`;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const brief = `Found ${matches.length} matching compacted summary(ies):\n${briefLines.join("\n")}`;
|
|
51
|
+
|
|
52
|
+
const indexTerms = new Set([query]);
|
|
53
|
+
matches.forEach(s => {
|
|
54
|
+
s.summary_text.split(/\W+/).forEach(word => {
|
|
55
|
+
if (word.length > 3) indexTerms.add(word.toLowerCase());
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return createToolResult(brief, JSON.stringify(matches), Array.from(indexTerms));
|
|
60
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { readdir, stat } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
|
|
4
|
-
export async function listDirTool({ path }, context = {}) {
|
|
5
|
-
try {
|
|
6
|
-
const targetPath = path || context.workspaceRoot;
|
|
7
|
-
const entries = await readdir(targetPath);
|
|
8
|
-
const lines = [];
|
|
9
|
-
for (const entry of entries.sort()) {
|
|
10
|
-
const s = await stat(join(targetPath, entry));
|
|
11
|
-
lines.push(`${s.isDirectory() ? "d" : "-"} ${entry}`);
|
|
12
|
-
}
|
|
13
|
-
return lines.join("\n");
|
|
14
|
-
} catch (err) {
|
|
15
|
-
return `Error listing directory: ${err.message}`;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
1
|
+
import { readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export async function listDirTool({ path }, context = {}) {
|
|
5
|
+
try {
|
|
6
|
+
const targetPath = path || context.workspaceRoot;
|
|
7
|
+
const entries = await readdir(targetPath);
|
|
8
|
+
const lines = [];
|
|
9
|
+
for (const entry of entries.sort()) {
|
|
10
|
+
const s = await stat(join(targetPath, entry));
|
|
11
|
+
lines.push(`${s.isDirectory() ? "d" : "-"} ${entry}`);
|
|
12
|
+
}
|
|
13
|
+
return lines.join("\n");
|
|
14
|
+
} catch (err) {
|
|
15
|
+
return `Error listing directory: ${err.message}`;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import { readdir, access } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { createToolResult } from "../tool-result-contract.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* List all available skills by name.
|
|
7
|
-
*
|
|
8
|
-
* @param {Object} input - {}
|
|
9
|
-
* @param {Object} context - { workspaceRoot, installDir }
|
|
10
|
-
*/
|
|
11
|
-
export async function listSkillsHandler(_, { workspaceRoot, installDir }) {
|
|
12
|
-
const skillDirs = [
|
|
13
|
-
{ path: join(workspaceRoot, "skills"), label: "workspace" },
|
|
14
|
-
{ path: join(installDir, "skills"), label: "install" },
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
const skillNames = new Set();
|
|
18
|
-
|
|
19
|
-
for (const { path } of skillDirs) {
|
|
20
|
-
try {
|
|
21
|
-
const entries = await readdir(path, { withFileTypes: true });
|
|
22
|
-
for (const entry of entries) {
|
|
23
|
-
if (entry.isDirectory()) {
|
|
24
|
-
const skillName = entry.name;
|
|
25
|
-
const skillPath = join(path, skillName, "SKILL.md");
|
|
26
|
-
try {
|
|
27
|
-
await access(skillPath);
|
|
28
|
-
skillNames.add(skillName);
|
|
29
|
-
} catch {
|
|
30
|
-
// No SKILL.md here, skip
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
} catch (err) {
|
|
35
|
-
if (err.code !== "ENOENT") throw err;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (skillNames.size === 0) {
|
|
40
|
-
return createToolResult("No skills found.", [], ["skills"]);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const sortedNames = Array.from(skillNames).sort();
|
|
44
|
-
const brief = "Available skills:\n" + sortedNames.join("\n");
|
|
45
|
-
return createToolResult(brief, sortedNames, ["skills", "list"]);
|
|
46
|
-
}
|
|
1
|
+
import { readdir, access } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { createToolResult } from "../tool-result-contract.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* List all available skills by name.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} input - {}
|
|
9
|
+
* @param {Object} context - { workspaceRoot, installDir }
|
|
10
|
+
*/
|
|
11
|
+
export async function listSkillsHandler(_, { workspaceRoot, installDir }) {
|
|
12
|
+
const skillDirs = [
|
|
13
|
+
{ path: join(workspaceRoot, "skills"), label: "workspace" },
|
|
14
|
+
{ path: join(installDir, "skills"), label: "install" },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const skillNames = new Set();
|
|
18
|
+
|
|
19
|
+
for (const { path } of skillDirs) {
|
|
20
|
+
try {
|
|
21
|
+
const entries = await readdir(path, { withFileTypes: true });
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
const skillName = entry.name;
|
|
25
|
+
const skillPath = join(path, skillName, "SKILL.md");
|
|
26
|
+
try {
|
|
27
|
+
await access(skillPath);
|
|
28
|
+
skillNames.add(skillName);
|
|
29
|
+
} catch {
|
|
30
|
+
// No SKILL.md here, skip
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if (err.code !== "ENOENT") throw err;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (skillNames.size === 0) {
|
|
40
|
+
return createToolResult("No skills found.", [], ["skills"]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const sortedNames = Array.from(skillNames).sort();
|
|
44
|
+
const brief = "Available skills:\n" + sortedNames.join("\n");
|
|
45
|
+
return createToolResult(brief, sortedNames, ["skills", "list"]);
|
|
46
|
+
}
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { createToolResult } from "../tool-result-contract.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Load a skill by name to get instructions for a specific task.
|
|
7
|
-
* Skills provide step-by-step guidance for tools, APIs, and workflows.
|
|
8
|
-
*
|
|
9
|
-
* @param {Object} input - { name }
|
|
10
|
-
* @param {Object} context - { workspaceRoot, installDir }
|
|
11
|
-
*/
|
|
12
|
-
export async function loadSkillHandler({ name }, { workspaceRoot, installDir }) {
|
|
13
|
-
const skillPaths = [
|
|
14
|
-
join(workspaceRoot, "skills", name, "SKILL.md"),
|
|
15
|
-
join(installDir, "skills", name, "SKILL.md"),
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
let content = null;
|
|
19
|
-
for (const skillPath of skillPaths) {
|
|
20
|
-
try {
|
|
21
|
-
content = await readFile(skillPath, "utf8");
|
|
22
|
-
break;
|
|
23
|
-
} catch (err) {
|
|
24
|
-
if (err.code !== "ENOENT") throw err;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (content === null) {
|
|
29
|
-
return createToolResult(
|
|
30
|
-
`Skill '${name}' not found. Available skills can be listed with list_skills.`,
|
|
31
|
-
null,
|
|
32
|
-
[name]
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const cappedContent = content.length > 6000 ? content.slice(0, 6000) + "\n[truncated]" : content;
|
|
37
|
-
return createToolResult(cappedContent, content, [name, "skill"]);
|
|
38
|
-
}
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { createToolResult } from "../tool-result-contract.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Load a skill by name to get instructions for a specific task.
|
|
7
|
+
* Skills provide step-by-step guidance for tools, APIs, and workflows.
|
|
8
|
+
*
|
|
9
|
+
* @param {Object} input - { name }
|
|
10
|
+
* @param {Object} context - { workspaceRoot, installDir }
|
|
11
|
+
*/
|
|
12
|
+
export async function loadSkillHandler({ name }, { workspaceRoot, installDir }) {
|
|
13
|
+
const skillPaths = [
|
|
14
|
+
join(workspaceRoot, "skills", name, "SKILL.md"),
|
|
15
|
+
join(installDir, "skills", name, "SKILL.md"),
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
let content = null;
|
|
19
|
+
for (const skillPath of skillPaths) {
|
|
20
|
+
try {
|
|
21
|
+
content = await readFile(skillPath, "utf8");
|
|
22
|
+
break;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
if (err.code !== "ENOENT") throw err;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (content === null) {
|
|
29
|
+
return createToolResult(
|
|
30
|
+
`Skill '${name}' not found. Available skills can be listed with list_skills.`,
|
|
31
|
+
null,
|
|
32
|
+
[name]
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const cappedContent = content.length > 6000 ? content.slice(0, 6000) + "\n[truncated]" : content;
|
|
37
|
+
return createToolResult(cappedContent, content, [name, "skill"]);
|
|
38
|
+
}
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* memory_search micro tool — semantic + lexical search across agent memory.
|
|
3
|
-
*
|
|
4
|
-
* Returns a typed tool result contract: { brief, raw, index_terms }.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { createToolResult } from "../tool-result-contract.js";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @param {{ query: string, agentId?: string, limit?: number }} input
|
|
11
|
-
* @param {{ memoryStore: object, callerAgentId: string }} context
|
|
12
|
-
* @returns {Promise<import('../tool-result-contract.js').ToolResultContract>}
|
|
13
|
-
*/
|
|
14
|
-
export async function executeMemorySearch(input, context) {
|
|
15
|
-
const { query, agentId, limit = 5 } = input;
|
|
16
|
-
const { memoryStore, callerAgentId } = context;
|
|
17
|
-
|
|
18
|
-
if (!query) throw new Error("memory_search requires a query parameter.");
|
|
19
|
-
|
|
20
|
-
const targetAgent = agentId || callerAgentId;
|
|
21
|
-
const result = await memoryStore.query(targetAgent, query, { limit });
|
|
22
|
-
|
|
23
|
-
if (!result.items || result.items.length === 0) {
|
|
24
|
-
return createToolResult(
|
|
25
|
-
`No results found for "${query}" in ${targetAgent}'s memory.`,
|
|
26
|
-
JSON.stringify(result),
|
|
27
|
-
[query]
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Brief: concise list of titles + snippet (≤200 tokens target)
|
|
32
|
-
const briefLines = result.items.map((item, i) => {
|
|
33
|
-
const snippet = (item.content || item.summary || "").slice(0, 80);
|
|
34
|
-
return `${i + 1}. **${item.title || "untitled"}** — ${snippet}${snippet.length >= 80 ? "..." : ""}`;
|
|
35
|
-
});
|
|
36
|
-
const brief = `Found ${result.items.length} result(s) for "${query}":\n${briefLines.join("\n")}`;
|
|
37
|
-
|
|
38
|
-
// Index terms: query words + titles
|
|
39
|
-
const indexTerms = [
|
|
40
|
-
query,
|
|
41
|
-
...result.items.map((item) => item.title).filter(Boolean),
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
return createToolResult(brief, JSON.stringify(result), indexTerms);
|
|
45
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* memory_search micro tool — semantic + lexical search across agent memory.
|
|
3
|
+
*
|
|
4
|
+
* Returns a typed tool result contract: { brief, raw, index_terms }.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createToolResult } from "../tool-result-contract.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {{ query: string, agentId?: string, limit?: number }} input
|
|
11
|
+
* @param {{ memoryStore: object, callerAgentId: string }} context
|
|
12
|
+
* @returns {Promise<import('../tool-result-contract.js').ToolResultContract>}
|
|
13
|
+
*/
|
|
14
|
+
export async function executeMemorySearch(input, context) {
|
|
15
|
+
const { query, agentId, limit = 5 } = input;
|
|
16
|
+
const { memoryStore, callerAgentId } = context;
|
|
17
|
+
|
|
18
|
+
if (!query) throw new Error("memory_search requires a query parameter.");
|
|
19
|
+
|
|
20
|
+
const targetAgent = agentId || callerAgentId;
|
|
21
|
+
const result = await memoryStore.query(targetAgent, query, { limit });
|
|
22
|
+
|
|
23
|
+
if (!result.items || result.items.length === 0) {
|
|
24
|
+
return createToolResult(
|
|
25
|
+
`No results found for "${query}" in ${targetAgent}'s memory.`,
|
|
26
|
+
JSON.stringify(result),
|
|
27
|
+
[query]
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Brief: concise list of titles + snippet (≤200 tokens target)
|
|
32
|
+
const briefLines = result.items.map((item, i) => {
|
|
33
|
+
const snippet = (item.content || item.summary || "").slice(0, 80);
|
|
34
|
+
return `${i + 1}. **${item.title || "untitled"}** — ${snippet}${snippet.length >= 80 ? "..." : ""}`;
|
|
35
|
+
});
|
|
36
|
+
const brief = `Found ${result.items.length} result(s) for "${query}":\n${briefLines.join("\n")}`;
|
|
37
|
+
|
|
38
|
+
// Index terms: query words + titles
|
|
39
|
+
const indexTerms = [
|
|
40
|
+
query,
|
|
41
|
+
...result.items.map((item) => item.title).filter(Boolean),
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
return createToolResult(brief, JSON.stringify(result), indexTerms);
|
|
45
|
+
}
|