@tangle-network/agent-runtime 0.19.0 → 0.20.0

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.
@@ -0,0 +1,1141 @@
1
+ import {
2
+ runLoop
3
+ } from "./chunk-VFUEE6DF.js";
4
+ import {
5
+ coderProfile,
6
+ multiHarnessCoderFanout
7
+ } from "./chunk-Z5LKAYAS.js";
8
+ import {
9
+ NotFoundError
10
+ } from "./chunk-RZAOYKCO.js";
11
+
12
+ // src/mcp/delegates.ts
13
+ function createDefaultCoderDelegate(options) {
14
+ const sandboxClient = options.sandboxClient;
15
+ const fanoutHarnesses = options.fanoutHarnesses;
16
+ const maxConcurrency = options.maxConcurrency ?? 4;
17
+ return async (args, ctx) => {
18
+ const task = {
19
+ goal: buildCoderGoal(args),
20
+ repoRoot: args.repoRoot,
21
+ testCmd: args.config?.testCmd,
22
+ typecheckCmd: args.config?.typecheckCmd,
23
+ forbiddenPaths: args.config?.forbiddenPaths,
24
+ maxDiffLines: args.config?.maxDiffLines
25
+ };
26
+ const variants = Math.max(1, Math.trunc(args.variants ?? 1));
27
+ ctx.report({ iteration: 0, phase: "starting" });
28
+ if (variants <= 1) {
29
+ const { agentRunSpec, output, validator } = coderProfile({ task });
30
+ const result2 = await runLoop({
31
+ driver: singleShotDriver,
32
+ agentRun: agentRunSpec,
33
+ output,
34
+ validator,
35
+ task,
36
+ ctx: { sandboxClient, signal: ctx.signal },
37
+ maxIterations: 1,
38
+ maxConcurrency
39
+ });
40
+ const winner2 = result2.winner;
41
+ if (!winner2) {
42
+ throw new Error("coder delegate produced no winner");
43
+ }
44
+ ctx.report({ iteration: 1, phase: "completed" });
45
+ return winner2.output;
46
+ }
47
+ const fanout = multiHarnessCoderFanout(
48
+ fanoutHarnesses && fanoutHarnesses.length > 0 ? { harnesses: fanoutHarnesses.slice(0, variants) } : { harnesses: void 0 }
49
+ );
50
+ const agentRuns = fanout.agentRuns.slice(0, variants);
51
+ const result = await runLoop({
52
+ driver: fanout.driver,
53
+ agentRuns,
54
+ output: fanout.output,
55
+ validator: fanout.validator,
56
+ task,
57
+ ctx: { sandboxClient, signal: ctx.signal },
58
+ maxIterations: variants,
59
+ maxConcurrency: Math.min(maxConcurrency, variants)
60
+ });
61
+ const winner = result.winner;
62
+ if (!winner) {
63
+ throw new Error("coder delegate fanout produced no winner");
64
+ }
65
+ ctx.report({ iteration: agentRuns.length, phase: "completed" });
66
+ return winner.output;
67
+ };
68
+ }
69
+ function buildCoderGoal(args) {
70
+ if (!args.contextHint) return args.goal;
71
+ return [args.goal, "", "## Context", args.contextHint].join("\n");
72
+ }
73
+ var singleShotDriver = {
74
+ name: "mcp-single-shot",
75
+ async plan(task, history) {
76
+ return history.length === 0 ? [task] : [];
77
+ },
78
+ decide(history) {
79
+ return history.length > 0 ? "pick-winner" : "fail";
80
+ }
81
+ };
82
+
83
+ // src/mcp/feedback-store.ts
84
+ var InMemoryFeedbackStore = class {
85
+ events = [];
86
+ async put(event) {
87
+ this.events.push({ ...event });
88
+ }
89
+ async list(filter = {}) {
90
+ let out = this.events;
91
+ if (filter.namespace !== void 0) {
92
+ out = out.filter((event) => event.namespace === filter.namespace);
93
+ }
94
+ if (filter.refersToRef !== void 0) {
95
+ out = out.filter((event) => event.refersTo.ref === filter.refersToRef);
96
+ }
97
+ return out.map((event) => ({ ...event }));
98
+ }
99
+ };
100
+ function eventToSnapshot(event) {
101
+ const snap = {
102
+ id: event.id,
103
+ score: event.rating.score,
104
+ by: event.by,
105
+ notes: event.rating.notes,
106
+ capturedAt: event.capturedAt
107
+ };
108
+ if (event.rating.label) snap.label = event.rating.label;
109
+ return snap;
110
+ }
111
+
112
+ // src/mcp/task-queue.ts
113
+ var DelegationTaskQueue = class {
114
+ records = /* @__PURE__ */ new Map();
115
+ controllers = /* @__PURE__ */ new Map();
116
+ byIdempotencyKey = /* @__PURE__ */ new Map();
117
+ generateId;
118
+ now;
119
+ constructor(options = {}) {
120
+ this.generateId = options.generateId ?? randomTaskId;
121
+ this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
122
+ }
123
+ /**
124
+ * Kick off a delegation in the background. Returns immediately. The
125
+ * `taskId` is queryable via `status` once this method returns.
126
+ */
127
+ submit(input) {
128
+ if (input.idempotencyKey) {
129
+ const existing = this.byIdempotencyKey.get(input.idempotencyKey);
130
+ if (existing && this.records.has(existing)) {
131
+ return { taskId: existing, reused: true };
132
+ }
133
+ }
134
+ const taskId = this.generateId();
135
+ const controller = new AbortController();
136
+ const record = {
137
+ taskId,
138
+ profile: input.profile,
139
+ namespace: input.namespace,
140
+ args: input.args,
141
+ status: "pending",
142
+ startedAt: this.now(),
143
+ feedback: [],
144
+ idempotencyKey: input.idempotencyKey
145
+ };
146
+ this.records.set(taskId, record);
147
+ this.controllers.set(taskId, controller);
148
+ if (input.idempotencyKey) this.byIdempotencyKey.set(input.idempotencyKey, taskId);
149
+ queueMicrotask(() => {
150
+ this.execute(taskId, input, controller);
151
+ });
152
+ return { taskId, reused: false };
153
+ }
154
+ /**
155
+ * Snapshot the current state of a delegation. Returns `undefined` for
156
+ * unknown ids so callers can distinguish missing from terminal.
157
+ */
158
+ status(taskId) {
159
+ const record = this.records.get(taskId);
160
+ if (!record) return void 0;
161
+ return toStatusResult(record);
162
+ }
163
+ /**
164
+ * Abort an in-flight delegation. Returns `false` if the task is unknown
165
+ * or already terminal. The underlying `run` function MUST honor the
166
+ * abort signal for the cancel to take effect; the queue marks the
167
+ * record `cancelled` regardless so a misbehaving runner cannot pin the
168
+ * UI on `running` forever.
169
+ */
170
+ cancel(taskId) {
171
+ const record = this.records.get(taskId);
172
+ if (!record) return false;
173
+ if (isTerminal(record.status)) return false;
174
+ const controller = this.controllers.get(taskId);
175
+ controller?.abort();
176
+ record.status = "cancelled";
177
+ record.completedAt = this.now();
178
+ record.error = { message: "cancelled by caller", kind: "CancelledError" };
179
+ return true;
180
+ }
181
+ /**
182
+ * Append a feedback event to the matching delegation. Returns `false`
183
+ * when `ref` does not name a known taskId — the caller should still
184
+ * record the feedback through a different surface (artifact/outcome
185
+ * kinds are not queue-bound).
186
+ */
187
+ attachFeedback(taskId, snapshot) {
188
+ const record = this.records.get(taskId);
189
+ if (!record) return false;
190
+ record.feedback.push(snapshot);
191
+ return true;
192
+ }
193
+ /**
194
+ * Query the recorded delegations. Returns entries newest-first (by
195
+ * `startedAt`), truncated to `limit`.
196
+ */
197
+ history(args = {}) {
198
+ const limit = clampLimit(args.limit);
199
+ const since = args.since ? Date.parse(args.since) : Number.NEGATIVE_INFINITY;
200
+ const out = [];
201
+ for (const record of this.records.values()) {
202
+ if (args.namespace && record.namespace !== args.namespace) continue;
203
+ if (args.profile && record.profile !== args.profile) continue;
204
+ if (Number.isFinite(since) && Date.parse(record.startedAt) < since) continue;
205
+ out.push(toHistoryEntry(record));
206
+ }
207
+ out.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
208
+ return out.slice(0, limit);
209
+ }
210
+ /** Test-only — number of in-flight (non-terminal) records. */
211
+ inflightCount() {
212
+ let n = 0;
213
+ for (const record of this.records.values()) {
214
+ if (!isTerminal(record.status)) n += 1;
215
+ }
216
+ return n;
217
+ }
218
+ async execute(taskId, input, controller) {
219
+ const record = this.records.get(taskId);
220
+ if (!record) return;
221
+ record.status = "running";
222
+ try {
223
+ const output = await input.run({
224
+ signal: controller.signal,
225
+ report: (progress) => {
226
+ if (record.status === "running") record.progress = progress;
227
+ }
228
+ });
229
+ if (currentStatus(record) === "cancelled") return;
230
+ record.status = "completed";
231
+ record.completedAt = this.now();
232
+ record.result = { profile: input.profile, output };
233
+ } catch (err) {
234
+ if (currentStatus(record) === "cancelled") return;
235
+ record.status = "failed";
236
+ record.completedAt = this.now();
237
+ record.error = errorToShape(err);
238
+ } finally {
239
+ this.controllers.delete(taskId);
240
+ }
241
+ }
242
+ };
243
+ function isTerminal(status) {
244
+ return status === "completed" || status === "failed" || status === "cancelled";
245
+ }
246
+ function currentStatus(record) {
247
+ return record.status;
248
+ }
249
+ function clampLimit(raw) {
250
+ if (!Number.isFinite(raw)) return 50;
251
+ const n = Math.trunc(raw);
252
+ if (n <= 0) return 50;
253
+ return Math.min(n, 500);
254
+ }
255
+ function toStatusResult(record) {
256
+ const out = {
257
+ taskId: record.taskId,
258
+ profile: record.profile,
259
+ status: record.status,
260
+ startedAt: record.startedAt
261
+ };
262
+ if (record.progress) out.progress = record.progress;
263
+ if (record.result) out.result = record.result;
264
+ if (record.error) out.error = record.error;
265
+ if (record.costUsd !== void 0) out.costUsd = record.costUsd;
266
+ if (record.completedAt) out.completedAt = record.completedAt;
267
+ return out;
268
+ }
269
+ function toHistoryEntry(record) {
270
+ const entry = {
271
+ taskId: record.taskId,
272
+ profile: record.profile,
273
+ args: record.args,
274
+ status: record.status,
275
+ startedAt: record.startedAt
276
+ };
277
+ if (record.namespace) entry.namespace = record.namespace;
278
+ if (record.completedAt) entry.completedAt = record.completedAt;
279
+ if (record.costUsd !== void 0) entry.costUsd = record.costUsd;
280
+ if (record.feedback.length > 0) entry.feedback = [...record.feedback];
281
+ return entry;
282
+ }
283
+ function errorToShape(err) {
284
+ if (err instanceof Error) {
285
+ return { message: err.message, kind: err.name || "Error" };
286
+ }
287
+ return { message: String(err), kind: "NonError" };
288
+ }
289
+ function randomTaskId() {
290
+ const t = Date.now().toString(36);
291
+ const r = Math.random().toString(36).slice(2, 10);
292
+ return `dlg-${t}-${r}`;
293
+ }
294
+ function hashIdempotencyInput(value) {
295
+ let str;
296
+ try {
297
+ str = JSON.stringify(canonicalize(value));
298
+ } catch {
299
+ str = String(value);
300
+ }
301
+ let h = 2166136261;
302
+ for (let i = 0; i < str.length; i += 1) {
303
+ h ^= str.charCodeAt(i);
304
+ h = Math.imul(h, 16777619);
305
+ }
306
+ return (h >>> 0).toString(16).padStart(8, "0");
307
+ }
308
+ function canonicalize(value) {
309
+ if (value === null || typeof value !== "object") return value;
310
+ if (Array.isArray(value)) return value.map(canonicalize);
311
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a.localeCompare(b));
312
+ const out = {};
313
+ for (const [k, v] of entries) out[k] = canonicalize(v);
314
+ return out;
315
+ }
316
+
317
+ // src/mcp/tools/delegate-code.ts
318
+ var DELEGATE_CODE_TOOL_NAME = "delegate_code";
319
+ var DELEGATE_CODE_DESCRIPTION = [
320
+ "Delegate a coding task to specialist coder agents that produce a validated patch.",
321
+ "",
322
+ "Use when: you need code written, fixed, refactored, or extended to satisfy a",
323
+ "user goal that touches a real repository. The coder runs in an isolated",
324
+ "sandbox, opens a fresh branch, keeps the diff minimal, runs the supplied",
325
+ "test + typecheck commands, and emits a unified-diff patch.",
326
+ "",
327
+ "Returns immediately with a taskId. Poll delegation_status to retrieve the",
328
+ "patch + validator verdict (typically minutes-to-hours, longer for large",
329
+ "changes). Identical inputs return the same taskId \u2014 safe to retry.",
330
+ "",
331
+ "When variants > 1, multiple coder harnesses (claude-code, codex, opencode)",
332
+ "attempt the task in parallel and the highest-scoring patch wins (smallest",
333
+ "passing diff). Use variants for high-stakes changes; single variant for",
334
+ "routine ones.",
335
+ "",
336
+ "Capability scope: the coder cannot modify paths outside repoRoot and cannot",
337
+ "touch paths in config.forbiddenPaths. The validator hard-fails on a",
338
+ "forbidden-path violation, diff above config.maxDiffLines, test failure, or",
339
+ "typecheck failure \u2014 none of those make it past the gate."
340
+ ].join("\n");
341
+ var DELEGATE_CODE_INPUT_SCHEMA = {
342
+ type: "object",
343
+ properties: {
344
+ goal: {
345
+ type: "string",
346
+ description: "Natural-language description of what the coder must accomplish."
347
+ },
348
+ repoRoot: {
349
+ type: "string",
350
+ description: "Absolute path inside the sandbox where the repo lives."
351
+ },
352
+ contextHint: {
353
+ type: "string",
354
+ description: "Optional free-form context the coder sees in the prompt prelude."
355
+ },
356
+ variants: {
357
+ type: "integer",
358
+ minimum: 1,
359
+ maximum: 8,
360
+ description: "Number of parallel coder harnesses. Default 1."
361
+ },
362
+ config: {
363
+ type: "object",
364
+ properties: {
365
+ testCmd: { type: "string" },
366
+ typecheckCmd: { type: "string" },
367
+ forbiddenPaths: { type: "array", items: { type: "string" } },
368
+ maxDiffLines: { type: "integer", minimum: 1 }
369
+ },
370
+ additionalProperties: false
371
+ },
372
+ namespace: {
373
+ type: "string",
374
+ description: "Multi-tenant scope (customer-id, workspace-id)."
375
+ }
376
+ },
377
+ required: ["goal", "repoRoot"],
378
+ additionalProperties: false
379
+ };
380
+ var SINGLE_VARIANT_ESTIMATE_MS = 6 * 60 * 1e3;
381
+ var FANOUT_PER_VARIANT_ESTIMATE_MS = 8 * 60 * 1e3;
382
+ function validateDelegateCodeArgs(raw) {
383
+ if (raw === null || typeof raw !== "object") {
384
+ throw new TypeError("delegate_code: arguments must be an object");
385
+ }
386
+ const value = raw;
387
+ const goal = value.goal;
388
+ if (typeof goal !== "string" || goal.trim().length === 0) {
389
+ throw new TypeError("delegate_code: `goal` must be a non-empty string");
390
+ }
391
+ const repoRoot = value.repoRoot;
392
+ if (typeof repoRoot !== "string" || repoRoot.trim().length === 0) {
393
+ throw new TypeError("delegate_code: `repoRoot` must be a non-empty string");
394
+ }
395
+ const args = { goal: goal.trim(), repoRoot: repoRoot.trim() };
396
+ if (typeof value.contextHint === "string") args.contextHint = value.contextHint;
397
+ if (value.variants !== void 0) {
398
+ const variants = Number(value.variants);
399
+ if (!Number.isFinite(variants) || variants < 1 || variants > 8) {
400
+ throw new RangeError("delegate_code: `variants` must be an integer in [1, 8]");
401
+ }
402
+ args.variants = Math.trunc(variants);
403
+ }
404
+ if (value.config !== void 0) {
405
+ args.config = validateConfig(value.config);
406
+ }
407
+ if (typeof value.namespace === "string") args.namespace = value.namespace;
408
+ return args;
409
+ }
410
+ function validateConfig(raw) {
411
+ if (raw === null || typeof raw !== "object") {
412
+ throw new TypeError("delegate_code: `config` must be an object");
413
+ }
414
+ const value = raw;
415
+ const out = {};
416
+ if (value.testCmd !== void 0) {
417
+ if (typeof value.testCmd !== "string") {
418
+ throw new TypeError("delegate_code: `config.testCmd` must be a string");
419
+ }
420
+ out.testCmd = value.testCmd;
421
+ }
422
+ if (value.typecheckCmd !== void 0) {
423
+ if (typeof value.typecheckCmd !== "string") {
424
+ throw new TypeError("delegate_code: `config.typecheckCmd` must be a string");
425
+ }
426
+ out.typecheckCmd = value.typecheckCmd;
427
+ }
428
+ if (value.forbiddenPaths !== void 0) {
429
+ if (!Array.isArray(value.forbiddenPaths)) {
430
+ throw new TypeError("delegate_code: `config.forbiddenPaths` must be a string array");
431
+ }
432
+ out.forbiddenPaths = value.forbiddenPaths.map((entry, i) => {
433
+ if (typeof entry !== "string") {
434
+ throw new TypeError(`delegate_code: forbiddenPaths[${i}] must be a string`);
435
+ }
436
+ return entry;
437
+ });
438
+ }
439
+ if (value.maxDiffLines !== void 0) {
440
+ const n = Number(value.maxDiffLines);
441
+ if (!Number.isFinite(n) || n < 1) {
442
+ throw new RangeError("delegate_code: `config.maxDiffLines` must be a positive integer");
443
+ }
444
+ out.maxDiffLines = Math.trunc(n);
445
+ }
446
+ return out;
447
+ }
448
+ function createDelegateCodeHandler(options) {
449
+ const estimateDurationMs = options.estimateDurationMs ?? defaultEstimate;
450
+ return async (raw) => {
451
+ const args = validateDelegateCodeArgs(raw);
452
+ const idempotencyKey = hashIdempotencyInput({
453
+ profile: "coder",
454
+ goal: args.goal,
455
+ repoRoot: args.repoRoot,
456
+ contextHint: args.contextHint,
457
+ variants: args.variants ?? 1,
458
+ config: args.config,
459
+ namespace: args.namespace
460
+ });
461
+ const submitted = options.queue.submit({
462
+ profile: "coder",
463
+ args,
464
+ namespace: args.namespace,
465
+ idempotencyKey,
466
+ run: async (ctx) => options.delegate(args, ctx)
467
+ });
468
+ return {
469
+ taskId: submitted.taskId,
470
+ estimatedDurationMs: estimateDurationMs(args)
471
+ };
472
+ };
473
+ }
474
+ function defaultEstimate(args) {
475
+ const variants = Math.max(1, args.variants ?? 1);
476
+ if (variants === 1) return SINGLE_VARIANT_ESTIMATE_MS;
477
+ return FANOUT_PER_VARIANT_ESTIMATE_MS;
478
+ }
479
+
480
+ // src/mcp/tools/delegate-feedback.ts
481
+ var DELEGATE_FEEDBACK_TOOL_NAME = "delegate_feedback";
482
+ var DELEGATE_FEEDBACK_DESCRIPTION = [
483
+ "Record feedback on a delegation, artifact, or outcome. Synchronous \u2014 the",
484
+ "event is durably stored when this call returns.",
485
+ "",
486
+ "Use when: you (the agent), the user, or a downstream judge has formed an",
487
+ "opinion about a piece of work and want it persisted for calibration,",
488
+ "pricing, or future routing. Every call is a new event \u2014 multiple ratings",
489
+ "on the same target are expected and never deduped.",
490
+ "",
491
+ "`refersTo.kind`:",
492
+ ' - "delegation": ref is a taskId returned by delegate_code/delegate_research',
493
+ ' - "artifact": ref is a URI/path/git-sha \u2014 anything you can dereference',
494
+ ' - "outcome": ref is a free-form description of a downstream result',
495
+ "",
496
+ "`by`:",
497
+ ' - "agent": the agent itself rated the work',
498
+ ' - "user": the human user rated it',
499
+ ' - "downstream-judge": an automated evaluator emitted the rating',
500
+ "",
501
+ "When ref names a known taskId, the rating is also attached to the",
502
+ "delegation record so delegation_history surfaces it inline."
503
+ ].join("\n");
504
+ var DELEGATE_FEEDBACK_INPUT_SCHEMA = {
505
+ type: "object",
506
+ properties: {
507
+ refersTo: {
508
+ type: "object",
509
+ properties: {
510
+ kind: { type: "string", enum: ["delegation", "artifact", "outcome"] },
511
+ ref: { type: "string" }
512
+ },
513
+ required: ["kind", "ref"],
514
+ additionalProperties: false
515
+ },
516
+ rating: {
517
+ type: "object",
518
+ properties: {
519
+ score: { type: "number", minimum: 0, maximum: 1 },
520
+ label: { type: "string", enum: ["good", "bad", "neutral", "mixed"] },
521
+ notes: { type: "string" }
522
+ },
523
+ required: ["score", "notes"],
524
+ additionalProperties: false
525
+ },
526
+ by: { type: "string", enum: ["agent", "user", "downstream-judge"] },
527
+ capturedAt: { type: "string" },
528
+ namespace: { type: "string" }
529
+ },
530
+ required: ["refersTo", "rating", "by"],
531
+ additionalProperties: false
532
+ };
533
+ function validateDelegateFeedbackArgs(raw) {
534
+ if (raw === null || typeof raw !== "object") {
535
+ throw new TypeError("delegate_feedback: arguments must be an object");
536
+ }
537
+ const value = raw;
538
+ const refersTo = validateRefersTo(value.refersTo);
539
+ const rating = validateRating(value.rating);
540
+ const by = value.by;
541
+ if (by !== "agent" && by !== "user" && by !== "downstream-judge") {
542
+ throw new TypeError(
543
+ 'delegate_feedback: `by` must be one of "agent" | "user" | "downstream-judge"'
544
+ );
545
+ }
546
+ const args = { refersTo, rating, by };
547
+ if (value.capturedAt !== void 0) {
548
+ if (typeof value.capturedAt !== "string" || Number.isNaN(Date.parse(value.capturedAt))) {
549
+ throw new TypeError("delegate_feedback: `capturedAt` must be an ISO datetime");
550
+ }
551
+ args.capturedAt = value.capturedAt;
552
+ }
553
+ if (typeof value.namespace === "string") args.namespace = value.namespace;
554
+ return args;
555
+ }
556
+ function validateRefersTo(raw) {
557
+ if (raw === null || typeof raw !== "object") {
558
+ throw new TypeError("delegate_feedback: `refersTo` must be an object");
559
+ }
560
+ const value = raw;
561
+ const kind = value.kind;
562
+ if (kind !== "delegation" && kind !== "artifact" && kind !== "outcome") {
563
+ throw new TypeError(
564
+ 'delegate_feedback: `refersTo.kind` must be one of "delegation" | "artifact" | "outcome"'
565
+ );
566
+ }
567
+ const ref = value.ref;
568
+ if (typeof ref !== "string" || ref.trim().length === 0) {
569
+ throw new TypeError("delegate_feedback: `refersTo.ref` must be a non-empty string");
570
+ }
571
+ return { kind, ref: ref.trim() };
572
+ }
573
+ function validateRating(raw) {
574
+ if (raw === null || typeof raw !== "object") {
575
+ throw new TypeError("delegate_feedback: `rating` must be an object");
576
+ }
577
+ const value = raw;
578
+ const score = Number(value.score);
579
+ if (!Number.isFinite(score) || score < 0 || score > 1) {
580
+ throw new RangeError("delegate_feedback: `rating.score` must be a number in [0, 1]");
581
+ }
582
+ const notes = value.notes;
583
+ if (typeof notes !== "string") {
584
+ throw new TypeError("delegate_feedback: `rating.notes` must be a string");
585
+ }
586
+ const rating = { score, notes };
587
+ const label = value.label;
588
+ if (label !== void 0) {
589
+ if (label !== "good" && label !== "bad" && label !== "neutral" && label !== "mixed") {
590
+ throw new TypeError(
591
+ 'delegate_feedback: `rating.label` must be one of "good" | "bad" | "neutral" | "mixed"'
592
+ );
593
+ }
594
+ rating.label = label;
595
+ }
596
+ return rating;
597
+ }
598
+ function createDelegateFeedbackHandler(options) {
599
+ const generateId = options.generateId ?? randomFeedbackId;
600
+ const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
601
+ return async (raw) => {
602
+ const args = validateDelegateFeedbackArgs(raw);
603
+ const id = generateId();
604
+ const event = {
605
+ id,
606
+ refersTo: args.refersTo,
607
+ rating: args.rating,
608
+ by: args.by,
609
+ capturedAt: args.capturedAt ?? now(),
610
+ namespace: args.namespace
611
+ };
612
+ await options.store.put(event);
613
+ if (args.refersTo.kind === "delegation") {
614
+ options.queue.attachFeedback(args.refersTo.ref, eventToSnapshot(event));
615
+ }
616
+ return { recorded: true, id };
617
+ };
618
+ }
619
+ function randomFeedbackId() {
620
+ const t = Date.now().toString(36);
621
+ const r = Math.random().toString(36).slice(2, 10);
622
+ return `fbk-${t}-${r}`;
623
+ }
624
+
625
+ // src/mcp/tools/delegate-research.ts
626
+ var DELEGATE_RESEARCH_TOOL_NAME = "delegate_research";
627
+ var DELEGATE_RESEARCH_DESCRIPTION = [
628
+ "Delegate a research question to specialist researcher agents that produce",
629
+ "source-grounded, evidence-bearing knowledge items.",
630
+ "",
631
+ "Use when: you need to answer a factual question with external evidence \u2014",
632
+ "audience research, competitive intelligence, recency-bound web searches,",
633
+ "corpus / docs lookups. The researcher emits items[] with provenance, a",
634
+ "citations[] index, and proposedWrites[] you decide whether to persist.",
635
+ "",
636
+ "Returns immediately with a taskId. Poll delegation_status to retrieve the",
637
+ "items + verdict. Identical inputs return the same taskId \u2014 safe to retry.",
638
+ "",
639
+ "When variants > 1, multiple researcher harnesses run in parallel and the",
640
+ "highest-scoring valid output wins (citation density \xD7 source diversity \xD7",
641
+ "recency match \xD7 gap coverage). Use variants when answers might disagree.",
642
+ "",
643
+ "Multi-tenant isolation: every item carries `namespace`. The validator",
644
+ "hard-fails when any item is scoped outside `namespace`. Never pass another",
645
+ "tenant's namespace."
646
+ ].join("\n");
647
+ var VALID_SOURCES = ["web", "corpus", "twitter", "github", "docs"];
648
+ var DELEGATE_RESEARCH_INPUT_SCHEMA = {
649
+ type: "object",
650
+ properties: {
651
+ question: {
652
+ type: "string",
653
+ description: "The research question to answer."
654
+ },
655
+ namespace: {
656
+ type: "string",
657
+ description: "Multi-tenant scope (customer-id, workspace-id). REQUIRED."
658
+ },
659
+ scope: { type: "string", description: 'Bound, e.g. "audience for cpg-founder ICP".' },
660
+ sources: {
661
+ type: "array",
662
+ items: { type: "string", enum: [...VALID_SOURCES] }
663
+ },
664
+ variants: { type: "integer", minimum: 1, maximum: 8 },
665
+ config: {
666
+ type: "object",
667
+ properties: {
668
+ recencyWindow: {
669
+ type: "object",
670
+ properties: {
671
+ since: { type: "string", description: "ISO datetime" },
672
+ until: { type: "string", description: "ISO datetime" }
673
+ },
674
+ additionalProperties: false
675
+ },
676
+ maxItems: { type: "integer", minimum: 1 },
677
+ minConfidence: { type: "number", minimum: 0, maximum: 1 }
678
+ },
679
+ additionalProperties: false
680
+ }
681
+ },
682
+ required: ["question", "namespace"],
683
+ additionalProperties: false
684
+ };
685
+ var SINGLE_VARIANT_ESTIMATE_MS2 = 4 * 60 * 1e3;
686
+ var FANOUT_PER_VARIANT_ESTIMATE_MS2 = 6 * 60 * 1e3;
687
+ function validateDelegateResearchArgs(raw) {
688
+ if (raw === null || typeof raw !== "object") {
689
+ throw new TypeError("delegate_research: arguments must be an object");
690
+ }
691
+ const value = raw;
692
+ const question = value.question;
693
+ if (typeof question !== "string" || question.trim().length === 0) {
694
+ throw new TypeError("delegate_research: `question` must be a non-empty string");
695
+ }
696
+ const namespace = value.namespace;
697
+ if (typeof namespace !== "string" || namespace.trim().length === 0) {
698
+ throw new TypeError("delegate_research: `namespace` is required");
699
+ }
700
+ const args = { question: question.trim(), namespace: namespace.trim() };
701
+ if (typeof value.scope === "string") args.scope = value.scope;
702
+ if (value.sources !== void 0) {
703
+ if (!Array.isArray(value.sources)) {
704
+ throw new TypeError("delegate_research: `sources` must be a string array");
705
+ }
706
+ const sources = value.sources.map((src, i) => {
707
+ if (typeof src !== "string" || !VALID_SOURCES.includes(src)) {
708
+ throw new TypeError(
709
+ `delegate_research: sources[${i}] must be one of ${VALID_SOURCES.join("|")}`
710
+ );
711
+ }
712
+ return src;
713
+ });
714
+ args.sources = sources;
715
+ }
716
+ if (value.variants !== void 0) {
717
+ const variants = Number(value.variants);
718
+ if (!Number.isFinite(variants) || variants < 1 || variants > 8) {
719
+ throw new RangeError("delegate_research: `variants` must be an integer in [1, 8]");
720
+ }
721
+ args.variants = Math.trunc(variants);
722
+ }
723
+ if (value.config !== void 0) {
724
+ args.config = validateConfig2(value.config);
725
+ }
726
+ return args;
727
+ }
728
+ function validateConfig2(raw) {
729
+ if (raw === null || typeof raw !== "object") {
730
+ throw new TypeError("delegate_research: `config` must be an object");
731
+ }
732
+ const value = raw;
733
+ const out = {};
734
+ if (value.recencyWindow !== void 0) {
735
+ if (value.recencyWindow === null || typeof value.recencyWindow !== "object") {
736
+ throw new TypeError("delegate_research: `config.recencyWindow` must be an object");
737
+ }
738
+ const window = value.recencyWindow;
739
+ const windowOut = {};
740
+ if (window.since !== void 0) {
741
+ if (typeof window.since !== "string" || Number.isNaN(Date.parse(window.since))) {
742
+ throw new TypeError("delegate_research: `recencyWindow.since` must be an ISO datetime");
743
+ }
744
+ windowOut.since = window.since;
745
+ }
746
+ if (window.until !== void 0) {
747
+ if (typeof window.until !== "string" || Number.isNaN(Date.parse(window.until))) {
748
+ throw new TypeError("delegate_research: `recencyWindow.until` must be an ISO datetime");
749
+ }
750
+ windowOut.until = window.until;
751
+ }
752
+ out.recencyWindow = windowOut;
753
+ }
754
+ if (value.maxItems !== void 0) {
755
+ const n = Number(value.maxItems);
756
+ if (!Number.isFinite(n) || n < 1) {
757
+ throw new RangeError("delegate_research: `config.maxItems` must be a positive integer");
758
+ }
759
+ out.maxItems = Math.trunc(n);
760
+ }
761
+ if (value.minConfidence !== void 0) {
762
+ const n = Number(value.minConfidence);
763
+ if (!Number.isFinite(n) || n < 0 || n > 1) {
764
+ throw new RangeError("delegate_research: `config.minConfidence` must be in [0, 1]");
765
+ }
766
+ out.minConfidence = n;
767
+ }
768
+ return out;
769
+ }
770
+ function createDelegateResearchHandler(options) {
771
+ const estimateDurationMs = options.estimateDurationMs ?? defaultEstimate2;
772
+ return async (raw) => {
773
+ const args = validateDelegateResearchArgs(raw);
774
+ const idempotencyKey = hashIdempotencyInput({
775
+ profile: "researcher",
776
+ question: args.question,
777
+ namespace: args.namespace,
778
+ scope: args.scope,
779
+ sources: args.sources,
780
+ variants: args.variants ?? 1,
781
+ config: args.config
782
+ });
783
+ const submitted = options.queue.submit({
784
+ profile: "researcher",
785
+ args,
786
+ namespace: args.namespace,
787
+ idempotencyKey,
788
+ run: async (ctx) => options.delegate(args, ctx)
789
+ });
790
+ return {
791
+ taskId: submitted.taskId,
792
+ estimatedDurationMs: estimateDurationMs(args)
793
+ };
794
+ };
795
+ }
796
+ function defaultEstimate2(args) {
797
+ const variants = Math.max(1, args.variants ?? 1);
798
+ if (variants === 1) return SINGLE_VARIANT_ESTIMATE_MS2;
799
+ return FANOUT_PER_VARIANT_ESTIMATE_MS2;
800
+ }
801
+
802
+ // src/mcp/tools/delegation-history.ts
803
+ var DELEGATION_HISTORY_TOOL_NAME = "delegation_history";
804
+ var DELEGATION_HISTORY_DESCRIPTION = [
805
+ "Read past delegations newest-first. Each entry carries the original",
806
+ "arguments, current status, cost, and any feedback attached via",
807
+ "delegate_feedback.",
808
+ "",
809
+ 'Use when: you want to introspect prior decisions \u2014 "have I asked this',
810
+ "question before?",
811
+ "did the last patch land?",
812
+ "what's the historical",
813
+ 'success rate of coder delegations on this repo?". Feed the results back',
814
+ "into your own routing and calibration.",
815
+ "",
816
+ 'Filters: `namespace` (multi-tenant scope), `profile` ("coder" | "researcher"),',
817
+ "`since` (ISO date \u2014 only delegations started at-or-after). `limit` defaults",
818
+ "to 50, capped at 500."
819
+ ].join("\n");
820
+ var DELEGATION_HISTORY_INPUT_SCHEMA = {
821
+ type: "object",
822
+ properties: {
823
+ namespace: { type: "string" },
824
+ profile: { type: "string", enum: ["coder", "researcher"] },
825
+ since: { type: "string", description: "ISO datetime \u2014 earliest startedAt to include." },
826
+ limit: { type: "integer", minimum: 1, maximum: 500 }
827
+ },
828
+ additionalProperties: false
829
+ };
830
+ function validateDelegationHistoryArgs(raw) {
831
+ if (raw === void 0 || raw === null) return {};
832
+ if (typeof raw !== "object") {
833
+ throw new TypeError("delegation_history: arguments must be an object");
834
+ }
835
+ const value = raw;
836
+ const out = {};
837
+ if (value.namespace !== void 0) {
838
+ if (typeof value.namespace !== "string") {
839
+ throw new TypeError("delegation_history: `namespace` must be a string");
840
+ }
841
+ out.namespace = value.namespace;
842
+ }
843
+ if (value.profile !== void 0) {
844
+ if (value.profile !== "coder" && value.profile !== "researcher") {
845
+ throw new TypeError('delegation_history: `profile` must be "coder" or "researcher"');
846
+ }
847
+ out.profile = value.profile;
848
+ }
849
+ if (value.since !== void 0) {
850
+ if (typeof value.since !== "string" || Number.isNaN(Date.parse(value.since))) {
851
+ throw new TypeError("delegation_history: `since` must be an ISO datetime");
852
+ }
853
+ out.since = value.since;
854
+ }
855
+ if (value.limit !== void 0) {
856
+ const n = Number(value.limit);
857
+ if (!Number.isFinite(n) || n < 1 || n > 500) {
858
+ throw new RangeError("delegation_history: `limit` must be an integer in [1, 500]");
859
+ }
860
+ out.limit = Math.trunc(n);
861
+ }
862
+ return out;
863
+ }
864
+ function createDelegationHistoryHandler(options) {
865
+ return async (raw) => {
866
+ const args = validateDelegationHistoryArgs(raw);
867
+ return { delegations: options.queue.history(args) };
868
+ };
869
+ }
870
+
871
+ // src/mcp/tools/delegation-status.ts
872
+ var DELEGATION_STATUS_TOOL_NAME = "delegation_status";
873
+ var DELEGATION_STATUS_DESCRIPTION = [
874
+ "Poll the status of an async delegation. Returns the current state",
875
+ "(pending | running | completed | failed | cancelled), optional progress,",
876
+ 'and the final result when status === "completed".',
877
+ "",
878
+ "Use when: you previously called delegate_code or delegate_research and",
879
+ "need to know whether the work is done. The agent's right rhythm is to",
880
+ "call this every minute or two while waiting; do not busy-poll.",
881
+ "",
882
+ "For a completed coder task, `result.output` is a CoderOutput with branch,",
883
+ "patch, test/typecheck results, and diff stats. For a completed research",
884
+ "task, `result.output` is the items + citations + proposedWrites bundle.",
885
+ "",
886
+ "Throws NotFoundError when taskId is unknown \u2014 never silently returns",
887
+ "`pending` for a typo."
888
+ ].join("\n");
889
+ var DELEGATION_STATUS_INPUT_SCHEMA = {
890
+ type: "object",
891
+ properties: {
892
+ taskId: { type: "string", description: "Returned by delegate_code / delegate_research." }
893
+ },
894
+ required: ["taskId"],
895
+ additionalProperties: false
896
+ };
897
+ function validateDelegationStatusArgs(raw) {
898
+ if (raw === null || typeof raw !== "object") {
899
+ throw new TypeError("delegation_status: arguments must be an object");
900
+ }
901
+ const value = raw;
902
+ const taskId = value.taskId;
903
+ if (typeof taskId !== "string" || taskId.trim().length === 0) {
904
+ throw new TypeError("delegation_status: `taskId` must be a non-empty string");
905
+ }
906
+ return { taskId: taskId.trim() };
907
+ }
908
+ function createDelegationStatusHandler(options) {
909
+ return async (raw) => {
910
+ const args = validateDelegationStatusArgs(raw);
911
+ const status = options.queue.status(args.taskId);
912
+ if (!status) {
913
+ throw new NotFoundError(`delegation_status: unknown taskId "${args.taskId}"`);
914
+ }
915
+ return status;
916
+ };
917
+ }
918
+
919
+ // src/mcp/server.ts
920
+ import { createInterface } from "readline";
921
+ import { Readable, Writable } from "stream";
922
+ var PROTOCOL_VERSION = "2024-11-05";
923
+ var DEFAULT_SERVER_NAME = "agent-runtime-mcp";
924
+ var DEFAULT_SERVER_VERSION = "0.20.0";
925
+ function createMcpServer(options = {}) {
926
+ const queue = options.queue ?? new DelegationTaskQueue();
927
+ const feedbackStore = options.feedbackStore ?? new InMemoryFeedbackStore();
928
+ const serverName = options.serverName ?? DEFAULT_SERVER_NAME;
929
+ const serverVersion = options.serverVersion ?? DEFAULT_SERVER_VERSION;
930
+ const tools = /* @__PURE__ */ new Map();
931
+ if (options.coderDelegate) {
932
+ tools.set(DELEGATE_CODE_TOOL_NAME, {
933
+ name: DELEGATE_CODE_TOOL_NAME,
934
+ description: DELEGATE_CODE_DESCRIPTION,
935
+ inputSchema: DELEGATE_CODE_INPUT_SCHEMA,
936
+ handler: createDelegateCodeHandler({ queue, delegate: options.coderDelegate })
937
+ });
938
+ }
939
+ if (options.researcherDelegate) {
940
+ tools.set(DELEGATE_RESEARCH_TOOL_NAME, {
941
+ name: DELEGATE_RESEARCH_TOOL_NAME,
942
+ description: DELEGATE_RESEARCH_DESCRIPTION,
943
+ inputSchema: DELEGATE_RESEARCH_INPUT_SCHEMA,
944
+ handler: createDelegateResearchHandler({ queue, delegate: options.researcherDelegate })
945
+ });
946
+ }
947
+ tools.set(DELEGATE_FEEDBACK_TOOL_NAME, {
948
+ name: DELEGATE_FEEDBACK_TOOL_NAME,
949
+ description: DELEGATE_FEEDBACK_DESCRIPTION,
950
+ inputSchema: DELEGATE_FEEDBACK_INPUT_SCHEMA,
951
+ handler: createDelegateFeedbackHandler({ queue, store: feedbackStore })
952
+ });
953
+ tools.set(DELEGATION_STATUS_TOOL_NAME, {
954
+ name: DELEGATION_STATUS_TOOL_NAME,
955
+ description: DELEGATION_STATUS_DESCRIPTION,
956
+ inputSchema: DELEGATION_STATUS_INPUT_SCHEMA,
957
+ handler: createDelegationStatusHandler({ queue })
958
+ });
959
+ tools.set(DELEGATION_HISTORY_TOOL_NAME, {
960
+ name: DELEGATION_HISTORY_TOOL_NAME,
961
+ description: DELEGATION_HISTORY_DESCRIPTION,
962
+ inputSchema: DELEGATION_HISTORY_INPUT_SCHEMA,
963
+ handler: createDelegationHistoryHandler({ queue })
964
+ });
965
+ let stopped = false;
966
+ let activeReadline;
967
+ async function handle(message) {
968
+ if (stopped) {
969
+ return rpcError(message.id ?? null, -32099, "server stopped");
970
+ }
971
+ if (message.method === "initialize") {
972
+ return rpcResult(message.id ?? null, {
973
+ protocolVersion: PROTOCOL_VERSION,
974
+ capabilities: { tools: {} },
975
+ serverInfo: { name: serverName, version: serverVersion }
976
+ });
977
+ }
978
+ if (message.method === "notifications/initialized") {
979
+ return null;
980
+ }
981
+ if (message.method === "tools/list") {
982
+ return rpcResult(message.id ?? null, {
983
+ tools: [...tools.values()].map((tool) => ({
984
+ name: tool.name,
985
+ description: tool.description,
986
+ inputSchema: tool.inputSchema
987
+ }))
988
+ });
989
+ }
990
+ if (message.method === "tools/call") {
991
+ const params = message.params ?? {};
992
+ const name = typeof params.name === "string" ? params.name : "";
993
+ const tool = tools.get(name);
994
+ if (!tool) {
995
+ return rpcError(message.id ?? null, -32601, `unknown tool: ${name}`);
996
+ }
997
+ try {
998
+ const output = await tool.handler(params.arguments ?? {});
999
+ return rpcResult(message.id ?? null, {
1000
+ content: [{ type: "text", text: JSON.stringify(output) }],
1001
+ structuredContent: output,
1002
+ isError: false
1003
+ });
1004
+ } catch (err) {
1005
+ const reason = err instanceof Error ? err.message : String(err);
1006
+ const code = err instanceof TypeError || err instanceof RangeError ? -32602 : -32e3;
1007
+ return rpcError(message.id ?? null, code, reason);
1008
+ }
1009
+ }
1010
+ if (message.id === void 0 || message.id === null) return null;
1011
+ return rpcError(message.id, -32601, `unknown method: ${message.method}`);
1012
+ }
1013
+ async function serve(transport) {
1014
+ const input = transport?.input ?? process.stdin;
1015
+ const output = transport?.output ?? process.stdout;
1016
+ const rl = createInterface({ input, crlfDelay: Number.POSITIVE_INFINITY });
1017
+ activeReadline = rl;
1018
+ return new Promise((resolve, reject) => {
1019
+ rl.on("line", (line) => {
1020
+ const trimmed = line.trim();
1021
+ if (!trimmed) return;
1022
+ let parsed;
1023
+ try {
1024
+ parsed = JSON.parse(trimmed);
1025
+ } catch (err) {
1026
+ writeResponse(output, rpcError(null, -32700, `parse error: ${err.message}`));
1027
+ return;
1028
+ }
1029
+ if (!parsed || parsed.jsonrpc !== "2.0" || typeof parsed.method !== "string") {
1030
+ writeResponse(output, rpcError(parsed?.id ?? null, -32600, "invalid request"));
1031
+ return;
1032
+ }
1033
+ void handle(parsed).then((response) => {
1034
+ if (response) writeResponse(output, response);
1035
+ });
1036
+ });
1037
+ rl.on("close", () => resolve());
1038
+ rl.on("error", (err) => reject(err));
1039
+ if (stopped) {
1040
+ rl.close();
1041
+ resolve();
1042
+ }
1043
+ });
1044
+ }
1045
+ function stop() {
1046
+ stopped = true;
1047
+ activeReadline?.close();
1048
+ activeReadline = void 0;
1049
+ }
1050
+ return {
1051
+ tools,
1052
+ queue,
1053
+ feedbackStore,
1054
+ handle,
1055
+ serve,
1056
+ stop
1057
+ };
1058
+ }
1059
+ function rpcResult(id, result) {
1060
+ return { jsonrpc: "2.0", id, result };
1061
+ }
1062
+ function rpcError(id, code, message, data) {
1063
+ return {
1064
+ jsonrpc: "2.0",
1065
+ id,
1066
+ error: data === void 0 ? { code, message } : { code, message, data }
1067
+ };
1068
+ }
1069
+ function writeResponse(output, response) {
1070
+ output.write(`${JSON.stringify(response)}
1071
+ `);
1072
+ }
1073
+ function createInProcessTransport() {
1074
+ const responses = [];
1075
+ const input = new Readable({ read() {
1076
+ } });
1077
+ const output = new Writable({
1078
+ write(chunk, _enc, cb) {
1079
+ const text = chunk.toString("utf8");
1080
+ for (const line of text.split("\n")) {
1081
+ const trimmed = line.trim();
1082
+ if (!trimmed) continue;
1083
+ try {
1084
+ responses.push(JSON.parse(trimmed));
1085
+ } catch {
1086
+ }
1087
+ }
1088
+ cb();
1089
+ }
1090
+ });
1091
+ return {
1092
+ transport: { input, output },
1093
+ clientWrite(line) {
1094
+ input.push(`${line}
1095
+ `);
1096
+ },
1097
+ clientClose() {
1098
+ input.push(null);
1099
+ },
1100
+ async readServer() {
1101
+ for (let i = 0; i < 5; i += 1) await new Promise((r) => setImmediate(r));
1102
+ return [...responses];
1103
+ }
1104
+ };
1105
+ }
1106
+
1107
+ export {
1108
+ createDefaultCoderDelegate,
1109
+ InMemoryFeedbackStore,
1110
+ eventToSnapshot,
1111
+ DelegationTaskQueue,
1112
+ hashIdempotencyInput,
1113
+ DELEGATE_CODE_TOOL_NAME,
1114
+ DELEGATE_CODE_DESCRIPTION,
1115
+ DELEGATE_CODE_INPUT_SCHEMA,
1116
+ validateDelegateCodeArgs,
1117
+ createDelegateCodeHandler,
1118
+ DELEGATE_FEEDBACK_TOOL_NAME,
1119
+ DELEGATE_FEEDBACK_DESCRIPTION,
1120
+ DELEGATE_FEEDBACK_INPUT_SCHEMA,
1121
+ validateDelegateFeedbackArgs,
1122
+ createDelegateFeedbackHandler,
1123
+ DELEGATE_RESEARCH_TOOL_NAME,
1124
+ DELEGATE_RESEARCH_DESCRIPTION,
1125
+ DELEGATE_RESEARCH_INPUT_SCHEMA,
1126
+ validateDelegateResearchArgs,
1127
+ createDelegateResearchHandler,
1128
+ DELEGATION_HISTORY_TOOL_NAME,
1129
+ DELEGATION_HISTORY_DESCRIPTION,
1130
+ DELEGATION_HISTORY_INPUT_SCHEMA,
1131
+ validateDelegationHistoryArgs,
1132
+ createDelegationHistoryHandler,
1133
+ DELEGATION_STATUS_TOOL_NAME,
1134
+ DELEGATION_STATUS_DESCRIPTION,
1135
+ DELEGATION_STATUS_INPUT_SCHEMA,
1136
+ validateDelegationStatusArgs,
1137
+ createDelegationStatusHandler,
1138
+ createMcpServer,
1139
+ createInProcessTransport
1140
+ };
1141
+ //# sourceMappingURL=chunk-LPPM7EGS.js.map