@dotsetlabs/dotclaw 2.4.0 → 2.6.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/.env.example +9 -10
- package/README.md +8 -4
- package/config-examples/runtime.json +34 -8
- package/config-examples/tool-policy.json +12 -2
- package/container/agent-runner/package-lock.json +2 -2
- package/container/agent-runner/package.json +1 -1
- package/container/agent-runner/src/agent-config.ts +19 -3
- package/container/agent-runner/src/container-protocol.ts +11 -0
- package/container/agent-runner/src/context-overflow-recovery.ts +39 -0
- package/container/agent-runner/src/index.ts +603 -165
- package/container/agent-runner/src/openrouter-input.ts +159 -0
- package/container/agent-runner/src/system-prompt.ts +13 -3
- package/container/agent-runner/src/tool-loop-policy.ts +741 -0
- package/container/agent-runner/src/tools.ts +211 -8
- package/dist/agent-context.d.ts +1 -0
- package/dist/agent-context.d.ts.map +1 -1
- package/dist/agent-context.js +21 -9
- package/dist/agent-context.js.map +1 -1
- package/dist/agent-execution.d.ts +2 -0
- package/dist/agent-execution.d.ts.map +1 -1
- package/dist/agent-execution.js +164 -15
- package/dist/agent-execution.js.map +1 -1
- package/dist/agent-semaphore.d.ts +24 -1
- package/dist/agent-semaphore.d.ts.map +1 -1
- package/dist/agent-semaphore.js +109 -20
- package/dist/agent-semaphore.js.map +1 -1
- package/dist/cli.js +3 -11
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/container-protocol.d.ts +22 -0
- package/dist/container-protocol.d.ts.map +1 -1
- package/dist/container-protocol.js.map +1 -1
- package/dist/container-runner.d.ts +7 -0
- package/dist/container-runner.d.ts.map +1 -1
- package/dist/container-runner.js +417 -143
- package/dist/container-runner.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +46 -12
- package/dist/db.js.map +1 -1
- package/dist/error-messages.d.ts.map +1 -1
- package/dist/error-messages.js +18 -4
- package/dist/error-messages.js.map +1 -1
- package/dist/failover-policy.d.ts +41 -0
- package/dist/failover-policy.d.ts.map +1 -0
- package/dist/failover-policy.js +261 -0
- package/dist/failover-policy.js.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/ipc-dispatcher.d.ts.map +1 -1
- package/dist/ipc-dispatcher.js +27 -43
- package/dist/ipc-dispatcher.js.map +1 -1
- package/dist/mcp-config.d.ts +22 -0
- package/dist/mcp-config.d.ts.map +1 -0
- package/dist/mcp-config.js +94 -0
- package/dist/mcp-config.js.map +1 -0
- package/dist/memory-backend.d.ts +27 -0
- package/dist/memory-backend.d.ts.map +1 -0
- package/dist/memory-backend.js +112 -0
- package/dist/memory-backend.js.map +1 -0
- package/dist/memory-recall.d.ts.map +1 -1
- package/dist/memory-recall.js +135 -22
- package/dist/memory-recall.js.map +1 -1
- package/dist/memory-store.d.ts +1 -0
- package/dist/memory-store.d.ts.map +1 -1
- package/dist/memory-store.js +55 -7
- package/dist/memory-store.js.map +1 -1
- package/dist/message-pipeline.d.ts +24 -0
- package/dist/message-pipeline.d.ts.map +1 -1
- package/dist/message-pipeline.js +131 -27
- package/dist/message-pipeline.js.map +1 -1
- package/dist/metrics.d.ts +1 -0
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +9 -0
- package/dist/metrics.js.map +1 -1
- package/dist/providers/discord/discord-provider.d.ts.map +1 -1
- package/dist/providers/discord/discord-provider.js +72 -4
- package/dist/providers/discord/discord-provider.js.map +1 -1
- package/dist/providers/telegram/telegram-provider.d.ts.map +1 -1
- package/dist/providers/telegram/telegram-provider.js +65 -3
- package/dist/providers/telegram/telegram-provider.js.map +1 -1
- package/dist/recall-policy.d.ts +12 -0
- package/dist/recall-policy.d.ts.map +1 -0
- package/dist/recall-policy.js +89 -0
- package/dist/recall-policy.js.map +1 -0
- package/dist/runtime-config.d.ts +33 -0
- package/dist/runtime-config.d.ts.map +1 -1
- package/dist/runtime-config.js +109 -9
- package/dist/runtime-config.js.map +1 -1
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +125 -33
- package/dist/streaming.js.map +1 -1
- package/dist/task-scheduler.d.ts.map +1 -1
- package/dist/task-scheduler.js +4 -2
- package/dist/task-scheduler.js.map +1 -1
- package/dist/tool-policy.d.ts.map +1 -1
- package/dist/tool-policy.js +26 -4
- package/dist/tool-policy.js.map +1 -1
- package/dist/trace-writer.d.ts +12 -0
- package/dist/trace-writer.d.ts.map +1 -1
- package/dist/trace-writer.js.map +1 -1
- package/dist/turn-hygiene.d.ts +14 -0
- package/dist/turn-hygiene.d.ts.map +1 -0
- package/dist/turn-hygiene.js +214 -0
- package/dist/turn-hygiene.js.map +1 -0
- package/dist/webhook.d.ts.map +1 -1
- package/dist/webhook.js +1 -0
- package/dist/webhook.js.map +1 -1
- package/package.json +15 -1
- package/scripts/benchmark-baseline.js +365 -0
- package/scripts/benchmark-harness.js +1413 -0
- package/scripts/benchmark-scenarios.js +301 -0
- package/scripts/canary-suite.js +123 -0
- package/scripts/generate-controlled-traces.js +230 -0
- package/scripts/release-slo-check.js +214 -0
- package/scripts/run-live-canary.js +339 -0
|
@@ -180,6 +180,22 @@ export type ToolPolicy = {
|
|
|
180
180
|
default_max_per_run?: number;
|
|
181
181
|
};
|
|
182
182
|
|
|
183
|
+
function applyPolicyToolFilter(tools: Tool[], policy?: ToolPolicy): Tool[] {
|
|
184
|
+
if (!policy) return tools;
|
|
185
|
+
const allowSet = Array.isArray(policy.allow)
|
|
186
|
+
? new Set(policy.allow.map(name => String(name || '').trim().toLowerCase()).filter(Boolean))
|
|
187
|
+
: null;
|
|
188
|
+
const denySet = new Set((policy.deny || []).map(name => String(name || '').trim().toLowerCase()).filter(Boolean));
|
|
189
|
+
return tools.filter((entry) => {
|
|
190
|
+
const name = entry?.function?.name;
|
|
191
|
+
if (!name) return false;
|
|
192
|
+
const normalized = name.toLowerCase();
|
|
193
|
+
if (denySet.has(normalized)) return false;
|
|
194
|
+
if (allowSet && !allowSet.has(normalized)) return false;
|
|
195
|
+
return true;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
183
199
|
const TOOL_OUTPUT_PREVIEW_BYTES = 6000;
|
|
184
200
|
|
|
185
201
|
function getAllowedRoots(isMain: boolean): string[] {
|
|
@@ -449,7 +465,188 @@ async function assertUrlAllowed(params: {
|
|
|
449
465
|
}
|
|
450
466
|
}
|
|
451
467
|
|
|
468
|
+
function parseLooseJson(value: string): unknown {
|
|
469
|
+
let parsed: unknown = value;
|
|
470
|
+
for (let i = 0; i < 2 && typeof parsed === 'string'; i += 1) {
|
|
471
|
+
parsed = JSON.parse(parsed);
|
|
472
|
+
}
|
|
473
|
+
return parsed;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function toPositiveInt(value: unknown): number | undefined {
|
|
477
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
478
|
+
const rounded = Math.floor(value);
|
|
479
|
+
return rounded > 0 ? rounded : undefined;
|
|
480
|
+
}
|
|
481
|
+
if (typeof value === 'string') {
|
|
482
|
+
const parsed = Number(value.trim());
|
|
483
|
+
if (!Number.isFinite(parsed)) return undefined;
|
|
484
|
+
const rounded = Math.floor(parsed);
|
|
485
|
+
return rounded > 0 ? rounded : undefined;
|
|
486
|
+
}
|
|
487
|
+
return undefined;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function toNonNegativeInt(value: unknown): number | undefined {
|
|
491
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
492
|
+
const rounded = Math.floor(value);
|
|
493
|
+
return rounded >= 0 ? rounded : undefined;
|
|
494
|
+
}
|
|
495
|
+
if (typeof value === 'string') {
|
|
496
|
+
const parsed = Number(value.trim());
|
|
497
|
+
if (!Number.isFinite(parsed)) return undefined;
|
|
498
|
+
const rounded = Math.floor(parsed);
|
|
499
|
+
return rounded >= 0 ? rounded : undefined;
|
|
500
|
+
}
|
|
501
|
+
return undefined;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function setAlias(record: Record<string, unknown>, canonical: string, aliases: string[]): void {
|
|
505
|
+
const existing = record[canonical];
|
|
506
|
+
if (typeof existing === 'string' && existing.trim()) return;
|
|
507
|
+
for (const alias of aliases) {
|
|
508
|
+
const value = record[alias];
|
|
509
|
+
if (typeof value === 'string' && value.trim()) {
|
|
510
|
+
record[canonical] = value;
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function sanitizeNumericField(
|
|
517
|
+
record: Record<string, unknown>,
|
|
518
|
+
field: string,
|
|
519
|
+
mode: 'positive' | 'nonnegative'
|
|
520
|
+
): void {
|
|
521
|
+
if (!(field in record)) return;
|
|
522
|
+
const coerced = mode === 'positive'
|
|
523
|
+
? toPositiveInt(record[field])
|
|
524
|
+
: toNonNegativeInt(record[field]);
|
|
525
|
+
if (coerced === undefined) {
|
|
526
|
+
delete record[field];
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
record[field] = coerced;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function normalizeToolArgs(name: string, args: unknown): unknown {
|
|
533
|
+
const normalizedName = name.trim().toLowerCase();
|
|
534
|
+
let value = args;
|
|
535
|
+
|
|
536
|
+
if (typeof value === 'string') {
|
|
537
|
+
const trimmed = value.trim();
|
|
538
|
+
if (!trimmed) return value;
|
|
539
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('"')) {
|
|
540
|
+
try {
|
|
541
|
+
value = parseLooseJson(trimmed);
|
|
542
|
+
} catch {
|
|
543
|
+
// Keep the original string if JSON is malformed/truncated.
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (typeof value === 'string') {
|
|
547
|
+
switch (normalizedName) {
|
|
548
|
+
case 'bash':
|
|
549
|
+
case 'process':
|
|
550
|
+
return { command: value };
|
|
551
|
+
case 'python':
|
|
552
|
+
return { code: value };
|
|
553
|
+
case 'webfetch':
|
|
554
|
+
return { url: value.trim() };
|
|
555
|
+
case 'websearch':
|
|
556
|
+
return { query: value };
|
|
557
|
+
case 'read':
|
|
558
|
+
return { path: value };
|
|
559
|
+
case 'glob':
|
|
560
|
+
return { pattern: value };
|
|
561
|
+
case 'grep':
|
|
562
|
+
return { pattern: value };
|
|
563
|
+
default:
|
|
564
|
+
return value;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
570
|
+
return value;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const record = { ...(value as Record<string, unknown>) };
|
|
574
|
+
const pathAliases = ['file', 'filePath', 'filepath', 'file_path', 'targetPath', 'target_path'];
|
|
575
|
+
|
|
576
|
+
switch (normalizedName) {
|
|
577
|
+
case 'read':
|
|
578
|
+
case 'write':
|
|
579
|
+
case 'edit':
|
|
580
|
+
case 'analyzeimage':
|
|
581
|
+
setAlias(record, 'path', pathAliases);
|
|
582
|
+
break;
|
|
583
|
+
case 'bash':
|
|
584
|
+
case 'process':
|
|
585
|
+
setAlias(record, 'command', ['cmd', 'shell', 'script', 'input']);
|
|
586
|
+
break;
|
|
587
|
+
case 'python':
|
|
588
|
+
setAlias(record, 'code', ['script', 'input', 'content', 'text']);
|
|
589
|
+
break;
|
|
590
|
+
case 'glob':
|
|
591
|
+
setAlias(record, 'pattern', ['glob', 'path']);
|
|
592
|
+
break;
|
|
593
|
+
case 'grep':
|
|
594
|
+
setAlias(record, 'pattern', ['query', 'search', 'text']);
|
|
595
|
+
setAlias(record, 'path', pathAliases);
|
|
596
|
+
break;
|
|
597
|
+
case 'websearch':
|
|
598
|
+
setAlias(record, 'query', ['q', 'search', 'text', 'input']);
|
|
599
|
+
break;
|
|
600
|
+
case 'webfetch':
|
|
601
|
+
setAlias(record, 'url', ['uri', 'href', 'link']);
|
|
602
|
+
break;
|
|
603
|
+
case 'sendfile':
|
|
604
|
+
case 'sendphoto':
|
|
605
|
+
case 'sendvoice':
|
|
606
|
+
case 'sendaudio':
|
|
607
|
+
setAlias(record, 'path', pathAliases);
|
|
608
|
+
break;
|
|
609
|
+
default:
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (normalizedName === 'write') {
|
|
614
|
+
setAlias(record, 'content', ['text', 'body', 'data', 'value']);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (normalizedName === 'python' && typeof record.code !== 'string' && record.code !== undefined) {
|
|
618
|
+
record.code = String(record.code);
|
|
619
|
+
}
|
|
620
|
+
if ((normalizedName === 'bash' || normalizedName === 'process') && typeof record.command !== 'string' && record.command !== undefined) {
|
|
621
|
+
record.command = String(record.command);
|
|
622
|
+
}
|
|
623
|
+
if (normalizedName === 'write' && typeof record.content !== 'string' && record.content !== undefined) {
|
|
624
|
+
record.content = String(record.content);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const positiveFields = [
|
|
628
|
+
'maxBytes', 'maxResults', 'timeoutMs', 'count', 'depth', 'duration', 'reply_to_message_id', 'message_id', 'limit'
|
|
629
|
+
];
|
|
630
|
+
for (const field of positiveFields) {
|
|
631
|
+
sanitizeNumericField(record, field, 'positive');
|
|
632
|
+
}
|
|
633
|
+
sanitizeNumericField(record, 'offset', 'nonnegative');
|
|
634
|
+
|
|
635
|
+
return record;
|
|
636
|
+
}
|
|
637
|
+
|
|
452
638
|
function sanitizeToolArgs(args: unknown): unknown {
|
|
639
|
+
if (typeof args === 'string') {
|
|
640
|
+
const trimmed = args.trim();
|
|
641
|
+
if ((trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('"')) && trimmed.length <= 64_000) {
|
|
642
|
+
try {
|
|
643
|
+
return sanitizeToolArgs(parseLooseJson(trimmed));
|
|
644
|
+
} catch {
|
|
645
|
+
// Fall through to length-limited string handling.
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return args.length > 512 ? `${args.slice(0, 512)}…` : args;
|
|
649
|
+
}
|
|
453
650
|
if (!args || typeof args !== 'object') return args;
|
|
454
651
|
const record = { ...(args as Record<string, unknown>) };
|
|
455
652
|
|
|
@@ -713,13 +910,14 @@ async function runCommand(command: string, timeoutMs: number, outputLimit: numbe
|
|
|
713
910
|
}
|
|
714
911
|
|
|
715
912
|
async function readFileSafe(filePath: string, maxBytes: number) {
|
|
913
|
+
const safeMaxBytes = Math.max(1, Math.floor(Number.isFinite(maxBytes) ? maxBytes : 1));
|
|
716
914
|
const stat = fs.statSync(filePath);
|
|
717
|
-
if (stat.size <=
|
|
915
|
+
if (stat.size <= safeMaxBytes) {
|
|
718
916
|
return { content: fs.readFileSync(filePath, 'utf-8'), truncated: false, size: stat.size };
|
|
719
917
|
}
|
|
720
918
|
const fd = fs.openSync(filePath, 'r');
|
|
721
|
-
const buffer = Buffer.allocUnsafe(
|
|
722
|
-
const bytesRead = fs.readSync(fd, buffer, 0,
|
|
919
|
+
const buffer = Buffer.allocUnsafe(safeMaxBytes);
|
|
920
|
+
const bytesRead = fs.readSync(fd, buffer, 0, safeMaxBytes, 0);
|
|
723
921
|
fs.closeSync(fd);
|
|
724
922
|
return { content: buffer.subarray(0, bytesRead).toString('utf-8'), truncated: true, size: stat.size };
|
|
725
923
|
}
|
|
@@ -972,6 +1170,7 @@ export function createTools(
|
|
|
972
1170
|
return async (args: TInput): Promise<TOutput> => {
|
|
973
1171
|
const start = Date.now();
|
|
974
1172
|
const normalizedName = name.toLowerCase();
|
|
1173
|
+
const normalizedArgs = normalizeToolArgs(name, args) as TInput;
|
|
975
1174
|
try {
|
|
976
1175
|
if (denyList.includes(normalizedName)) {
|
|
977
1176
|
throw new Error(`Tool is disabled by policy: ${name}`);
|
|
@@ -986,7 +1185,7 @@ export function createTools(
|
|
|
986
1185
|
}
|
|
987
1186
|
usageCounts.set(name, currentCount + 1);
|
|
988
1187
|
|
|
989
|
-
const rawResult = await execute(
|
|
1188
|
+
const rawResult = await execute(normalizedArgs);
|
|
990
1189
|
const result = await maybeSummarizeToolResult(name, rawResult, runtime);
|
|
991
1190
|
if (onToolResult) {
|
|
992
1191
|
const preview = extractToolOutputText(result);
|
|
@@ -1015,7 +1214,7 @@ export function createTools(
|
|
|
1015
1214
|
}
|
|
1016
1215
|
onToolCall?.({
|
|
1017
1216
|
name,
|
|
1018
|
-
args: sanitizeToolArgs(
|
|
1217
|
+
args: sanitizeToolArgs(normalizedArgs),
|
|
1019
1218
|
ok: true,
|
|
1020
1219
|
duration_ms: Date.now() - start,
|
|
1021
1220
|
output_bytes: outputBytes,
|
|
@@ -1025,7 +1224,7 @@ export function createTools(
|
|
|
1025
1224
|
} catch (err) {
|
|
1026
1225
|
onToolCall?.({
|
|
1027
1226
|
name,
|
|
1028
|
-
args: sanitizeToolArgs(
|
|
1227
|
+
args: sanitizeToolArgs(normalizedArgs),
|
|
1029
1228
|
ok: false,
|
|
1030
1229
|
duration_ms: Date.now() - start,
|
|
1031
1230
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -1173,7 +1372,11 @@ export function createTools(
|
|
|
1173
1372
|
}),
|
|
1174
1373
|
execute: wrapExecute('Read', async ({ path: inputPath, maxBytes }: { path: string; maxBytes?: number }) => {
|
|
1175
1374
|
const resolved = resolvePath(inputPath, isMain, true);
|
|
1176
|
-
const
|
|
1375
|
+
const requestedMaxBytes = Number.isFinite(maxBytes)
|
|
1376
|
+
? Math.floor(maxBytes as number)
|
|
1377
|
+
: runtime.outputLimitBytes;
|
|
1378
|
+
const clampedMaxBytes = Math.max(1, Math.min(requestedMaxBytes, runtime.outputLimitBytes));
|
|
1379
|
+
const { content, truncated, size } = await readFileSafe(resolved, clampedMaxBytes);
|
|
1177
1380
|
return { path: resolved, content, truncated, size };
|
|
1178
1381
|
})
|
|
1179
1382
|
});
|
|
@@ -2603,7 +2806,7 @@ export function createTools(
|
|
|
2603
2806
|
}
|
|
2604
2807
|
tools.push(analyzeImageTool as Tool);
|
|
2605
2808
|
|
|
2606
|
-
return tools;
|
|
2809
|
+
return applyPolicyToolFilter(tools, policy);
|
|
2607
2810
|
}
|
|
2608
2811
|
|
|
2609
2812
|
/**
|
package/dist/agent-context.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ToolPolicy } from './tool-policy.js';
|
|
|
2
2
|
import { loadModelRegistry, getTokenEstimateConfig, getModelPricing, ModelCapabilities } from './model-registry.js';
|
|
3
3
|
export type AgentContext = {
|
|
4
4
|
memoryRecall: string[];
|
|
5
|
+
memoryRecallAttempted: boolean;
|
|
5
6
|
userProfile: string | null;
|
|
6
7
|
memoryStats: {
|
|
7
8
|
total: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-context.d.ts","sourceRoot":"","sources":["../src/agent-context.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-context.d.ts","sourceRoot":"","sources":["../src/agent-context.ts"],"names":[],"mappings":"AAEA,OAAO,EAA+C,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG3F,OAAO,EAAgB,iBAAiB,EAAE,sBAAsB,EAAE,eAAe,EAAwB,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAIxJ,MAAM,MAAM,YAAY,GAAG;IACzB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5E,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,UAAU,EAAE,UAAU,CAAC;IACvB,eAAe,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAC9G,aAAa,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE;YAAE,cAAc,CAAC,EAAE,MAAM,CAAC;YAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IAC3H,aAAa,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;IACpD,YAAY,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;IACjD,aAAa,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;IACzD,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE;QACP,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;CACH,CAAC;AA4BF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,UAAU,CAiB3F;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,YAAY,CAAC,CA4GxB"}
|
package/dist/agent-context.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildUserProfile, getMemoryStats } from './memory-store.js';
|
|
1
|
+
import { resolveMemoryBackend } from './memory-backend.js';
|
|
3
2
|
import { loadPersonalizedBehaviorConfig } from './personalization.js';
|
|
4
3
|
import { getEffectiveToolPolicy, mergeToolPolicyDeny } from './tool-policy.js';
|
|
5
4
|
import { applyToolBudgets } from './tool-budgets.js';
|
|
6
5
|
import { getToolReliability } from './db.js';
|
|
7
6
|
import { resolveModel, loadModelRegistry, getTokenEstimateConfig, getModelPricing, getModelCapabilities } from './model-registry.js';
|
|
8
7
|
import { loadRuntimeConfig } from './runtime-config.js';
|
|
8
|
+
import { optimizeRecallQuery, resolveRecallBudget, shouldRunMemoryRecall } from './recall-policy.js';
|
|
9
9
|
/**
|
|
10
10
|
* Calculate dynamic memory token budget based on model capabilities
|
|
11
11
|
* Reserves 15% of available context for memories (min 800, max 4000)
|
|
@@ -38,6 +38,7 @@ export function applyToolAllowOverride(policy, toolAllow) {
|
|
|
38
38
|
export async function buildAgentContext(params) {
|
|
39
39
|
const startedAt = Date.now();
|
|
40
40
|
const runtime = loadRuntimeConfig();
|
|
41
|
+
const memoryBackend = await resolveMemoryBackend();
|
|
41
42
|
// Use routing.model as the base — model.json per_user/per_group overrides take priority
|
|
42
43
|
const defaultModel = runtime.host.routing.model || runtime.host.defaultModel;
|
|
43
44
|
const modelRegistry = loadModelRegistry(defaultModel);
|
|
@@ -55,23 +56,33 @@ export async function buildAgentContext(params) {
|
|
|
55
56
|
const dynamicMemoryBudget = calculateMemoryBudget(modelCapabilities, runtime.agent.context.maxOutputTokens, params.recallMaxTokens);
|
|
56
57
|
let memoryRecall = [];
|
|
57
58
|
let memoryRecallMs;
|
|
58
|
-
|
|
59
|
+
const optimizedRecallQuery = optimizeRecallQuery(params.recallQuery, params.messageText || '');
|
|
60
|
+
const recallBudget = resolveRecallBudget({
|
|
61
|
+
query: optimizedRecallQuery,
|
|
62
|
+
maxResults: params.recallMaxResults,
|
|
63
|
+
maxTokens: params.recallMaxTokens
|
|
64
|
+
});
|
|
65
|
+
const recallEnabled = params.recallEnabled !== false
|
|
66
|
+
&& recallBudget.maxResults > 0
|
|
67
|
+
&& recallBudget.maxTokens > 0
|
|
68
|
+
&& shouldRunMemoryRecall(optimizedRecallQuery);
|
|
69
|
+
if (recallEnabled) {
|
|
59
70
|
const recallStart = Date.now();
|
|
60
|
-
memoryRecall = await
|
|
71
|
+
memoryRecall = await memoryBackend.buildRecall({
|
|
61
72
|
groupFolder: params.groupFolder,
|
|
62
73
|
userId: params.userId ?? null,
|
|
63
|
-
query:
|
|
64
|
-
maxResults:
|
|
65
|
-
maxTokens: dynamicMemoryBudget,
|
|
74
|
+
query: optimizedRecallQuery,
|
|
75
|
+
maxResults: recallBudget.maxResults,
|
|
76
|
+
maxTokens: Math.min(dynamicMemoryBudget, recallBudget.maxTokens),
|
|
66
77
|
minScore: runtime.host.memory.recall.minScore
|
|
67
78
|
});
|
|
68
79
|
memoryRecallMs = Date.now() - recallStart;
|
|
69
80
|
}
|
|
70
|
-
const userProfile = buildUserProfile({
|
|
81
|
+
const userProfile = memoryBackend.buildUserProfile({
|
|
71
82
|
groupFolder: params.groupFolder,
|
|
72
83
|
userId: params.userId ?? null
|
|
73
84
|
});
|
|
74
|
-
const memoryStats =
|
|
85
|
+
const memoryStats = memoryBackend.getStats({
|
|
75
86
|
groupFolder: params.groupFolder,
|
|
76
87
|
userId: params.userId ?? null
|
|
77
88
|
});
|
|
@@ -101,6 +112,7 @@ export async function buildAgentContext(params) {
|
|
|
101
112
|
}));
|
|
102
113
|
return {
|
|
103
114
|
memoryRecall,
|
|
115
|
+
memoryRecallAttempted: recallEnabled,
|
|
104
116
|
userProfile,
|
|
105
117
|
memoryStats,
|
|
106
118
|
behaviorConfig,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-context.js","sourceRoot":"","sources":["../src/agent-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"agent-context.js","sourceRoot":"","sources":["../src/agent-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentContext } from './agent-context.js';
|
|
2
|
+
import { AgentExecutionLane } from './agent-semaphore.js';
|
|
2
3
|
import type { ContainerOutput } from './container-protocol.js';
|
|
3
4
|
import type { RegisteredGroup } from './types.js';
|
|
4
5
|
export type TraceBase = {
|
|
@@ -54,6 +55,7 @@ export declare function executeAgentRun(params: {
|
|
|
54
55
|
maxToolSteps?: number;
|
|
55
56
|
timeoutMs?: number;
|
|
56
57
|
timezone?: string;
|
|
58
|
+
lane?: AgentExecutionLane;
|
|
57
59
|
streamDir?: string;
|
|
58
60
|
attachments?: Array<{
|
|
59
61
|
type: 'photo' | 'document' | 'voice' | 'video' | 'audio';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-execution.d.ts","sourceRoot":"","sources":["../src/agent-execution.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-execution.d.ts","sourceRoot":"","sources":["../src/agent-execution.ts"],"names":[],"mappings":"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"}
|
package/dist/agent-execution.js
CHANGED
|
@@ -8,7 +8,9 @@ import { withGroupLock } from './locks.js';
|
|
|
8
8
|
import { getModelPricing } from './model-registry.js';
|
|
9
9
|
import { computeCostUSD } from './cost.js';
|
|
10
10
|
import { writeTrace } from './trace-writer.js';
|
|
11
|
-
import { recordLatency, recordTokenUsage, recordCost, recordMemoryRecall, recordMemoryUpsert, recordMemoryExtract, recordToolCall, recordError, recordStageLatency } from './metrics.js';
|
|
11
|
+
import { recordLatency, recordTokenUsage, recordCost, recordMemoryRecall, recordMemoryUpsert, recordMemoryExtract, recordToolCall, recordError, recordStageLatency, recordFailover } from './metrics.js';
|
|
12
|
+
import { loadRuntimeConfig } from './runtime-config.js';
|
|
13
|
+
import { buildFailoverEnvelope, chooseNextHostModelChain, downgradeReasoningEffort, reduceToolStepBudget, registerModelFailureCooldown } from './failover-policy.js';
|
|
12
14
|
import { emitHook } from './hooks.js';
|
|
13
15
|
import { logger } from './logger.js';
|
|
14
16
|
export class AgentExecutionError extends Error {
|
|
@@ -46,7 +48,20 @@ function buildTaskSnapshot() {
|
|
|
46
48
|
last_error: t.last_error ?? null
|
|
47
49
|
}));
|
|
48
50
|
}
|
|
51
|
+
function buildModelChain(primary, fallbacks) {
|
|
52
|
+
const deduped = [];
|
|
53
|
+
const seen = new Set();
|
|
54
|
+
for (const candidate of [primary, ...(fallbacks || [])]) {
|
|
55
|
+
const normalized = (candidate || '').trim();
|
|
56
|
+
if (!normalized || seen.has(normalized))
|
|
57
|
+
continue;
|
|
58
|
+
deduped.push(normalized);
|
|
59
|
+
seen.add(normalized);
|
|
60
|
+
}
|
|
61
|
+
return deduped;
|
|
62
|
+
}
|
|
49
63
|
export async function executeAgentRun(params) {
|
|
64
|
+
const runStartedAt = Date.now();
|
|
50
65
|
const group = params.group;
|
|
51
66
|
const isMain = group.folder === MAIN_GROUP_FOLDER;
|
|
52
67
|
const persistSession = params.persistSession !== false;
|
|
@@ -80,7 +95,65 @@ export async function executeAgentRun(params) {
|
|
|
80
95
|
const resolvedMaxOutputTokens = outputCandidates.length > 0
|
|
81
96
|
? Math.min(...outputCandidates)
|
|
82
97
|
: Infinity;
|
|
83
|
-
const
|
|
98
|
+
const runtime = loadRuntimeConfig();
|
|
99
|
+
const hostFailover = runtime.host.routing.hostFailover;
|
|
100
|
+
const maxHostRetries = hostFailover.enabled ? Math.max(0, hostFailover.maxRetries) : 0;
|
|
101
|
+
const lane = params.lane || (params.isScheduledTask ? 'scheduled' : 'interactive');
|
|
102
|
+
const initialModelChain = buildModelChain(params.modelOverride || context.resolvedModel.model, params.modelFallbacks);
|
|
103
|
+
const initialChainSelection = chooseNextHostModelChain({
|
|
104
|
+
modelChain: initialModelChain,
|
|
105
|
+
attemptedPrimaryModels: new Set()
|
|
106
|
+
});
|
|
107
|
+
const initialPrimary = initialModelChain[0] || context.resolvedModel.model;
|
|
108
|
+
if (initialChainSelection && initialChainSelection.model !== initialPrimary) {
|
|
109
|
+
logger.info({
|
|
110
|
+
chatJid: params.chatJid,
|
|
111
|
+
groupFolder: group.folder,
|
|
112
|
+
skippedModel: initialPrimary,
|
|
113
|
+
selectedModel: initialChainSelection.model
|
|
114
|
+
}, 'Skipping primary model due to active host cooldown');
|
|
115
|
+
}
|
|
116
|
+
const attemptedPrimaryModels = new Set();
|
|
117
|
+
let activeModelChain = initialChainSelection
|
|
118
|
+
? [initialChainSelection.model, ...initialChainSelection.fallbacks]
|
|
119
|
+
: [...initialModelChain];
|
|
120
|
+
let activeReasoningEffort = params.reasoningEffort;
|
|
121
|
+
let activeMaxToolSteps = params.maxToolSteps;
|
|
122
|
+
let hostAttempts = 0;
|
|
123
|
+
let hostRecovered = false;
|
|
124
|
+
let lastFailoverCategory;
|
|
125
|
+
let lastFailoverEnvelope;
|
|
126
|
+
const failoverEnvelopes = [];
|
|
127
|
+
const markFailoverExhaustedIfNeeded = () => {
|
|
128
|
+
if (hostAttempts > 1 && lastFailoverCategory) {
|
|
129
|
+
recordFailover('exhausted', lastFailoverCategory);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const planHostRetry = (hostAttempt, envelope) => {
|
|
133
|
+
const canRetry = hostAttempt < maxHostRetries && envelope.retryable;
|
|
134
|
+
if (!canRetry)
|
|
135
|
+
return null;
|
|
136
|
+
return chooseNextHostModelChain({
|
|
137
|
+
modelChain: initialModelChain,
|
|
138
|
+
attemptedPrimaryModels
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
const applyHostRetry = (hostAttempt, envelope, nextChain, message) => {
|
|
142
|
+
recordFailover('attempt', envelope.category);
|
|
143
|
+
activeModelChain = [nextChain.model, ...nextChain.fallbacks];
|
|
144
|
+
activeReasoningEffort = downgradeReasoningEffort(activeReasoningEffort);
|
|
145
|
+
activeMaxToolSteps = reduceToolStepBudget(activeMaxToolSteps);
|
|
146
|
+
logger.warn({
|
|
147
|
+
chatJid: params.chatJid,
|
|
148
|
+
groupFolder: group.folder,
|
|
149
|
+
attempt: hostAttempt + 1,
|
|
150
|
+
category: envelope.category,
|
|
151
|
+
source: envelope.source,
|
|
152
|
+
statusCode: envelope.statusCode,
|
|
153
|
+
retryModel: nextChain.model
|
|
154
|
+
}, message);
|
|
155
|
+
};
|
|
156
|
+
const runContainer = (modelOverride, modelFallbacks) => runContainerAgent(group, {
|
|
84
157
|
prompt: params.prompt,
|
|
85
158
|
sessionId: params.sessionId,
|
|
86
159
|
groupFolder: group.folder,
|
|
@@ -91,15 +164,16 @@ export async function executeAgentRun(params) {
|
|
|
91
164
|
userId: params.userId ?? undefined,
|
|
92
165
|
userName: params.userName,
|
|
93
166
|
memoryRecall: context.memoryRecall,
|
|
167
|
+
memoryRecallAttempted: context.memoryRecallAttempted,
|
|
94
168
|
userProfile: context.userProfile,
|
|
95
169
|
memoryStats: context.memoryStats,
|
|
96
170
|
tokenEstimate: context.tokenEstimate,
|
|
97
171
|
toolReliability: context.toolReliability,
|
|
98
172
|
behaviorConfig: context.behaviorConfig,
|
|
99
173
|
toolPolicy: context.toolPolicy,
|
|
100
|
-
modelOverride
|
|
101
|
-
modelFallbacks
|
|
102
|
-
reasoningEffort:
|
|
174
|
+
modelOverride,
|
|
175
|
+
modelFallbacks,
|
|
176
|
+
reasoningEffort: activeReasoningEffort,
|
|
103
177
|
modelCapabilities: {
|
|
104
178
|
context_length: context.modelCapabilities.context_length,
|
|
105
179
|
max_completion_tokens: context.modelCapabilities.max_completion_tokens,
|
|
@@ -109,7 +183,7 @@ export async function executeAgentRun(params) {
|
|
|
109
183
|
modelTemperature: context.resolvedModel.override?.temperature,
|
|
110
184
|
timezone: params.timezone || TIMEZONE,
|
|
111
185
|
hostPlatform: `${process.platform}/${process.arch}`,
|
|
112
|
-
maxToolSteps:
|
|
186
|
+
maxToolSteps: activeMaxToolSteps,
|
|
113
187
|
streamDir: params.streamDir,
|
|
114
188
|
attachments: params.attachments
|
|
115
189
|
}, { abortSignal: params.abortSignal, timeoutMs: params.timeoutMs });
|
|
@@ -121,17 +195,81 @@ export async function executeAgentRun(params) {
|
|
|
121
195
|
model: params.modelOverride || context.resolvedModel.model,
|
|
122
196
|
source: params.isScheduledTask ? 'scheduler' : 'message'
|
|
123
197
|
});
|
|
124
|
-
let output;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
198
|
+
let output = null;
|
|
199
|
+
let lastException = null;
|
|
200
|
+
for (let hostAttempt = 0; hostAttempt <= maxHostRetries; hostAttempt += 1) {
|
|
201
|
+
const nextPrimary = activeModelChain[0] || context.resolvedModel.model;
|
|
202
|
+
const nextFallbacks = activeModelChain.slice(1);
|
|
203
|
+
attemptedPrimaryModels.add(nextPrimary);
|
|
204
|
+
hostAttempts = hostAttempt + 1;
|
|
205
|
+
try {
|
|
206
|
+
const runner = () => (useGroupLock
|
|
207
|
+
? withGroupLock(group.folder, () => runContainer(nextPrimary, nextFallbacks))
|
|
208
|
+
: runContainer(nextPrimary, nextFallbacks));
|
|
209
|
+
const attemptOutput = useSemaphore
|
|
210
|
+
? await runWithAgentSemaphore(runner, { lane })
|
|
211
|
+
: await runner();
|
|
212
|
+
if (attemptOutput.status !== 'error') {
|
|
213
|
+
output = attemptOutput;
|
|
214
|
+
hostRecovered = hostAttempts > 1;
|
|
215
|
+
if (hostRecovered && lastFailoverCategory) {
|
|
216
|
+
recordFailover('recovered', lastFailoverCategory);
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
const envelope = buildFailoverEnvelope({
|
|
221
|
+
error: attemptOutput.error || 'Unknown error',
|
|
222
|
+
source: 'container_output',
|
|
223
|
+
attempt: hostAttempts,
|
|
224
|
+
model: attemptOutput.model || nextPrimary
|
|
225
|
+
});
|
|
226
|
+
failoverEnvelopes.push(envelope);
|
|
227
|
+
lastFailoverEnvelope = envelope;
|
|
228
|
+
lastFailoverCategory = envelope.category;
|
|
229
|
+
registerModelFailureCooldown(envelope.model || nextPrimary, envelope.category, hostFailover);
|
|
230
|
+
const nextChain = planHostRetry(hostAttempt, envelope);
|
|
231
|
+
if (!nextChain) {
|
|
232
|
+
markFailoverExhaustedIfNeeded();
|
|
233
|
+
output = attemptOutput;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
applyHostRetry(hostAttempt, envelope, nextChain, 'Host-level failover retry');
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
lastException = err;
|
|
241
|
+
const envelope = buildFailoverEnvelope({
|
|
242
|
+
error: err instanceof Error ? err.message : String(err),
|
|
243
|
+
source: 'runtime_exception',
|
|
244
|
+
attempt: hostAttempts,
|
|
245
|
+
model: nextPrimary
|
|
246
|
+
});
|
|
247
|
+
failoverEnvelopes.push(envelope);
|
|
248
|
+
lastFailoverEnvelope = envelope;
|
|
249
|
+
lastFailoverCategory = envelope.category;
|
|
250
|
+
registerModelFailureCooldown(nextPrimary, envelope.category, hostFailover);
|
|
251
|
+
const nextChain = planHostRetry(hostAttempt, envelope);
|
|
252
|
+
if (!nextChain) {
|
|
253
|
+
markFailoverExhaustedIfNeeded();
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
applyHostRetry(hostAttempt, envelope, nextChain, 'Host-level failover retry after runtime error');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (!output && lastException) {
|
|
260
|
+
const message = lastException instanceof Error ? lastException.message : String(lastException);
|
|
133
261
|
throw new AgentExecutionError(message, context);
|
|
134
262
|
}
|
|
263
|
+
if (!output) {
|
|
264
|
+
throw new AgentExecutionError('No output from agent run', context);
|
|
265
|
+
}
|
|
266
|
+
output.host_failover_attempts = hostAttempts;
|
|
267
|
+
output.host_failover_recovered = hostRecovered;
|
|
268
|
+
output.host_failover_category = lastFailoverCategory;
|
|
269
|
+
output.host_failover_source = lastFailoverEnvelope?.source;
|
|
270
|
+
output.host_failover_status_code = lastFailoverEnvelope?.statusCode;
|
|
271
|
+
output.host_failover_envelopes = failoverEnvelopes.length > 0 ? failoverEnvelopes : undefined;
|
|
272
|
+
output.latency_ms = Math.max(1, Date.now() - runStartedAt);
|
|
135
273
|
void emitHook('agent:complete', {
|
|
136
274
|
group_folder: group.folder,
|
|
137
275
|
chat_jid: params.chatJid,
|
|
@@ -189,6 +327,17 @@ export function recordAgentTelemetry(params) {
|
|
|
189
327
|
session_recall_count: output?.session_recall_count,
|
|
190
328
|
memory_items_upserted: output?.memory_items_upserted,
|
|
191
329
|
memory_items_extracted: output?.memory_items_extracted,
|
|
330
|
+
host_failover_attempts: output?.host_failover_attempts,
|
|
331
|
+
host_failover_recovered: output?.host_failover_recovered,
|
|
332
|
+
host_failover_category: output?.host_failover_category,
|
|
333
|
+
host_failover_source: output?.host_failover_source,
|
|
334
|
+
host_failover_status_code: output?.host_failover_status_code,
|
|
335
|
+
host_failover_envelopes: output?.host_failover_envelopes,
|
|
336
|
+
tool_retry_attempts: output?.tool_retry_attempts,
|
|
337
|
+
tool_outcome_verification_forced: output?.tool_outcome_verification_forced,
|
|
338
|
+
tool_loop_breaker_triggered: output?.tool_loop_breaker_triggered,
|
|
339
|
+
tool_loop_breaker_reason: output?.tool_loop_breaker_reason,
|
|
340
|
+
memory_extraction_error: output?.memory_extraction_error,
|
|
192
341
|
timings: Object.keys(timingBundle).length > 0 ? timingBundle : undefined,
|
|
193
342
|
error_code: params.errorMessage || (output?.status === 'error' ? output?.error : undefined)
|
|
194
343
|
});
|