@davidjinguoxu/agentcore 0.4.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/LICENSE +21 -0
- package/README.md +198 -0
- package/config/default-triggers.json +119 -0
- package/dist/adapters/runtime-adapter.d.ts +124 -0
- package/dist/adapters/runtime-adapter.d.ts.map +1 -0
- package/dist/adapters/runtime-adapter.js +5 -0
- package/dist/adapters/runtime-adapter.js.map +1 -0
- package/dist/config/datacore-constants.d.ts +21 -0
- package/dist/config/datacore-constants.d.ts.map +1 -0
- package/dist/config/datacore-constants.js +68 -0
- package/dist/config/datacore-constants.js.map +1 -0
- package/dist/domain/embeddable.d.ts +19 -0
- package/dist/domain/embeddable.d.ts.map +1 -0
- package/dist/domain/embeddable.js +106 -0
- package/dist/domain/embeddable.js.map +1 -0
- package/dist/domain/gold-store.d.ts +11 -0
- package/dist/domain/gold-store.d.ts.map +1 -0
- package/dist/domain/gold-store.js +210 -0
- package/dist/domain/gold-store.js.map +1 -0
- package/dist/domain/inbox.d.ts +14 -0
- package/dist/domain/inbox.d.ts.map +1 -0
- package/dist/domain/inbox.js +138 -0
- package/dist/domain/inbox.js.map +1 -0
- package/dist/domain/office-status.d.ts +31 -0
- package/dist/domain/office-status.d.ts.map +1 -0
- package/dist/domain/office-status.js +138 -0
- package/dist/domain/office-status.js.map +1 -0
- package/dist/domain/questions.d.ts +3 -0
- package/dist/domain/questions.d.ts.map +1 -0
- package/dist/domain/questions.js +60 -0
- package/dist/domain/questions.js.map +1 -0
- package/dist/domain/run-manager.d.ts +14 -0
- package/dist/domain/run-manager.d.ts.map +1 -0
- package/dist/domain/run-manager.js +25 -0
- package/dist/domain/run-manager.js.map +1 -0
- package/dist/domain/task-event-index.d.ts +31 -0
- package/dist/domain/task-event-index.d.ts.map +1 -0
- package/dist/domain/task-event-index.js +117 -0
- package/dist/domain/task-event-index.js.map +1 -0
- package/dist/domain/tasks.d.ts +3 -0
- package/dist/domain/tasks.d.ts.map +1 -0
- package/dist/domain/tasks.js +112 -0
- package/dist/domain/tasks.js.map +1 -0
- package/dist/domain/warm-start-builders.d.ts +18 -0
- package/dist/domain/warm-start-builders.d.ts.map +1 -0
- package/dist/domain/warm-start-builders.js +157 -0
- package/dist/domain/warm-start-builders.js.map +1 -0
- package/dist/domain/warm-start-extractors.d.ts +37 -0
- package/dist/domain/warm-start-extractors.d.ts.map +1 -0
- package/dist/domain/warm-start-extractors.js +372 -0
- package/dist/domain/warm-start-extractors.js.map +1 -0
- package/dist/domain/warm-start.d.ts +17 -0
- package/dist/domain/warm-start.d.ts.map +1 -0
- package/dist/domain/warm-start.js +225 -0
- package/dist/domain/warm-start.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-slim-ack.d.ts +12 -0
- package/dist/mcp-slim-ack.d.ts.map +1 -0
- package/dist/mcp-slim-ack.js +16 -0
- package/dist/mcp-slim-ack.js.map +1 -0
- package/dist/routing/capability-registry.d.ts +38 -0
- package/dist/routing/capability-registry.d.ts.map +1 -0
- package/dist/routing/capability-registry.js +52 -0
- package/dist/routing/capability-registry.js.map +1 -0
- package/dist/routing/runtime-target-resolver.d.ts +42 -0
- package/dist/routing/runtime-target-resolver.d.ts.map +1 -0
- package/dist/routing/runtime-target-resolver.js +139 -0
- package/dist/routing/runtime-target-resolver.js.map +1 -0
- package/dist/search/azure-search-client.d.ts +53 -0
- package/dist/search/azure-search-client.d.ts.map +1 -0
- package/dist/search/azure-search-client.js +191 -0
- package/dist/search/azure-search-client.js.map +1 -0
- package/dist/search/circuit-breaker.d.ts +46 -0
- package/dist/search/circuit-breaker.d.ts.map +1 -0
- package/dist/search/circuit-breaker.js +87 -0
- package/dist/search/circuit-breaker.js.map +1 -0
- package/dist/search/deep-search.d.ts +24 -0
- package/dist/search/deep-search.d.ts.map +1 -0
- package/dist/search/deep-search.js +98 -0
- package/dist/search/deep-search.js.map +1 -0
- package/dist/search/search.d.ts +3 -0
- package/dist/search/search.d.ts.map +1 -0
- package/dist/search/search.js +76 -0
- package/dist/search/search.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +39 -0
- package/dist/server.js.map +1 -0
- package/dist/skills/index.d.ts +3 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +8 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/knowledge.d.ts +3 -0
- package/dist/skills/knowledge.d.ts.map +1 -0
- package/dist/skills/knowledge.js +78 -0
- package/dist/skills/knowledge.js.map +1 -0
- package/dist/skills/task-workflow.d.ts +3 -0
- package/dist/skills/task-workflow.d.ts.map +1 -0
- package/dist/skills/task-workflow.js +205 -0
- package/dist/skills/task-workflow.js.map +1 -0
- package/dist/storage/active-backend.d.ts +4 -0
- package/dist/storage/active-backend.d.ts.map +1 -0
- package/dist/storage/active-backend.js +12 -0
- package/dist/storage/active-backend.js.map +1 -0
- package/dist/storage/backend.d.ts +12 -0
- package/dist/storage/backend.d.ts.map +1 -0
- package/dist/storage/backend.js +5 -0
- package/dist/storage/backend.js.map +1 -0
- package/dist/storage/cosmos-client.d.ts +6 -0
- package/dist/storage/cosmos-client.d.ts.map +1 -0
- package/dist/storage/cosmos-client.js +38 -0
- package/dist/storage/cosmos-client.js.map +1 -0
- package/dist/storage/lancedb-backend.d.ts +13 -0
- package/dist/storage/lancedb-backend.d.ts.map +1 -0
- package/dist/storage/lancedb-backend.js +373 -0
- package/dist/storage/lancedb-backend.js.map +1 -0
- package/dist/storage/store.d.ts +8 -0
- package/dist/storage/store.d.ts.map +1 -0
- package/dist/storage/store.js +294 -0
- package/dist/storage/store.js.map +1 -0
- package/dist/storage/task-compaction.d.ts +12 -0
- package/dist/storage/task-compaction.d.ts.map +1 -0
- package/dist/storage/task-compaction.js +112 -0
- package/dist/storage/task-compaction.js.map +1 -0
- package/dist/storage/task-context-snapshot.d.ts +6 -0
- package/dist/storage/task-context-snapshot.d.ts.map +1 -0
- package/dist/storage/task-context-snapshot.js +87 -0
- package/dist/storage/task-context-snapshot.js.map +1 -0
- package/dist/storage/trigger-engine.d.ts +39 -0
- package/dist/storage/trigger-engine.d.ts.map +1 -0
- package/dist/storage/trigger-engine.js +212 -0
- package/dist/storage/trigger-engine.js.map +1 -0
- package/dist/tools.d.ts +3 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +413 -0
- package/dist/tools.js.map +1 -0
- package/dist/transport/client.d.ts +68 -0
- package/dist/transport/client.d.ts.map +1 -0
- package/dist/transport/client.js +210 -0
- package/dist/transport/client.js.map +1 -0
- package/dist/transport/http-server.d.ts +3 -0
- package/dist/transport/http-server.d.ts.map +1 -0
- package/dist/transport/http-server.js +337 -0
- package/dist/transport/http-server.js.map +1 -0
- package/dist/types.d.ts +185 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +71 -0
- package/scripts/run-server.mjs +3 -0
- package/scripts/stdio-only.mjs +23 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// trigger-engine.ts — How do triggers fire?
|
|
2
|
+
// Checks rules on every appendEvent(). Rules live in ~/.datacore/triggers.json.
|
|
3
|
+
//
|
|
4
|
+
// Review loop: every task_reviewed event MUST set context.verdict to "pass" or "fail"
|
|
5
|
+
// so conditions like context.verdict match (TR-002/TR-003). Canonical TR-001 dispatch
|
|
6
|
+
// body is exported as TR001_REVIEW_DISPATCH_MESSAGE below — keep in sync with
|
|
7
|
+
// packages/triggers/src/trigger-engine.ts DEFAULT_RULES (TR-001).
|
|
8
|
+
// Firings are logged as trigger_fired events. Failure policy: log and continue.
|
|
9
|
+
//
|
|
10
|
+
// IMPORTANT: executeTriggerActions calls appendEvent() directly (same-process).
|
|
11
|
+
// It must NOT use the HTTP API — that path has auth + port-conflict issues.
|
|
12
|
+
// The appendEvent import uses a lazy getter to break the circular dependency
|
|
13
|
+
// (store.ts imports trigger-engine.ts, trigger-engine.ts needs appendEvent).
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
import os from "node:os";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
import { execFile } from "node:child_process";
|
|
19
|
+
let _execFile = execFile;
|
|
20
|
+
export function _setExecFileFn(fn) {
|
|
21
|
+
_execFile = fn;
|
|
22
|
+
}
|
|
23
|
+
let _appendEvent = async () => { };
|
|
24
|
+
export function _setAppendEventFn(fn) {
|
|
25
|
+
_appendEvent = fn;
|
|
26
|
+
}
|
|
27
|
+
// TTL cache — re-read triggers.json at most once per minute
|
|
28
|
+
let _cachedRules = null;
|
|
29
|
+
let _cacheTime = 0;
|
|
30
|
+
const CACHE_TTL_MS = 60_000;
|
|
31
|
+
export function _resetRulesCache() {
|
|
32
|
+
_cachedRules = null;
|
|
33
|
+
_cacheTime = 0;
|
|
34
|
+
}
|
|
35
|
+
/** TR-001 dispatch message template (sync with packages/triggers/src/trigger-engine.ts TR-001). */
|
|
36
|
+
export const TR001_REVIEW_DISPATCH_MESSAGE = "Task ${context.task_id} completed by ${context.builder}. " +
|
|
37
|
+
"Commit: ${context.commit_hash}. Summary: ${context.summary}. " +
|
|
38
|
+
"Procedure: Follow SK-T01 (review-task) — datacore/docs/design/08-skills-system.md Section 4. " +
|
|
39
|
+
'Read full history with get_tasks(task_id="${context.task_id}") then verify each acceptance criterion. ' +
|
|
40
|
+
'REQUIRED: call log_event(source="claude-desktop", type="task_reviewed", ' +
|
|
41
|
+
'content="<task_id> review: <one-line summary>", ' +
|
|
42
|
+
'context={ task_id: "${context.task_id}", verdict: "pass"|"fail", score: <1-10>, criteria_results: [...], ' +
|
|
43
|
+
'status: ("rework" if verdict is "fail" else "completed") }). ' +
|
|
44
|
+
'You MUST include context.verdict as the string "pass" or "fail" (TR-002/TR-003 match on context.verdict). ' +
|
|
45
|
+
'You MUST set context.status to "rework" when verdict is "fail" and "completed" when verdict is "pass" so the task board reflects review outcome. ' +
|
|
46
|
+
"Chat-only response will not close the loop.";
|
|
47
|
+
function resolveTriggersPath() {
|
|
48
|
+
return (process.env.AGENTCORE_TRIGGERS_PATH ?? // primary
|
|
49
|
+
process.env.DATACORE_TRIGGERS_PATH ?? // legacy alias
|
|
50
|
+
path.join(os.homedir(), ".agentcore", "triggers.json"));
|
|
51
|
+
}
|
|
52
|
+
// Bundled default rules — used when no user triggers.json exists.
|
|
53
|
+
// Provides the core review loop (TR-001/002/003) and safety nets (TR-006/009)
|
|
54
|
+
// using log_event actions only (no dispatch scripts required).
|
|
55
|
+
function resolveDefaultTriggersPath() {
|
|
56
|
+
return path.join(path.dirname(fileURLToPath(import.meta.url)), "../../config/default-triggers.json");
|
|
57
|
+
}
|
|
58
|
+
function loadRules() {
|
|
59
|
+
// Disable triggers in test mode
|
|
60
|
+
if (process.env.AGENTCORE_TRIGGERS_DISABLED === "true")
|
|
61
|
+
return [];
|
|
62
|
+
if (process.env.DATACORE_TRIGGERS_DISABLED === "true")
|
|
63
|
+
return []; // legacy alias
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
if (_cachedRules !== null && now - _cacheTime < CACHE_TTL_MS)
|
|
66
|
+
return _cachedRules;
|
|
67
|
+
// Try user config first, fall back to bundled defaults
|
|
68
|
+
const candidates = [resolveTriggersPath(), resolveDefaultTriggersPath()];
|
|
69
|
+
for (const p of candidates) {
|
|
70
|
+
try {
|
|
71
|
+
const raw = fs.readFileSync(p, "utf8");
|
|
72
|
+
const config = JSON.parse(raw);
|
|
73
|
+
_cachedRules = config.rules.filter((r) => r.enabled);
|
|
74
|
+
_cacheTime = now;
|
|
75
|
+
return _cachedRules;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
function matchesCondition(rule, record) {
|
|
84
|
+
const cond = rule.condition;
|
|
85
|
+
if (cond.event_type && record.type !== cond.event_type)
|
|
86
|
+
return false;
|
|
87
|
+
const ctx = (record.context ?? {});
|
|
88
|
+
for (const [key, expected] of Object.entries(cond)) {
|
|
89
|
+
if (key === "event_type")
|
|
90
|
+
continue;
|
|
91
|
+
if (key.startsWith("context.")) {
|
|
92
|
+
const field = key.slice(8);
|
|
93
|
+
if (ctx[field] !== expected)
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
function interpolate(template, record) {
|
|
100
|
+
const ctx = (record.context ?? {});
|
|
101
|
+
return template.replace(/\$\{context\.(\w+)\}/g, (_, field) => {
|
|
102
|
+
return String(ctx[field] ?? "");
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
export function checkTriggers(record) {
|
|
106
|
+
// Guard: never trigger on trigger_fired events (prevents infinite loops)
|
|
107
|
+
if (record.type === "trigger_fired")
|
|
108
|
+
return [];
|
|
109
|
+
const rules = loadRules();
|
|
110
|
+
const matches = [];
|
|
111
|
+
for (const rule of rules) {
|
|
112
|
+
if (matchesCondition(rule, record)) {
|
|
113
|
+
const msg = rule.action.message
|
|
114
|
+
? interpolate(rule.action.message, record)
|
|
115
|
+
: "";
|
|
116
|
+
matches.push({ rule, resolvedMessage: msg });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return matches;
|
|
120
|
+
}
|
|
121
|
+
async function logTriggerFired(rule, record, result, detail) {
|
|
122
|
+
try {
|
|
123
|
+
// Use direct appendEvent (injected by store.ts) — NOT the HTTP API.
|
|
124
|
+
// The HTTP path has auth requirements and port-conflict risks. Direct call is safe.
|
|
125
|
+
await _appendEvent({
|
|
126
|
+
source: "datacore",
|
|
127
|
+
type: "trigger_fired",
|
|
128
|
+
content: `${rule.rule_id} ${result}: ${rule.name} — ${detail}`,
|
|
129
|
+
context: {
|
|
130
|
+
rule_id: rule.rule_id,
|
|
131
|
+
rule_name: rule.name,
|
|
132
|
+
triggered_by_event: record._event_id,
|
|
133
|
+
triggered_by_type: record.type,
|
|
134
|
+
action_type: rule.action.type,
|
|
135
|
+
result,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
process.stderr.write(`[trigger] Failed to log trigger_fired for ${rule.rule_id}\n`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function findDispatchScript() {
|
|
144
|
+
// Primary: resolve relative to this source file's location.
|
|
145
|
+
// trigger-engine.ts lives at engine/src/storage/ (compiled: engine/dist/storage/)
|
|
146
|
+
// dispatch-to.sh lives at scripts/dispatch-to.sh (3 levels up from dist/storage/)
|
|
147
|
+
const __dir = path.dirname(fileURLToPath(import.meta.url));
|
|
148
|
+
const fromSourceFile = path.resolve(__dir, "..", "..", "..", "scripts", "dispatch-to.sh");
|
|
149
|
+
if (fs.existsSync(fromSourceFile))
|
|
150
|
+
return fromSourceFile;
|
|
151
|
+
// Fallback: cwd-relative (works when invoked from repo root)
|
|
152
|
+
const fromCwd = path.join(process.cwd(), "scripts", "dispatch-to.sh");
|
|
153
|
+
if (fs.existsSync(fromCwd))
|
|
154
|
+
return fromCwd;
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
export async function executeTriggerActions(matches, record) {
|
|
158
|
+
const dispatchScript = findDispatchScript();
|
|
159
|
+
for (const { rule, resolvedMessage } of matches) {
|
|
160
|
+
try {
|
|
161
|
+
const action = rule.action;
|
|
162
|
+
if (action.type === "dispatch" && action.target && dispatchScript) {
|
|
163
|
+
const resolvedTarget = interpolate(action.target, record).trim();
|
|
164
|
+
if (!resolvedTarget) {
|
|
165
|
+
await logTriggerFired(rule, record, "failed", "Dispatch skipped: target empty after interpolation (check context.assigned_to, etc.)");
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
// Use execFile with args array — no shell interpolation, no injection risk
|
|
169
|
+
_execFile(dispatchScript, [resolvedTarget, resolvedMessage], { timeout: 30000 }, () => { });
|
|
170
|
+
await logTriggerFired(rule, record, "success", `Dispatched to ${resolvedTarget}: ${resolvedMessage.slice(0, 100)}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else if (action.type === "dispatch_multi" && dispatchScript) {
|
|
174
|
+
const ctx = (record.context ?? {});
|
|
175
|
+
const targets = Array.isArray(ctx.respond_by)
|
|
176
|
+
? ctx.respond_by
|
|
177
|
+
: [];
|
|
178
|
+
for (const target of targets) {
|
|
179
|
+
_execFile(dispatchScript, [target, resolvedMessage], { timeout: 30000 }, () => { });
|
|
180
|
+
}
|
|
181
|
+
await logTriggerFired(rule, record, "success", `Dispatched to ${targets.length} agents: ${targets.join(", ")}`);
|
|
182
|
+
}
|
|
183
|
+
else if (action.type === "log_event" && action.event) {
|
|
184
|
+
const evt = action.event;
|
|
185
|
+
const interpolatedContext = evt.context
|
|
186
|
+
? Object.fromEntries(Object.entries(evt.context).map(([k, v]) => [
|
|
187
|
+
k,
|
|
188
|
+
typeof v === "string" ? interpolate(v, record) : v,
|
|
189
|
+
]))
|
|
190
|
+
: { triggered_by: rule.rule_id, ...(record.context ?? {}) };
|
|
191
|
+
// Use direct appendEvent — same reason as logTriggerFired above.
|
|
192
|
+
await _appendEvent({
|
|
193
|
+
source: String(evt.source ?? "datacore"),
|
|
194
|
+
type: interpolate(String(evt.type ?? "trigger_action"), record),
|
|
195
|
+
content: interpolate(String(evt.content ?? ""), record),
|
|
196
|
+
context: interpolatedContext,
|
|
197
|
+
});
|
|
198
|
+
await logTriggerFired(rule, record, "success", `Logged event: ${evt.type}`);
|
|
199
|
+
}
|
|
200
|
+
else if (action.type === "notify") {
|
|
201
|
+
process.stderr.write(`[trigger] ${rule.rule_id}: ${resolvedMessage}\n`);
|
|
202
|
+
await logTriggerFired(rule, record, "success", resolvedMessage);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
207
|
+
process.stderr.write(`[trigger] ${rule.rule_id} FAILED: ${msg}\n`);
|
|
208
|
+
await logTriggerFired(rule, record, "failed", msg);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=trigger-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trigger-engine.js","sourceRoot":"","sources":["../../src/storage/trigger-engine.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAyBzE,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAohBrD"}
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
// tools.ts — What can agents do?
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { appendEvent, getBronzeDir } from "./storage/store.js";
|
|
4
|
+
import { searchEvents } from "./search/search.js";
|
|
5
|
+
import { getTasks } from "./domain/tasks.js";
|
|
6
|
+
import { deepSearch } from "./search/deep-search.js";
|
|
7
|
+
import { upsertEntity, queryEntities, getGoldDir, getLinkedEntities, } from "./domain/gold-store.js";
|
|
8
|
+
import { getQuestions } from "./domain/questions.js";
|
|
9
|
+
import { buildWarmStartPreamble } from "./domain/warm-start.js";
|
|
10
|
+
import { registerSkills } from "./skills/index.js";
|
|
11
|
+
import { bronzeWriteAck, goldUpsertAck } from "./mcp-slim-ack.js";
|
|
12
|
+
function toTextResult(text, structuredContent) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text }],
|
|
15
|
+
...(structuredContent !== undefined
|
|
16
|
+
? { structuredContent: structuredContent }
|
|
17
|
+
: {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function registerTools(server) {
|
|
21
|
+
// ─── log_event ─────────────────────────────────────────────
|
|
22
|
+
server.tool("log_event", "Append a raw event to the Bronze layer in Azure Cosmos DB.", {
|
|
23
|
+
source: z.string().min(1),
|
|
24
|
+
type: z.string().min(1),
|
|
25
|
+
content: z.string().min(1),
|
|
26
|
+
project: z.string().min(1).optional(),
|
|
27
|
+
context: z.record(z.string(), z.unknown()).optional(),
|
|
28
|
+
}, {
|
|
29
|
+
readOnlyHint: false,
|
|
30
|
+
destructiveHint: true,
|
|
31
|
+
idempotentHint: false,
|
|
32
|
+
openWorldHint: true,
|
|
33
|
+
}, async ({ source, type, content, project, context }) => {
|
|
34
|
+
const logged = await appendEvent({
|
|
35
|
+
source,
|
|
36
|
+
type,
|
|
37
|
+
content,
|
|
38
|
+
project,
|
|
39
|
+
context,
|
|
40
|
+
});
|
|
41
|
+
return toTextResult(`Logged ${type} from ${source} to ${logged.filePath}`, bronzeWriteAck(logged.record));
|
|
42
|
+
});
|
|
43
|
+
// ─── search ───────────────────────────────────────────────
|
|
44
|
+
server.tool("search", "Search collected Bronze events using case-insensitive full-text matching.", {
|
|
45
|
+
query: z.string().min(1),
|
|
46
|
+
max_results: z.number().int().min(1).max(100).optional(),
|
|
47
|
+
source: z.string().optional().describe("Filter by source"),
|
|
48
|
+
type: z.string().optional().describe("Filter by event type"),
|
|
49
|
+
}, {
|
|
50
|
+
readOnlyHint: true,
|
|
51
|
+
destructiveHint: false,
|
|
52
|
+
idempotentHint: true,
|
|
53
|
+
openWorldHint: true,
|
|
54
|
+
}, async ({ query, max_results: maxResults, source, type }) => {
|
|
55
|
+
const result = await searchEvents({ query, maxResults, source, type });
|
|
56
|
+
if (result.results.length === 0) {
|
|
57
|
+
const filters = [source && `source=${source}`, type && `type=${type}`]
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join(", ");
|
|
60
|
+
const filterNote = filters ? ` (filters: ${filters})` : "";
|
|
61
|
+
return toTextResult(`No events matched "${query}"${filterNote} in ${result.bronzeDir}`, result);
|
|
62
|
+
}
|
|
63
|
+
const summary = result.results
|
|
64
|
+
.map((match, index) => {
|
|
65
|
+
const timestamp = match.timestamp ?? "?";
|
|
66
|
+
const src = match.source ?? "?";
|
|
67
|
+
const typ = match.type ?? "?";
|
|
68
|
+
return `${index + 1}. [${src}/${typ}] ${timestamp} ${match.snippet}`;
|
|
69
|
+
})
|
|
70
|
+
.join("\n");
|
|
71
|
+
return toTextResult(`Found ${result.results.length} match(es) for "${query}" in ${getBronzeDir()}\n${summary}`, result);
|
|
72
|
+
});
|
|
73
|
+
// ─── get_tasks ─────────────────────────────────────────────
|
|
74
|
+
server.tool("get_tasks", "Query task board from Bronze events.", {
|
|
75
|
+
status: z
|
|
76
|
+
.enum(["active", "completed", "failed", "rework", "all"])
|
|
77
|
+
.optional(),
|
|
78
|
+
assigned_to: z.string().optional(),
|
|
79
|
+
task_type: z.string().optional(),
|
|
80
|
+
task_id: z.string().optional(),
|
|
81
|
+
limit: z.number().int().min(1).max(50).optional(),
|
|
82
|
+
}, {
|
|
83
|
+
readOnlyHint: true,
|
|
84
|
+
destructiveHint: false,
|
|
85
|
+
idempotentHint: true,
|
|
86
|
+
openWorldHint: true,
|
|
87
|
+
}, async ({ status, assigned_to, task_type, task_id, limit }) => {
|
|
88
|
+
const result = await getTasks({
|
|
89
|
+
status,
|
|
90
|
+
assigned_to,
|
|
91
|
+
task_type,
|
|
92
|
+
task_id,
|
|
93
|
+
limit,
|
|
94
|
+
});
|
|
95
|
+
if (result.mode === "history") {
|
|
96
|
+
const { events, totalEvents } = result;
|
|
97
|
+
if (events.length === 0) {
|
|
98
|
+
return toTextResult(`No task events found for task_id "${task_id}"`, result);
|
|
99
|
+
}
|
|
100
|
+
const timeline = events
|
|
101
|
+
.map((ev, i) => {
|
|
102
|
+
const ts = ev.timestamp ?? "?";
|
|
103
|
+
const src = ev.source ?? "?";
|
|
104
|
+
return `${i + 1}. [${ev.type}] ${ts} by ${src}\n ${ev.content}`;
|
|
105
|
+
})
|
|
106
|
+
.join("\n\n");
|
|
107
|
+
const created = events[0];
|
|
108
|
+
const ctx = created.context ?? {};
|
|
109
|
+
const contextSummary = [
|
|
110
|
+
ctx.problem && `Problem: ${ctx.problem}`,
|
|
111
|
+
ctx.impact && `Impact: ${ctx.impact}`,
|
|
112
|
+
ctx.project && `Project: ${ctx.project}`,
|
|
113
|
+
ctx.assigned_to && `Assigned to: ${ctx.assigned_to}`,
|
|
114
|
+
]
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
.join("\n");
|
|
117
|
+
return toTextResult(`Task ${task_id} — ${totalEvents} events\n\n` +
|
|
118
|
+
(contextSummary ? `--- Context ---\n${contextSummary}\n\n` : "") +
|
|
119
|
+
`--- Timeline ---\n${timeline}`, result);
|
|
120
|
+
}
|
|
121
|
+
// Board mode
|
|
122
|
+
const { tasks, total_tasks } = result;
|
|
123
|
+
if (tasks.length === 0) {
|
|
124
|
+
return toTextResult(`No ${status ?? "active"} tasks found`, result);
|
|
125
|
+
}
|
|
126
|
+
const board = tasks
|
|
127
|
+
.map((t) => {
|
|
128
|
+
const parts = [
|
|
129
|
+
`[${t.task_id}] ${t.status.toUpperCase()}`,
|
|
130
|
+
t.assigned_to ? `→ ${t.assigned_to}` : "→ unassigned",
|
|
131
|
+
t.task_type ? `(${t.task_type})` : "",
|
|
132
|
+
];
|
|
133
|
+
const header = parts.filter(Boolean).join(" ");
|
|
134
|
+
const lines = [header];
|
|
135
|
+
if (t.summary)
|
|
136
|
+
lines.push(` Summary: ${t.summary.slice(0, 200)}`);
|
|
137
|
+
if (t.problem)
|
|
138
|
+
lines.push(` Why: ${t.problem}`);
|
|
139
|
+
return lines.join("\n");
|
|
140
|
+
})
|
|
141
|
+
.join("\n\n");
|
|
142
|
+
return toTextResult(`Task Board (${status ?? "active"}) — ${total_tasks} task(s)\n\n${board}`, result);
|
|
143
|
+
});
|
|
144
|
+
// ─── deep_search ─────────────────────────────────────────
|
|
145
|
+
server.tool("deep_search", "Semantic (vector) search over Bronze events — finds by meaning, not keywords. " +
|
|
146
|
+
"Local mode: LanceDB full-text index. Cloud mode: Azure AI Search (set AZURE_SEARCH_ENDPOINT + AZURE_SEARCH_KEY).", {
|
|
147
|
+
query: z.string().min(1).describe("Natural language search query"),
|
|
148
|
+
num_results: z
|
|
149
|
+
.number()
|
|
150
|
+
.int()
|
|
151
|
+
.min(1)
|
|
152
|
+
.max(20)
|
|
153
|
+
.optional()
|
|
154
|
+
.describe("Max results (default 5)"),
|
|
155
|
+
source: z.string().optional().describe("Filter by source"),
|
|
156
|
+
type: z.string().optional().describe("Filter by event type"),
|
|
157
|
+
mode: z
|
|
158
|
+
.enum(["hybrid", "semantic"])
|
|
159
|
+
.optional()
|
|
160
|
+
.describe("'hybrid' (default) = keyword+semantic, 'semantic' = vector only"),
|
|
161
|
+
}, {
|
|
162
|
+
readOnlyHint: true,
|
|
163
|
+
destructiveHint: false,
|
|
164
|
+
idempotentHint: true,
|
|
165
|
+
openWorldHint: true,
|
|
166
|
+
}, async ({ query, num_results: numResults, source, type, mode }) => {
|
|
167
|
+
try {
|
|
168
|
+
const result = await deepSearch({
|
|
169
|
+
query,
|
|
170
|
+
numResults,
|
|
171
|
+
source,
|
|
172
|
+
type,
|
|
173
|
+
mode,
|
|
174
|
+
});
|
|
175
|
+
if (result.results.length === 0) {
|
|
176
|
+
return toTextResult(`No semantic matches for "${query}"`, result);
|
|
177
|
+
}
|
|
178
|
+
const summary = result.results
|
|
179
|
+
.map((r, i) => {
|
|
180
|
+
const ts = r.timestamp ?? "?";
|
|
181
|
+
const src = r.source ?? "?";
|
|
182
|
+
const typ = r.type ?? "?";
|
|
183
|
+
const content = (r.content ?? "").slice(0, 200);
|
|
184
|
+
return `${i + 1}. [${src}/${typ}] ${ts}\n ${content}`;
|
|
185
|
+
})
|
|
186
|
+
.join("\n\n");
|
|
187
|
+
return toTextResult(`Found ${result.totalResults} semantic match(es) for "${query}" (${result.mode} mode)\n\n${summary}`, result);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
191
|
+
if (msg.includes("DATABRICKS_HOST")) {
|
|
192
|
+
return toTextResult("deep_search not configured. Set DATABRICKS_HOST and DATABRICKS_TOKEN env vars. " +
|
|
193
|
+
"Run Databricks notebooks 01 and 02 first.");
|
|
194
|
+
}
|
|
195
|
+
return toTextResult(`deep_search error: ${msg}`);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
// ─── get_facts ────────────────────────────────────────────
|
|
199
|
+
server.tool("get_facts", "Query Gold entities — structured facts extracted from Bronze events. " +
|
|
200
|
+
'Answers "what do we know about X?" rather than fuzzy search.', {
|
|
201
|
+
entity_type: z
|
|
202
|
+
.string()
|
|
203
|
+
.optional()
|
|
204
|
+
.describe("Filter by entity type: decision, fact, project, tool"),
|
|
205
|
+
project: z
|
|
206
|
+
.string()
|
|
207
|
+
.optional()
|
|
208
|
+
.describe("Filter by project name (partial match)"),
|
|
209
|
+
tag: z.string().optional().describe("Filter by tag (partial match)"),
|
|
210
|
+
query: z
|
|
211
|
+
.string()
|
|
212
|
+
.optional()
|
|
213
|
+
.describe("Keyword search within entity summaries and data"),
|
|
214
|
+
}, {
|
|
215
|
+
readOnlyHint: true,
|
|
216
|
+
destructiveHint: false,
|
|
217
|
+
idempotentHint: true,
|
|
218
|
+
openWorldHint: false,
|
|
219
|
+
}, async ({ entity_type, project, tag, query }) => {
|
|
220
|
+
const result = await queryEntities({ entity_type, project, tag, query });
|
|
221
|
+
if (result.total === 0) {
|
|
222
|
+
const filters = [
|
|
223
|
+
entity_type && `type=${entity_type}`,
|
|
224
|
+
project && `project=${project}`,
|
|
225
|
+
tag && `tag=${tag}`,
|
|
226
|
+
query && `query="${query}"`,
|
|
227
|
+
]
|
|
228
|
+
.filter(Boolean)
|
|
229
|
+
.join(", ");
|
|
230
|
+
return toTextResult(`No Gold entities found${filters ? ` (${filters})` : ""} in ${getGoldDir()}`, result);
|
|
231
|
+
}
|
|
232
|
+
const lines = result.entities
|
|
233
|
+
.map((e, i) => {
|
|
234
|
+
const meta = [
|
|
235
|
+
e.project && `project=${e.project}`,
|
|
236
|
+
e.tags?.length && `tags=[${e.tags.join(", ")}]`,
|
|
237
|
+
e.links_to?.length && `links_to=[${e.links_to.join(", ")}]`,
|
|
238
|
+
e.source_events?.length && `sources=${e.source_events.length}`,
|
|
239
|
+
]
|
|
240
|
+
.filter(Boolean)
|
|
241
|
+
.join(" ");
|
|
242
|
+
return `${i + 1}. [${e.entity_type}] ${e.summary}${meta ? `\n ${meta}` : ""}`;
|
|
243
|
+
})
|
|
244
|
+
.join("\n\n");
|
|
245
|
+
return toTextResult(`Found ${result.total} Gold ${result.total === 1 ? "entity" : "entities"}\n\n${lines}`, result);
|
|
246
|
+
});
|
|
247
|
+
// ─── get_linked ─────────────────────────────────────────────
|
|
248
|
+
server.tool("get_linked", "Traverse the Gold knowledge graph from one entity: outbound links_to targets and inbound linkers (DC-T12).", {
|
|
249
|
+
entity_id: z
|
|
250
|
+
.string()
|
|
251
|
+
.min(1)
|
|
252
|
+
.describe("Gold entity_id (UUID from add_entity / get_facts structured output)"),
|
|
253
|
+
}, {
|
|
254
|
+
readOnlyHint: true,
|
|
255
|
+
destructiveHint: false,
|
|
256
|
+
idempotentHint: true,
|
|
257
|
+
openWorldHint: false,
|
|
258
|
+
}, async ({ entity_id }) => {
|
|
259
|
+
const result = await getLinkedEntities(entity_id);
|
|
260
|
+
if (!result.entity) {
|
|
261
|
+
return toTextResult(`No Gold entity with entity_id=${entity_id}`, result);
|
|
262
|
+
}
|
|
263
|
+
const e = result.entity;
|
|
264
|
+
const outLines = result.links_out.map((x) => ` → [${x.entity_type}] ${x.summary} (${x.entity_id})`);
|
|
265
|
+
const inLines = result.links_in.map((x) => ` ← [${x.entity_type}] ${x.summary} (${x.entity_id})`);
|
|
266
|
+
const text = [
|
|
267
|
+
`[${e.entity_type}] ${e.summary}`,
|
|
268
|
+
`id=${e.entity_id}`,
|
|
269
|
+
result.links_out.length
|
|
270
|
+
? `Outbound (${result.links_out.length}):\n${outLines.join("\n")}`
|
|
271
|
+
: "Outbound: (none)",
|
|
272
|
+
result.links_in.length
|
|
273
|
+
? `Inbound (${result.links_in.length}):\n${inLines.join("\n")}`
|
|
274
|
+
: "Inbound: (none)",
|
|
275
|
+
`Total connections: ${result.total_connections}`,
|
|
276
|
+
].join("\n\n");
|
|
277
|
+
return toTextResult(text, result);
|
|
278
|
+
});
|
|
279
|
+
// ─── add_entity ───────────────────────────────────────────
|
|
280
|
+
server.tool("add_entity", "Create or update a Gold entity (structured fact). " +
|
|
281
|
+
"Upserts by summary+project — same summary in same project updates rather than duplicates.", {
|
|
282
|
+
entity_type: z
|
|
283
|
+
.string()
|
|
284
|
+
.min(1)
|
|
285
|
+
.describe("Entity type: decision, fact, project, tool, or custom"),
|
|
286
|
+
summary: z.string().min(1).describe("One-sentence summary of the entity"),
|
|
287
|
+
project: z.string().optional().describe("Project this entity belongs to"),
|
|
288
|
+
tags: z.array(z.string()).optional().describe("Tags for filtering"),
|
|
289
|
+
links_to: z
|
|
290
|
+
.array(z.string())
|
|
291
|
+
.optional()
|
|
292
|
+
.describe("Other Gold entity_ids this row links to (knowledge graph edges)"),
|
|
293
|
+
source_events: z
|
|
294
|
+
.array(z.string())
|
|
295
|
+
.optional()
|
|
296
|
+
.describe("Bronze event IDs that inform this entity"),
|
|
297
|
+
data: z
|
|
298
|
+
.record(z.string(), z.unknown())
|
|
299
|
+
.optional()
|
|
300
|
+
.describe("Structured entity data"),
|
|
301
|
+
}, {
|
|
302
|
+
readOnlyHint: false,
|
|
303
|
+
destructiveHint: false,
|
|
304
|
+
idempotentHint: true,
|
|
305
|
+
openWorldHint: false,
|
|
306
|
+
}, async ({ entity_type, summary, project, tags, links_to, source_events, data, }) => {
|
|
307
|
+
const result = await upsertEntity({
|
|
308
|
+
entity_type,
|
|
309
|
+
summary,
|
|
310
|
+
project,
|
|
311
|
+
tags,
|
|
312
|
+
links_to,
|
|
313
|
+
source_events,
|
|
314
|
+
data,
|
|
315
|
+
});
|
|
316
|
+
return toTextResult(`${result.action === "created" ? "Created" : "Updated"} Gold entity ${result.entity_id} in ${result.file_path}`, goldUpsertAck(result));
|
|
317
|
+
});
|
|
318
|
+
// ─── get_questions ────────────────────────────────────────
|
|
319
|
+
server.tool("get_questions", "Query async questions between AI agents. " +
|
|
320
|
+
'Agents post questions via log_event(type="question"), others answer via log_event(type="answer"). ' +
|
|
321
|
+
"Check at session start for pending questions directed to you.", {
|
|
322
|
+
directed_to: z
|
|
323
|
+
.string()
|
|
324
|
+
.optional()
|
|
325
|
+
.describe('Filter by who the question is directed to (e.g. "claude-desktop")'),
|
|
326
|
+
status: z
|
|
327
|
+
.enum(["open", "answered", "all"])
|
|
328
|
+
.optional()
|
|
329
|
+
.describe("Filter by status: open (unanswered), answered, or all. Default: open"),
|
|
330
|
+
task_id: z
|
|
331
|
+
.string()
|
|
332
|
+
.optional()
|
|
333
|
+
.describe("Filter by task_id in question context"),
|
|
334
|
+
limit: z
|
|
335
|
+
.number()
|
|
336
|
+
.optional()
|
|
337
|
+
.describe("Max results to return. Default: 10"),
|
|
338
|
+
}, {
|
|
339
|
+
readOnlyHint: true,
|
|
340
|
+
destructiveHint: false,
|
|
341
|
+
idempotentHint: true,
|
|
342
|
+
openWorldHint: false,
|
|
343
|
+
}, async ({ directed_to, status, task_id, limit }) => {
|
|
344
|
+
const result = await getQuestions({
|
|
345
|
+
directed_to,
|
|
346
|
+
status,
|
|
347
|
+
task_id,
|
|
348
|
+
limit,
|
|
349
|
+
});
|
|
350
|
+
if (result.total === 0) {
|
|
351
|
+
const filters = [
|
|
352
|
+
directed_to && `directed_to=${directed_to}`,
|
|
353
|
+
status && `status=${status}`,
|
|
354
|
+
task_id && `task_id=${task_id}`,
|
|
355
|
+
]
|
|
356
|
+
.filter(Boolean)
|
|
357
|
+
.join(", ");
|
|
358
|
+
return toTextResult(`No questions found${filters ? ` (${filters})` : ""} in ${result.bronzeDir}`, result);
|
|
359
|
+
}
|
|
360
|
+
const lines = result.questions
|
|
361
|
+
.map((q, i) => {
|
|
362
|
+
const meta = [
|
|
363
|
+
q.asked_by && `from=${q.asked_by}`,
|
|
364
|
+
q.directed_to && `to=${q.directed_to}`,
|
|
365
|
+
q.task_id && `task=${q.task_id}`,
|
|
366
|
+
]
|
|
367
|
+
.filter(Boolean)
|
|
368
|
+
.join(" ");
|
|
369
|
+
const statusIcon = q.status === "open" ? "❓" : "✅";
|
|
370
|
+
let line = `${i + 1}. ${statusIcon} [${q.thread_id}] ${q.question.slice(0, 200)}`;
|
|
371
|
+
if (meta)
|
|
372
|
+
line += `\n ${meta}`;
|
|
373
|
+
if (q.answer)
|
|
374
|
+
line += `\n → ${q.answer.slice(0, 200)}`;
|
|
375
|
+
return line;
|
|
376
|
+
})
|
|
377
|
+
.join("\n\n");
|
|
378
|
+
return toTextResult(`Found ${result.total} question${result.total === 1 ? "" : "s"}\n\n${lines}`, result);
|
|
379
|
+
});
|
|
380
|
+
// ─── warm_start ──────────────────────────────────────────
|
|
381
|
+
server.tool("warm_start", "Assemble a structured context preamble for a task. " +
|
|
382
|
+
"Returns layered context: identity (L0), task spec (L1), project gotchas (L2), " +
|
|
383
|
+
"knowledge graph links (L3), recent completions (L4), retry context (L5), file manifest (L6). " +
|
|
384
|
+
"Call at session start before working on any task.", {
|
|
385
|
+
task_id: z.string().min(1).describe("Task ID to assemble context for"),
|
|
386
|
+
agent: z
|
|
387
|
+
.string()
|
|
388
|
+
.min(1)
|
|
389
|
+
.describe("Agent identity (e.g. codex, cursor, claude-desktop)"),
|
|
390
|
+
budget_tokens: z
|
|
391
|
+
.number()
|
|
392
|
+
.int()
|
|
393
|
+
.min(1000)
|
|
394
|
+
.max(32000)
|
|
395
|
+
.optional()
|
|
396
|
+
.describe("Max token budget for preamble (default 8000)"),
|
|
397
|
+
}, {
|
|
398
|
+
readOnlyHint: true,
|
|
399
|
+
destructiveHint: false,
|
|
400
|
+
idempotentHint: true,
|
|
401
|
+
openWorldHint: false,
|
|
402
|
+
}, async ({ task_id, agent, budget_tokens }) => {
|
|
403
|
+
const result = await buildWarmStartPreamble({
|
|
404
|
+
task_id,
|
|
405
|
+
agent,
|
|
406
|
+
budget_tokens,
|
|
407
|
+
});
|
|
408
|
+
return toTextResult(result.preamble, result);
|
|
409
|
+
});
|
|
410
|
+
// ─── Higher-level skills (task workflow & knowledge capture) ────────────────
|
|
411
|
+
registerSkills(server);
|
|
412
|
+
}
|
|
413
|
+
//# sourceMappingURL=tools.js.map
|