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.
- package/README.md +24 -1
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/server.d.ts +7 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +294 -101
- package/dist/api/server.js.map +1 -1
- package/dist/api/types.d.ts +102 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/cli/commands/chat.d.ts.map +1 -1
- package/dist/cli/commands/chat.js +82 -19
- package/dist/cli/commands/chat.js.map +1 -1
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +379 -162
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +65 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/discord/channelSubsHandler.d.ts +17 -0
- package/dist/discord/channelSubsHandler.d.ts.map +1 -0
- package/dist/discord/channelSubsHandler.js +288 -0
- package/dist/discord/channelSubsHandler.js.map +1 -0
- package/dist/discord/curatedPicksHandler.d.ts +17 -0
- package/dist/discord/curatedPicksHandler.d.ts.map +1 -0
- package/dist/discord/curatedPicksHandler.js +186 -0
- package/dist/discord/curatedPicksHandler.js.map +1 -0
- package/dist/discord/welcomeHandler.d.ts +16 -0
- package/dist/discord/welcomeHandler.d.ts.map +1 -0
- package/dist/discord/welcomeHandler.js +122 -0
- package/dist/discord/welcomeHandler.js.map +1 -0
- package/dist/discovery/WunderlandDiscoveryManager.d.ts +10 -0
- package/dist/discovery/WunderlandDiscoveryManager.d.ts.map +1 -1
- package/dist/discovery/WunderlandDiscoveryManager.js +38 -2
- package/dist/discovery/WunderlandDiscoveryManager.js.map +1 -1
- package/dist/discovery/index.d.ts +1 -1
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/index.js.map +1 -1
- package/dist/public/index.d.ts +11 -1
- package/dist/public/index.d.ts.map +1 -1
- package/dist/public/index.js +113 -16
- package/dist/public/index.js.map +1 -1
- package/dist/public/turn-tool-selection.d.ts +15 -0
- package/dist/public/turn-tool-selection.d.ts.map +1 -0
- package/dist/public/turn-tool-selection.js +94 -0
- package/dist/public/turn-tool-selection.js.map +1 -0
- package/dist/runtime/adaptive-execution.d.ts +102 -0
- package/dist/runtime/adaptive-execution.d.ts.map +1 -0
- package/dist/runtime/adaptive-execution.js +398 -0
- package/dist/runtime/adaptive-execution.js.map +1 -0
- package/dist/runtime/tool-calling.d.ts +2 -0
- package/dist/runtime/tool-calling.d.ts.map +1 -1
- package/dist/runtime/tool-calling.js +43 -0
- package/dist/runtime/tool-calling.js.map +1 -1
- package/package.json +1 -1
- 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
|
|
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
|
package/dist/api/index.d.ts
CHANGED
|
@@ -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
|
package/dist/api/index.d.ts.map
CHANGED
|
@@ -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"}
|
package/dist/api/index.js.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/api/server.d.ts
CHANGED
|
@@ -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. */
|
package/dist/api/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAgBtD,OAAO,EAGL,KAAK,iBAAiB,EAEvB,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"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"}
|
package/dist/api/server.js
CHANGED
|
@@ -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: {
|
|
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
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
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
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
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
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
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
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
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:
|
|
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
|
-
|
|
1381
|
-
|
|
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
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
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', {
|