opencode-swarm 5.0.2 → 5.0.4

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.
@@ -62,6 +62,18 @@ export declare const GuardrailsConfigSchema: z.ZodObject<{
62
62
  }, z.core.$strip>>>;
63
63
  }, z.core.$strip>;
64
64
  export type GuardrailsConfig = z.infer<typeof GuardrailsConfigSchema>;
65
+ /**
66
+ * Strip any swarm prefix from an agent name to get the base agent name.
67
+ * Works with any swarm name by checking if the name (or suffix after removing
68
+ * a prefix) matches a known agent name from ALL_AGENT_NAMES.
69
+ *
70
+ * Examples: 'local_architect' → 'architect', 'enterprise_coder' → 'coder',
71
+ * 'architect' → 'architect', 'unknown_thing' → 'unknown_thing'
72
+ *
73
+ * @param name - The agent name (possibly prefixed)
74
+ * @returns The base agent name if recognized, or the original name
75
+ */
76
+ export declare function stripKnownSwarmPrefix(name: string): string;
65
77
  /**
66
78
  * Resolve guardrails configuration for a specific agent.
67
79
  * Merges the base config with built-in defaults (for the architect) and
package/dist/index.js CHANGED
@@ -13638,12 +13638,26 @@ var GuardrailsConfigSchema = exports_external.object({
13638
13638
  warning_threshold: exports_external.number().min(0.1).max(0.9).default(0.5),
13639
13639
  profiles: exports_external.record(exports_external.string(), GuardrailsProfileSchema).optional()
13640
13640
  });
13641
+ function stripKnownSwarmPrefix(name) {
13642
+ if (!name)
13643
+ return name;
13644
+ if (ALL_AGENT_NAMES.includes(name))
13645
+ return name;
13646
+ for (const agentName of ALL_AGENT_NAMES) {
13647
+ const suffix = `_${agentName}`;
13648
+ if (name.endsWith(suffix)) {
13649
+ return agentName;
13650
+ }
13651
+ }
13652
+ return name;
13653
+ }
13641
13654
  function resolveGuardrailsConfig(base, agentName) {
13642
13655
  if (!agentName) {
13643
13656
  return base;
13644
13657
  }
13645
- const builtIn = agentName === ORCHESTRATOR_NAME ? DEFAULT_ARCHITECT_PROFILE : undefined;
13646
- const userProfile = base.profiles?.[agentName];
13658
+ const baseName = stripKnownSwarmPrefix(agentName);
13659
+ const builtIn = baseName === ORCHESTRATOR_NAME ? DEFAULT_ARCHITECT_PROFILE : undefined;
13660
+ const userProfile = base.profiles?.[baseName] ?? base.profiles?.[agentName];
13647
13661
  if (!builtIn && !userProfile) {
13648
13662
  return base;
13649
13663
  }
@@ -15941,11 +15955,11 @@ var swarmState = {
15941
15955
  pendingEvents: 0,
15942
15956
  agentSessions: new Map
15943
15957
  };
15944
- function startAgentSession(sessionId, agentName, staleDurationMs = 3600000) {
15958
+ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
15945
15959
  const now = Date.now();
15946
15960
  const staleIds = [];
15947
15961
  for (const [id, session] of swarmState.agentSessions) {
15948
- if (now - session.startTime > staleDurationMs) {
15962
+ if (now - session.lastToolCallTime > staleDurationMs) {
15949
15963
  staleIds.push(id);
15950
15964
  }
15951
15965
  }
@@ -15955,6 +15969,7 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 3600000) {
15955
15969
  const sessionState = {
15956
15970
  agentName,
15957
15971
  startTime: now,
15972
+ lastToolCallTime: now,
15958
15973
  toolCallCount: 0,
15959
15974
  consecutiveErrors: 0,
15960
15975
  recentToolCalls: [],
@@ -15966,6 +15981,24 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 3600000) {
15966
15981
  function getAgentSession(sessionId) {
15967
15982
  return swarmState.agentSessions.get(sessionId);
15968
15983
  }
15984
+ function ensureAgentSession(sessionId, agentName) {
15985
+ const now = Date.now();
15986
+ let session = swarmState.agentSessions.get(sessionId);
15987
+ if (session) {
15988
+ if (agentName && session.agentName === "unknown") {
15989
+ session.agentName = agentName;
15990
+ session.startTime = now;
15991
+ }
15992
+ session.lastToolCallTime = now;
15993
+ return session;
15994
+ }
15995
+ startAgentSession(sessionId, agentName ?? "unknown");
15996
+ session = swarmState.agentSessions.get(sessionId);
15997
+ if (!session) {
15998
+ throw new Error(`Failed to create guardrail session for ${sessionId}`);
15999
+ }
16000
+ return session;
16001
+ }
15969
16002
 
15970
16003
  // src/hooks/agent-activity.ts
15971
16004
  function createAgentActivityHooks(config2, directory) {
@@ -16194,6 +16227,7 @@ function createDelegationTrackerHook(config2) {
16194
16227
  }
16195
16228
  const previousAgent = swarmState.activeAgent.get(input.sessionID);
16196
16229
  swarmState.activeAgent.set(input.sessionID, input.agent);
16230
+ ensureAgentSession(input.sessionID, input.agent);
16197
16231
  if (config2.hooks?.delegation_tracker === true && previousAgent && previousAgent !== input.agent) {
16198
16232
  const entry = {
16199
16233
  from: previousAgent,
@@ -16220,15 +16254,8 @@ function createGuardrailsHooks(config2) {
16220
16254
  }
16221
16255
  return {
16222
16256
  toolBefore: async (input, output) => {
16223
- let session = getAgentSession(input.sessionID);
16224
- if (!session) {
16225
- startAgentSession(input.sessionID, "unknown");
16226
- session = getAgentSession(input.sessionID);
16227
- if (!session) {
16228
- warn(`Failed to create session for ${input.sessionID}`);
16229
- return;
16230
- }
16231
- }
16257
+ const agentName = swarmState.activeAgent.get(input.sessionID);
16258
+ const session = ensureAgentSession(input.sessionID, agentName);
16232
16259
  const agentConfig = resolveGuardrailsConfig(config2, session.agentName);
16233
16260
  if (session.hardLimitHit) {
16234
16261
  throw new Error("\uD83D\uDED1 CIRCUIT BREAKER: Agent blocked. Hard limit was previously triggered. Stop making tool calls and return your progress summary.");
@@ -16258,10 +16285,22 @@ function createGuardrailsHooks(config2) {
16258
16285
  const elapsedMinutes = (Date.now() - session.startTime) / 60000;
16259
16286
  if (session.toolCallCount >= agentConfig.max_tool_calls) {
16260
16287
  session.hardLimitHit = true;
16288
+ warn("Circuit breaker: tool call limit hit", {
16289
+ sessionID: input.sessionID,
16290
+ agentName: session.agentName,
16291
+ resolvedMaxCalls: agentConfig.max_tool_calls,
16292
+ currentCalls: session.toolCallCount
16293
+ });
16261
16294
  throw new Error(`\uD83D\uDED1 CIRCUIT BREAKER: Tool call limit reached (${session.toolCallCount}/${agentConfig.max_tool_calls}). Stop making tool calls and return your progress summary.`);
16262
16295
  }
16263
16296
  if (elapsedMinutes >= agentConfig.max_duration_minutes) {
16264
16297
  session.hardLimitHit = true;
16298
+ warn("Circuit breaker: duration limit hit", {
16299
+ sessionID: input.sessionID,
16300
+ agentName: session.agentName,
16301
+ resolvedMaxMinutes: agentConfig.max_duration_minutes,
16302
+ elapsedMinutes: Math.floor(elapsedMinutes)
16303
+ });
16265
16304
  throw new Error(`\uD83D\uDED1 CIRCUIT BREAKER: Duration limit reached (${Math.floor(elapsedMinutes)} min). Stop making tool calls and return your progress summary.`);
16266
16305
  }
16267
16306
  if (repetitionCount >= agentConfig.max_repetitions) {
@@ -16467,7 +16506,7 @@ function extractAgentContext(contextContent, activeAgent, maxChars) {
16467
16506
  const activitySection = activityMatch[1].trim();
16468
16507
  if (!activitySection || activitySection === "No tool activity recorded yet.")
16469
16508
  return null;
16470
- const agentName = activeAgent.replace(/^(?:paid|local|mega|default)_/, "");
16509
+ const agentName = stripKnownSwarmPrefix(activeAgent);
16471
16510
  let contextSummary;
16472
16511
  switch (agentName) {
16473
16512
  case "coder":
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) */
@@ -82,7 +84,7 @@ export declare function resetSwarmState(): void;
82
84
  * Also removes any stale sessions older than staleDurationMs.
83
85
  * @param sessionId - The session identifier
84
86
  * @param agentName - The agent associated with this session
85
- * @param staleDurationMs - Age threshold for stale session eviction (default: 60 min)
87
+ * @param staleDurationMs - Age threshold for stale session eviction (default: 120 min)
86
88
  */
87
89
  export declare function startAgentSession(sessionId: string, agentName: string, staleDurationMs?: number): void;
88
90
  /**
@@ -96,3 +98,13 @@ export declare function endAgentSession(sessionId: string): void;
96
98
  * @returns The AgentSessionState or undefined if not found
97
99
  */
98
100
  export declare function getAgentSession(sessionId: string): AgentSessionState | undefined;
101
+ /**
102
+ * Ensure a guardrail session exists for the given sessionID.
103
+ * If one exists and agentName is provided and different, update it.
104
+ * If none exists, create one.
105
+ * Always updates lastToolCallTime.
106
+ * @param sessionId - The session identifier
107
+ * @param agentName - Optional agent name (if known)
108
+ * @returns The AgentSessionState
109
+ */
110
+ 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.2",
3
+ "version": "5.0.4",
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",