@oka-core/reason 0.2.15 → 0.2.17
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/dist/abort-controller.d.ts +19 -0
- package/dist/abort-controller.d.ts.map +1 -0
- package/dist/abort-controller.js +53 -0
- package/dist/activity-tracker.d.ts +48 -0
- package/dist/activity-tracker.d.ts.map +1 -0
- package/dist/activity-tracker.js +80 -0
- package/dist/analytics.d.ts +49 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +88 -0
- package/dist/array.d.ts +12 -0
- package/dist/array.d.ts.map +1 -0
- package/dist/array.js +20 -0
- package/dist/async-context.d.ts +20 -0
- package/dist/async-context.d.ts.map +1 -0
- package/dist/async-context.js +25 -0
- package/dist/binary-check.d.ts +16 -0
- package/dist/binary-check.d.ts.map +1 -0
- package/dist/binary-check.js +43 -0
- package/dist/buffered-writer.d.ts +30 -0
- package/dist/buffered-writer.d.ts.map +1 -0
- package/dist/buffered-writer.js +87 -0
- package/dist/circular-buffer.d.ts +28 -0
- package/dist/circular-buffer.d.ts.map +1 -0
- package/dist/circular-buffer.js +61 -0
- package/dist/cleanup-registry.d.ts +23 -0
- package/dist/cleanup-registry.d.ts.map +1 -0
- package/dist/cleanup-registry.js +34 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +32 -10
- package/dist/combined-abort-signal.d.ts +25 -0
- package/dist/combined-abort-signal.d.ts.map +1 -0
- package/dist/combined-abort-signal.js +47 -0
- package/dist/cron-lock.d.ts +29 -0
- package/dist/cron-lock.d.ts.map +1 -0
- package/dist/cron-lock.js +127 -0
- package/dist/cron-scheduler.d.ts +41 -0
- package/dist/cron-scheduler.d.ts.map +1 -0
- package/dist/cron-scheduler.js +189 -0
- package/dist/cron-tasks.d.ts +86 -0
- package/dist/cron-tasks.d.ts.map +1 -0
- package/dist/cron-tasks.js +205 -0
- package/dist/cron.d.ts +35 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +215 -0
- package/dist/env.d.ts +26 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +50 -0
- package/dist/errors.d.ts +99 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +214 -0
- package/dist/format.d.ts +21 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +48 -0
- package/dist/fps-tracker.d.ts +22 -0
- package/dist/fps-tracker.d.ts.map +1 -0
- package/dist/fps-tracker.js +44 -0
- package/dist/graceful-shutdown.d.ts +35 -0
- package/dist/graceful-shutdown.d.ts.map +1 -0
- package/dist/graceful-shutdown.js +89 -0
- package/dist/hash.d.ts +21 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +31 -0
- package/dist/heap-diagnostics.d.ts +68 -0
- package/dist/heap-diagnostics.d.ts.map +1 -0
- package/dist/heap-diagnostics.js +110 -0
- package/dist/idle-timeout.d.ts +21 -0
- package/dist/idle-timeout.d.ts.map +1 -0
- package/dist/idle-timeout.js +42 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/intl.d.ts +18 -0
- package/dist/intl.d.ts.map +1 -0
- package/dist/intl.js +75 -0
- package/dist/jsonl.d.ts +16 -0
- package/dist/jsonl.d.ts.map +1 -0
- package/dist/jsonl.js +60 -0
- package/dist/lazy-schema.d.ts +6 -0
- package/dist/lazy-schema.d.ts.map +1 -0
- package/dist/lazy-schema.js +8 -0
- package/dist/memo.d.ts +64 -0
- package/dist/memo.d.ts.map +1 -0
- package/dist/memo.js +162 -0
- package/dist/pkce.d.ts +13 -0
- package/dist/pkce.d.ts.map +1 -0
- package/dist/pkce.js +28 -0
- package/dist/priority-queue.d.ts +36 -0
- package/dist/priority-queue.d.ts.map +1 -0
- package/dist/priority-queue.js +97 -0
- package/dist/process-utils.d.ts +20 -0
- package/dist/process-utils.d.ts.map +1 -0
- package/dist/process-utils.js +54 -0
- package/dist/query-guard.d.ts +34 -0
- package/dist/query-guard.d.ts.map +1 -0
- package/dist/query-guard.js +74 -0
- package/dist/retry.d.ts +60 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +89 -0
- package/dist/schemas.d.ts +6 -6
- package/dist/secrets.d.ts +44 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +115 -0
- package/dist/semantic-types.d.ts +39 -0
- package/dist/semantic-types.d.ts.map +1 -0
- package/dist/semantic-types.js +49 -0
- package/dist/sequential.d.ts +21 -0
- package/dist/sequential.d.ts.map +1 -0
- package/dist/sequential.js +49 -0
- package/dist/signal.d.ts +29 -0
- package/dist/signal.d.ts.map +1 -0
- package/dist/signal.js +39 -0
- package/dist/sleep.d.ts +21 -0
- package/dist/sleep.d.ts.map +1 -0
- package/dist/sleep.js +58 -0
- package/dist/slow-ops.d.ts +41 -0
- package/dist/slow-ops.d.ts.map +1 -0
- package/dist/slow-ops.js +133 -0
- package/dist/store.d.ts +20 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +34 -0
- package/dist/stream.d.ts +29 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +92 -0
- package/dist/string-utils.d.ts +46 -0
- package/dist/string-utils.d.ts.map +1 -0
- package/dist/string-utils.js +69 -0
- package/dist/strip-bom.d.ts +8 -0
- package/dist/strip-bom.d.ts.map +1 -0
- package/dist/strip-bom.js +10 -0
- package/dist/subprocess-env.d.ts +25 -0
- package/dist/subprocess-env.d.ts.map +1 -0
- package/dist/subprocess-env.js +55 -0
- package/dist/telemetry.d.ts +16 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +61 -0
- package/dist/temp-file.d.ts +18 -0
- package/dist/temp-file.d.ts.map +1 -0
- package/dist/temp-file.js +26 -0
- package/dist/tool-contract.d.ts +85 -0
- package/dist/tool-contract.d.ts.map +1 -0
- package/dist/tool-contract.js +101 -0
- package/dist/tools/read.d.ts +2 -10
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +662 -537
- package/dist/tools/write.d.ts +3 -2
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +329 -177
- package/dist/uuid.d.ts +20 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +28 -0
- package/dist/validation.d.ts +64 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +236 -0
- package/dist/with-resolvers.d.ts +12 -0
- package/dist/with-resolvers.d.ts.map +1 -0
- package/dist/with-resolvers.js +14 -0
- package/dist/xml-escape.d.ts +12 -0
- package/dist/xml-escape.d.ts.map +1 -0
- package/dist/xml-escape.js +15 -0
- package/package.json +8 -1
package/dist/tools/write.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { OkaClient } from "../client.js";
|
|
3
|
+
import { type ToolDef } from "../tool-contract.js";
|
|
4
|
+
export declare function buildWriteTools(client: OkaClient): ToolDef[];
|
|
3
5
|
/**
|
|
4
|
-
* Register
|
|
5
|
-
* Each tool validates input, creates a reasoning event, and fires it to the API.
|
|
6
|
+
* Register write tools on the MCP server.
|
|
6
7
|
*/
|
|
7
8
|
export declare function registerWriteTools(server: McpServer, client: OkaClient): void;
|
|
8
9
|
//# sourceMappingURL=write.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/tools/write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/tools/write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAM9C,OAAO,EAA+B,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAehF,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,EAAE,CA2T5D;AAID;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,CAE7E"}
|
package/dist/tools/write.js
CHANGED
|
@@ -1,182 +1,334 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { toUserMessage, toSafeTelemetryMessage } from "../errors.js";
|
|
3
|
+
import { SecretDetectedError } from "../errors.js";
|
|
4
|
+
import { scanForSecrets, containsSecrets } from "../secrets.js";
|
|
5
|
+
import { validateInput } from "../validation.js";
|
|
6
|
+
import { analytics } from "../analytics.js";
|
|
7
|
+
import { buildTool, registerToolPool } from "../tool-contract.js";
|
|
8
|
+
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
9
|
+
function assertNoSecrets(...values) {
|
|
10
|
+
for (const v of values) {
|
|
11
|
+
if (v && containsSecrets(v)) {
|
|
12
|
+
const matches = scanForSecrets(v);
|
|
13
|
+
throw new SecretDetectedError(matches[0].patternName);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// ─── Tool definitions ─────────────────────────────────────────────────
|
|
18
|
+
export function buildWriteTools(client) {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- variance between ToolDef<SpecificShape> and ToolDef<ZodRawShape>
|
|
20
|
+
return [
|
|
21
|
+
buildTool({
|
|
22
|
+
name: "decision",
|
|
23
|
+
description: "Record an agent decision with rationale and alternatives considered",
|
|
24
|
+
inputSchema: {
|
|
25
|
+
description: z.string().describe("What was decided"),
|
|
26
|
+
rationale: z.string().describe("Why this choice was made"),
|
|
27
|
+
alternatives_considered: z
|
|
28
|
+
.array(z.string())
|
|
29
|
+
.default([])
|
|
30
|
+
.describe("Other options that were evaluated"),
|
|
31
|
+
confidence: z
|
|
32
|
+
.number()
|
|
33
|
+
.min(0)
|
|
34
|
+
.max(1)
|
|
35
|
+
.describe("Confidence in the decision (0-1)"),
|
|
36
|
+
},
|
|
37
|
+
isReadOnly: false,
|
|
38
|
+
call: async (params) => {
|
|
39
|
+
analytics.track("tool.invoked", { tool: "decision" });
|
|
40
|
+
try {
|
|
41
|
+
validateInput(params.description, 10_000);
|
|
42
|
+
validateInput(params.rationale, 50_000);
|
|
43
|
+
assertNoSecrets(params.description, params.rationale);
|
|
44
|
+
await client.ingest({ event_type: "decision", payload: params });
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: `Decision recorded: ${params.description} (confidence: ${params.confidence})`,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
analytics.track("tool.error", {
|
|
56
|
+
tool: "decision",
|
|
57
|
+
error: toSafeTelemetryMessage(error),
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
content: [{ type: "text", text: toUserMessage(error) }],
|
|
61
|
+
isError: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
buildTool({
|
|
67
|
+
name: "explore",
|
|
68
|
+
description: "Record an area the agent explored and what was found",
|
|
69
|
+
inputSchema: {
|
|
70
|
+
area: z.string().describe("The area or module explored"),
|
|
71
|
+
findings: z.string().describe("What was discovered"),
|
|
72
|
+
relevance_score: z
|
|
73
|
+
.number()
|
|
74
|
+
.min(0)
|
|
75
|
+
.max(1)
|
|
76
|
+
.describe("How relevant to the current task (0-1)"),
|
|
77
|
+
},
|
|
78
|
+
isReadOnly: false,
|
|
79
|
+
call: async (params) => {
|
|
80
|
+
analytics.track("tool.invoked", { tool: "explore" });
|
|
81
|
+
try {
|
|
82
|
+
validateInput(params.area, 1_000);
|
|
83
|
+
validateInput(params.findings, 50_000);
|
|
84
|
+
assertNoSecrets(params.area, params.findings);
|
|
85
|
+
await client.ingest({ event_type: "exploration", payload: params });
|
|
86
|
+
return {
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: `Exploration recorded: ${params.area} (relevance: ${params.relevance_score})`,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
analytics.track("tool.error", {
|
|
97
|
+
tool: "explore",
|
|
98
|
+
error: toSafeTelemetryMessage(error),
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
content: [{ type: "text", text: toUserMessage(error) }],
|
|
102
|
+
isError: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
buildTool({
|
|
108
|
+
name: "deviation",
|
|
109
|
+
description: "Record when the agent deviated from the original plan",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
planned_action: z.string().describe("What was originally planned"),
|
|
112
|
+
actual_action: z.string().describe("What was actually done"),
|
|
113
|
+
reason: z.string().describe("Why the deviation occurred"),
|
|
114
|
+
},
|
|
115
|
+
isReadOnly: false,
|
|
116
|
+
call: async (params) => {
|
|
117
|
+
analytics.track("tool.invoked", { tool: "deviation" });
|
|
118
|
+
try {
|
|
119
|
+
validateInput(params.planned_action, 10_000);
|
|
120
|
+
validateInput(params.actual_action, 10_000);
|
|
121
|
+
validateInput(params.reason, 50_000);
|
|
122
|
+
assertNoSecrets(params.planned_action, params.actual_action, params.reason);
|
|
123
|
+
await client.ingest({ event_type: "deviation", payload: params });
|
|
124
|
+
return {
|
|
125
|
+
content: [
|
|
126
|
+
{
|
|
127
|
+
type: "text",
|
|
128
|
+
text: `Deviation recorded: planned "${params.planned_action}" → actual "${params.actual_action}"`,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
analytics.track("tool.error", {
|
|
135
|
+
tool: "deviation",
|
|
136
|
+
error: toSafeTelemetryMessage(error),
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
content: [{ type: "text", text: toUserMessage(error) }],
|
|
140
|
+
isError: true,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
}),
|
|
145
|
+
buildTool({
|
|
146
|
+
name: "done",
|
|
147
|
+
description: "Record task completion with outcome and quality signals. " +
|
|
148
|
+
"If you used Oka learnings during the task, include their IDs in used_learning_ids.",
|
|
149
|
+
inputSchema: {
|
|
150
|
+
task_id: z.string().describe("Identifier for the completed task"),
|
|
151
|
+
outcome: z
|
|
152
|
+
.enum(["success", "partial", "failed"])
|
|
153
|
+
.describe("Task outcome"),
|
|
154
|
+
artifacts: z
|
|
155
|
+
.array(z.string())
|
|
156
|
+
.default([])
|
|
157
|
+
.describe("Files or PRs produced"),
|
|
158
|
+
quality_signals: z
|
|
159
|
+
.record(z.string(), z.number())
|
|
160
|
+
.default({})
|
|
161
|
+
.describe("Quality metrics (e.g., test_coverage: 0.92)"),
|
|
162
|
+
used_learning_ids: z
|
|
163
|
+
.array(z.string())
|
|
164
|
+
.optional()
|
|
165
|
+
.describe("IDs of Oka learnings used during the task"),
|
|
166
|
+
},
|
|
167
|
+
isReadOnly: false,
|
|
168
|
+
call: async (params) => {
|
|
169
|
+
analytics.track("tool.invoked", { tool: "done" });
|
|
170
|
+
try {
|
|
171
|
+
validateInput(params.task_id, 1_000);
|
|
172
|
+
const { used_learning_ids, ...completionPayload } = params;
|
|
173
|
+
await client.ingest({
|
|
174
|
+
event_type: "completion",
|
|
175
|
+
payload: completionPayload,
|
|
176
|
+
});
|
|
177
|
+
if (used_learning_ids?.length) {
|
|
178
|
+
client.reportUsedLearnings(used_learning_ids);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
content: [
|
|
182
|
+
{
|
|
183
|
+
type: "text",
|
|
184
|
+
text: `Completion recorded: ${params.task_id} → ${params.outcome}`,
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
analytics.track("tool.error", {
|
|
191
|
+
tool: "done",
|
|
192
|
+
error: toSafeTelemetryMessage(error),
|
|
193
|
+
});
|
|
194
|
+
return {
|
|
195
|
+
content: [{ type: "text", text: toUserMessage(error) }],
|
|
196
|
+
isError: true,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
}),
|
|
201
|
+
buildTool({
|
|
202
|
+
name: "observe",
|
|
203
|
+
description: "Record a general observation that doesn't fit decision/explore/deviation/done. " +
|
|
204
|
+
"Use this for architectural insights, code quality notes, dependency observations, " +
|
|
205
|
+
"performance findings, or any other knowledge worth preserving.",
|
|
206
|
+
inputSchema: {
|
|
207
|
+
category: z
|
|
208
|
+
.enum([
|
|
209
|
+
"architecture",
|
|
210
|
+
"bug_pattern",
|
|
211
|
+
"convention",
|
|
212
|
+
"performance",
|
|
213
|
+
"security",
|
|
214
|
+
"workflow",
|
|
215
|
+
"dependency",
|
|
216
|
+
"code_quality",
|
|
217
|
+
"other",
|
|
218
|
+
])
|
|
219
|
+
.describe("Category of the observation"),
|
|
220
|
+
summary: z.string().describe("Brief summary of the observation"),
|
|
221
|
+
details: z.string().optional().describe("Detailed description"),
|
|
222
|
+
confidence: z
|
|
223
|
+
.number()
|
|
224
|
+
.min(0)
|
|
225
|
+
.max(1)
|
|
226
|
+
.default(0.7)
|
|
227
|
+
.describe("Confidence in this observation (0-1)"),
|
|
228
|
+
file_patterns: z
|
|
229
|
+
.array(z.string())
|
|
230
|
+
.default([])
|
|
231
|
+
.describe("Related file patterns (e.g., 'src/auth/**')"),
|
|
232
|
+
tags: z
|
|
233
|
+
.array(z.string())
|
|
234
|
+
.default([])
|
|
235
|
+
.describe("Tags for categorization"),
|
|
236
|
+
},
|
|
237
|
+
isReadOnly: false,
|
|
238
|
+
call: async (params) => {
|
|
239
|
+
analytics.track("tool.invoked", { tool: "observe" });
|
|
240
|
+
try {
|
|
241
|
+
validateInput(params.summary, 10_000);
|
|
242
|
+
if (params.details)
|
|
243
|
+
validateInput(params.details, 50_000);
|
|
244
|
+
assertNoSecrets(params.summary, params.details);
|
|
245
|
+
await client.ingest({
|
|
246
|
+
event_type: "exploration",
|
|
247
|
+
payload: {
|
|
248
|
+
area: params.category,
|
|
249
|
+
findings: params.summary,
|
|
250
|
+
details: params.details,
|
|
251
|
+
relevance_score: params.confidence,
|
|
252
|
+
observation_category: params.category,
|
|
253
|
+
file_patterns: params.file_patterns,
|
|
254
|
+
tags: params.tags,
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
return {
|
|
258
|
+
content: [
|
|
259
|
+
{
|
|
260
|
+
type: "text",
|
|
261
|
+
text: `Observation recorded: [${params.category}] ${params.summary}`,
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
analytics.track("tool.error", {
|
|
268
|
+
tool: "observe",
|
|
269
|
+
error: toSafeTelemetryMessage(error),
|
|
270
|
+
});
|
|
271
|
+
return {
|
|
272
|
+
content: [{ type: "text", text: toUserMessage(error) }],
|
|
273
|
+
isError: true,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
}),
|
|
278
|
+
buildTool({
|
|
279
|
+
name: "feedback",
|
|
280
|
+
description: "Report whether a learning from Oka was useful or not. " +
|
|
281
|
+
"Improves future search quality. Call after using context or semantic_search.",
|
|
282
|
+
inputSchema: {
|
|
283
|
+
learning_id: z.string().describe("Learning ID from search results"),
|
|
284
|
+
useful: z.boolean().describe("Was this learning useful?"),
|
|
285
|
+
query_feedback_id: z
|
|
286
|
+
.string()
|
|
287
|
+
.optional()
|
|
288
|
+
.describe("The ref ID from the search that returned this learning"),
|
|
289
|
+
reason: z
|
|
290
|
+
.enum(["helpful", "outdated", "wrong", "irrelevant"])
|
|
291
|
+
.optional()
|
|
292
|
+
.describe("Why the learning was or wasn't useful"),
|
|
293
|
+
},
|
|
294
|
+
isReadOnly: false,
|
|
295
|
+
call: async (params) => {
|
|
296
|
+
analytics.track("tool.invoked", { tool: "feedback" });
|
|
297
|
+
try {
|
|
298
|
+
validateInput(params.learning_id, 200);
|
|
299
|
+
await client.submitFeedback({
|
|
300
|
+
learning_id: params.learning_id,
|
|
301
|
+
useful: params.useful,
|
|
302
|
+
query_feedback_id: params.query_feedback_id,
|
|
303
|
+
reason: params.reason,
|
|
304
|
+
});
|
|
305
|
+
return {
|
|
306
|
+
content: [
|
|
307
|
+
{
|
|
308
|
+
type: "text",
|
|
309
|
+
text: `Feedback recorded: ${params.learning_id.slice(0, 8)} → ${params.useful ? "useful" : "not useful"}`,
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
analytics.track("tool.error", {
|
|
316
|
+
tool: "feedback",
|
|
317
|
+
error: toSafeTelemetryMessage(error),
|
|
318
|
+
});
|
|
319
|
+
return {
|
|
320
|
+
content: [{ type: "text", text: toUserMessage(error) }],
|
|
321
|
+
isError: true,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
}),
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
// ─── Registration ─────────────────────────────────────────────────────
|
|
2
329
|
/**
|
|
3
|
-
* Register
|
|
4
|
-
* Each tool validates input, creates a reasoning event, and fires it to the API.
|
|
330
|
+
* Register write tools on the MCP server.
|
|
5
331
|
*/
|
|
6
332
|
export function registerWriteTools(server, client) {
|
|
7
|
-
|
|
8
|
-
description: z.string().describe("What was decided"),
|
|
9
|
-
rationale: z.string().describe("Why this choice was made"),
|
|
10
|
-
alternatives_considered: z
|
|
11
|
-
.array(z.string())
|
|
12
|
-
.default([])
|
|
13
|
-
.describe("Other options that were evaluated"),
|
|
14
|
-
confidence: z
|
|
15
|
-
.number()
|
|
16
|
-
.min(0)
|
|
17
|
-
.max(1)
|
|
18
|
-
.describe("Confidence in the decision (0-1)"),
|
|
19
|
-
}, async (params) => {
|
|
20
|
-
await client.ingest({ event_type: "decision", payload: params });
|
|
21
|
-
return {
|
|
22
|
-
content: [
|
|
23
|
-
{
|
|
24
|
-
type: "text",
|
|
25
|
-
text: `Decision recorded: ${params.description} (confidence: ${params.confidence})`,
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
};
|
|
29
|
-
});
|
|
30
|
-
server.tool("explore", "Record an area the agent explored and what was found", {
|
|
31
|
-
area: z.string().describe("The area or module explored"),
|
|
32
|
-
findings: z.string().describe("What was discovered"),
|
|
33
|
-
relevance_score: z
|
|
34
|
-
.number()
|
|
35
|
-
.min(0)
|
|
36
|
-
.max(1)
|
|
37
|
-
.describe("How relevant to the current task (0-1)"),
|
|
38
|
-
}, async (params) => {
|
|
39
|
-
await client.ingest({ event_type: "exploration", payload: params });
|
|
40
|
-
return {
|
|
41
|
-
content: [
|
|
42
|
-
{
|
|
43
|
-
type: "text",
|
|
44
|
-
text: `Exploration recorded: ${params.area} (relevance: ${params.relevance_score})`,
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
};
|
|
48
|
-
});
|
|
49
|
-
server.tool("deviation", "Record when the agent deviated from the original plan", {
|
|
50
|
-
planned_action: z.string().describe("What was originally planned"),
|
|
51
|
-
actual_action: z.string().describe("What was actually done"),
|
|
52
|
-
reason: z.string().describe("Why the deviation occurred"),
|
|
53
|
-
}, async (params) => {
|
|
54
|
-
await client.ingest({ event_type: "deviation", payload: params });
|
|
55
|
-
return {
|
|
56
|
-
content: [
|
|
57
|
-
{
|
|
58
|
-
type: "text",
|
|
59
|
-
text: `Deviation recorded: planned "${params.planned_action}" → actual "${params.actual_action}"`,
|
|
60
|
-
},
|
|
61
|
-
],
|
|
62
|
-
};
|
|
63
|
-
});
|
|
64
|
-
server.tool("done", "Record task completion with outcome and quality signals. " +
|
|
65
|
-
"If you used Oka learnings during the task, include their IDs in used_learning_ids.", {
|
|
66
|
-
task_id: z.string().describe("Identifier for the completed task"),
|
|
67
|
-
outcome: z
|
|
68
|
-
.enum(["success", "partial", "failed"])
|
|
69
|
-
.describe("Task outcome"),
|
|
70
|
-
artifacts: z
|
|
71
|
-
.array(z.string())
|
|
72
|
-
.default([])
|
|
73
|
-
.describe("Files or PRs produced"),
|
|
74
|
-
quality_signals: z
|
|
75
|
-
.record(z.string(), z.number())
|
|
76
|
-
.default({})
|
|
77
|
-
.describe("Quality metrics (e.g., test_coverage: 0.92)"),
|
|
78
|
-
used_learning_ids: z
|
|
79
|
-
.array(z.string())
|
|
80
|
-
.optional()
|
|
81
|
-
.describe("IDs of Oka learnings used during the task"),
|
|
82
|
-
}, async (params) => {
|
|
83
|
-
const { used_learning_ids, ...completionPayload } = params;
|
|
84
|
-
await client.ingest({
|
|
85
|
-
event_type: "completion",
|
|
86
|
-
payload: completionPayload,
|
|
87
|
-
});
|
|
88
|
-
// Report implicit feedback for LTR training
|
|
89
|
-
if (used_learning_ids?.length) {
|
|
90
|
-
client.reportUsedLearnings(used_learning_ids);
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
content: [
|
|
94
|
-
{
|
|
95
|
-
type: "text",
|
|
96
|
-
text: `Completion recorded: ${params.task_id} → ${params.outcome}`,
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
};
|
|
100
|
-
});
|
|
101
|
-
// ─── observe (general-purpose) ─────────────────────────────────
|
|
102
|
-
server.tool("observe", "Record a general observation that doesn't fit decision/explore/deviation/done. " +
|
|
103
|
-
"Use this for architectural insights, code quality notes, dependency observations, " +
|
|
104
|
-
"performance findings, or any other knowledge worth preserving.", {
|
|
105
|
-
category: z
|
|
106
|
-
.enum([
|
|
107
|
-
"architecture",
|
|
108
|
-
"bug_pattern",
|
|
109
|
-
"convention",
|
|
110
|
-
"performance",
|
|
111
|
-
"security",
|
|
112
|
-
"workflow",
|
|
113
|
-
"dependency",
|
|
114
|
-
"code_quality",
|
|
115
|
-
"other",
|
|
116
|
-
])
|
|
117
|
-
.describe("Category of the observation"),
|
|
118
|
-
summary: z.string().describe("Brief summary of the observation"),
|
|
119
|
-
details: z.string().optional().describe("Detailed description"),
|
|
120
|
-
confidence: z
|
|
121
|
-
.number()
|
|
122
|
-
.min(0)
|
|
123
|
-
.max(1)
|
|
124
|
-
.default(0.7)
|
|
125
|
-
.describe("Confidence in this observation (0-1)"),
|
|
126
|
-
file_patterns: z
|
|
127
|
-
.array(z.string())
|
|
128
|
-
.default([])
|
|
129
|
-
.describe("Related file patterns (e.g., 'src/auth/**')"),
|
|
130
|
-
tags: z.array(z.string()).default([]).describe("Tags for categorization"),
|
|
131
|
-
}, async (params) => {
|
|
132
|
-
await client.ingest({
|
|
133
|
-
event_type: "exploration",
|
|
134
|
-
payload: {
|
|
135
|
-
area: params.category,
|
|
136
|
-
findings: params.summary,
|
|
137
|
-
details: params.details,
|
|
138
|
-
relevance_score: params.confidence,
|
|
139
|
-
observation_category: params.category,
|
|
140
|
-
file_patterns: params.file_patterns,
|
|
141
|
-
tags: params.tags,
|
|
142
|
-
},
|
|
143
|
-
});
|
|
144
|
-
return {
|
|
145
|
-
content: [
|
|
146
|
-
{
|
|
147
|
-
type: "text",
|
|
148
|
-
text: `Observation recorded: [${params.category}] ${params.summary}`,
|
|
149
|
-
},
|
|
150
|
-
],
|
|
151
|
-
};
|
|
152
|
-
});
|
|
153
|
-
// ─── feedback (LTR training signal) ──────────────────────────────
|
|
154
|
-
server.tool("feedback", "Report whether a learning from Oka was useful or not. " +
|
|
155
|
-
"Improves future search quality. Call after using context or semantic_search.", {
|
|
156
|
-
learning_id: z.string().describe("Learning ID from search results"),
|
|
157
|
-
useful: z.boolean().describe("Was this learning useful?"),
|
|
158
|
-
query_feedback_id: z
|
|
159
|
-
.string()
|
|
160
|
-
.optional()
|
|
161
|
-
.describe("The ref ID from the search that returned this learning"),
|
|
162
|
-
reason: z
|
|
163
|
-
.enum(["helpful", "outdated", "wrong", "irrelevant"])
|
|
164
|
-
.optional()
|
|
165
|
-
.describe("Why the learning was or wasn't useful"),
|
|
166
|
-
}, async (params) => {
|
|
167
|
-
await client.submitFeedback({
|
|
168
|
-
learning_id: params.learning_id,
|
|
169
|
-
useful: params.useful,
|
|
170
|
-
query_feedback_id: params.query_feedback_id,
|
|
171
|
-
reason: params.reason,
|
|
172
|
-
});
|
|
173
|
-
return {
|
|
174
|
-
content: [
|
|
175
|
-
{
|
|
176
|
-
type: "text",
|
|
177
|
-
text: `Feedback recorded: ${params.learning_id.slice(0, 8)} → ${params.useful ? "useful" : "not useful"}`,
|
|
178
|
-
},
|
|
179
|
-
],
|
|
180
|
-
};
|
|
181
|
-
});
|
|
333
|
+
registerToolPool(buildWriteTools(client), server);
|
|
182
334
|
}
|
package/dist/uuid.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UUID validation and labeled ID generation — zero external dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Inspired by Claude Code's `src/utils/uuid.ts`.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Validate that a value is a well-formed UUID string.
|
|
8
|
+
* Returns the string if valid, null otherwise.
|
|
9
|
+
*/
|
|
10
|
+
export declare function validateUuid(maybeUuid: unknown): string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Generate a random hex ID with an optional label prefix.
|
|
13
|
+
*
|
|
14
|
+
* Format: `{label-}{16 hex chars}` or just `{16 hex chars}`.
|
|
15
|
+
*
|
|
16
|
+
* @example createId() // "a3f2c1b4d5e6f7a8"
|
|
17
|
+
* @example createId("agent") // "agent-a3f2c1b4d5e6f7a8"
|
|
18
|
+
*/
|
|
19
|
+
export declare function createId(label?: string): string;
|
|
20
|
+
//# sourceMappingURL=uuid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH;;;GAGG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAG9D;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAG/C"}
|
package/dist/uuid.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UUID validation and labeled ID generation — zero external dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Inspired by Claude Code's `src/utils/uuid.ts`.
|
|
5
|
+
*/
|
|
6
|
+
import { randomBytes } from "crypto";
|
|
7
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
8
|
+
/**
|
|
9
|
+
* Validate that a value is a well-formed UUID string.
|
|
10
|
+
* Returns the string if valid, null otherwise.
|
|
11
|
+
*/
|
|
12
|
+
export function validateUuid(maybeUuid) {
|
|
13
|
+
if (typeof maybeUuid !== "string")
|
|
14
|
+
return null;
|
|
15
|
+
return UUID_REGEX.test(maybeUuid) ? maybeUuid : null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Generate a random hex ID with an optional label prefix.
|
|
19
|
+
*
|
|
20
|
+
* Format: `{label-}{16 hex chars}` or just `{16 hex chars}`.
|
|
21
|
+
*
|
|
22
|
+
* @example createId() // "a3f2c1b4d5e6f7a8"
|
|
23
|
+
* @example createId("agent") // "agent-a3f2c1b4d5e6f7a8"
|
|
24
|
+
*/
|
|
25
|
+
export function createId(label) {
|
|
26
|
+
const suffix = randomBytes(8).toString("hex");
|
|
27
|
+
return label ? `${label}-${suffix}` : suffix;
|
|
28
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input validation and path security — pure domain layer.
|
|
3
|
+
*
|
|
4
|
+
* ZERO external dependencies. Inspired by Claude Code's hardened
|
|
5
|
+
* validation patterns for MCP tool inputs.
|
|
6
|
+
*
|
|
7
|
+
* @module validation
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Validate and sanitize a free-text input string.
|
|
11
|
+
*
|
|
12
|
+
* Rejects null bytes, control characters (except \t, \n, \r),
|
|
13
|
+
* and inputs exceeding `maxLength`. Returns the trimmed string.
|
|
14
|
+
*
|
|
15
|
+
* @param input - Raw input string
|
|
16
|
+
* @param maxLength - Maximum allowed length (after trim)
|
|
17
|
+
* @returns Trimmed, validated input
|
|
18
|
+
* @throws {ValidationError} On invalid input
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateInput(input: string, maxLength: number): string;
|
|
21
|
+
/**
|
|
22
|
+
* Validate a file path against security constraints and allowed prefixes.
|
|
23
|
+
*
|
|
24
|
+
* Checks for:
|
|
25
|
+
* - Null bytes
|
|
26
|
+
* - URL-encoded traversal sequences (`%2e%2e`, `%2f`, `%5c`)
|
|
27
|
+
* - NFKC normalization attacks (path changes after normalization)
|
|
28
|
+
* - `..` path components (directory traversal)
|
|
29
|
+
* - Prefix boundary enforcement (`/allowed` must not match `/allowed-evil`)
|
|
30
|
+
*
|
|
31
|
+
* @param path - The file path to validate
|
|
32
|
+
* @param allowedPrefixes - Array of allowed path prefixes
|
|
33
|
+
* @returns The validated, normalized path
|
|
34
|
+
* @throws {PathSecurityError} On any security violation
|
|
35
|
+
*/
|
|
36
|
+
export declare function validatePath(path: string, allowedPrefixes: string[]): string;
|
|
37
|
+
/**
|
|
38
|
+
* Check whether a path refers to a blocked device or virtual filesystem.
|
|
39
|
+
*
|
|
40
|
+
* Blocks `/dev/*`, `/proc/self/fd/*`, `/sys/*` and specific device nodes.
|
|
41
|
+
*
|
|
42
|
+
* @param path - The path to check
|
|
43
|
+
* @returns `true` if the path is a blocked device path
|
|
44
|
+
*/
|
|
45
|
+
export declare function isBlockedDevicePath(path: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Check whether a filename (or path) refers to a sensitive/dangerous file.
|
|
48
|
+
*
|
|
49
|
+
* Detects config files (`.gitconfig`, `.bashrc`, `.env`), credential files
|
|
50
|
+
* (`credentials.json`, SSH keys), and sensitive directories (`.ssh/`, `.aws/`).
|
|
51
|
+
*
|
|
52
|
+
* @param filename - Filename or path to check (basename is extracted)
|
|
53
|
+
* @returns `true` if the file is considered dangerous
|
|
54
|
+
*/
|
|
55
|
+
export declare function isDangerousFile(filename: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Validate that a file size is within acceptable bounds.
|
|
58
|
+
*
|
|
59
|
+
* @param bytes - Actual size in bytes
|
|
60
|
+
* @param maxBytes - Maximum allowed size in bytes
|
|
61
|
+
* @throws {ContentTooLargeError} If size exceeds the limit
|
|
62
|
+
*/
|
|
63
|
+
export declare function validateFileSize(bytes: number, maxBytes: number): void;
|
|
64
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiFH;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CA4BtE;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,GAAG,MAAM,CAmD5E;AAID;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAYzD;AAID;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAwBzD;AAID;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAUtE"}
|