wunderland 0.31.0 → 0.32.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.
Files changed (56) hide show
  1. package/README.md +24 -1
  2. package/dist/api/index.d.ts +1 -1
  3. package/dist/api/index.d.ts.map +1 -1
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/api/server.d.ts +7 -1
  6. package/dist/api/server.d.ts.map +1 -1
  7. package/dist/api/server.js +294 -101
  8. package/dist/api/server.js.map +1 -1
  9. package/dist/api/types.d.ts +102 -0
  10. package/dist/api/types.d.ts.map +1 -1
  11. package/dist/cli/commands/chat.d.ts.map +1 -1
  12. package/dist/cli/commands/chat.js +82 -19
  13. package/dist/cli/commands/chat.js.map +1 -1
  14. package/dist/cli/commands/start.d.ts.map +1 -1
  15. package/dist/cli/commands/start.js +379 -162
  16. package/dist/cli/commands/start.js.map +1 -1
  17. package/dist/config/schema.d.ts.map +1 -1
  18. package/dist/config/schema.js +65 -0
  19. package/dist/config/schema.js.map +1 -1
  20. package/dist/discord/channelSubsHandler.d.ts +17 -0
  21. package/dist/discord/channelSubsHandler.d.ts.map +1 -0
  22. package/dist/discord/channelSubsHandler.js +288 -0
  23. package/dist/discord/channelSubsHandler.js.map +1 -0
  24. package/dist/discord/curatedPicksHandler.d.ts +17 -0
  25. package/dist/discord/curatedPicksHandler.d.ts.map +1 -0
  26. package/dist/discord/curatedPicksHandler.js +186 -0
  27. package/dist/discord/curatedPicksHandler.js.map +1 -0
  28. package/dist/discord/welcomeHandler.d.ts +16 -0
  29. package/dist/discord/welcomeHandler.d.ts.map +1 -0
  30. package/dist/discord/welcomeHandler.js +122 -0
  31. package/dist/discord/welcomeHandler.js.map +1 -0
  32. package/dist/discovery/WunderlandDiscoveryManager.d.ts +10 -0
  33. package/dist/discovery/WunderlandDiscoveryManager.d.ts.map +1 -1
  34. package/dist/discovery/WunderlandDiscoveryManager.js +38 -2
  35. package/dist/discovery/WunderlandDiscoveryManager.js.map +1 -1
  36. package/dist/discovery/index.d.ts +1 -1
  37. package/dist/discovery/index.d.ts.map +1 -1
  38. package/dist/discovery/index.js.map +1 -1
  39. package/dist/public/index.d.ts +11 -1
  40. package/dist/public/index.d.ts.map +1 -1
  41. package/dist/public/index.js +113 -16
  42. package/dist/public/index.js.map +1 -1
  43. package/dist/public/turn-tool-selection.d.ts +15 -0
  44. package/dist/public/turn-tool-selection.d.ts.map +1 -0
  45. package/dist/public/turn-tool-selection.js +94 -0
  46. package/dist/public/turn-tool-selection.js.map +1 -0
  47. package/dist/runtime/adaptive-execution.d.ts +102 -0
  48. package/dist/runtime/adaptive-execution.d.ts.map +1 -0
  49. package/dist/runtime/adaptive-execution.js +398 -0
  50. package/dist/runtime/adaptive-execution.js.map +1 -0
  51. package/dist/runtime/tool-calling.d.ts +2 -0
  52. package/dist/runtime/tool-calling.d.ts.map +1 -1
  53. package/dist/runtime/tool-calling.js +43 -0
  54. package/dist/runtime/tool-calling.js.map +1 -1
  55. package/package.json +1 -1
  56. package/presets/catalog-data.json +95 -222
package/README.md CHANGED
@@ -66,6 +66,7 @@
66
66
  - **8 agent presets** -- Pre-configured agent archetypes with recommended extensions, skills, and personalities
67
67
  - **Preset-to-extension auto-mapping** -- Presets automatically load recommended tools, voice providers, and skills
68
68
  - **Capability discovery** -- 3-tier semantic search across tools, skills, extensions, and channels (~90% token reduction vs static loading)
69
+ - **Adaptive execution runtime** -- Rolling task-outcome KPI telemetry with SQL persistence (`@framers/sql-storage-adapter`) and automatic degraded-mode recovery (`discovered -> all`, configurable fail-open)
69
70
  - **Schema-on-demand** -- `--lazy-tools` starts with only meta tools, then dynamically loads extension packs as needed
70
71
  - **8 built-in tools** -- SocialPostTool, SerperSearchTool, GiphySearchTool, ImageSearchTool, TextToSpeechTool, NewsSearchTool, RAGTool, MemoryReadTool
71
72
  - **Operational safety** -- 6-step LLM guard chain with circuit breakers, cost guards, stuck detection, action dedup, content similarity checks, and audit logging
@@ -145,7 +146,8 @@ const app = await createWunderland({
145
146
  tools: ['web-search', 'web-browser', 'giphy'],
146
147
  voice: ['voice-synthesis'],
147
148
  },
148
- // discovery is enabled by default indexes tools + skills for semantic search
149
+ // discovery is enabled by default with aggressive recall.
150
+ // per-turn tool schemas are narrowed to discovered capabilities unless degraded.
149
151
  });
150
152
 
151
153
  const session = app.session();
@@ -220,6 +222,27 @@ console.log('Discovery:', diag.discovery); // { initialized: true, capabilityC
220
222
 
221
223
  See `docs/LIBRARY_API.md` for the full API reference (approvals, custom tools, diagnostics, advanced modules).
222
224
 
225
+ ### Discovery recall + dynamic tool exposure
226
+
227
+ ```ts
228
+ const app = await createWunderland({
229
+ llm: { providerId: 'openai' },
230
+ tools: 'curated',
231
+ discovery: {
232
+ recallProfile: 'aggressive', // default: aggressive | balanced | precision
233
+ },
234
+ });
235
+
236
+ const session = app.session();
237
+ await session.sendText('Investigate recent SQL adapter changes', {
238
+ toolSelectionMode: 'discovered', // default when discovery has results
239
+ // toolSelectionMode: 'all', // optional per-turn override
240
+ });
241
+ ```
242
+
243
+ - `toolSelectionMode` automatically falls back to `all` when discovery has no usable tool hits.
244
+ - Adaptive degraded mode can force `all` tool exposure for recovery.
245
+
223
246
  ### CLI
224
247
 
225
248
  ```bash
@@ -4,6 +4,6 @@
4
4
  */
5
5
  export { createWunderlandChatRuntime, type WunderlandChatRuntime } from './chat-runtime.js';
6
6
  export { createWunderlandServer, type WunderlandServerHandle } from './server.js';
7
- export type { WunderlandAgentConfig, WunderlandExecutionMode, WunderlandLLMConfig, WunderlandProviderId, WunderlandWorkspace, } from './types.js';
7
+ export type { WunderlandAdaptiveExecutionConfig, WunderlandAgentConfig, WunderlandExecutionMode, WunderlandLLMConfig, WunderlandProviderId, WunderlandTaskOutcomeTelemetryConfig, WunderlandTaskOutcomeTelemetryScope, WunderlandToolFailureMode, WunderlandWorkspace, } from './types.js';
8
8
  export { RateLimiter, type RateLimiterConfig, type RateLimitResult } from './rate-limiter.js';
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,2BAA2B,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC5F,OAAO,EAAE,sBAAsB,EAAE,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAClF,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,KAAK,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,2BAA2B,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC5F,OAAO,EAAE,sBAAsB,EAAE,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAClF,YAAY,EACV,iCAAiC,EACjC,qBAAqB,EACrB,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,EACpB,oCAAoC,EACpC,mCAAmC,EACnC,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,KAAK,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,2BAA2B,EAA8B,MAAM,mBAAmB,CAAC;AAC5F,OAAO,EAAE,sBAAsB,EAA+B,MAAM,aAAa,CAAC;AASlF,OAAO,EAAE,WAAW,EAAgD,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,2BAA2B,EAA8B,MAAM,mBAAmB,CAAC;AAC5F,OAAO,EAAE,sBAAsB,EAA+B,MAAM,aAAa,CAAC;AAalF,OAAO,EAAE,WAAW,EAAgD,MAAM,mBAAmB,CAAC"}
@@ -14,7 +14,7 @@
14
14
  import { type Server } from 'node:http';
15
15
  import { type LLMProviderConfig } from '../runtime/tool-calling.js';
16
16
  import { type NormalizedRuntimePolicy } from '../runtime/policy.js';
17
- import type { WunderlandAgentConfig, WunderlandProviderId, WunderlandWorkspace } from './types.js';
17
+ import type { WunderlandAdaptiveExecutionConfig, WunderlandAgentConfig, WunderlandProviderId, WunderlandTaskOutcomeTelemetryConfig, WunderlandToolFailureMode, WunderlandWorkspace } from './types.js';
18
18
  type LoggerLike = {
19
19
  debug?: (msg: string, meta?: unknown) => void;
20
20
  info?: (msg: string, meta?: unknown) => void;
@@ -75,6 +75,12 @@ export declare function createWunderlandServer(opts?: {
75
75
  apiKey: string;
76
76
  baseUrl?: string;
77
77
  }>;
78
+ /** Override default tool-call failure behavior for this runtime. */
79
+ toolFailureMode?: WunderlandToolFailureMode;
80
+ /** Runtime task-outcome telemetry controls. */
81
+ taskOutcomeTelemetry?: WunderlandTaskOutcomeTelemetryConfig;
82
+ /** Runtime adaptive execution controls. */
83
+ adaptiveExecution?: WunderlandAdaptiveExecutionConfig;
78
84
  /** Override HITL secret (otherwise config/env/random). */
79
85
  hitlSecret?: string;
80
86
  /** Optional OpenAI-compatible fallback provider config. */
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"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"}
@@ -24,6 +24,7 @@ import { createWunderlandSeed, DEFAULT_INFERENCE_HIERARCHY, DEFAULT_SECURITY_PRO
24
24
  import { loadDotEnvIntoProcessUpward } from '../cli/config/env-manager.js';
25
25
  import { resolveAgentWorkspaceBaseDir, sanitizeAgentWorkspaceId } from '../runtime/workspace.js';
26
26
  import { runToolCallingTurn, safeJsonStringify, } from '../runtime/tool-calling.js';
27
+ import { WunderlandAdaptiveExecutionRuntime } from '../runtime/adaptive-execution.js';
27
28
  import { createSchemaOnDemandTools } from '../cli/openai/schema-on-demand.js';
28
29
  import { startWunderlandOtel, shutdownWunderlandOtel } from '../observability/otel.js';
29
30
  import { filterToolMapByPolicy, getPermissionsForSet, normalizeRuntimePolicy, } from '../runtime/policy.js';
@@ -578,6 +579,28 @@ export async function createWunderlandServer(opts) {
578
579
  ? !!process.env['ANTHROPIC_API_KEY']
579
580
  : !!llmApiKey || !!openrouterFallback;
580
581
  const openaiFallbackEnabled = providerId === 'openai' && !!openrouterFallback;
582
+ const telemetryConfig = {
583
+ ...(cfg.taskOutcomeTelemetry ?? {}),
584
+ ...(opts?.taskOutcomeTelemetry ?? {}),
585
+ storage: {
586
+ ...(cfg.taskOutcomeTelemetry?.storage ?? {}),
587
+ ...(opts?.taskOutcomeTelemetry?.storage ?? {}),
588
+ },
589
+ };
590
+ const adaptiveConfig = {
591
+ ...(cfg.adaptiveExecution ?? {}),
592
+ ...(opts?.adaptiveExecution ?? {}),
593
+ };
594
+ const adaptiveRuntime = new WunderlandAdaptiveExecutionRuntime({
595
+ toolFailureMode: opts?.toolFailureMode ?? cfg.toolFailureMode,
596
+ taskOutcomeTelemetry: telemetryConfig,
597
+ adaptiveExecution: adaptiveConfig,
598
+ logger,
599
+ });
600
+ await adaptiveRuntime.initialize();
601
+ const defaultTenantId = typeof cfg?.organizationId === 'string' && String(cfg.organizationId).trim()
602
+ ? String(cfg.organizationId).trim()
603
+ : undefined;
581
604
  const preloadedPackages = [];
582
605
  let activePacks = [];
583
606
  let allTools = [];
@@ -592,6 +615,7 @@ export async function createWunderlandServer(opts) {
592
615
  const fromEnv = String(process.env['WUNDERLAND_HITL_SECRET'] || '').trim();
593
616
  return fromCfg || fromEnv || randomUUID();
594
617
  })();
618
+ const toolApiSecret = String(process.env['WUNDERLAND_TOOL_API_KEY'] || '').trim();
595
619
  const sseClients = new Set();
596
620
  async function broadcastHitlUpdate(payload) {
597
621
  const data = JSON.stringify(payload);
@@ -1025,13 +1049,31 @@ export async function createWunderlandServer(opts) {
1025
1049
  catch {
1026
1050
  // ignore
1027
1051
  }
1052
+ const tenantId = (typeof message?.organizationId === 'string' && String(message.organizationId).trim())
1053
+ || defaultTenantId;
1054
+ const adaptiveDecision = adaptiveRuntime.resolveTurnDecision({
1055
+ scope: {
1056
+ sessionId: sessionKey,
1057
+ userId: senderId,
1058
+ personaId: seed.seedId,
1059
+ tenantId: tenantId || undefined,
1060
+ },
1061
+ });
1028
1062
  let reply = '';
1063
+ let turnFailed = false;
1064
+ let fallbackTriggered = false;
1065
+ let toolCallCount = 0;
1029
1066
  try {
1030
1067
  if (canUseLLM) {
1031
1068
  const toolContext = {
1032
1069
  gmiId: `wunderland-channel-${sessionKey}`,
1033
1070
  personaId: seed.seedId,
1034
- userContext: { userId: senderId, platform, conversationId },
1071
+ userContext: {
1072
+ userId: senderId,
1073
+ platform,
1074
+ conversationId,
1075
+ ...(tenantId ? { organizationId: tenantId } : null),
1076
+ },
1035
1077
  agentWorkspace: { agentId: workspaceAgentId, baseDir: workspaceBaseDir },
1036
1078
  permissionSet: policy.permissionSet,
1037
1079
  securityTier: policy.securityTier,
@@ -1039,6 +1081,13 @@ export async function createWunderlandServer(opts) {
1039
1081
  toolAccessProfile: policy.toolAccessProfile,
1040
1082
  interactiveSession: false,
1041
1083
  turnApprovalMode,
1084
+ toolFailureMode: adaptiveDecision.toolFailureMode,
1085
+ adaptiveExecution: {
1086
+ degraded: adaptiveDecision.degraded,
1087
+ reason: adaptiveDecision.reason,
1088
+ actions: adaptiveDecision.actions,
1089
+ kpi: adaptiveDecision.kpi ?? undefined,
1090
+ },
1042
1091
  ...(policy.folderPermissions ? { folderPermissions: policy.folderPermissions } : null),
1043
1092
  wrapToolOutputs: policy.wrapToolOutputs,
1044
1093
  };
@@ -1051,6 +1100,10 @@ export async function createWunderlandServer(opts) {
1051
1100
  toolContext,
1052
1101
  maxRounds: 8,
1053
1102
  dangerouslySkipPermissions: autoApproveToolCalls,
1103
+ toolFailureMode: adaptiveDecision.toolFailureMode,
1104
+ onToolCall: () => {
1105
+ toolCallCount += 1;
1106
+ },
1054
1107
  askPermission: async (tool, args) => {
1055
1108
  if (autoApproveToolCalls)
1056
1109
  return true;
@@ -1110,6 +1163,7 @@ export async function createWunderlandServer(opts) {
1110
1163
  baseUrl: llmBaseUrl,
1111
1164
  fallback: providerId === 'openai' ? openrouterFallback : undefined,
1112
1165
  onFallback: (err, provider) => {
1166
+ fallbackTriggered = true;
1113
1167
  logger.warn?.('[wunderland/api] fallback activated', { error: err.message, provider });
1114
1168
  },
1115
1169
  });
@@ -1119,7 +1173,30 @@ export async function createWunderlandServer(opts) {
1119
1173
  messages.push({ role: 'assistant', content: reply });
1120
1174
  }
1121
1175
  }
1176
+ catch (error) {
1177
+ turnFailed = true;
1178
+ throw error;
1179
+ }
1122
1180
  finally {
1181
+ try {
1182
+ await adaptiveRuntime.recordTurnOutcome({
1183
+ scope: {
1184
+ sessionId: sessionKey,
1185
+ userId: senderId,
1186
+ personaId: seed.seedId,
1187
+ tenantId: tenantId || undefined,
1188
+ },
1189
+ degraded: adaptiveDecision.degraded || fallbackTriggered,
1190
+ replyText: reply,
1191
+ didFail: turnFailed,
1192
+ toolCallCount,
1193
+ });
1194
+ }
1195
+ catch (error) {
1196
+ logger.warn?.('[wunderland/api][channels] failed to record adaptive outcome', {
1197
+ error: error instanceof Error ? error.message : String(error),
1198
+ });
1199
+ }
1123
1200
  try {
1124
1201
  const adapter = adapterByPlatform.get(platform);
1125
1202
  if (adapter)
@@ -1163,7 +1240,7 @@ export async function createWunderlandServer(opts) {
1163
1240
  try {
1164
1241
  res.setHeader('Access-Control-Allow-Origin', '*');
1165
1242
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');
1166
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Wunderland-HITL-Secret');
1243
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Wunderland-HITL-Secret, X-Api-Key');
1167
1244
  if (req.method === 'OPTIONS') {
1168
1245
  res.writeHead(204);
1169
1246
  res.end();
@@ -1347,115 +1424,230 @@ export async function createWunderlandServer(opts) {
1347
1424
  sendJson(res, 400, { error: 'Missing "message" in JSON body.' });
1348
1425
  return;
1349
1426
  }
1350
- let reply;
1351
- if (canUseLLM) {
1352
- const sessionId = typeof parsed.sessionId === 'string' && parsed.sessionId.trim() ? parsed.sessionId.trim().slice(0, 128) : 'default';
1353
- if (parsed.reset === true) {
1354
- sessions.delete(sessionId);
1427
+ let reply = '';
1428
+ let turnFailed = false;
1429
+ let fallbackTriggered = false;
1430
+ let toolCallCount = 0;
1431
+ const sessionId = typeof parsed.sessionId === 'string' && parsed.sessionId.trim() ? parsed.sessionId.trim().slice(0, 128) : 'default';
1432
+ const requestedToolFailureMode = typeof parsed.toolFailureMode === 'string' ? parsed.toolFailureMode : undefined;
1433
+ const tenantId = (typeof parsed.tenantId === 'string' && parsed.tenantId.trim())
1434
+ || defaultTenantId;
1435
+ const adaptiveDecision = adaptiveRuntime.resolveTurnDecision({
1436
+ scope: {
1437
+ sessionId,
1438
+ userId: sessionId,
1439
+ personaId: seed.seedId,
1440
+ tenantId: tenantId || undefined,
1441
+ },
1442
+ requestedToolFailureMode,
1443
+ });
1444
+ if (parsed.reset === true) {
1445
+ sessions.delete(sessionId);
1446
+ }
1447
+ let messages = sessions.get(sessionId);
1448
+ if (!messages) {
1449
+ messages = [{ role: 'system', content: systemPrompt }];
1450
+ sessions.set(sessionId, messages);
1451
+ }
1452
+ if (messages.length > 200) {
1453
+ messages = [messages[0], ...messages.slice(-120)];
1454
+ sessions.set(sessionId, messages);
1455
+ }
1456
+ messages.push({ role: 'user', content: message });
1457
+ try {
1458
+ if (canUseLLM) {
1459
+ const toolContext = {
1460
+ gmiId: `wunderland-server-${sessionId}`,
1461
+ personaId: seed.seedId,
1462
+ userContext: {
1463
+ userId: sessionId,
1464
+ ...(tenantId ? { organizationId: tenantId } : null),
1465
+ },
1466
+ agentWorkspace: { agentId: workspaceAgentId, baseDir: workspaceBaseDir },
1467
+ permissionSet: policy.permissionSet,
1468
+ securityTier: policy.securityTier,
1469
+ executionMode: policy.executionMode,
1470
+ toolAccessProfile: policy.toolAccessProfile,
1471
+ interactiveSession: false,
1472
+ turnApprovalMode,
1473
+ toolFailureMode: adaptiveDecision.toolFailureMode,
1474
+ adaptiveExecution: {
1475
+ degraded: adaptiveDecision.degraded,
1476
+ reason: adaptiveDecision.reason,
1477
+ actions: adaptiveDecision.actions,
1478
+ kpi: adaptiveDecision.kpi ?? undefined,
1479
+ },
1480
+ ...(policy.folderPermissions ? { folderPermissions: policy.folderPermissions } : null),
1481
+ wrapToolOutputs: policy.wrapToolOutputs,
1482
+ };
1483
+ reply = await runToolCallingTurn({
1484
+ providerId,
1485
+ apiKey: llmApiKey,
1486
+ model,
1487
+ messages,
1488
+ toolMap,
1489
+ toolContext,
1490
+ maxRounds: 8,
1491
+ dangerouslySkipPermissions: autoApproveToolCalls,
1492
+ toolFailureMode: adaptiveDecision.toolFailureMode,
1493
+ onToolCall: () => {
1494
+ toolCallCount += 1;
1495
+ },
1496
+ askPermission: async (tool, args) => {
1497
+ if (autoApproveToolCalls)
1498
+ return true;
1499
+ const preview = safeJsonStringify(args, 1800);
1500
+ const effectLabel = tool.hasSideEffects === true ? 'side effects' : 'read-only';
1501
+ const actionId = `tool-${seedId}-${randomUUID()}`;
1502
+ const decision = await hitlManager.requestApproval({
1503
+ actionId,
1504
+ description: `Allow ${tool.name} (${effectLabel})?\\n\\n${preview}`,
1505
+ severity: tool.hasSideEffects === true ? 'high' : 'low',
1506
+ category: toAgentosApprovalCategory(tool),
1507
+ agentId: seed.seedId,
1508
+ context: { toolName: tool.name, args, sessionId },
1509
+ reversible: tool.hasSideEffects !== true,
1510
+ requestedAt: new Date(),
1511
+ timeoutMs: 5 * 60_000,
1512
+ });
1513
+ return decision.approved === true;
1514
+ },
1515
+ askCheckpoint: turnApprovalMode === 'off'
1516
+ ? undefined
1517
+ : async ({ round, toolCalls }) => {
1518
+ if (autoApproveToolCalls)
1519
+ return true;
1520
+ const checkpointId = `checkpoint-${seedId}-${sessionId}-${round}-${randomUUID()}`;
1521
+ const completedWork = toolCalls.map((c) => {
1522
+ const effect = c.hasSideEffects ? 'side effects' : 'read-only';
1523
+ const preview = safeJsonStringify(c.args, 800);
1524
+ return `${c.toolName} (${effect})\\n${preview}`;
1525
+ });
1526
+ const timeoutMs = 5 * 60_000;
1527
+ const checkpointPromise = hitlManager
1528
+ .checkpoint({
1529
+ checkpointId,
1530
+ workflowId: `chat-${sessionId}`,
1531
+ currentPhase: `tool-round-${round}`,
1532
+ progress: Math.min(1, (round + 1) / 8),
1533
+ completedWork,
1534
+ upcomingWork: ['Continue to next LLM round'],
1535
+ issues: [],
1536
+ notes: 'Continue?',
1537
+ checkpointAt: new Date(),
1538
+ })
1539
+ .catch(() => ({ decision: 'abort' }));
1540
+ const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve({ decision: 'abort' }), timeoutMs));
1541
+ const decision = (await Promise.race([checkpointPromise, timeoutPromise]));
1542
+ if (decision?.decision !== 'continue') {
1543
+ try {
1544
+ await hitlManager.cancelRequest(checkpointId, 'checkpoint_timeout_or_abort');
1545
+ }
1546
+ catch {
1547
+ // ignore
1548
+ }
1549
+ }
1550
+ return decision?.decision === 'continue';
1551
+ },
1552
+ baseUrl: llmBaseUrl,
1553
+ fallback: providerId === 'openai' ? openrouterFallback : undefined,
1554
+ onFallback: (err, provider) => {
1555
+ fallbackTriggered = true;
1556
+ logger.warn?.('[wunderland/api] fallback activated', { error: err.message, provider });
1557
+ },
1558
+ });
1559
+ }
1560
+ else {
1561
+ reply =
1562
+ 'No LLM credentials configured. I can run, but I cannot generate real replies yet.\\n\\n' +
1563
+ 'Set an API key in .env (OPENAI_API_KEY / OPENROUTER_API_KEY / ANTHROPIC_API_KEY) or use Ollama, then retry.\\n\\n' +
1564
+ `You said: ${message}`;
1355
1565
  }
1356
- let messages = sessions.get(sessionId);
1357
- if (!messages) {
1358
- messages = [{ role: 'system', content: systemPrompt }];
1359
- sessions.set(sessionId, messages);
1566
+ }
1567
+ catch (error) {
1568
+ turnFailed = true;
1569
+ throw error;
1570
+ }
1571
+ finally {
1572
+ try {
1573
+ await adaptiveRuntime.recordTurnOutcome({
1574
+ scope: {
1575
+ sessionId,
1576
+ userId: sessionId,
1577
+ personaId: seed.seedId,
1578
+ tenantId: tenantId || undefined,
1579
+ },
1580
+ degraded: adaptiveDecision.degraded || fallbackTriggered,
1581
+ replyText: reply,
1582
+ didFail: turnFailed,
1583
+ toolCallCount,
1584
+ });
1360
1585
  }
1361
- if (messages.length > 200) {
1362
- messages = [messages[0], ...messages.slice(-120)];
1363
- sessions.set(sessionId, messages);
1586
+ catch (error) {
1587
+ logger.warn?.('[wunderland/api][chat] failed to record adaptive outcome', {
1588
+ error: error instanceof Error ? error.message : String(error),
1589
+ });
1364
1590
  }
1365
- messages.push({ role: 'user', content: message });
1366
- const toolContext = {
1367
- gmiId: `wunderland-server-${sessionId}`,
1368
- personaId: seed.seedId,
1369
- userContext: { userId: sessionId },
1370
- agentWorkspace: { agentId: workspaceAgentId, baseDir: workspaceBaseDir },
1591
+ }
1592
+ sendJson(res, 200, { reply });
1593
+ return;
1594
+ }
1595
+ // ── Tool Execution API ──────────────────────────────────────
1596
+ if (url.pathname === '/api/tools' && req.method === 'GET') {
1597
+ if (toolApiSecret && req.headers['x-api-key'] !== toolApiSecret) {
1598
+ sendJson(res, 401, { error: 'Unauthorized' });
1599
+ return;
1600
+ }
1601
+ const tools = Array.from(toolMap.values()).map((t) => ({
1602
+ name: t.name,
1603
+ description: t.description,
1604
+ inputSchema: t.inputSchema,
1605
+ category: t.category,
1606
+ hasSideEffects: t.hasSideEffects,
1607
+ }));
1608
+ sendJson(res, 200, { tools });
1609
+ return;
1610
+ }
1611
+ if (url.pathname.startsWith('/api/tools/') && req.method === 'POST') {
1612
+ if (toolApiSecret && req.headers['x-api-key'] !== toolApiSecret) {
1613
+ sendJson(res, 401, { error: 'Unauthorized' });
1614
+ return;
1615
+ }
1616
+ const toolName = decodeURIComponent(url.pathname.slice('/api/tools/'.length));
1617
+ const tool = toolMap.get(toolName);
1618
+ if (!tool) {
1619
+ sendJson(res, 404, { error: `Tool '${toolName}' not found` });
1620
+ return;
1621
+ }
1622
+ let args = {};
1623
+ try {
1624
+ const body = await readBody(req);
1625
+ if (body)
1626
+ args = JSON.parse(body);
1627
+ }
1628
+ catch {
1629
+ sendJson(res, 400, { error: 'Invalid JSON body' });
1630
+ return;
1631
+ }
1632
+ try {
1633
+ const ctx = {
1634
+ gmiId: 'tool-api',
1635
+ personaId: seedId,
1371
1636
  permissionSet: policy.permissionSet,
1372
1637
  securityTier: policy.securityTier,
1373
- executionMode: policy.executionMode,
1638
+ executionMode: 'autonomous',
1374
1639
  toolAccessProfile: policy.toolAccessProfile,
1375
1640
  interactiveSession: false,
1376
- turnApprovalMode,
1377
- ...(policy.folderPermissions ? { folderPermissions: policy.folderPermissions } : null),
1378
- wrapToolOutputs: policy.wrapToolOutputs,
1379
1641
  };
1380
- reply = await runToolCallingTurn({
1381
- providerId,
1382
- apiKey: llmApiKey,
1383
- model,
1384
- messages,
1385
- toolMap,
1386
- toolContext,
1387
- maxRounds: 8,
1388
- dangerouslySkipPermissions: autoApproveToolCalls,
1389
- askPermission: async (tool, args) => {
1390
- if (autoApproveToolCalls)
1391
- return true;
1392
- const preview = safeJsonStringify(args, 1800);
1393
- const effectLabel = tool.hasSideEffects === true ? 'side effects' : 'read-only';
1394
- const actionId = `tool-${seedId}-${randomUUID()}`;
1395
- const decision = await hitlManager.requestApproval({
1396
- actionId,
1397
- description: `Allow ${tool.name} (${effectLabel})?\\n\\n${preview}`,
1398
- severity: tool.hasSideEffects === true ? 'high' : 'low',
1399
- category: toAgentosApprovalCategory(tool),
1400
- agentId: seed.seedId,
1401
- context: { toolName: tool.name, args, sessionId },
1402
- reversible: tool.hasSideEffects !== true,
1403
- requestedAt: new Date(),
1404
- timeoutMs: 5 * 60_000,
1405
- });
1406
- return decision.approved === true;
1407
- },
1408
- askCheckpoint: turnApprovalMode === 'off'
1409
- ? undefined
1410
- : async ({ round, toolCalls }) => {
1411
- if (autoApproveToolCalls)
1412
- return true;
1413
- const checkpointId = `checkpoint-${seedId}-${sessionId}-${round}-${randomUUID()}`;
1414
- const completedWork = toolCalls.map((c) => {
1415
- const effect = c.hasSideEffects ? 'side effects' : 'read-only';
1416
- const preview = safeJsonStringify(c.args, 800);
1417
- return `${c.toolName} (${effect})\\n${preview}`;
1418
- });
1419
- const timeoutMs = 5 * 60_000;
1420
- const checkpointPromise = hitlManager
1421
- .checkpoint({
1422
- checkpointId,
1423
- workflowId: `chat-${sessionId}`,
1424
- currentPhase: `tool-round-${round}`,
1425
- progress: Math.min(1, (round + 1) / 8),
1426
- completedWork,
1427
- upcomingWork: ['Continue to next LLM round'],
1428
- issues: [],
1429
- notes: 'Continue?',
1430
- checkpointAt: new Date(),
1431
- })
1432
- .catch(() => ({ decision: 'abort' }));
1433
- const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve({ decision: 'abort' }), timeoutMs));
1434
- const decision = (await Promise.race([checkpointPromise, timeoutPromise]));
1435
- if (decision?.decision !== 'continue') {
1436
- try {
1437
- await hitlManager.cancelRequest(checkpointId, 'checkpoint_timeout_or_abort');
1438
- }
1439
- catch {
1440
- // ignore
1441
- }
1442
- }
1443
- return decision?.decision === 'continue';
1444
- },
1445
- baseUrl: llmBaseUrl,
1446
- fallback: providerId === 'openai' ? openrouterFallback : undefined,
1447
- onFallback: (err, provider) => {
1448
- logger.warn?.('[wunderland/api] fallback activated', { error: err.message, provider });
1449
- },
1450
- });
1642
+ const result = await tool.execute(args, ctx);
1643
+ sendJson(res, result.success ? 200 : 500, result);
1451
1644
  }
1452
- else {
1453
- reply =
1454
- 'No LLM credentials configured. I can run, but I cannot generate real replies yet.\\n\\n' +
1455
- 'Set an API key in .env (OPENAI_API_KEY / OPENROUTER_API_KEY / ANTHROPIC_API_KEY) or use Ollama, then retry.\\n\\n' +
1456
- `You said: ${message}`;
1645
+ catch (err) {
1646
+ sendJson(res, 500, {
1647
+ success: false,
1648
+ error: err instanceof Error ? err.message : 'Tool execution failed',
1649
+ });
1457
1650
  }
1458
- sendJson(res, 200, { reply });
1459
1651
  return;
1460
1652
  }
1461
1653
  for (const handler of loadedHttpHandlers) {
@@ -1496,6 +1688,7 @@ export async function createWunderlandServer(opts) {
1496
1688
  await new Promise((resolve, reject) => {
1497
1689
  server.close((err) => (err ? reject(err) : resolve()));
1498
1690
  });
1691
+ await adaptiveRuntime.close();
1499
1692
  await shutdownWunderlandOtel();
1500
1693
  };
1501
1694
  logger.info?.('[wunderland/api] server started', {