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