agentflow-core 0.8.0 → 0.8.1
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 +99 -0
- package/dist/{chunk-DY7YHFIB.js → chunk-BYWLDTZK.js} +2 -1
- package/dist/{chunk-6X5HU5LB.js → chunk-NVFWBTAZ.js} +75 -37
- package/dist/cli.cjs +76 -38
- package/dist/cli.js +6 -6
- package/dist/index.cjs +945 -740
- package/dist/index.d.cts +344 -137
- package/dist/index.d.ts +344 -137
- package/dist/index.js +380 -215
- package/dist/{loader-LYRR6LMM.js → loader-JMFEFI3Q.js} +1 -1
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
auditProcesses,
|
|
3
3
|
createGraphBuilder,
|
|
4
4
|
createTraceStore,
|
|
5
|
+
discoverAllProcessConfigs,
|
|
5
6
|
discoverProcessConfig,
|
|
6
7
|
findWaitingOn,
|
|
7
8
|
formatAuditReport,
|
|
@@ -23,11 +24,11 @@ import {
|
|
|
23
24
|
stitchTrace,
|
|
24
25
|
toAsciiTree,
|
|
25
26
|
toTimeline
|
|
26
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-NVFWBTAZ.js";
|
|
27
28
|
import {
|
|
28
29
|
graphToJson,
|
|
29
30
|
loadGraph
|
|
30
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-BYWLDTZK.js";
|
|
31
32
|
|
|
32
33
|
// src/process-mining.ts
|
|
33
34
|
function getPathSignature(graph) {
|
|
@@ -93,7 +94,7 @@ function discoverProcess(graphs) {
|
|
|
93
94
|
steps: [...steps].sort(),
|
|
94
95
|
transitions,
|
|
95
96
|
totalGraphs: graphs.length,
|
|
96
|
-
agentId: graphs[0]
|
|
97
|
+
agentId: graphs[0]?.agentId ?? ""
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
100
|
function findVariants(graphs) {
|
|
@@ -125,13 +126,13 @@ function findVariants(graphs) {
|
|
|
125
126
|
}
|
|
126
127
|
function percentile(sorted, p) {
|
|
127
128
|
if (sorted.length === 0) return 0;
|
|
128
|
-
if (sorted.length === 1) return sorted[0];
|
|
129
|
+
if (sorted.length === 1) return sorted[0] ?? 0;
|
|
129
130
|
const index = p / 100 * (sorted.length - 1);
|
|
130
131
|
const lower = Math.floor(index);
|
|
131
132
|
const upper = Math.ceil(index);
|
|
132
|
-
if (lower === upper) return sorted[lower];
|
|
133
|
+
if (lower === upper) return sorted[lower] ?? 0;
|
|
133
134
|
const weight = index - lower;
|
|
134
|
-
return sorted[lower] * (1 - weight) + sorted[upper] * weight;
|
|
135
|
+
return (sorted[lower] ?? 0) * (1 - weight) + (sorted[upper] ?? 0) * weight;
|
|
135
136
|
}
|
|
136
137
|
function getBottlenecks(graphs) {
|
|
137
138
|
if (graphs.length === 0) return [];
|
|
@@ -158,8 +159,8 @@ function getBottlenecks(graphs) {
|
|
|
158
159
|
median: percentile(sorted, 50),
|
|
159
160
|
p95: percentile(sorted, 95),
|
|
160
161
|
p99: percentile(sorted, 99),
|
|
161
|
-
min: sorted[0],
|
|
162
|
-
max: sorted[sorted.length - 1]
|
|
162
|
+
min: sorted[0] ?? 0,
|
|
163
|
+
max: sorted[sorted.length - 1] ?? 0
|
|
163
164
|
},
|
|
164
165
|
percentOfGraphs: sorted.length / total * 100
|
|
165
166
|
});
|
|
@@ -342,6 +343,256 @@ function createEventEmitter(config) {
|
|
|
342
343
|
};
|
|
343
344
|
}
|
|
344
345
|
|
|
346
|
+
// src/guards.ts
|
|
347
|
+
function explainMessage(explanation) {
|
|
348
|
+
const sourceLabel = explanation.source === "static" ? "static config" : explanation.source === "soma-policy" ? `soma-policy${explanation.evidence ? ` (${explanation.evidence})` : ""}` : explanation.source === "assertion" ? "assertion" : "adaptive";
|
|
349
|
+
return `This run was stopped because ${explanation.rule} reached ${explanation.actual}, which exceeds the limit of ${explanation.threshold}. Source: ${sourceLabel}.`;
|
|
350
|
+
}
|
|
351
|
+
var DEFAULT_TIMEOUTS = {
|
|
352
|
+
tool: 3e4,
|
|
353
|
+
// 30s
|
|
354
|
+
agent: 3e5,
|
|
355
|
+
// 5m
|
|
356
|
+
subagent: 3e5,
|
|
357
|
+
// 5m
|
|
358
|
+
wait: 6e5,
|
|
359
|
+
// 10m
|
|
360
|
+
decision: 3e4,
|
|
361
|
+
// 30s
|
|
362
|
+
custom: 3e4
|
|
363
|
+
// 30s
|
|
364
|
+
};
|
|
365
|
+
function checkGuards(graph, config) {
|
|
366
|
+
const violations = [];
|
|
367
|
+
const now = Date.now();
|
|
368
|
+
const timeouts = { ...DEFAULT_TIMEOUTS, ...config?.timeouts };
|
|
369
|
+
const maxReasoningSteps = config?.maxReasoningSteps ?? 25;
|
|
370
|
+
const maxDepth = config?.maxDepth ?? 10;
|
|
371
|
+
const maxAgentSpawns = config?.maxAgentSpawns ?? 50;
|
|
372
|
+
for (const node of graph.nodes.values()) {
|
|
373
|
+
if (node.status === "running" && node.endTime === null) {
|
|
374
|
+
const timeoutThreshold = timeouts[node.type];
|
|
375
|
+
const elapsed = now - node.startTime;
|
|
376
|
+
if (elapsed > timeoutThreshold) {
|
|
377
|
+
const explanation = {
|
|
378
|
+
rule: "timeout",
|
|
379
|
+
threshold: timeoutThreshold,
|
|
380
|
+
actual: elapsed,
|
|
381
|
+
source: "static"
|
|
382
|
+
};
|
|
383
|
+
violations.push({
|
|
384
|
+
type: "timeout",
|
|
385
|
+
nodeId: node.id,
|
|
386
|
+
message: explainMessage(explanation),
|
|
387
|
+
timestamp: now,
|
|
388
|
+
explanation
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const depth = getDepth(graph);
|
|
394
|
+
if (depth > maxDepth) {
|
|
395
|
+
const explanation = {
|
|
396
|
+
rule: "max-depth",
|
|
397
|
+
threshold: maxDepth,
|
|
398
|
+
actual: depth,
|
|
399
|
+
source: "static"
|
|
400
|
+
};
|
|
401
|
+
violations.push({
|
|
402
|
+
type: "spawn-explosion",
|
|
403
|
+
nodeId: graph.rootNodeId,
|
|
404
|
+
message: explainMessage(explanation),
|
|
405
|
+
timestamp: now,
|
|
406
|
+
explanation
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
let agentCount = 0;
|
|
410
|
+
for (const node of graph.nodes.values()) {
|
|
411
|
+
if (node.type === "agent" || node.type === "subagent") {
|
|
412
|
+
agentCount++;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (agentCount > maxAgentSpawns) {
|
|
416
|
+
const explanation = {
|
|
417
|
+
rule: "max-agent-spawns",
|
|
418
|
+
threshold: maxAgentSpawns,
|
|
419
|
+
actual: agentCount,
|
|
420
|
+
source: "static"
|
|
421
|
+
};
|
|
422
|
+
violations.push({
|
|
423
|
+
type: "spawn-explosion",
|
|
424
|
+
nodeId: graph.rootNodeId,
|
|
425
|
+
message: explainMessage(explanation),
|
|
426
|
+
timestamp: now,
|
|
427
|
+
explanation
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
violations.push(...detectReasoningLoops(graph, maxReasoningSteps, now));
|
|
431
|
+
if (config?.policySource) {
|
|
432
|
+
violations.push(
|
|
433
|
+
...checkPolicyViolations(graph, config.policySource, config.policyThresholds, now)
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
return violations;
|
|
437
|
+
}
|
|
438
|
+
function detectReasoningLoops(graph, maxSteps, timestamp) {
|
|
439
|
+
const violations = [];
|
|
440
|
+
const reported = /* @__PURE__ */ new Set();
|
|
441
|
+
function walk(nodeId, consecutiveCount, consecutiveType) {
|
|
442
|
+
const node = getNode(graph, nodeId);
|
|
443
|
+
if (!node) return;
|
|
444
|
+
let newCount;
|
|
445
|
+
let newType;
|
|
446
|
+
if (node.type === consecutiveType) {
|
|
447
|
+
newCount = consecutiveCount + 1;
|
|
448
|
+
newType = node.type;
|
|
449
|
+
} else {
|
|
450
|
+
newCount = 1;
|
|
451
|
+
newType = node.type;
|
|
452
|
+
}
|
|
453
|
+
if (newCount > maxSteps && !reported.has(newType)) {
|
|
454
|
+
reported.add(newType);
|
|
455
|
+
const explanation = {
|
|
456
|
+
rule: "max-reasoning-steps",
|
|
457
|
+
threshold: maxSteps,
|
|
458
|
+
actual: newCount,
|
|
459
|
+
source: "static"
|
|
460
|
+
};
|
|
461
|
+
violations.push({
|
|
462
|
+
type: "reasoning-loop",
|
|
463
|
+
nodeId: node.id,
|
|
464
|
+
message: explainMessage(explanation),
|
|
465
|
+
timestamp,
|
|
466
|
+
explanation
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
const children = getChildren(graph, nodeId);
|
|
470
|
+
for (const child of children) {
|
|
471
|
+
walk(child.id, newCount, newType);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
walk(graph.rootNodeId, 0, null);
|
|
475
|
+
return violations;
|
|
476
|
+
}
|
|
477
|
+
function checkPolicyViolations(graph, policySource, thresholds, timestamp) {
|
|
478
|
+
const violations = [];
|
|
479
|
+
const maxFailureRate = thresholds?.maxFailureRate ?? 0.5;
|
|
480
|
+
const minConformance = thresholds?.minConformance ?? 0.7;
|
|
481
|
+
const failureRate = policySource.recentFailureRate(graph.agentId);
|
|
482
|
+
if (failureRate > maxFailureRate) {
|
|
483
|
+
const explanation = {
|
|
484
|
+
rule: "max-failure-rate",
|
|
485
|
+
threshold: maxFailureRate,
|
|
486
|
+
actual: failureRate,
|
|
487
|
+
source: "soma-policy"
|
|
488
|
+
};
|
|
489
|
+
violations.push({
|
|
490
|
+
type: "high-failure-rate",
|
|
491
|
+
nodeId: graph.rootNodeId,
|
|
492
|
+
message: explainMessage(explanation),
|
|
493
|
+
timestamp,
|
|
494
|
+
explanation
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
const conformanceScore = policySource.lastConformanceScore(graph.agentId);
|
|
498
|
+
if (conformanceScore !== null && conformanceScore < minConformance) {
|
|
499
|
+
const explanation = {
|
|
500
|
+
rule: "min-conformance",
|
|
501
|
+
threshold: minConformance,
|
|
502
|
+
actual: conformanceScore,
|
|
503
|
+
source: "soma-policy"
|
|
504
|
+
};
|
|
505
|
+
violations.push({
|
|
506
|
+
type: "conformance-drift",
|
|
507
|
+
nodeId: graph.rootNodeId,
|
|
508
|
+
message: explainMessage(explanation),
|
|
509
|
+
timestamp,
|
|
510
|
+
explanation
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
for (const node of graph.nodes.values()) {
|
|
514
|
+
if (node.status === "running" && policySource.isKnownBottleneck(node.name)) {
|
|
515
|
+
const explanation = {
|
|
516
|
+
rule: "known-bottleneck",
|
|
517
|
+
threshold: "flagged",
|
|
518
|
+
actual: "running",
|
|
519
|
+
source: "soma-policy"
|
|
520
|
+
};
|
|
521
|
+
violations.push({
|
|
522
|
+
type: "known-bottleneck",
|
|
523
|
+
nodeId: node.id,
|
|
524
|
+
message: explainMessage(explanation),
|
|
525
|
+
timestamp,
|
|
526
|
+
explanation
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return violations;
|
|
531
|
+
}
|
|
532
|
+
function withGuards(builder, config) {
|
|
533
|
+
const logger = config?.logger ?? ((msg) => console.warn(`[AgentFlow Guard] ${msg}`));
|
|
534
|
+
const onViolation = config?.onViolation ?? "warn";
|
|
535
|
+
function handleViolations(violations) {
|
|
536
|
+
if (violations.length === 0) return;
|
|
537
|
+
for (const violation of violations) {
|
|
538
|
+
const message = `Guard violation: ${violation.message}`;
|
|
539
|
+
switch (onViolation) {
|
|
540
|
+
case "warn":
|
|
541
|
+
logger(message);
|
|
542
|
+
break;
|
|
543
|
+
case "error":
|
|
544
|
+
logger(message);
|
|
545
|
+
builder.pushEvent({
|
|
546
|
+
eventType: "custom",
|
|
547
|
+
nodeId: violation.nodeId,
|
|
548
|
+
data: {
|
|
549
|
+
guardViolation: violation.type,
|
|
550
|
+
message: violation.message,
|
|
551
|
+
severity: "error"
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
break;
|
|
555
|
+
case "abort":
|
|
556
|
+
throw new Error(`AgentFlow guard violation: ${violation.message}`);
|
|
557
|
+
default:
|
|
558
|
+
logger(message);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return {
|
|
563
|
+
get graphId() {
|
|
564
|
+
return builder.graphId;
|
|
565
|
+
},
|
|
566
|
+
get traceContext() {
|
|
567
|
+
return builder.traceContext;
|
|
568
|
+
},
|
|
569
|
+
startNode: (opts) => builder.startNode(opts),
|
|
570
|
+
endNode: (nodeId, status) => {
|
|
571
|
+
builder.endNode(nodeId, status);
|
|
572
|
+
const snapshot = builder.getSnapshot();
|
|
573
|
+
const violations = checkGuards(snapshot, config);
|
|
574
|
+
handleViolations(violations);
|
|
575
|
+
},
|
|
576
|
+
failNode: (nodeId, error) => {
|
|
577
|
+
builder.failNode(nodeId, error);
|
|
578
|
+
const snapshot = builder.getSnapshot();
|
|
579
|
+
const violations = checkGuards(snapshot, config);
|
|
580
|
+
handleViolations(violations);
|
|
581
|
+
},
|
|
582
|
+
addEdge: (from, to, type) => builder.addEdge(from, to, type),
|
|
583
|
+
pushEvent: (event) => builder.pushEvent(event),
|
|
584
|
+
updateState: (nodeId, state) => builder.updateState(nodeId, state),
|
|
585
|
+
withParent: (parentId, fn) => builder.withParent(parentId, fn),
|
|
586
|
+
getSnapshot: () => builder.getSnapshot(),
|
|
587
|
+
build: () => {
|
|
588
|
+
const snapshot = builder.getSnapshot();
|
|
589
|
+
const violations = checkGuards(snapshot, config);
|
|
590
|
+
handleViolations(violations);
|
|
591
|
+
return builder.build();
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
345
596
|
// src/prompt-builder.ts
|
|
346
597
|
var ROLE = "You are analyzing execution data for an AI agent system. Provide clear, actionable analysis based on the data below.";
|
|
347
598
|
function fmtDuration(ms) {
|
|
@@ -672,201 +923,6 @@ function createInsightEngine(store, analysisFn, config) {
|
|
|
672
923
|
};
|
|
673
924
|
}
|
|
674
925
|
|
|
675
|
-
// src/guards.ts
|
|
676
|
-
var DEFAULT_TIMEOUTS = {
|
|
677
|
-
tool: 3e4,
|
|
678
|
-
// 30s
|
|
679
|
-
agent: 3e5,
|
|
680
|
-
// 5m
|
|
681
|
-
subagent: 3e5,
|
|
682
|
-
// 5m
|
|
683
|
-
wait: 6e5,
|
|
684
|
-
// 10m
|
|
685
|
-
decision: 3e4,
|
|
686
|
-
// 30s
|
|
687
|
-
custom: 3e4
|
|
688
|
-
// 30s
|
|
689
|
-
};
|
|
690
|
-
function checkGuards(graph, config) {
|
|
691
|
-
const violations = [];
|
|
692
|
-
const now = Date.now();
|
|
693
|
-
const timeouts = { ...DEFAULT_TIMEOUTS, ...config?.timeouts };
|
|
694
|
-
const maxReasoningSteps = config?.maxReasoningSteps ?? 25;
|
|
695
|
-
const maxDepth = config?.maxDepth ?? 10;
|
|
696
|
-
const maxAgentSpawns = config?.maxAgentSpawns ?? 50;
|
|
697
|
-
for (const node of graph.nodes.values()) {
|
|
698
|
-
if (node.status === "running" && node.endTime === null) {
|
|
699
|
-
const timeoutThreshold = timeouts[node.type];
|
|
700
|
-
const elapsed = now - node.startTime;
|
|
701
|
-
if (elapsed > timeoutThreshold) {
|
|
702
|
-
violations.push({
|
|
703
|
-
type: "timeout",
|
|
704
|
-
nodeId: node.id,
|
|
705
|
-
message: `Node ${node.id} (${node.type}: ${node.name}) has been running for ${elapsed}ms, exceeding timeout of ${timeoutThreshold}ms`,
|
|
706
|
-
timestamp: now
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
const depth = getDepth(graph);
|
|
712
|
-
if (depth > maxDepth) {
|
|
713
|
-
violations.push({
|
|
714
|
-
type: "spawn-explosion",
|
|
715
|
-
nodeId: graph.rootNodeId,
|
|
716
|
-
message: `Graph depth ${depth} exceeds maximum depth of ${maxDepth}`,
|
|
717
|
-
timestamp: now
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
let agentCount = 0;
|
|
721
|
-
for (const node of graph.nodes.values()) {
|
|
722
|
-
if (node.type === "agent" || node.type === "subagent") {
|
|
723
|
-
agentCount++;
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
if (agentCount > maxAgentSpawns) {
|
|
727
|
-
violations.push({
|
|
728
|
-
type: "spawn-explosion",
|
|
729
|
-
nodeId: graph.rootNodeId,
|
|
730
|
-
message: `Total agent/subagent count ${agentCount} exceeds maximum of ${maxAgentSpawns}`,
|
|
731
|
-
timestamp: now
|
|
732
|
-
});
|
|
733
|
-
}
|
|
734
|
-
violations.push(...detectReasoningLoops(graph, maxReasoningSteps, now));
|
|
735
|
-
if (config?.policySource) {
|
|
736
|
-
violations.push(...checkPolicyViolations(graph, config.policySource, config.policyThresholds, now));
|
|
737
|
-
}
|
|
738
|
-
return violations;
|
|
739
|
-
}
|
|
740
|
-
function detectReasoningLoops(graph, maxSteps, timestamp) {
|
|
741
|
-
const violations = [];
|
|
742
|
-
const reported = /* @__PURE__ */ new Set();
|
|
743
|
-
function walk(nodeId, consecutiveCount, consecutiveType) {
|
|
744
|
-
const node = getNode(graph, nodeId);
|
|
745
|
-
if (!node) return;
|
|
746
|
-
let newCount;
|
|
747
|
-
let newType;
|
|
748
|
-
if (node.type === consecutiveType) {
|
|
749
|
-
newCount = consecutiveCount + 1;
|
|
750
|
-
newType = node.type;
|
|
751
|
-
} else {
|
|
752
|
-
newCount = 1;
|
|
753
|
-
newType = node.type;
|
|
754
|
-
}
|
|
755
|
-
if (newCount > maxSteps && !reported.has(newType)) {
|
|
756
|
-
reported.add(newType);
|
|
757
|
-
violations.push({
|
|
758
|
-
type: "reasoning-loop",
|
|
759
|
-
nodeId: node.id,
|
|
760
|
-
message: `Detected ${newCount} consecutive ${newType} nodes along path to ${node.name}`,
|
|
761
|
-
timestamp
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
const children = getChildren(graph, nodeId);
|
|
765
|
-
for (const child of children) {
|
|
766
|
-
walk(child.id, newCount, newType);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
walk(graph.rootNodeId, 0, null);
|
|
770
|
-
return violations;
|
|
771
|
-
}
|
|
772
|
-
function checkPolicyViolations(graph, policySource, thresholds, timestamp) {
|
|
773
|
-
const violations = [];
|
|
774
|
-
const maxFailureRate = thresholds?.maxFailureRate ?? 0.5;
|
|
775
|
-
const minConformance = thresholds?.minConformance ?? 0.7;
|
|
776
|
-
const failureRate = policySource.recentFailureRate(graph.agentId);
|
|
777
|
-
if (failureRate > maxFailureRate) {
|
|
778
|
-
violations.push({
|
|
779
|
-
type: "high-failure-rate",
|
|
780
|
-
nodeId: graph.rootNodeId,
|
|
781
|
-
message: `Agent ${graph.agentId} has a recent failure rate of ${(failureRate * 100).toFixed(0)}% (threshold: ${(maxFailureRate * 100).toFixed(0)}%)`,
|
|
782
|
-
timestamp
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
const conformanceScore = policySource.lastConformanceScore(graph.agentId);
|
|
786
|
-
if (conformanceScore !== null && conformanceScore < minConformance) {
|
|
787
|
-
violations.push({
|
|
788
|
-
type: "conformance-drift",
|
|
789
|
-
nodeId: graph.rootNodeId,
|
|
790
|
-
message: `Agent ${graph.agentId} conformance score ${(conformanceScore * 100).toFixed(0)}% is below threshold ${(minConformance * 100).toFixed(0)}%`,
|
|
791
|
-
timestamp
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
|
-
for (const node of graph.nodes.values()) {
|
|
795
|
-
if (node.status === "running" && policySource.isKnownBottleneck(node.name)) {
|
|
796
|
-
violations.push({
|
|
797
|
-
type: "known-bottleneck",
|
|
798
|
-
nodeId: node.id,
|
|
799
|
-
message: `Node ${node.name} (${node.type}) is a known bottleneck`,
|
|
800
|
-
timestamp
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
return violations;
|
|
805
|
-
}
|
|
806
|
-
function withGuards(builder, config) {
|
|
807
|
-
const logger = config?.logger ?? ((msg) => console.warn(`[AgentFlow Guard] ${msg}`));
|
|
808
|
-
const onViolation = config?.onViolation ?? "warn";
|
|
809
|
-
function handleViolations(violations) {
|
|
810
|
-
if (violations.length === 0) return;
|
|
811
|
-
for (const violation of violations) {
|
|
812
|
-
const message = `Guard violation: ${violation.message}`;
|
|
813
|
-
switch (onViolation) {
|
|
814
|
-
case "warn":
|
|
815
|
-
logger(message);
|
|
816
|
-
break;
|
|
817
|
-
case "error":
|
|
818
|
-
logger(message);
|
|
819
|
-
builder.pushEvent({
|
|
820
|
-
eventType: "custom",
|
|
821
|
-
nodeId: violation.nodeId,
|
|
822
|
-
data: {
|
|
823
|
-
guardViolation: violation.type,
|
|
824
|
-
message: violation.message,
|
|
825
|
-
severity: "error"
|
|
826
|
-
}
|
|
827
|
-
});
|
|
828
|
-
break;
|
|
829
|
-
case "abort":
|
|
830
|
-
throw new Error(`AgentFlow guard violation: ${violation.message}`);
|
|
831
|
-
default:
|
|
832
|
-
logger(message);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
return {
|
|
837
|
-
get graphId() {
|
|
838
|
-
return builder.graphId;
|
|
839
|
-
},
|
|
840
|
-
get traceContext() {
|
|
841
|
-
return builder.traceContext;
|
|
842
|
-
},
|
|
843
|
-
startNode: (opts) => builder.startNode(opts),
|
|
844
|
-
endNode: (nodeId, status) => {
|
|
845
|
-
builder.endNode(nodeId, status);
|
|
846
|
-
const snapshot = builder.getSnapshot();
|
|
847
|
-
const violations = checkGuards(snapshot, config);
|
|
848
|
-
handleViolations(violations);
|
|
849
|
-
},
|
|
850
|
-
failNode: (nodeId, error) => {
|
|
851
|
-
builder.failNode(nodeId, error);
|
|
852
|
-
const snapshot = builder.getSnapshot();
|
|
853
|
-
const violations = checkGuards(snapshot, config);
|
|
854
|
-
handleViolations(violations);
|
|
855
|
-
},
|
|
856
|
-
addEdge: (from, to, type) => builder.addEdge(from, to, type),
|
|
857
|
-
pushEvent: (event) => builder.pushEvent(event),
|
|
858
|
-
updateState: (nodeId, state) => builder.updateState(nodeId, state),
|
|
859
|
-
withParent: (parentId, fn) => builder.withParent(parentId, fn),
|
|
860
|
-
getSnapshot: () => builder.getSnapshot(),
|
|
861
|
-
build: () => {
|
|
862
|
-
const snapshot = builder.getSnapshot();
|
|
863
|
-
const violations = checkGuards(snapshot, config);
|
|
864
|
-
handleViolations(violations);
|
|
865
|
-
return builder.build();
|
|
866
|
-
}
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
|
|
870
926
|
// src/json-event-writer.ts
|
|
871
927
|
import { mkdirSync, writeFileSync } from "fs";
|
|
872
928
|
import { join } from "path";
|
|
@@ -980,7 +1036,8 @@ function createKnowledgeStore(config) {
|
|
|
980
1036
|
mkdirSync2(dir, { recursive: true });
|
|
981
1037
|
}
|
|
982
1038
|
function profilePath(agentId) {
|
|
983
|
-
|
|
1039
|
+
const safe = agentId.replace(/[/\\]/g, "_").replace(/\.\./g, "_");
|
|
1040
|
+
return join2(profilesDir, `${safe}.json`);
|
|
984
1041
|
}
|
|
985
1042
|
function appendExecutionEvent(event) {
|
|
986
1043
|
const dateDir = join2(eventsDir, event.agentId, toDateDir(event.timestamp));
|
|
@@ -1105,7 +1162,7 @@ function createKnowledgeStore(config) {
|
|
|
1105
1162
|
continue;
|
|
1106
1163
|
}
|
|
1107
1164
|
for (const file of files) {
|
|
1108
|
-
const ts = Number.parseInt(file.split("-")[0], 10);
|
|
1165
|
+
const ts = Number.parseInt(file.split("-")[0] ?? "", 10);
|
|
1109
1166
|
if (!Number.isNaN(ts) && ts < options.olderThan) {
|
|
1110
1167
|
try {
|
|
1111
1168
|
rmSync(join2(agentPatternDir, file));
|
|
@@ -1193,7 +1250,7 @@ function createPolicySource(store) {
|
|
|
1193
1250
|
}
|
|
1194
1251
|
for (const agentId of agentIds) {
|
|
1195
1252
|
const profile = store.getAgentProfile(agentId);
|
|
1196
|
-
if (profile
|
|
1253
|
+
if (profile?.knownBottlenecks.includes(nodeName)) {
|
|
1197
1254
|
return true;
|
|
1198
1255
|
}
|
|
1199
1256
|
}
|
|
@@ -1209,6 +1266,118 @@ function createPolicySource(store) {
|
|
|
1209
1266
|
};
|
|
1210
1267
|
}
|
|
1211
1268
|
|
|
1269
|
+
// src/receipts.ts
|
|
1270
|
+
function extractTokenCost(node) {
|
|
1271
|
+
const semantic = node.metadata?.semantic;
|
|
1272
|
+
if (semantic?.tokenCost !== void 0 && semantic.tokenCost !== null) {
|
|
1273
|
+
return semantic.tokenCost;
|
|
1274
|
+
}
|
|
1275
|
+
if (node.state?.tokenCost !== void 0 && node.state.tokenCost !== null) {
|
|
1276
|
+
return node.state.tokenCost;
|
|
1277
|
+
}
|
|
1278
|
+
return null;
|
|
1279
|
+
}
|
|
1280
|
+
function extractError(node) {
|
|
1281
|
+
if (node.state?.error !== void 0 && node.state.error !== null) {
|
|
1282
|
+
return String(node.state.error);
|
|
1283
|
+
}
|
|
1284
|
+
if (node.metadata?.error !== void 0 && node.metadata.error !== null) {
|
|
1285
|
+
return String(node.metadata.error);
|
|
1286
|
+
}
|
|
1287
|
+
return null;
|
|
1288
|
+
}
|
|
1289
|
+
function nodeDuration(node) {
|
|
1290
|
+
if (node.endTime === null) return null;
|
|
1291
|
+
return node.endTime - node.startTime;
|
|
1292
|
+
}
|
|
1293
|
+
function toReceipt(graph) {
|
|
1294
|
+
const nodes = [...graph.nodes.values()];
|
|
1295
|
+
nodes.sort((a, b) => a.startTime - b.startTime);
|
|
1296
|
+
const steps = nodes.map((node) => ({
|
|
1297
|
+
nodeId: node.id,
|
|
1298
|
+
name: node.name,
|
|
1299
|
+
type: node.type,
|
|
1300
|
+
status: node.status,
|
|
1301
|
+
durationMs: nodeDuration(node),
|
|
1302
|
+
tokenCost: extractTokenCost(node),
|
|
1303
|
+
error: extractError(node)
|
|
1304
|
+
}));
|
|
1305
|
+
let succeeded = 0;
|
|
1306
|
+
let failed = 0;
|
|
1307
|
+
const skipped = 0;
|
|
1308
|
+
for (const node of nodes) {
|
|
1309
|
+
if (node.status === "completed") {
|
|
1310
|
+
succeeded++;
|
|
1311
|
+
} else if (node.status === "failed" || node.status === "hung" || node.status === "timeout") {
|
|
1312
|
+
failed++;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
const attempted = nodes.length;
|
|
1316
|
+
let totalTokenCost = null;
|
|
1317
|
+
for (const step of steps) {
|
|
1318
|
+
if (step.tokenCost !== null) {
|
|
1319
|
+
totalTokenCost = (totalTokenCost ?? 0) + step.tokenCost;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
const totalDurationMs = graph.endTime !== null ? graph.endTime - graph.startTime : null;
|
|
1323
|
+
return {
|
|
1324
|
+
runId: graph.id,
|
|
1325
|
+
agentId: graph.agentId,
|
|
1326
|
+
status: graph.status,
|
|
1327
|
+
startTime: graph.startTime,
|
|
1328
|
+
endTime: graph.endTime,
|
|
1329
|
+
totalDurationMs,
|
|
1330
|
+
totalTokenCost,
|
|
1331
|
+
steps,
|
|
1332
|
+
summary: { attempted, succeeded, failed, skipped }
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
function formatReceipt(receipt) {
|
|
1336
|
+
const lines = [];
|
|
1337
|
+
lines.push("=== Run Receipt ===");
|
|
1338
|
+
lines.push(`Run: ${receipt.runId}`);
|
|
1339
|
+
lines.push(`Agent: ${receipt.agentId}`);
|
|
1340
|
+
lines.push(`Status: ${receipt.status}`);
|
|
1341
|
+
lines.push(
|
|
1342
|
+
`Duration: ${receipt.totalDurationMs !== null ? `${receipt.totalDurationMs}ms` : "\u2014"}`
|
|
1343
|
+
);
|
|
1344
|
+
lines.push("");
|
|
1345
|
+
const s = receipt.summary;
|
|
1346
|
+
lines.push(
|
|
1347
|
+
`Summary: ${s.attempted} attempted, ${s.succeeded} succeeded, ${s.failed} failed, ${s.skipped} skipped`
|
|
1348
|
+
);
|
|
1349
|
+
lines.push("");
|
|
1350
|
+
const nameWidth = Math.max(4, ...receipt.steps.map((st) => st.name.length));
|
|
1351
|
+
const typeWidth = Math.max(4, ...receipt.steps.map((st) => st.type.length));
|
|
1352
|
+
const pad = (str, width) => str.padEnd(width);
|
|
1353
|
+
const padNum = (str, width) => str.padStart(width);
|
|
1354
|
+
const idxWidth = Math.max(2, String(receipt.steps.length).length);
|
|
1355
|
+
const statusWidth = 9;
|
|
1356
|
+
const durWidth = 10;
|
|
1357
|
+
const tokWidth = 8;
|
|
1358
|
+
lines.push(
|
|
1359
|
+
` ${padNum("#", idxWidth)} | ${pad("Step", nameWidth)} | ${pad("Type", typeWidth)} | ${pad("Status", statusWidth)} | ${padNum("Duration", durWidth)} | ${padNum("Tokens", tokWidth)}`
|
|
1360
|
+
);
|
|
1361
|
+
lines.push(
|
|
1362
|
+
`${"-".repeat(idxWidth + 1)}|${"-".repeat(nameWidth + 2)}|${"-".repeat(typeWidth + 2)}|${"-".repeat(statusWidth + 2)}|${"-".repeat(durWidth + 2)}|${"-".repeat(tokWidth + 2)}`
|
|
1363
|
+
);
|
|
1364
|
+
for (let i = 0; i < receipt.steps.length; i++) {
|
|
1365
|
+
const step = receipt.steps[i];
|
|
1366
|
+
const durStr = step.durationMs !== null ? `${step.durationMs}ms` : "\u2014";
|
|
1367
|
+
const tokStr = step.tokenCost !== null ? String(step.tokenCost) : "\u2014";
|
|
1368
|
+
lines.push(
|
|
1369
|
+
` ${padNum(String(i + 1), idxWidth)} | ${pad(step.name, nameWidth)} | ${pad(step.type, typeWidth)} | ${pad(step.status, statusWidth)} | ${padNum(durStr, durWidth)} | ${padNum(tokStr, tokWidth)}`
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
lines.push("");
|
|
1373
|
+
if (receipt.totalTokenCost !== null) {
|
|
1374
|
+
lines.push(`Total token cost: ${receipt.totalTokenCost}`);
|
|
1375
|
+
} else {
|
|
1376
|
+
lines.push("Total token cost: no cost data");
|
|
1377
|
+
}
|
|
1378
|
+
return lines.join("\n");
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1212
1381
|
// src/soma-event-writer.ts
|
|
1213
1382
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1214
1383
|
import { join as join4 } from "path";
|
|
@@ -1241,11 +1410,7 @@ function formatDuration(ms) {
|
|
|
1241
1410
|
function executionEventToMarkdown(event) {
|
|
1242
1411
|
const isCompleted = event.eventType === "execution.completed";
|
|
1243
1412
|
const subtype = isCompleted ? "completed" : "failed";
|
|
1244
|
-
const tags = [
|
|
1245
|
-
"agentflow/execution",
|
|
1246
|
-
`agent/${event.agentId}`,
|
|
1247
|
-
`status/${subtype}`
|
|
1248
|
-
];
|
|
1413
|
+
const tags = ["agentflow/execution", `agent/${event.agentId}`, `status/${subtype}`];
|
|
1249
1414
|
if (event.processContext?.isAnomaly) {
|
|
1250
1415
|
tags.push("agentflow/anomaly");
|
|
1251
1416
|
}
|
|
@@ -1303,10 +1468,7 @@ ${body.join("\n")}`;
|
|
|
1303
1468
|
}
|
|
1304
1469
|
function patternEventToMarkdown(event) {
|
|
1305
1470
|
const { pattern } = event;
|
|
1306
|
-
const tags = [
|
|
1307
|
-
"agentflow/pattern",
|
|
1308
|
-
`agent/${event.agentId}`
|
|
1309
|
-
];
|
|
1471
|
+
const tags = ["agentflow/pattern", `agent/${event.agentId}`];
|
|
1310
1472
|
const frontmatter = {
|
|
1311
1473
|
type: "synthesis",
|
|
1312
1474
|
subtype: "pattern-discovery",
|
|
@@ -1393,11 +1555,13 @@ export {
|
|
|
1393
1555
|
createPolicySource,
|
|
1394
1556
|
createSomaEventWriter,
|
|
1395
1557
|
createTraceStore,
|
|
1558
|
+
discoverAllProcessConfigs,
|
|
1396
1559
|
discoverProcess,
|
|
1397
1560
|
discoverProcessConfig,
|
|
1398
1561
|
findVariants,
|
|
1399
1562
|
findWaitingOn,
|
|
1400
1563
|
formatAuditReport,
|
|
1564
|
+
formatReceipt,
|
|
1401
1565
|
getBottlenecks,
|
|
1402
1566
|
getChildren,
|
|
1403
1567
|
getCriticalPath,
|
|
@@ -1419,6 +1583,7 @@ export {
|
|
|
1419
1583
|
startWatch,
|
|
1420
1584
|
stitchTrace,
|
|
1421
1585
|
toAsciiTree,
|
|
1586
|
+
toReceipt,
|
|
1422
1587
|
toTimeline,
|
|
1423
1588
|
withGuards
|
|
1424
1589
|
};
|