@tangle-network/agent-runtime 0.8.0 → 0.11.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.
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
1
3
  // src/errors.ts
2
4
  import { AgentEvalError } from "@tangle-network/agent-eval";
3
5
  import {
@@ -290,6 +292,279 @@ function stringValue(value) {
290
292
  return typeof value === "string" && value.length > 0 ? value : void 0;
291
293
  }
292
294
 
295
+ // src/chat-turn.ts
296
+ import { mergeAgentProfiles } from "@tangle-network/sandbox";
297
+ var RUNTIME_PATH = "/runtime/agents/run/stream";
298
+ async function* runChatTurn(options) {
299
+ const turnProfile = options.overlay ? composeTurnProfile(options.profile, options.overlay) : options.profile;
300
+ const url = `${options.sandbox.runtimeUrl}${RUNTIME_PATH}`;
301
+ const backendType = turnProfile.metadata?.backend?.type ?? "claude-code";
302
+ const body = JSON.stringify({
303
+ backend: {
304
+ type: backendType,
305
+ profile: turnProfile,
306
+ ...options.modelOverride ? { model: { default: options.modelOverride } } : {}
307
+ },
308
+ messages: [...options.priorMessages, { role: "user", content: options.message }]
309
+ });
310
+ const headers = {
311
+ "Content-Type": "application/json",
312
+ Accept: "text/event-stream"
313
+ };
314
+ if (options.sandbox.authHeader) {
315
+ headers[options.sandbox.authHeader.name] = options.sandbox.authHeader.value;
316
+ }
317
+ const fetchImpl = options.fetch ?? fetch;
318
+ const response = await fetchImpl(url, {
319
+ method: "POST",
320
+ headers,
321
+ body,
322
+ signal: options.signal
323
+ });
324
+ if (!response.ok || !response.body) {
325
+ const text = response.body ? await response.text() : "";
326
+ throw new ChatTurnError(
327
+ `runChatTurn: sandbox returned ${response.status} ${response.statusText}: ${text.slice(0, 500)}`,
328
+ response.status
329
+ );
330
+ }
331
+ yield* parseSseStream(response.body);
332
+ }
333
+ function composeTurnProfile(base, overlay) {
334
+ const partial = {};
335
+ if (overlay.promptOverlay) partial.prompt = overlay.promptOverlay;
336
+ if (overlay.resourcesOverlay) partial.resources = overlay.resourcesOverlay;
337
+ if (overlay.subagentsOverlay) partial.subagents = overlay.subagentsOverlay;
338
+ if (overlay.metadata) partial.metadata = overlay.metadata;
339
+ return mergeAgentProfiles(base, partial) ?? base;
340
+ }
341
+ async function* parseSseStream(stream) {
342
+ const decoder = new TextDecoder();
343
+ const reader = stream.getReader();
344
+ let buffer = "";
345
+ try {
346
+ while (true) {
347
+ const { value, done } = await reader.read();
348
+ if (done) break;
349
+ buffer += decoder.decode(value, { stream: true });
350
+ let idx = buffer.indexOf("\n");
351
+ while (idx >= 0) {
352
+ const line = buffer.slice(0, idx).replace(/\r$/, "").trim();
353
+ buffer = buffer.slice(idx + 1);
354
+ idx = buffer.indexOf("\n");
355
+ if (!line) continue;
356
+ const payload = line.startsWith("data:") ? line.slice(5).trim() : line;
357
+ if (payload === "[DONE]" || payload === "") continue;
358
+ try {
359
+ const parsed = JSON.parse(payload);
360
+ yield parsed;
361
+ } catch {
362
+ }
363
+ }
364
+ }
365
+ } finally {
366
+ reader.releaseLock();
367
+ }
368
+ }
369
+ var ChatTurnError = class extends Error {
370
+ constructor(message, status) {
371
+ super(message);
372
+ this.status = status;
373
+ this.name = "ChatTurnError";
374
+ }
375
+ status;
376
+ };
377
+ function sandboxAsChatTurnTarget(instance, opts) {
378
+ const base = instance.url ?? instance.connection?.runtimeUrl ?? "";
379
+ if (!base) {
380
+ throw new ChatTurnError(
381
+ `sandboxAsChatTurnTarget: SandboxInstance has neither .url nor .connection.runtimeUrl set`
382
+ );
383
+ }
384
+ return {
385
+ id: instance.id,
386
+ runtimeUrl: base.replace(/\/+$/, ""),
387
+ authHeader: opts?.authHeader
388
+ };
389
+ }
390
+
391
+ // src/intent-router.ts
392
+ var DEFAULT_PATTERN_WEIGHT = 1.5;
393
+ var DEFAULT_MIN_SCORE = 1;
394
+ function isMatcher(value) {
395
+ if (!value || typeof value !== "object") return false;
396
+ const v = value;
397
+ return Array.isArray(v.keywords) || Array.isArray(v.patterns) || typeof v.minScore === "number";
398
+ }
399
+ function readMatcher(subagent) {
400
+ const m = subagent.metadata;
401
+ if (!m) return null;
402
+ const raw = m.matchers ?? m.matcher ?? null;
403
+ if (!raw) return null;
404
+ if (Array.isArray(raw)) {
405
+ const merged = { keywords: [], patterns: [], minScore: DEFAULT_MIN_SCORE };
406
+ for (const entry of raw) {
407
+ if (!isMatcher(entry)) continue;
408
+ merged.keywords = [...merged.keywords ?? [], ...entry.keywords ?? []];
409
+ merged.patterns = [...merged.patterns ?? [], ...entry.patterns ?? []];
410
+ if (entry.minScore !== void 0) merged.minScore = entry.minScore;
411
+ }
412
+ return merged;
413
+ }
414
+ if (isMatcher(raw)) return raw;
415
+ return null;
416
+ }
417
+ function classifyIntent(profile, message, opts = {}) {
418
+ const lower = message.toLowerCase();
419
+ const subagents = profile.subagents ?? {};
420
+ const skip = new Set(opts.skip ?? []);
421
+ const defaultMinScore = opts.defaultMinScore ?? DEFAULT_MIN_SCORE;
422
+ const scores = {};
423
+ const evaluated = {};
424
+ let bestId = null;
425
+ let bestScore = -Infinity;
426
+ let bestSubagent = null;
427
+ for (const [id, subagent] of Object.entries(subagents)) {
428
+ if (skip.has(id)) continue;
429
+ if (opts.filter && !opts.filter(subagent, id)) continue;
430
+ const matcher = readMatcher(subagent);
431
+ if (!matcher) {
432
+ evaluated[id] = { keywordHits: 0, patternHits: 0, minScore: defaultMinScore };
433
+ scores[id] = 0;
434
+ continue;
435
+ }
436
+ const weight = matcher.weight ?? 1;
437
+ let kHits = 0;
438
+ for (const kw of matcher.keywords ?? []) {
439
+ if (kw && lower.includes(kw.toLowerCase())) kHits += 1;
440
+ }
441
+ let pHits = 0;
442
+ for (const p of matcher.patterns ?? []) {
443
+ const re = p instanceof RegExp ? p : new RegExp(p, "i");
444
+ if (re.test(message)) pHits += 1;
445
+ }
446
+ const score = kHits * weight + pHits * DEFAULT_PATTERN_WEIGHT * weight;
447
+ const minScore = matcher.minScore ?? defaultMinScore;
448
+ scores[id] = score;
449
+ evaluated[id] = { keywordHits: kHits, patternHits: pHits, minScore };
450
+ if (score >= minScore && score > bestScore) {
451
+ bestScore = score;
452
+ bestId = id;
453
+ bestSubagent = subagent;
454
+ }
455
+ }
456
+ if (bestId === null) {
457
+ return { id: null, subagent: null, score: 0, scores, evaluated };
458
+ }
459
+ return { id: bestId, subagent: bestSubagent, score: bestScore, scores, evaluated };
460
+ }
461
+
462
+ // src/profile-conformance.ts
463
+ var DEFAULT_SHELL_CAPS = [
464
+ "bash",
465
+ "sh",
466
+ "python",
467
+ "node",
468
+ "curl",
469
+ "web",
470
+ "browser",
471
+ "shell"
472
+ ];
473
+ var DEFAULT_MIN_PROMPT_CHARS = 800;
474
+ function checkSystemPrompt(profile, minChars) {
475
+ const prompt = profile.prompt?.systemPrompt;
476
+ if (!prompt || prompt.trim().length < minChars) {
477
+ return [
478
+ {
479
+ severity: "error",
480
+ code: "system-prompt-too-short",
481
+ path: "prompt.systemPrompt",
482
+ message: `prompt.systemPrompt is ${prompt?.length ?? 0} chars; min required is ${minChars}. Profiles below this threshold are almost always placeholders.`
483
+ }
484
+ ];
485
+ }
486
+ return [];
487
+ }
488
+ function checkToolsVsMcp(profile, opts) {
489
+ const issues = [];
490
+ const shellCaps = new Set(opts.knownShellCapabilities ?? DEFAULT_SHELL_CAPS);
491
+ const allowedWithoutMcp = new Set(opts.toolsAllowedWithoutMcp ?? []);
492
+ const tools = profile.tools ?? {};
493
+ const mcp = profile.mcp ?? {};
494
+ for (const [name, value] of Object.entries(tools)) {
495
+ if (shellCaps.has(name)) {
496
+ issues.push({
497
+ severity: "error",
498
+ code: "shell-capability-as-tool",
499
+ path: `tools.${name}`,
500
+ message: `'${name}' is a shell capability, not a tool. Move to permissions and remove from tools.`
501
+ });
502
+ continue;
503
+ }
504
+ if (allowedWithoutMcp.has(name)) continue;
505
+ if (value === false) continue;
506
+ if (!mcp[name]) {
507
+ issues.push({
508
+ severity: "error",
509
+ code: "decorative-tool-without-mcp",
510
+ path: `tools.${name}`,
511
+ message: `'${name}' declared in tools but no corresponding mcp[${name}] AgentProfileMcpServer. Either wire an MCP server, list this name in toolsAllowedWithoutMcp (if it's a file-mounted CLI binary), or remove from tools.`
512
+ });
513
+ }
514
+ }
515
+ return issues;
516
+ }
517
+ function checkSubagentShape(subagent, id) {
518
+ const issues = [];
519
+ if (!subagent.description || subagent.description.trim().length < 40) {
520
+ issues.push({
521
+ severity: "error",
522
+ code: "subagent-description-too-short",
523
+ path: `subagents.${id}.description`,
524
+ message: `subagents.${id}.description is missing or too short. Required for routing UX + audit trail.`
525
+ });
526
+ }
527
+ if (!subagent.prompt || subagent.prompt.trim().length < 100) {
528
+ issues.push({
529
+ severity: "error",
530
+ code: "subagent-prompt-too-short",
531
+ path: `subagents.${id}.prompt`,
532
+ message: `subagents.${id}.prompt is missing or below 100 chars. Either ship the specialist prompt or move this subagent under metadata.plannedSubagents.`
533
+ });
534
+ }
535
+ return issues;
536
+ }
537
+ function checkScaffoldSubagents(profile, strict) {
538
+ const issues = [];
539
+ const subagents = profile.subagents ?? {};
540
+ for (const [id, subagent] of Object.entries(subagents)) {
541
+ const meta = subagent.metadata;
542
+ const status = meta?.status;
543
+ if (status === "scaffold" || status === "not-implemented" || meta?.implemented === false) {
544
+ issues.push({
545
+ severity: strict ? "error" : "warn",
546
+ code: "subagent-scaffold-shipped",
547
+ path: `subagents.${id}`,
548
+ message: `subagents.${id} is a scaffold (metadata.status='${status}'). Router must gate on metadata.status === 'full' OR caller must opt into scaffolds explicitly.`
549
+ });
550
+ }
551
+ }
552
+ return issues;
553
+ }
554
+ function assertProfileConformance(profile, opts = {}) {
555
+ const issues = [
556
+ ...checkSystemPrompt(profile, opts.minSystemPromptChars ?? DEFAULT_MIN_PROMPT_CHARS),
557
+ ...checkToolsVsMcp(profile, opts),
558
+ ...checkScaffoldSubagents(profile, opts.strictNoScaffolds ?? false)
559
+ ];
560
+ for (const [id, subagent] of Object.entries(profile.subagents ?? {})) {
561
+ issues.push(...checkSubagentShape(subagent, id));
562
+ }
563
+ const errors = issues.filter((i) => i.severity === "error");
564
+ const warnings = issues.filter((i) => i.severity === "warn");
565
+ return { valid: errors.length === 0, errors, warnings };
566
+ }
567
+
293
568
  // src/readiness.ts
294
569
  var DEFAULT_MINIMUM_READINESS_SCORE = 0.7;
295
570
  function decideKnowledgeReadiness(report, options = {}) {
@@ -1296,9 +1571,9 @@ function projectToTraceEvent(event) {
1296
1571
  phase: "tool_call",
1297
1572
  toolName: event.toolName,
1298
1573
  toolCallId: event.toolCallId
1299
- // Args intentionally omitted at this layer; consumers attach the
1300
- // payload to a `ToolSpan` if they need to retain it. Trace events
1301
- // are point-in-time markers, not the canonical store for tool I/O.
1574
+ // Args omitted trace events are point-in-time markers, not the
1575
+ // canonical store for tool I/O. Attach payloads to a `ToolSpan`
1576
+ // when retention is required.
1302
1577
  }
1303
1578
  };
1304
1579
  case "tool_result":
@@ -1363,6 +1638,7 @@ export {
1363
1638
  AgentEvalError2 as AgentEvalError,
1364
1639
  BackendTransportError,
1365
1640
  CaptureIntegrityError,
1641
+ ChatTurnError,
1366
1642
  ConfigError,
1367
1643
  InMemoryRuntimeSessionStore,
1368
1644
  JudgeError,
@@ -1372,6 +1648,9 @@ export {
1372
1648
  SessionMismatchError,
1373
1649
  ValidationError,
1374
1650
  VerificationError,
1651
+ assertProfileConformance,
1652
+ classifyIntent,
1653
+ composeTurnProfile,
1375
1654
  createIterableBackend,
1376
1655
  createOpenAICompatibleBackend,
1377
1656
  createRuntimeEventCollector,
@@ -1383,7 +1662,9 @@ export {
1383
1662
  readinessServerSentEvent,
1384
1663
  runAgentTask,
1385
1664
  runAgentTaskStream,
1665
+ runChatTurn,
1386
1666
  runtimeStreamServerSentEvent,
1667
+ sandboxAsChatTurnTarget,
1387
1668
  sanitizeAgentRuntimeEvent,
1388
1669
  sanitizeKnowledgeReadinessReport,
1389
1670
  sanitizeRuntimeStreamEvent,