@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.
Files changed (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +198 -0
  3. package/config/default-triggers.json +119 -0
  4. package/dist/adapters/runtime-adapter.d.ts +124 -0
  5. package/dist/adapters/runtime-adapter.d.ts.map +1 -0
  6. package/dist/adapters/runtime-adapter.js +5 -0
  7. package/dist/adapters/runtime-adapter.js.map +1 -0
  8. package/dist/config/datacore-constants.d.ts +21 -0
  9. package/dist/config/datacore-constants.d.ts.map +1 -0
  10. package/dist/config/datacore-constants.js +68 -0
  11. package/dist/config/datacore-constants.js.map +1 -0
  12. package/dist/domain/embeddable.d.ts +19 -0
  13. package/dist/domain/embeddable.d.ts.map +1 -0
  14. package/dist/domain/embeddable.js +106 -0
  15. package/dist/domain/embeddable.js.map +1 -0
  16. package/dist/domain/gold-store.d.ts +11 -0
  17. package/dist/domain/gold-store.d.ts.map +1 -0
  18. package/dist/domain/gold-store.js +210 -0
  19. package/dist/domain/gold-store.js.map +1 -0
  20. package/dist/domain/inbox.d.ts +14 -0
  21. package/dist/domain/inbox.d.ts.map +1 -0
  22. package/dist/domain/inbox.js +138 -0
  23. package/dist/domain/inbox.js.map +1 -0
  24. package/dist/domain/office-status.d.ts +31 -0
  25. package/dist/domain/office-status.d.ts.map +1 -0
  26. package/dist/domain/office-status.js +138 -0
  27. package/dist/domain/office-status.js.map +1 -0
  28. package/dist/domain/questions.d.ts +3 -0
  29. package/dist/domain/questions.d.ts.map +1 -0
  30. package/dist/domain/questions.js +60 -0
  31. package/dist/domain/questions.js.map +1 -0
  32. package/dist/domain/run-manager.d.ts +14 -0
  33. package/dist/domain/run-manager.d.ts.map +1 -0
  34. package/dist/domain/run-manager.js +25 -0
  35. package/dist/domain/run-manager.js.map +1 -0
  36. package/dist/domain/task-event-index.d.ts +31 -0
  37. package/dist/domain/task-event-index.d.ts.map +1 -0
  38. package/dist/domain/task-event-index.js +117 -0
  39. package/dist/domain/task-event-index.js.map +1 -0
  40. package/dist/domain/tasks.d.ts +3 -0
  41. package/dist/domain/tasks.d.ts.map +1 -0
  42. package/dist/domain/tasks.js +112 -0
  43. package/dist/domain/tasks.js.map +1 -0
  44. package/dist/domain/warm-start-builders.d.ts +18 -0
  45. package/dist/domain/warm-start-builders.d.ts.map +1 -0
  46. package/dist/domain/warm-start-builders.js +157 -0
  47. package/dist/domain/warm-start-builders.js.map +1 -0
  48. package/dist/domain/warm-start-extractors.d.ts +37 -0
  49. package/dist/domain/warm-start-extractors.d.ts.map +1 -0
  50. package/dist/domain/warm-start-extractors.js +372 -0
  51. package/dist/domain/warm-start-extractors.js.map +1 -0
  52. package/dist/domain/warm-start.d.ts +17 -0
  53. package/dist/domain/warm-start.d.ts.map +1 -0
  54. package/dist/domain/warm-start.js +225 -0
  55. package/dist/domain/warm-start.js.map +1 -0
  56. package/dist/index.d.ts +7 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +8 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/mcp-slim-ack.d.ts +12 -0
  61. package/dist/mcp-slim-ack.d.ts.map +1 -0
  62. package/dist/mcp-slim-ack.js +16 -0
  63. package/dist/mcp-slim-ack.js.map +1 -0
  64. package/dist/routing/capability-registry.d.ts +38 -0
  65. package/dist/routing/capability-registry.d.ts.map +1 -0
  66. package/dist/routing/capability-registry.js +52 -0
  67. package/dist/routing/capability-registry.js.map +1 -0
  68. package/dist/routing/runtime-target-resolver.d.ts +42 -0
  69. package/dist/routing/runtime-target-resolver.d.ts.map +1 -0
  70. package/dist/routing/runtime-target-resolver.js +139 -0
  71. package/dist/routing/runtime-target-resolver.js.map +1 -0
  72. package/dist/search/azure-search-client.d.ts +53 -0
  73. package/dist/search/azure-search-client.d.ts.map +1 -0
  74. package/dist/search/azure-search-client.js +191 -0
  75. package/dist/search/azure-search-client.js.map +1 -0
  76. package/dist/search/circuit-breaker.d.ts +46 -0
  77. package/dist/search/circuit-breaker.d.ts.map +1 -0
  78. package/dist/search/circuit-breaker.js +87 -0
  79. package/dist/search/circuit-breaker.js.map +1 -0
  80. package/dist/search/deep-search.d.ts +24 -0
  81. package/dist/search/deep-search.d.ts.map +1 -0
  82. package/dist/search/deep-search.js +98 -0
  83. package/dist/search/deep-search.js.map +1 -0
  84. package/dist/search/search.d.ts +3 -0
  85. package/dist/search/search.d.ts.map +1 -0
  86. package/dist/search/search.js +76 -0
  87. package/dist/search/search.js.map +1 -0
  88. package/dist/server.d.ts +3 -0
  89. package/dist/server.d.ts.map +1 -0
  90. package/dist/server.js +39 -0
  91. package/dist/server.js.map +1 -0
  92. package/dist/skills/index.d.ts +3 -0
  93. package/dist/skills/index.d.ts.map +1 -0
  94. package/dist/skills/index.js +8 -0
  95. package/dist/skills/index.js.map +1 -0
  96. package/dist/skills/knowledge.d.ts +3 -0
  97. package/dist/skills/knowledge.d.ts.map +1 -0
  98. package/dist/skills/knowledge.js +78 -0
  99. package/dist/skills/knowledge.js.map +1 -0
  100. package/dist/skills/task-workflow.d.ts +3 -0
  101. package/dist/skills/task-workflow.d.ts.map +1 -0
  102. package/dist/skills/task-workflow.js +205 -0
  103. package/dist/skills/task-workflow.js.map +1 -0
  104. package/dist/storage/active-backend.d.ts +4 -0
  105. package/dist/storage/active-backend.d.ts.map +1 -0
  106. package/dist/storage/active-backend.js +12 -0
  107. package/dist/storage/active-backend.js.map +1 -0
  108. package/dist/storage/backend.d.ts +12 -0
  109. package/dist/storage/backend.d.ts.map +1 -0
  110. package/dist/storage/backend.js +5 -0
  111. package/dist/storage/backend.js.map +1 -0
  112. package/dist/storage/cosmos-client.d.ts +6 -0
  113. package/dist/storage/cosmos-client.d.ts.map +1 -0
  114. package/dist/storage/cosmos-client.js +38 -0
  115. package/dist/storage/cosmos-client.js.map +1 -0
  116. package/dist/storage/lancedb-backend.d.ts +13 -0
  117. package/dist/storage/lancedb-backend.d.ts.map +1 -0
  118. package/dist/storage/lancedb-backend.js +373 -0
  119. package/dist/storage/lancedb-backend.js.map +1 -0
  120. package/dist/storage/store.d.ts +8 -0
  121. package/dist/storage/store.d.ts.map +1 -0
  122. package/dist/storage/store.js +294 -0
  123. package/dist/storage/store.js.map +1 -0
  124. package/dist/storage/task-compaction.d.ts +12 -0
  125. package/dist/storage/task-compaction.d.ts.map +1 -0
  126. package/dist/storage/task-compaction.js +112 -0
  127. package/dist/storage/task-compaction.js.map +1 -0
  128. package/dist/storage/task-context-snapshot.d.ts +6 -0
  129. package/dist/storage/task-context-snapshot.d.ts.map +1 -0
  130. package/dist/storage/task-context-snapshot.js +87 -0
  131. package/dist/storage/task-context-snapshot.js.map +1 -0
  132. package/dist/storage/trigger-engine.d.ts +39 -0
  133. package/dist/storage/trigger-engine.d.ts.map +1 -0
  134. package/dist/storage/trigger-engine.js +212 -0
  135. package/dist/storage/trigger-engine.js.map +1 -0
  136. package/dist/tools.d.ts +3 -0
  137. package/dist/tools.d.ts.map +1 -0
  138. package/dist/tools.js +413 -0
  139. package/dist/tools.js.map +1 -0
  140. package/dist/transport/client.d.ts +68 -0
  141. package/dist/transport/client.d.ts.map +1 -0
  142. package/dist/transport/client.js +210 -0
  143. package/dist/transport/client.js.map +1 -0
  144. package/dist/transport/http-server.d.ts +3 -0
  145. package/dist/transport/http-server.d.ts.map +1 -0
  146. package/dist/transport/http-server.js +337 -0
  147. package/dist/transport/http-server.js.map +1 -0
  148. package/dist/types.d.ts +185 -0
  149. package/dist/types.d.ts.map +1 -0
  150. package/dist/types.js +3 -0
  151. package/dist/types.js.map +1 -0
  152. package/package.json +71 -0
  153. package/scripts/run-server.mjs +3 -0
  154. 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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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerTools(server: McpServer): void;
3
+ //# sourceMappingURL=tools.d.ts.map
@@ -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