opencode-swarm 5.0.3 → 5.0.5
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/dist/config/schema.d.ts +10 -2
- package/dist/index.js +103 -36
- package/dist/state.d.ts +15 -1
- package/package.json +1 -1
package/dist/config/schema.d.ts
CHANGED
|
@@ -45,7 +45,15 @@ export declare const GuardrailsProfileSchema: z.ZodObject<{
|
|
|
45
45
|
warning_threshold: z.ZodOptional<z.ZodNumber>;
|
|
46
46
|
}, z.core.$strip>;
|
|
47
47
|
export type GuardrailsProfile = z.infer<typeof GuardrailsProfileSchema>;
|
|
48
|
-
export declare const
|
|
48
|
+
export declare const DEFAULT_AGENT_PROFILES: Record<string, GuardrailsProfile>;
|
|
49
|
+
/** @deprecated Use DEFAULT_AGENT_PROFILES.architect instead */
|
|
50
|
+
export declare const DEFAULT_ARCHITECT_PROFILE: {
|
|
51
|
+
max_tool_calls?: number | undefined;
|
|
52
|
+
max_duration_minutes?: number | undefined;
|
|
53
|
+
max_repetitions?: number | undefined;
|
|
54
|
+
max_consecutive_errors?: number | undefined;
|
|
55
|
+
warning_threshold?: number | undefined;
|
|
56
|
+
};
|
|
49
57
|
export declare const GuardrailsConfigSchema: z.ZodObject<{
|
|
50
58
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
51
59
|
max_tool_calls: z.ZodDefault<z.ZodNumber>;
|
|
@@ -76,7 +84,7 @@ export type GuardrailsConfig = z.infer<typeof GuardrailsConfigSchema>;
|
|
|
76
84
|
export declare function stripKnownSwarmPrefix(name: string): string;
|
|
77
85
|
/**
|
|
78
86
|
* Resolve guardrails configuration for a specific agent.
|
|
79
|
-
* Merges the base config with built-in defaults
|
|
87
|
+
* Merges the base config with built-in agent-type defaults and
|
|
80
88
|
* any per-agent profile overrides. Merge order: base < built-in < user profile.
|
|
81
89
|
*
|
|
82
90
|
* @param base - The base guardrails configuration
|
package/dist/index.js
CHANGED
|
@@ -13623,19 +13623,52 @@ var GuardrailsProfileSchema = exports_external.object({
|
|
|
13623
13623
|
max_consecutive_errors: exports_external.number().min(2).max(20).optional(),
|
|
13624
13624
|
warning_threshold: exports_external.number().min(0.1).max(0.9).optional()
|
|
13625
13625
|
});
|
|
13626
|
-
var
|
|
13627
|
-
|
|
13628
|
-
|
|
13629
|
-
|
|
13630
|
-
|
|
13626
|
+
var DEFAULT_AGENT_PROFILES = {
|
|
13627
|
+
architect: {
|
|
13628
|
+
max_tool_calls: 800,
|
|
13629
|
+
max_duration_minutes: 90,
|
|
13630
|
+
max_consecutive_errors: 8,
|
|
13631
|
+
warning_threshold: 0.75
|
|
13632
|
+
},
|
|
13633
|
+
coder: {
|
|
13634
|
+
max_tool_calls: 400,
|
|
13635
|
+
max_duration_minutes: 45,
|
|
13636
|
+
warning_threshold: 0.85
|
|
13637
|
+
},
|
|
13638
|
+
test_engineer: {
|
|
13639
|
+
max_tool_calls: 400,
|
|
13640
|
+
max_duration_minutes: 45,
|
|
13641
|
+
warning_threshold: 0.85
|
|
13642
|
+
},
|
|
13643
|
+
explorer: {
|
|
13644
|
+
max_tool_calls: 150,
|
|
13645
|
+
max_duration_minutes: 20,
|
|
13646
|
+
warning_threshold: 0.75
|
|
13647
|
+
},
|
|
13648
|
+
reviewer: {
|
|
13649
|
+
max_tool_calls: 200,
|
|
13650
|
+
max_duration_minutes: 30,
|
|
13651
|
+
warning_threshold: 0.65
|
|
13652
|
+
},
|
|
13653
|
+
critic: {
|
|
13654
|
+
max_tool_calls: 200,
|
|
13655
|
+
max_duration_minutes: 30,
|
|
13656
|
+
warning_threshold: 0.65
|
|
13657
|
+
},
|
|
13658
|
+
sme: {
|
|
13659
|
+
max_tool_calls: 200,
|
|
13660
|
+
max_duration_minutes: 30,
|
|
13661
|
+
warning_threshold: 0.65
|
|
13662
|
+
}
|
|
13631
13663
|
};
|
|
13664
|
+
var DEFAULT_ARCHITECT_PROFILE = DEFAULT_AGENT_PROFILES.architect;
|
|
13632
13665
|
var GuardrailsConfigSchema = exports_external.object({
|
|
13633
13666
|
enabled: exports_external.boolean().default(true),
|
|
13634
13667
|
max_tool_calls: exports_external.number().min(10).max(1000).default(200),
|
|
13635
13668
|
max_duration_minutes: exports_external.number().min(1).max(120).default(30),
|
|
13636
13669
|
max_repetitions: exports_external.number().min(3).max(50).default(10),
|
|
13637
13670
|
max_consecutive_errors: exports_external.number().min(2).max(20).default(5),
|
|
13638
|
-
warning_threshold: exports_external.number().min(0.1).max(0.9).default(0.
|
|
13671
|
+
warning_threshold: exports_external.number().min(0.1).max(0.9).default(0.75),
|
|
13639
13672
|
profiles: exports_external.record(exports_external.string(), GuardrailsProfileSchema).optional()
|
|
13640
13673
|
});
|
|
13641
13674
|
function stripKnownSwarmPrefix(name) {
|
|
@@ -13656,7 +13689,7 @@ function resolveGuardrailsConfig(base, agentName) {
|
|
|
13656
13689
|
return base;
|
|
13657
13690
|
}
|
|
13658
13691
|
const baseName = stripKnownSwarmPrefix(agentName);
|
|
13659
|
-
const builtIn = baseName
|
|
13692
|
+
const builtIn = DEFAULT_AGENT_PROFILES[baseName];
|
|
13660
13693
|
const userProfile = base.profiles?.[baseName] ?? base.profiles?.[agentName];
|
|
13661
13694
|
if (!builtIn && !userProfile) {
|
|
13662
13695
|
return base;
|
|
@@ -15955,11 +15988,11 @@ var swarmState = {
|
|
|
15955
15988
|
pendingEvents: 0,
|
|
15956
15989
|
agentSessions: new Map
|
|
15957
15990
|
};
|
|
15958
|
-
function startAgentSession(sessionId, agentName, staleDurationMs =
|
|
15991
|
+
function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
|
|
15959
15992
|
const now = Date.now();
|
|
15960
15993
|
const staleIds = [];
|
|
15961
15994
|
for (const [id, session] of swarmState.agentSessions) {
|
|
15962
|
-
if (now - session.
|
|
15995
|
+
if (now - session.lastToolCallTime > staleDurationMs) {
|
|
15963
15996
|
staleIds.push(id);
|
|
15964
15997
|
}
|
|
15965
15998
|
}
|
|
@@ -15969,10 +16002,12 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 3600000) {
|
|
|
15969
16002
|
const sessionState = {
|
|
15970
16003
|
agentName,
|
|
15971
16004
|
startTime: now,
|
|
16005
|
+
lastToolCallTime: now,
|
|
15972
16006
|
toolCallCount: 0,
|
|
15973
16007
|
consecutiveErrors: 0,
|
|
15974
16008
|
recentToolCalls: [],
|
|
15975
16009
|
warningIssued: false,
|
|
16010
|
+
warningReason: "",
|
|
15976
16011
|
hardLimitHit: false
|
|
15977
16012
|
};
|
|
15978
16013
|
swarmState.agentSessions.set(sessionId, sessionState);
|
|
@@ -15980,6 +16015,24 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 3600000) {
|
|
|
15980
16015
|
function getAgentSession(sessionId) {
|
|
15981
16016
|
return swarmState.agentSessions.get(sessionId);
|
|
15982
16017
|
}
|
|
16018
|
+
function ensureAgentSession(sessionId, agentName) {
|
|
16019
|
+
const now = Date.now();
|
|
16020
|
+
let session = swarmState.agentSessions.get(sessionId);
|
|
16021
|
+
if (session) {
|
|
16022
|
+
if (agentName && session.agentName === "unknown") {
|
|
16023
|
+
session.agentName = agentName;
|
|
16024
|
+
session.startTime = now;
|
|
16025
|
+
}
|
|
16026
|
+
session.lastToolCallTime = now;
|
|
16027
|
+
return session;
|
|
16028
|
+
}
|
|
16029
|
+
startAgentSession(sessionId, agentName ?? "unknown");
|
|
16030
|
+
session = swarmState.agentSessions.get(sessionId);
|
|
16031
|
+
if (!session) {
|
|
16032
|
+
throw new Error(`Failed to create guardrail session for ${sessionId}`);
|
|
16033
|
+
}
|
|
16034
|
+
return session;
|
|
16035
|
+
}
|
|
15983
16036
|
|
|
15984
16037
|
// src/hooks/agent-activity.ts
|
|
15985
16038
|
function createAgentActivityHooks(config2, directory) {
|
|
@@ -16208,6 +16261,7 @@ function createDelegationTrackerHook(config2) {
|
|
|
16208
16261
|
}
|
|
16209
16262
|
const previousAgent = swarmState.activeAgent.get(input.sessionID);
|
|
16210
16263
|
swarmState.activeAgent.set(input.sessionID, input.agent);
|
|
16264
|
+
ensureAgentSession(input.sessionID, input.agent);
|
|
16211
16265
|
if (config2.hooks?.delegation_tracker === true && previousAgent && previousAgent !== input.agent) {
|
|
16212
16266
|
const entry = {
|
|
16213
16267
|
from: previousAgent,
|
|
@@ -16234,22 +16288,8 @@ function createGuardrailsHooks(config2) {
|
|
|
16234
16288
|
}
|
|
16235
16289
|
return {
|
|
16236
16290
|
toolBefore: async (input, output) => {
|
|
16237
|
-
|
|
16238
|
-
|
|
16239
|
-
const agentName = swarmState.activeAgent.get(input.sessionID) ?? "unknown";
|
|
16240
|
-
startAgentSession(input.sessionID, agentName);
|
|
16241
|
-
session = getAgentSession(input.sessionID);
|
|
16242
|
-
if (!session) {
|
|
16243
|
-
warn(`Failed to create session for ${input.sessionID}`);
|
|
16244
|
-
return;
|
|
16245
|
-
}
|
|
16246
|
-
} else if (session.agentName === "unknown") {
|
|
16247
|
-
const activeAgentName = swarmState.activeAgent.get(input.sessionID);
|
|
16248
|
-
if (activeAgentName) {
|
|
16249
|
-
session.agentName = activeAgentName;
|
|
16250
|
-
session.startTime = Date.now();
|
|
16251
|
-
}
|
|
16252
|
-
}
|
|
16291
|
+
const agentName = swarmState.activeAgent.get(input.sessionID);
|
|
16292
|
+
const session = ensureAgentSession(input.sessionID, agentName);
|
|
16253
16293
|
const agentConfig = resolveGuardrailsConfig(config2, session.agentName);
|
|
16254
16294
|
if (session.hardLimitHit) {
|
|
16255
16295
|
throw new Error("\uD83D\uDED1 CIRCUIT BREAKER: Agent blocked. Hard limit was previously triggered. Stop making tool calls and return your progress summary.");
|
|
@@ -16279,27 +16319,53 @@ function createGuardrailsHooks(config2) {
|
|
|
16279
16319
|
const elapsedMinutes = (Date.now() - session.startTime) / 60000;
|
|
16280
16320
|
if (session.toolCallCount >= agentConfig.max_tool_calls) {
|
|
16281
16321
|
session.hardLimitHit = true;
|
|
16282
|
-
|
|
16322
|
+
warn("Circuit breaker: tool call limit hit", {
|
|
16323
|
+
sessionID: input.sessionID,
|
|
16324
|
+
agentName: session.agentName,
|
|
16325
|
+
resolvedMaxCalls: agentConfig.max_tool_calls,
|
|
16326
|
+
currentCalls: session.toolCallCount
|
|
16327
|
+
});
|
|
16328
|
+
throw new Error(`\uD83D\uDED1 LIMIT REACHED: Tool calls exhausted (${session.toolCallCount}/${agentConfig.max_tool_calls}). Finish the current operation and return your progress summary.`);
|
|
16283
16329
|
}
|
|
16284
16330
|
if (elapsedMinutes >= agentConfig.max_duration_minutes) {
|
|
16285
16331
|
session.hardLimitHit = true;
|
|
16286
|
-
|
|
16332
|
+
warn("Circuit breaker: duration limit hit", {
|
|
16333
|
+
sessionID: input.sessionID,
|
|
16334
|
+
agentName: session.agentName,
|
|
16335
|
+
resolvedMaxMinutes: agentConfig.max_duration_minutes,
|
|
16336
|
+
elapsedMinutes: Math.floor(elapsedMinutes)
|
|
16337
|
+
});
|
|
16338
|
+
throw new Error(`\uD83D\uDED1 LIMIT REACHED: Duration exhausted (${Math.floor(elapsedMinutes)}/${agentConfig.max_duration_minutes} min). Finish the current operation and return your progress summary.`);
|
|
16287
16339
|
}
|
|
16288
16340
|
if (repetitionCount >= agentConfig.max_repetitions) {
|
|
16289
16341
|
session.hardLimitHit = true;
|
|
16290
|
-
throw new Error(`\uD83D\uDED1
|
|
16342
|
+
throw new Error(`\uD83D\uDED1 LIMIT REACHED: Repeated the same tool call ${repetitionCount} times. This suggests a loop. Return your progress summary.`);
|
|
16291
16343
|
}
|
|
16292
16344
|
if (session.consecutiveErrors >= agentConfig.max_consecutive_errors) {
|
|
16293
16345
|
session.hardLimitHit = true;
|
|
16294
|
-
throw new Error(`\uD83D\uDED1
|
|
16346
|
+
throw new Error(`\uD83D\uDED1 LIMIT REACHED: ${session.consecutiveErrors} consecutive tool errors detected. Return your progress summary with details of what went wrong.`);
|
|
16295
16347
|
}
|
|
16296
16348
|
if (!session.warningIssued) {
|
|
16297
|
-
const
|
|
16298
|
-
const
|
|
16299
|
-
const
|
|
16300
|
-
const
|
|
16301
|
-
|
|
16349
|
+
const toolPct = session.toolCallCount / agentConfig.max_tool_calls;
|
|
16350
|
+
const durationPct = elapsedMinutes / agentConfig.max_duration_minutes;
|
|
16351
|
+
const repPct = repetitionCount / agentConfig.max_repetitions;
|
|
16352
|
+
const errorPct = session.consecutiveErrors / agentConfig.max_consecutive_errors;
|
|
16353
|
+
const reasons = [];
|
|
16354
|
+
if (toolPct >= agentConfig.warning_threshold) {
|
|
16355
|
+
reasons.push(`tool calls ${session.toolCallCount}/${agentConfig.max_tool_calls}`);
|
|
16356
|
+
}
|
|
16357
|
+
if (durationPct >= agentConfig.warning_threshold) {
|
|
16358
|
+
reasons.push(`duration ${Math.floor(elapsedMinutes)}/${agentConfig.max_duration_minutes} min`);
|
|
16359
|
+
}
|
|
16360
|
+
if (repPct >= agentConfig.warning_threshold) {
|
|
16361
|
+
reasons.push(`repetitions ${repetitionCount}/${agentConfig.max_repetitions}`);
|
|
16362
|
+
}
|
|
16363
|
+
if (errorPct >= agentConfig.warning_threshold) {
|
|
16364
|
+
reasons.push(`errors ${session.consecutiveErrors}/${agentConfig.max_consecutive_errors}`);
|
|
16365
|
+
}
|
|
16366
|
+
if (reasons.length > 0) {
|
|
16302
16367
|
session.warningIssued = true;
|
|
16368
|
+
session.warningReason = reasons.join(", ");
|
|
16303
16369
|
}
|
|
16304
16370
|
}
|
|
16305
16371
|
},
|
|
@@ -16342,11 +16408,12 @@ function createGuardrailsHooks(config2) {
|
|
|
16342
16408
|
return;
|
|
16343
16409
|
}
|
|
16344
16410
|
if (session.hardLimitHit) {
|
|
16345
|
-
textPart.text = `[\uD83D\uDED1
|
|
16411
|
+
textPart.text = `[\uD83D\uDED1 LIMIT REACHED: Your resource budget is exhausted. Do not make additional tool calls. Return a summary of your progress and any remaining work.]
|
|
16346
16412
|
|
|
16347
16413
|
` + textPart.text;
|
|
16348
16414
|
} else if (session.warningIssued) {
|
|
16349
|
-
|
|
16415
|
+
const reasonSuffix = session.warningReason ? ` (${session.warningReason})` : "";
|
|
16416
|
+
textPart.text = `[\u26A0\uFE0F APPROACHING LIMITS${reasonSuffix}: You still have capacity to finish your current step. Complete what you're working on, then return your results.]
|
|
16350
16417
|
|
|
16351
16418
|
` + textPart.text;
|
|
16352
16419
|
}
|
package/dist/state.d.ts
CHANGED
|
@@ -41,6 +41,8 @@ export interface AgentSessionState {
|
|
|
41
41
|
agentName: string;
|
|
42
42
|
/** Date.now() when session started */
|
|
43
43
|
startTime: number;
|
|
44
|
+
/** Timestamp of most recent tool call (for stale session eviction) */
|
|
45
|
+
lastToolCallTime: number;
|
|
44
46
|
/** Total tool calls in this session */
|
|
45
47
|
toolCallCount: number;
|
|
46
48
|
/** Consecutive errors (reset on success) */
|
|
@@ -53,6 +55,8 @@ export interface AgentSessionState {
|
|
|
53
55
|
}>;
|
|
54
56
|
/** Whether a soft warning has been issued */
|
|
55
57
|
warningIssued: boolean;
|
|
58
|
+
/** Human-readable warning reason (set when warningIssued = true) */
|
|
59
|
+
warningReason: string;
|
|
56
60
|
/** Whether a hard limit has been triggered */
|
|
57
61
|
hardLimitHit: boolean;
|
|
58
62
|
}
|
|
@@ -82,7 +86,7 @@ export declare function resetSwarmState(): void;
|
|
|
82
86
|
* Also removes any stale sessions older than staleDurationMs.
|
|
83
87
|
* @param sessionId - The session identifier
|
|
84
88
|
* @param agentName - The agent associated with this session
|
|
85
|
-
* @param staleDurationMs - Age threshold for stale session eviction (default:
|
|
89
|
+
* @param staleDurationMs - Age threshold for stale session eviction (default: 120 min)
|
|
86
90
|
*/
|
|
87
91
|
export declare function startAgentSession(sessionId: string, agentName: string, staleDurationMs?: number): void;
|
|
88
92
|
/**
|
|
@@ -96,3 +100,13 @@ export declare function endAgentSession(sessionId: string): void;
|
|
|
96
100
|
* @returns The AgentSessionState or undefined if not found
|
|
97
101
|
*/
|
|
98
102
|
export declare function getAgentSession(sessionId: string): AgentSessionState | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* Ensure a guardrail session exists for the given sessionID.
|
|
105
|
+
* If one exists and agentName is provided and different, update it.
|
|
106
|
+
* If none exists, create one.
|
|
107
|
+
* Always updates lastToolCallTime.
|
|
108
|
+
* @param sessionId - The session identifier
|
|
109
|
+
* @param agentName - Optional agent name (if known)
|
|
110
|
+
* @returns The AgentSessionState
|
|
111
|
+
*/
|
|
112
|
+
export declare function ensureAgentSession(sessionId: string, agentName?: string): AgentSessionState;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.5",
|
|
4
4
|
"description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|