opencode-swarm 5.0.5 → 5.0.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.
@@ -43,6 +43,7 @@ export declare const GuardrailsProfileSchema: z.ZodObject<{
43
43
  max_repetitions: z.ZodOptional<z.ZodNumber>;
44
44
  max_consecutive_errors: z.ZodOptional<z.ZodNumber>;
45
45
  warning_threshold: z.ZodOptional<z.ZodNumber>;
46
+ idle_timeout_minutes: z.ZodOptional<z.ZodNumber>;
46
47
  }, z.core.$strip>;
47
48
  export type GuardrailsProfile = z.infer<typeof GuardrailsProfileSchema>;
48
49
  export declare const DEFAULT_AGENT_PROFILES: Record<string, GuardrailsProfile>;
@@ -53,6 +54,7 @@ export declare const DEFAULT_ARCHITECT_PROFILE: {
53
54
  max_repetitions?: number | undefined;
54
55
  max_consecutive_errors?: number | undefined;
55
56
  warning_threshold?: number | undefined;
57
+ idle_timeout_minutes?: number | undefined;
56
58
  };
57
59
  export declare const GuardrailsConfigSchema: z.ZodObject<{
58
60
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -61,12 +63,14 @@ export declare const GuardrailsConfigSchema: z.ZodObject<{
61
63
  max_repetitions: z.ZodDefault<z.ZodNumber>;
62
64
  max_consecutive_errors: z.ZodDefault<z.ZodNumber>;
63
65
  warning_threshold: z.ZodDefault<z.ZodNumber>;
66
+ idle_timeout_minutes: z.ZodDefault<z.ZodNumber>;
64
67
  profiles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
65
68
  max_tool_calls: z.ZodOptional<z.ZodNumber>;
66
69
  max_duration_minutes: z.ZodOptional<z.ZodNumber>;
67
70
  max_repetitions: z.ZodOptional<z.ZodNumber>;
68
71
  max_consecutive_errors: z.ZodOptional<z.ZodNumber>;
69
72
  warning_threshold: z.ZodOptional<z.ZodNumber>;
73
+ idle_timeout_minutes: z.ZodOptional<z.ZodNumber>;
70
74
  }, z.core.$strip>>>;
71
75
  }, z.core.$strip>;
72
76
  export type GuardrailsConfig = z.infer<typeof GuardrailsConfigSchema>;
@@ -130,12 +134,14 @@ export declare const PluginConfigSchema: z.ZodObject<{
130
134
  max_repetitions: z.ZodDefault<z.ZodNumber>;
131
135
  max_consecutive_errors: z.ZodDefault<z.ZodNumber>;
132
136
  warning_threshold: z.ZodDefault<z.ZodNumber>;
137
+ idle_timeout_minutes: z.ZodDefault<z.ZodNumber>;
133
138
  profiles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
134
139
  max_tool_calls: z.ZodOptional<z.ZodNumber>;
135
140
  max_duration_minutes: z.ZodOptional<z.ZodNumber>;
136
141
  max_repetitions: z.ZodOptional<z.ZodNumber>;
137
142
  max_consecutive_errors: z.ZodOptional<z.ZodNumber>;
138
143
  warning_threshold: z.ZodOptional<z.ZodNumber>;
144
+ idle_timeout_minutes: z.ZodOptional<z.ZodNumber>;
139
145
  }, z.core.$strip>>>;
140
146
  }, z.core.$strip>>;
141
147
  evidence: z.ZodOptional<z.ZodObject<{
package/dist/index.js CHANGED
@@ -13618,15 +13618,16 @@ var EvidenceConfigSchema = exports_external.object({
13618
13618
  });
13619
13619
  var GuardrailsProfileSchema = exports_external.object({
13620
13620
  max_tool_calls: exports_external.number().min(10).max(1000).optional(),
13621
- max_duration_minutes: exports_external.number().min(1).max(120).optional(),
13621
+ max_duration_minutes: exports_external.number().min(0).max(480).optional(),
13622
13622
  max_repetitions: exports_external.number().min(3).max(50).optional(),
13623
13623
  max_consecutive_errors: exports_external.number().min(2).max(20).optional(),
13624
- warning_threshold: exports_external.number().min(0.1).max(0.9).optional()
13624
+ warning_threshold: exports_external.number().min(0.1).max(0.9).optional(),
13625
+ idle_timeout_minutes: exports_external.number().min(5).max(240).optional()
13625
13626
  });
13626
13627
  var DEFAULT_AGENT_PROFILES = {
13627
13628
  architect: {
13628
13629
  max_tool_calls: 800,
13629
- max_duration_minutes: 90,
13630
+ max_duration_minutes: 0,
13630
13631
  max_consecutive_errors: 8,
13631
13632
  warning_threshold: 0.75
13632
13633
  },
@@ -13665,10 +13666,11 @@ var DEFAULT_ARCHITECT_PROFILE = DEFAULT_AGENT_PROFILES.architect;
13665
13666
  var GuardrailsConfigSchema = exports_external.object({
13666
13667
  enabled: exports_external.boolean().default(true),
13667
13668
  max_tool_calls: exports_external.number().min(10).max(1000).default(200),
13668
- max_duration_minutes: exports_external.number().min(1).max(120).default(30),
13669
+ max_duration_minutes: exports_external.number().min(0).max(480).default(30),
13669
13670
  max_repetitions: exports_external.number().min(3).max(50).default(10),
13670
13671
  max_consecutive_errors: exports_external.number().min(2).max(20).default(5),
13671
13672
  warning_threshold: exports_external.number().min(0.1).max(0.9).default(0.75),
13673
+ idle_timeout_minutes: exports_external.number().min(5).max(240).default(60),
13672
13674
  profiles: exports_external.record(exports_external.string(), GuardrailsProfileSchema).optional()
13673
13675
  });
13674
13676
  function stripKnownSwarmPrefix(name) {
@@ -16008,7 +16010,8 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
16008
16010
  recentToolCalls: [],
16009
16011
  warningIssued: false,
16010
16012
  warningReason: "",
16011
- hardLimitHit: false
16013
+ hardLimitHit: false,
16014
+ lastSuccessTime: now
16012
16015
  };
16013
16016
  swarmState.agentSessions.set(sessionId, sessionState);
16014
16017
  }
@@ -16327,7 +16330,7 @@ function createGuardrailsHooks(config2) {
16327
16330
  });
16328
16331
  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.`);
16329
16332
  }
16330
- if (elapsedMinutes >= agentConfig.max_duration_minutes) {
16333
+ if (agentConfig.max_duration_minutes > 0 && elapsedMinutes >= agentConfig.max_duration_minutes) {
16331
16334
  session.hardLimitHit = true;
16332
16335
  warn("Circuit breaker: duration limit hit", {
16333
16336
  sessionID: input.sessionID,
@@ -16345,9 +16348,20 @@ function createGuardrailsHooks(config2) {
16345
16348
  session.hardLimitHit = true;
16346
16349
  throw new Error(`\uD83D\uDED1 LIMIT REACHED: ${session.consecutiveErrors} consecutive tool errors detected. Return your progress summary with details of what went wrong.`);
16347
16350
  }
16351
+ const idleMinutes = (Date.now() - session.lastSuccessTime) / 60000;
16352
+ if (idleMinutes >= agentConfig.idle_timeout_minutes) {
16353
+ session.hardLimitHit = true;
16354
+ warn("Circuit breaker: idle timeout hit", {
16355
+ sessionID: input.sessionID,
16356
+ agentName: session.agentName,
16357
+ idleTimeoutMinutes: agentConfig.idle_timeout_minutes,
16358
+ idleMinutes: Math.floor(idleMinutes)
16359
+ });
16360
+ throw new Error(`\uD83D\uDED1 LIMIT REACHED: No successful tool call for ${Math.floor(idleMinutes)} minutes (idle timeout: ${agentConfig.idle_timeout_minutes} min). This suggests the agent may be stuck. Return your progress summary.`);
16361
+ }
16348
16362
  if (!session.warningIssued) {
16349
16363
  const toolPct = session.toolCallCount / agentConfig.max_tool_calls;
16350
- const durationPct = elapsedMinutes / agentConfig.max_duration_minutes;
16364
+ const durationPct = agentConfig.max_duration_minutes > 0 ? elapsedMinutes / agentConfig.max_duration_minutes : 0;
16351
16365
  const repPct = repetitionCount / agentConfig.max_repetitions;
16352
16366
  const errorPct = session.consecutiveErrors / agentConfig.max_consecutive_errors;
16353
16367
  const reasons = [];
@@ -16379,6 +16393,7 @@ function createGuardrailsHooks(config2) {
16379
16393
  session.consecutiveErrors++;
16380
16394
  } else {
16381
16395
  session.consecutiveErrors = 0;
16396
+ session.lastSuccessTime = Date.now();
16382
16397
  }
16383
16398
  },
16384
16399
  messagesTransform: async (_input, output) => {
package/dist/state.d.ts CHANGED
@@ -59,6 +59,8 @@ export interface AgentSessionState {
59
59
  warningReason: string;
60
60
  /** Whether a hard limit has been triggered */
61
61
  hardLimitHit: boolean;
62
+ /** Timestamp of most recent SUCCESSFUL tool call (for idle timeout) */
63
+ lastSuccessTime: number;
62
64
  }
63
65
  /**
64
66
  * Singleton state object for sharing data across hooks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "5.0.5",
3
+ "version": "5.0.8",
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",
@@ -25,7 +25,8 @@
25
25
  "LICENSE"
26
26
  ],
27
27
  "scripts": {
28
- "build": "bun build src/index.ts --outdir dist --target bun --format esm && bun build src/cli/index.ts --outdir dist/cli --target bun --format esm && tsc --emitDeclarationOnly",
28
+ "clean": "rm -rf dist",
29
+ "build": "rm -rf dist && bun build src/index.ts --outdir dist --target bun --format esm && bun build src/cli/index.ts --outdir dist/cli --target bun --format esm && tsc --emitDeclarationOnly",
29
30
  "typecheck": "tsc --noEmit",
30
31
  "test": "bun test",
31
32
  "lint": "biome lint .",
@@ -1,2 +0,0 @@
1
- import type { AgentDefinition } from './architect';
2
- export declare function createAuditorAgent(model: string, customPrompt?: string, customAppendPrompt?: string): AgentDefinition;
@@ -1,2 +0,0 @@
1
- import type { AgentDefinition } from './architect';
2
- export declare function createSecurityReviewerAgent(model: string, customPrompt?: string, customAppendPrompt?: string): AgentDefinition;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const activeDirectorySMEConfig: SMEDomainConfig;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const aiSMEConfig: SMEDomainConfig;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const apiSMEConfig: SMEDomainConfig;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const azureSMEConfig: SMEDomainConfig;
@@ -1,13 +0,0 @@
1
- import type { AgentDefinition } from '../architect';
2
- /**
3
- * SME domain configuration
4
- */
5
- export interface SMEDomainConfig {
6
- domain: string;
7
- description: string;
8
- guidance: string;
9
- }
10
- /**
11
- * Create an SME agent definition
12
- */
13
- export declare function createSMEAgent(agentName: string, domainConfig: SMEDomainConfig, model: string, customPrompt?: string, customAppendPrompt?: string): AgentDefinition;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const databaseSMEConfig: SMEDomainConfig;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const devopsSMEConfig: SMEDomainConfig;
@@ -1,20 +0,0 @@
1
- import type { AgentDefinition } from '../architect';
2
- import { type SMEDomainConfig } from './base';
3
- export declare const SME_CONFIGS: Record<string, SMEDomainConfig>;
4
- export declare const AGENT_TO_DOMAIN: Record<string, string>;
5
- /**
6
- * Create all SME agent definitions
7
- */
8
- export declare function createAllSMEAgents(getModel: (agentName: string) => string, loadPrompt: (agentName: string) => {
9
- prompt?: string;
10
- appendPrompt?: string;
11
- }): AgentDefinition[];
12
- /**
13
- * Get list of available SME domains
14
- */
15
- export declare function listDomains(): string[];
16
- /**
17
- * Get SME agent name for a domain
18
- */
19
- export declare function domainToAgent(domain: string): string | undefined;
20
- export { createSMEAgent, type SMEDomainConfig } from './base';
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const linuxSMEConfig: SMEDomainConfig;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const networkSMEConfig: SMEDomainConfig;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const oracleSMEConfig: SMEDomainConfig;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const powershellSMEConfig: SMEDomainConfig;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const pythonSMEConfig: SMEDomainConfig;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const securitySMEConfig: SMEDomainConfig;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const uiUxSMEConfig: SMEDomainConfig;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const vmwareSMEConfig: SMEDomainConfig;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const webSMEConfig: SMEDomainConfig;
@@ -1,2 +0,0 @@
1
- import type { SMEDomainConfig } from './base';
2
- export declare const windowsSMEConfig: SMEDomainConfig;
@@ -1,3 +0,0 @@
1
- import type { AgentDefinition } from './architect';
2
- export declare function createUnifiedSMEAgent(model: string, customPrompt?: string, customAppendPrompt?: string): AgentDefinition;
3
- export declare const AVAILABLE_DOMAINS: string[];
@@ -1,178 +0,0 @@
1
- // src/state.ts
2
- var swarmState = {
3
- activeToolCalls: new Map,
4
- toolAggregates: new Map,
5
- activeAgent: new Map,
6
- delegationChains: new Map,
7
- pendingEvents: 0,
8
- agentSessions: new Map
9
- };
10
- function startAgentSession(sessionId, agentName, staleDurationMs = 3600000) {
11
- const now = Date.now();
12
- for (const [id, session] of swarmState.agentSessions) {
13
- if (now - session.startTime > staleDurationMs) {
14
- swarmState.agentSessions.delete(id);
15
- }
16
- }
17
- const sessionState = {
18
- agentName,
19
- startTime: now,
20
- toolCallCount: 0,
21
- consecutiveErrors: 0,
22
- recentToolCalls: [],
23
- warningIssued: false,
24
- hardLimitHit: false
25
- };
26
- swarmState.agentSessions.set(sessionId, sessionState);
27
- }
28
- function getAgentSession(sessionId) {
29
- return swarmState.agentSessions.get(sessionId);
30
- }
31
- // src/utils/logger.ts
32
- var DEBUG = process.env.OPENCODE_SWARM_DEBUG === "1";
33
- function warn(message, data) {
34
- const timestamp = new Date().toISOString();
35
- if (data !== undefined) {
36
- console.warn(`[opencode-swarm ${timestamp}] WARN: ${message}`, data);
37
- } else {
38
- console.warn(`[opencode-swarm ${timestamp}] WARN: ${message}`);
39
- }
40
- }
41
- // src/hooks/guardrails.ts
42
- function createGuardrailsHooks(config) {
43
- if (config.enabled === false) {
44
- return {
45
- toolBefore: async () => {},
46
- toolAfter: async () => {},
47
- messagesTransform: async () => {}
48
- };
49
- }
50
- return {
51
- toolBefore: async (input, output) => {
52
- let session = getAgentSession(input.sessionID);
53
- if (!session) {
54
- startAgentSession(input.sessionID, "unknown");
55
- session = getAgentSession(input.sessionID);
56
- if (!session) {
57
- warn(`Failed to create session for ${input.sessionID}`);
58
- return;
59
- }
60
- }
61
- if (session.hardLimitHit) {
62
- throw new Error("\uD83D\uDED1 CIRCUIT BREAKER: Agent blocked. Hard limit was previously triggered. Stop making tool calls and return your progress summary.");
63
- }
64
- session.toolCallCount++;
65
- const hash = hashArgs(output.args);
66
- session.recentToolCalls.push({
67
- tool: input.tool,
68
- argsHash: hash,
69
- timestamp: Date.now()
70
- });
71
- if (session.recentToolCalls.length > 20) {
72
- session.recentToolCalls.shift();
73
- }
74
- let repetitionCount = 0;
75
- if (session.recentToolCalls.length > 0) {
76
- const lastEntry = session.recentToolCalls[session.recentToolCalls.length - 1];
77
- for (let i = session.recentToolCalls.length - 1;i >= 0; i--) {
78
- const entry = session.recentToolCalls[i];
79
- if (entry.tool === lastEntry.tool && entry.argsHash === lastEntry.argsHash) {
80
- repetitionCount++;
81
- } else {
82
- break;
83
- }
84
- }
85
- }
86
- const elapsedMinutes = (Date.now() - session.startTime) / 60000;
87
- if (session.toolCallCount >= config.max_tool_calls) {
88
- session.hardLimitHit = true;
89
- throw new Error(`\uD83D\uDED1 CIRCUIT BREAKER: Tool call limit reached (${session.toolCallCount}/${config.max_tool_calls}). Stop making tool calls and return your progress summary.`);
90
- }
91
- if (elapsedMinutes >= config.max_duration_minutes) {
92
- session.hardLimitHit = true;
93
- throw new Error(`\uD83D\uDED1 CIRCUIT BREAKER: Duration limit reached (${Math.floor(elapsedMinutes)} min). Stop making tool calls and return your progress summary.`);
94
- }
95
- if (repetitionCount >= config.max_repetitions) {
96
- session.hardLimitHit = true;
97
- throw new Error(`\uD83D\uDED1 CIRCUIT BREAKER: Repetition detected (same call ${repetitionCount} times). Stop making tool calls and return your progress summary.`);
98
- }
99
- if (session.consecutiveErrors >= config.max_consecutive_errors) {
100
- session.hardLimitHit = true;
101
- throw new Error(`\uD83D\uDED1 CIRCUIT BREAKER: Too many consecutive errors (${session.consecutiveErrors}). Stop making tool calls and return your progress summary.`);
102
- }
103
- if (!session.warningIssued) {
104
- const toolWarning = session.toolCallCount >= config.max_tool_calls * config.warning_threshold;
105
- const durationWarning = elapsedMinutes >= config.max_duration_minutes * config.warning_threshold;
106
- const repetitionWarning = repetitionCount >= config.max_repetitions * config.warning_threshold;
107
- const errorWarning = session.consecutiveErrors >= config.max_consecutive_errors * config.warning_threshold;
108
- if (toolWarning || durationWarning || repetitionWarning || errorWarning) {
109
- session.warningIssued = true;
110
- }
111
- }
112
- },
113
- toolAfter: async (input, output) => {
114
- const session = getAgentSession(input.sessionID);
115
- if (!session) {
116
- return;
117
- }
118
- const outputStr = String(output.output ?? "");
119
- const hasError = output.output === null || output.output === undefined || outputStr === "" || outputStr.toLowerCase().includes("error");
120
- if (hasError) {
121
- session.consecutiveErrors++;
122
- } else {
123
- session.consecutiveErrors = 0;
124
- }
125
- },
126
- messagesTransform: async (input, output) => {
127
- const messages = output.messages;
128
- if (!messages || messages.length === 0) {
129
- return;
130
- }
131
- const lastMessage = messages[messages.length - 1];
132
- let sessionId = lastMessage.info?.sessionID;
133
- if (!sessionId) {
134
- for (const [id, session2] of swarmState.agentSessions) {
135
- if (session2.warningIssued || session2.hardLimitHit) {
136
- sessionId = id;
137
- break;
138
- }
139
- }
140
- }
141
- if (!sessionId) {
142
- return;
143
- }
144
- const session = getAgentSession(sessionId);
145
- if (!session || !session.warningIssued && !session.hardLimitHit) {
146
- return;
147
- }
148
- const textPart = lastMessage.parts.find((part) => part.type === "text" && typeof part.text === "string");
149
- if (!textPart) {
150
- return;
151
- }
152
- if (session.hardLimitHit) {
153
- textPart.text = `[\uD83D\uDED1 CIRCUIT BREAKER ACTIVE: You have exceeded your resource limits. Do NOT make any more tool calls. Immediately return a summary of your progress so far. Any further tool calls will be blocked.]
154
-
155
- ` + textPart.text;
156
- } else if (session.warningIssued) {
157
- textPart.text = `[⚠️ GUARDRAIL WARNING: You are approaching resource limits. Please wrap up your current task efficiently. Avoid unnecessary tool calls and prepare to return your results soon.]
158
-
159
- ` + textPart.text;
160
- }
161
- }
162
- };
163
- }
164
- function hashArgs(args) {
165
- try {
166
- if (typeof args !== "object" || args === null) {
167
- return 0;
168
- }
169
- const sortedKeys = Object.keys(args).sort();
170
- return Number(Bun.hash(JSON.stringify(args, sortedKeys)));
171
- } catch {
172
- return 0;
173
- }
174
- }
175
- export {
176
- hashArgs,
177
- createGuardrailsHooks
178
- };
package/dist/state.js DELETED
@@ -1,48 +0,0 @@
1
- // src/state.ts
2
- var swarmState = {
3
- activeToolCalls: new Map,
4
- toolAggregates: new Map,
5
- activeAgent: new Map,
6
- delegationChains: new Map,
7
- pendingEvents: 0,
8
- agentSessions: new Map
9
- };
10
- function resetSwarmState() {
11
- swarmState.activeToolCalls.clear();
12
- swarmState.toolAggregates.clear();
13
- swarmState.activeAgent.clear();
14
- swarmState.delegationChains.clear();
15
- swarmState.pendingEvents = 0;
16
- swarmState.agentSessions.clear();
17
- }
18
- function startAgentSession(sessionId, agentName, staleDurationMs = 3600000) {
19
- const now = Date.now();
20
- for (const [id, session] of swarmState.agentSessions) {
21
- if (now - session.startTime > staleDurationMs) {
22
- swarmState.agentSessions.delete(id);
23
- }
24
- }
25
- const sessionState = {
26
- agentName,
27
- startTime: now,
28
- toolCallCount: 0,
29
- consecutiveErrors: 0,
30
- recentToolCalls: [],
31
- warningIssued: false,
32
- hardLimitHit: false
33
- };
34
- swarmState.agentSessions.set(sessionId, sessionState);
35
- }
36
- function endAgentSession(sessionId) {
37
- swarmState.agentSessions.delete(sessionId);
38
- }
39
- function getAgentSession(sessionId) {
40
- return swarmState.agentSessions.get(sessionId);
41
- }
42
- export {
43
- swarmState,
44
- startAgentSession,
45
- resetSwarmState,
46
- getAgentSession,
47
- endAgentSession
48
- };