opencode-swarm-plugin 0.12.6 → 0.12.8
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/.beads/issues.jsonl +9 -0
- package/dist/index.js +326 -26
- package/dist/plugin.js +325 -26
- package/package.json +1 -1
- package/src/agent-mail.ts +184 -32
- package/src/learning.ts +277 -0
- package/src/swarm.ts +176 -1
package/src/agent-mail.ts
CHANGED
|
@@ -38,10 +38,10 @@ const RETRY_CONFIG = {
|
|
|
38
38
|
|
|
39
39
|
// Server recovery configuration
|
|
40
40
|
const RECOVERY_CONFIG = {
|
|
41
|
-
/** Max consecutive failures before attempting restart */
|
|
42
|
-
failureThreshold:
|
|
43
|
-
/** Cooldown between restart attempts (ms) */
|
|
44
|
-
restartCooldownMs:
|
|
41
|
+
/** Max consecutive failures before attempting restart (1 = restart on first "unexpected error") */
|
|
42
|
+
failureThreshold: 1,
|
|
43
|
+
/** Cooldown between restart attempts (ms) - 10 seconds */
|
|
44
|
+
restartCooldownMs: 10000,
|
|
45
45
|
/** Whether auto-restart is enabled */
|
|
46
46
|
enabled: process.env.OPENCODE_AGENT_MAIL_AUTO_RESTART !== "false",
|
|
47
47
|
};
|
|
@@ -704,6 +704,7 @@ export async function mcpCall<T>(
|
|
|
704
704
|
args: Record<string, unknown>,
|
|
705
705
|
): Promise<T> {
|
|
706
706
|
let lastError: Error | null = null;
|
|
707
|
+
let restartAttempted = false;
|
|
707
708
|
|
|
708
709
|
for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
|
|
709
710
|
// Apply backoff delay (except first attempt)
|
|
@@ -723,17 +724,39 @@ export async function mcpCall<T>(
|
|
|
723
724
|
return result;
|
|
724
725
|
} catch (error) {
|
|
725
726
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
727
|
+
const errorMessage = lastError.message.toLowerCase();
|
|
726
728
|
|
|
727
729
|
// Track consecutive failures
|
|
728
730
|
consecutiveFailures++;
|
|
729
731
|
|
|
730
|
-
// Check if error is retryable
|
|
732
|
+
// Check if error is retryable
|
|
731
733
|
const retryable = isRetryableError(error);
|
|
732
734
|
|
|
733
|
-
//
|
|
735
|
+
// AGGRESSIVE: If it's an "unexpected error", restart immediately (once per call)
|
|
736
|
+
const isUnexpectedError = errorMessage.includes("unexpected error");
|
|
737
|
+
if (isUnexpectedError && !restartAttempted && RECOVERY_CONFIG.enabled) {
|
|
738
|
+
console.warn(
|
|
739
|
+
`[agent-mail] "${toolName}" got unexpected error, restarting server immediately...`,
|
|
740
|
+
);
|
|
741
|
+
restartAttempted = true;
|
|
742
|
+
const restarted = await restartServer();
|
|
743
|
+
if (restarted) {
|
|
744
|
+
agentMailAvailable = null;
|
|
745
|
+
consecutiveFailures = 0;
|
|
746
|
+
// Small delay to let server stabilize
|
|
747
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
748
|
+
// Don't count this attempt - retry immediately
|
|
749
|
+
attempt--;
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Standard retry logic for other retryable errors
|
|
734
755
|
if (
|
|
756
|
+
!isUnexpectedError &&
|
|
735
757
|
consecutiveFailures >= RECOVERY_CONFIG.failureThreshold &&
|
|
736
|
-
RECOVERY_CONFIG.enabled
|
|
758
|
+
RECOVERY_CONFIG.enabled &&
|
|
759
|
+
!restartAttempted
|
|
737
760
|
) {
|
|
738
761
|
console.warn(
|
|
739
762
|
`[agent-mail] ${consecutiveFailures} consecutive failures, checking server health...`,
|
|
@@ -742,13 +765,11 @@ export async function mcpCall<T>(
|
|
|
742
765
|
const healthy = await isServerFunctional();
|
|
743
766
|
if (!healthy) {
|
|
744
767
|
console.warn("[agent-mail] Server unhealthy, attempting restart...");
|
|
768
|
+
restartAttempted = true;
|
|
745
769
|
const restarted = await restartServer();
|
|
746
770
|
if (restarted) {
|
|
747
|
-
// Reset availability cache since server restarted
|
|
748
771
|
agentMailAvailable = null;
|
|
749
|
-
// Only retry if the error was retryable in the first place
|
|
750
772
|
if (retryable) {
|
|
751
|
-
// Don't count this attempt against retries - try again
|
|
752
773
|
attempt--;
|
|
753
774
|
continue;
|
|
754
775
|
}
|
|
@@ -880,30 +901,88 @@ export const agentmail_init = tool({
|
|
|
880
901
|
);
|
|
881
902
|
}
|
|
882
903
|
|
|
883
|
-
//
|
|
884
|
-
const
|
|
885
|
-
|
|
886
|
-
});
|
|
904
|
+
// Retry loop with restart on failure
|
|
905
|
+
const MAX_INIT_RETRIES = 3;
|
|
906
|
+
let lastError: Error | null = null;
|
|
887
907
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
908
|
+
for (let attempt = 1; attempt <= MAX_INIT_RETRIES; attempt++) {
|
|
909
|
+
try {
|
|
910
|
+
// 1. Ensure project exists
|
|
911
|
+
const project = await mcpCall<ProjectInfo>("ensure_project", {
|
|
912
|
+
human_key: args.project_path,
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
// 2. Register agent
|
|
916
|
+
const agent = await mcpCall<AgentInfo>("register_agent", {
|
|
917
|
+
project_key: args.project_path,
|
|
918
|
+
program: "opencode",
|
|
919
|
+
model: "claude-opus-4",
|
|
920
|
+
name: args.agent_name, // undefined = auto-generate
|
|
921
|
+
task_description: args.task_description || "",
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
// 3. Store state using sessionID
|
|
925
|
+
const state: AgentMailState = {
|
|
926
|
+
projectKey: args.project_path,
|
|
927
|
+
agentName: agent.name,
|
|
928
|
+
reservations: [],
|
|
929
|
+
startedAt: new Date().toISOString(),
|
|
930
|
+
};
|
|
931
|
+
setState(ctx.sessionID, state);
|
|
932
|
+
|
|
933
|
+
// Success - if we retried, log it
|
|
934
|
+
if (attempt > 1) {
|
|
935
|
+
console.warn(
|
|
936
|
+
`[agent-mail] Init succeeded on attempt ${attempt} after restart`,
|
|
937
|
+
);
|
|
938
|
+
}
|
|
896
939
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
940
|
+
return JSON.stringify({ project, agent, available: true }, null, 2);
|
|
941
|
+
} catch (error) {
|
|
942
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
943
|
+
const isUnexpectedError = lastError.message
|
|
944
|
+
.toLowerCase()
|
|
945
|
+
.includes("unexpected error");
|
|
946
|
+
|
|
947
|
+
console.warn(
|
|
948
|
+
`[agent-mail] Init attempt ${attempt}/${MAX_INIT_RETRIES} failed: ${lastError.message}`,
|
|
949
|
+
);
|
|
950
|
+
|
|
951
|
+
// If it's an "unexpected error" and we have retries left, restart and retry
|
|
952
|
+
if (isUnexpectedError && attempt < MAX_INIT_RETRIES) {
|
|
953
|
+
console.warn(
|
|
954
|
+
"[agent-mail] Detected 'unexpected error', restarting server...",
|
|
955
|
+
);
|
|
956
|
+
const restarted = await restartServer();
|
|
957
|
+
if (restarted) {
|
|
958
|
+
// Clear cache and retry
|
|
959
|
+
agentMailAvailable = null;
|
|
960
|
+
consecutiveFailures = 0;
|
|
961
|
+
// Small delay to let server stabilize
|
|
962
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// For non-unexpected errors or if restart failed, don't retry
|
|
968
|
+
if (!isUnexpectedError) {
|
|
969
|
+
break;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
905
973
|
|
|
906
|
-
|
|
974
|
+
// All retries exhausted
|
|
975
|
+
return JSON.stringify(
|
|
976
|
+
{
|
|
977
|
+
error: `Agent Mail init failed after ${MAX_INIT_RETRIES} attempts`,
|
|
978
|
+
available: false,
|
|
979
|
+
lastError: lastError?.message,
|
|
980
|
+
hint: "Manually restart Agent Mail: pkill -f agent-mail && agent-mail serve",
|
|
981
|
+
fallback: "Swarm will continue without multi-agent coordination.",
|
|
982
|
+
},
|
|
983
|
+
null,
|
|
984
|
+
2,
|
|
985
|
+
);
|
|
907
986
|
},
|
|
908
987
|
});
|
|
909
988
|
|
|
@@ -1270,7 +1349,12 @@ export const agentmail_health = tool({
|
|
|
1270
1349
|
try {
|
|
1271
1350
|
const response = await fetch(`${AGENT_MAIL_URL}/health/liveness`);
|
|
1272
1351
|
if (response.ok) {
|
|
1273
|
-
|
|
1352
|
+
// Also check if MCP is functional
|
|
1353
|
+
const functional = await isServerFunctional();
|
|
1354
|
+
if (functional) {
|
|
1355
|
+
return "Agent Mail is running and functional";
|
|
1356
|
+
}
|
|
1357
|
+
return "Agent Mail health OK but MCP not responding - consider restart";
|
|
1274
1358
|
}
|
|
1275
1359
|
return `Agent Mail returned status ${response.status}`;
|
|
1276
1360
|
} catch (error) {
|
|
@@ -1279,6 +1363,73 @@ export const agentmail_health = tool({
|
|
|
1279
1363
|
},
|
|
1280
1364
|
});
|
|
1281
1365
|
|
|
1366
|
+
/**
|
|
1367
|
+
* Manually restart Agent Mail server
|
|
1368
|
+
*
|
|
1369
|
+
* Use when server is in bad state (health OK but MCP failing).
|
|
1370
|
+
* This kills the existing process and starts a fresh one.
|
|
1371
|
+
*/
|
|
1372
|
+
export const agentmail_restart = tool({
|
|
1373
|
+
description:
|
|
1374
|
+
"Manually restart Agent Mail server (use when getting 'unexpected error')",
|
|
1375
|
+
args: {
|
|
1376
|
+
force: tool.schema
|
|
1377
|
+
.boolean()
|
|
1378
|
+
.optional()
|
|
1379
|
+
.describe(
|
|
1380
|
+
"Force restart even if server appears healthy (default: false)",
|
|
1381
|
+
),
|
|
1382
|
+
},
|
|
1383
|
+
async execute(args) {
|
|
1384
|
+
// Check if restart is needed
|
|
1385
|
+
if (!args.force) {
|
|
1386
|
+
const functional = await isServerFunctional();
|
|
1387
|
+
if (functional) {
|
|
1388
|
+
return JSON.stringify(
|
|
1389
|
+
{
|
|
1390
|
+
restarted: false,
|
|
1391
|
+
reason: "Server is functional, no restart needed",
|
|
1392
|
+
hint: "Use force=true to restart anyway",
|
|
1393
|
+
},
|
|
1394
|
+
null,
|
|
1395
|
+
2,
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// Attempt restart
|
|
1401
|
+
console.warn("[agent-mail] Manual restart requested...");
|
|
1402
|
+
const success = await restartServer();
|
|
1403
|
+
|
|
1404
|
+
// Clear caches
|
|
1405
|
+
agentMailAvailable = null;
|
|
1406
|
+
consecutiveFailures = 0;
|
|
1407
|
+
|
|
1408
|
+
if (success) {
|
|
1409
|
+
return JSON.stringify(
|
|
1410
|
+
{
|
|
1411
|
+
restarted: true,
|
|
1412
|
+
success: true,
|
|
1413
|
+
message: "Agent Mail server restarted successfully",
|
|
1414
|
+
},
|
|
1415
|
+
null,
|
|
1416
|
+
2,
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
return JSON.stringify(
|
|
1421
|
+
{
|
|
1422
|
+
restarted: true,
|
|
1423
|
+
success: false,
|
|
1424
|
+
error: "Restart attempted but server did not come back up",
|
|
1425
|
+
hint: "Check server logs or manually start: agent-mail serve",
|
|
1426
|
+
},
|
|
1427
|
+
null,
|
|
1428
|
+
2,
|
|
1429
|
+
);
|
|
1430
|
+
},
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1282
1433
|
// ============================================================================
|
|
1283
1434
|
// Export all tools
|
|
1284
1435
|
// ============================================================================
|
|
@@ -1294,6 +1445,7 @@ export const agentMailTools = {
|
|
|
1294
1445
|
agentmail_ack: agentmail_ack,
|
|
1295
1446
|
agentmail_search: agentmail_search,
|
|
1296
1447
|
agentmail_health: agentmail_health,
|
|
1448
|
+
agentmail_restart: agentmail_restart,
|
|
1297
1449
|
};
|
|
1298
1450
|
|
|
1299
1451
|
// ============================================================================
|
package/src/learning.ts
CHANGED
|
@@ -64,6 +64,46 @@ export const CriterionWeightSchema = z.object({
|
|
|
64
64
|
});
|
|
65
65
|
export type CriterionWeight = z.infer<typeof CriterionWeightSchema>;
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Error types that can occur during subtask execution
|
|
69
|
+
*/
|
|
70
|
+
export const ErrorTypeSchema = z.enum([
|
|
71
|
+
"validation",
|
|
72
|
+
"timeout",
|
|
73
|
+
"conflict",
|
|
74
|
+
"tool_failure",
|
|
75
|
+
"unknown",
|
|
76
|
+
]);
|
|
77
|
+
export type ErrorType = z.infer<typeof ErrorTypeSchema>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* An error entry in the error accumulator
|
|
81
|
+
*
|
|
82
|
+
* Errors are accumulated during subtask execution and can be fed
|
|
83
|
+
* into retry prompts to help agents learn from past failures.
|
|
84
|
+
*/
|
|
85
|
+
export const ErrorEntrySchema = z.object({
|
|
86
|
+
/** Unique ID for this error entry */
|
|
87
|
+
id: z.string(),
|
|
88
|
+
/** The bead ID this error relates to */
|
|
89
|
+
bead_id: z.string(),
|
|
90
|
+
/** Type of error encountered */
|
|
91
|
+
error_type: ErrorTypeSchema,
|
|
92
|
+
/** Human-readable error message */
|
|
93
|
+
message: z.string(),
|
|
94
|
+
/** Optional stack trace for debugging */
|
|
95
|
+
stack_trace: z.string().optional(),
|
|
96
|
+
/** Tool that failed, if applicable */
|
|
97
|
+
tool_name: z.string().optional(),
|
|
98
|
+
/** When this error occurred */
|
|
99
|
+
timestamp: z.string(), // ISO-8601
|
|
100
|
+
/** Whether this error was resolved */
|
|
101
|
+
resolved: z.boolean().default(false),
|
|
102
|
+
/** Context about what was happening when error occurred */
|
|
103
|
+
context: z.string().optional(),
|
|
104
|
+
});
|
|
105
|
+
export type ErrorEntry = z.infer<typeof ErrorEntrySchema>;
|
|
106
|
+
|
|
67
107
|
/**
|
|
68
108
|
* Decomposition strategies for tracking which approach was used
|
|
69
109
|
*/
|
|
@@ -437,6 +477,241 @@ export class InMemoryFeedbackStorage implements FeedbackStorage {
|
|
|
437
477
|
}
|
|
438
478
|
}
|
|
439
479
|
|
|
480
|
+
// ============================================================================
|
|
481
|
+
// Error Accumulator
|
|
482
|
+
// ============================================================================
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Storage interface for error entries
|
|
486
|
+
*
|
|
487
|
+
* Similar to FeedbackStorage but for tracking errors during execution.
|
|
488
|
+
*/
|
|
489
|
+
export interface ErrorStorage {
|
|
490
|
+
/** Store an error entry */
|
|
491
|
+
store(entry: ErrorEntry): Promise<void>;
|
|
492
|
+
/** Get all errors for a bead */
|
|
493
|
+
getByBead(beadId: string): Promise<ErrorEntry[]>;
|
|
494
|
+
/** Get unresolved errors for a bead */
|
|
495
|
+
getUnresolvedByBead(beadId: string): Promise<ErrorEntry[]>;
|
|
496
|
+
/** Mark an error as resolved */
|
|
497
|
+
markResolved(id: string): Promise<void>;
|
|
498
|
+
/** Get all errors */
|
|
499
|
+
getAll(): Promise<ErrorEntry[]>;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* In-memory error storage
|
|
504
|
+
*
|
|
505
|
+
* Accumulates errors during subtask execution for feeding into retry prompts.
|
|
506
|
+
*/
|
|
507
|
+
export class InMemoryErrorStorage implements ErrorStorage {
|
|
508
|
+
private errors: ErrorEntry[] = [];
|
|
509
|
+
|
|
510
|
+
async store(entry: ErrorEntry): Promise<void> {
|
|
511
|
+
this.errors.push(entry);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async getByBead(beadId: string): Promise<ErrorEntry[]> {
|
|
515
|
+
return this.errors.filter((e) => e.bead_id === beadId);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async getUnresolvedByBead(beadId: string): Promise<ErrorEntry[]> {
|
|
519
|
+
return this.errors.filter((e) => e.bead_id === beadId && !e.resolved);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async markResolved(id: string): Promise<void> {
|
|
523
|
+
const error = this.errors.find((e) => e.id === id);
|
|
524
|
+
if (error) {
|
|
525
|
+
error.resolved = true;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
async getAll(): Promise<ErrorEntry[]> {
|
|
530
|
+
return [...this.errors];
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Error accumulator for tracking errors during subtask execution
|
|
536
|
+
*
|
|
537
|
+
* Implements patterns from "Patterns for Building AI Agents" p.40:
|
|
538
|
+
* - Examines and corrects errors when something goes wrong
|
|
539
|
+
* - Feeds error context into retry prompts
|
|
540
|
+
* - Tracks error patterns for learning
|
|
541
|
+
*/
|
|
542
|
+
export class ErrorAccumulator {
|
|
543
|
+
private storage: ErrorStorage;
|
|
544
|
+
|
|
545
|
+
constructor(storage?: ErrorStorage) {
|
|
546
|
+
this.storage = storage ?? new InMemoryErrorStorage();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Record an error during subtask execution
|
|
551
|
+
*
|
|
552
|
+
* @param beadId - Bead ID where error occurred
|
|
553
|
+
* @param errorType - Category of error
|
|
554
|
+
* @param message - Human-readable error message
|
|
555
|
+
* @param options - Additional context (stack trace, tool name, etc.)
|
|
556
|
+
* @returns The created error entry
|
|
557
|
+
*/
|
|
558
|
+
async recordError(
|
|
559
|
+
beadId: string,
|
|
560
|
+
errorType: ErrorType,
|
|
561
|
+
message: string,
|
|
562
|
+
options?: {
|
|
563
|
+
stack_trace?: string;
|
|
564
|
+
tool_name?: string;
|
|
565
|
+
context?: string;
|
|
566
|
+
},
|
|
567
|
+
): Promise<ErrorEntry> {
|
|
568
|
+
const entry: ErrorEntry = {
|
|
569
|
+
id: `${beadId}-${errorType}-${Date.now()}`,
|
|
570
|
+
bead_id: beadId,
|
|
571
|
+
error_type: errorType,
|
|
572
|
+
message,
|
|
573
|
+
stack_trace: options?.stack_trace,
|
|
574
|
+
tool_name: options?.tool_name,
|
|
575
|
+
timestamp: new Date().toISOString(),
|
|
576
|
+
resolved: false,
|
|
577
|
+
context: options?.context,
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const validated = ErrorEntrySchema.parse(entry);
|
|
581
|
+
await this.storage.store(validated);
|
|
582
|
+
|
|
583
|
+
return validated;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Get all errors for a bead (resolved and unresolved)
|
|
588
|
+
*/
|
|
589
|
+
async getErrors(beadId: string): Promise<ErrorEntry[]> {
|
|
590
|
+
return this.storage.getByBead(beadId);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Get only unresolved errors for a bead
|
|
595
|
+
*/
|
|
596
|
+
async getUnresolvedErrors(beadId: string): Promise<ErrorEntry[]> {
|
|
597
|
+
return this.storage.getUnresolvedByBead(beadId);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Mark an error as resolved
|
|
602
|
+
*/
|
|
603
|
+
async resolveError(errorId: string): Promise<void> {
|
|
604
|
+
await this.storage.markResolved(errorId);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Format errors as context for retry prompts
|
|
609
|
+
*
|
|
610
|
+
* Groups errors by type and provides structured feedback
|
|
611
|
+
* for the agent to learn from.
|
|
612
|
+
*
|
|
613
|
+
* @param beadId - Bead to get error context for
|
|
614
|
+
* @param includeResolved - Include resolved errors (default: false)
|
|
615
|
+
* @returns Formatted error context string
|
|
616
|
+
*/
|
|
617
|
+
async getErrorContext(
|
|
618
|
+
beadId: string,
|
|
619
|
+
includeResolved = false,
|
|
620
|
+
): Promise<string> {
|
|
621
|
+
const errors = includeResolved
|
|
622
|
+
? await this.getErrors(beadId)
|
|
623
|
+
: await this.getUnresolvedErrors(beadId);
|
|
624
|
+
|
|
625
|
+
if (errors.length === 0) {
|
|
626
|
+
return "";
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Group errors by type
|
|
630
|
+
const byType = errors.reduce(
|
|
631
|
+
(acc, err) => {
|
|
632
|
+
const type = err.error_type;
|
|
633
|
+
if (!acc[type]) {
|
|
634
|
+
acc[type] = [];
|
|
635
|
+
}
|
|
636
|
+
acc[type].push(err);
|
|
637
|
+
return acc;
|
|
638
|
+
},
|
|
639
|
+
{} as Record<ErrorType, ErrorEntry[]>,
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
// Format as structured feedback
|
|
643
|
+
const lines = [
|
|
644
|
+
"## Previous Errors",
|
|
645
|
+
"",
|
|
646
|
+
"The following errors were encountered during execution:",
|
|
647
|
+
"",
|
|
648
|
+
];
|
|
649
|
+
|
|
650
|
+
for (const [type, typeErrors] of Object.entries(byType)) {
|
|
651
|
+
lines.push(
|
|
652
|
+
`### ${type} (${typeErrors.length} error${typeErrors.length > 1 ? "s" : ""})`,
|
|
653
|
+
);
|
|
654
|
+
lines.push("");
|
|
655
|
+
|
|
656
|
+
for (const err of typeErrors) {
|
|
657
|
+
lines.push(`- **${err.message}**`);
|
|
658
|
+
if (err.context) {
|
|
659
|
+
lines.push(` - Context: ${err.context}`);
|
|
660
|
+
}
|
|
661
|
+
if (err.tool_name) {
|
|
662
|
+
lines.push(` - Tool: ${err.tool_name}`);
|
|
663
|
+
}
|
|
664
|
+
if (err.stack_trace) {
|
|
665
|
+
lines.push(` - Stack: \`${err.stack_trace.slice(0, 100)}...\``);
|
|
666
|
+
}
|
|
667
|
+
lines.push(
|
|
668
|
+
` - Time: ${new Date(err.timestamp).toLocaleString()}${err.resolved ? " (resolved)" : ""}`,
|
|
669
|
+
);
|
|
670
|
+
lines.push("");
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
lines.push(
|
|
675
|
+
"**Action Required**: Address these errors before proceeding. Consider:",
|
|
676
|
+
);
|
|
677
|
+
lines.push("- What caused each error?");
|
|
678
|
+
lines.push("- How can you prevent similar errors?");
|
|
679
|
+
lines.push("- Are there patterns across error types?");
|
|
680
|
+
lines.push("");
|
|
681
|
+
|
|
682
|
+
return lines.join("\n");
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Get error statistics for outcome tracking
|
|
687
|
+
*
|
|
688
|
+
* @param beadId - Bead to get stats for
|
|
689
|
+
* @returns Error counts and patterns
|
|
690
|
+
*/
|
|
691
|
+
async getErrorStats(beadId: string): Promise<{
|
|
692
|
+
total: number;
|
|
693
|
+
unresolved: number;
|
|
694
|
+
by_type: Record<ErrorType, number>;
|
|
695
|
+
}> {
|
|
696
|
+
const allErrors = await this.getErrors(beadId);
|
|
697
|
+
const unresolved = await this.getUnresolvedErrors(beadId);
|
|
698
|
+
|
|
699
|
+
const byType = allErrors.reduce(
|
|
700
|
+
(acc, err) => {
|
|
701
|
+
acc[err.error_type] = (acc[err.error_type] || 0) + 1;
|
|
702
|
+
return acc;
|
|
703
|
+
},
|
|
704
|
+
{} as Record<ErrorType, number>,
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
return {
|
|
708
|
+
total: allErrors.length,
|
|
709
|
+
unresolved: unresolved.length,
|
|
710
|
+
by_type: byType,
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
440
715
|
// ============================================================================
|
|
441
716
|
// Exports
|
|
442
717
|
// ============================================================================
|
|
@@ -448,4 +723,6 @@ export const learningSchemas = {
|
|
|
448
723
|
OutcomeSignalsSchema,
|
|
449
724
|
ScoredOutcomeSchema,
|
|
450
725
|
DecompositionStrategySchema,
|
|
726
|
+
ErrorTypeSchema,
|
|
727
|
+
ErrorEntrySchema,
|
|
451
728
|
};
|