opencode-swarm 5.1.0 → 5.1.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.
@@ -20,6 +20,8 @@ export declare const HooksConfigSchema: z.ZodObject<{
20
20
  agent_activity: z.ZodDefault<z.ZodBoolean>;
21
21
  delegation_tracker: z.ZodDefault<z.ZodBoolean>;
22
22
  agent_awareness_max_chars: z.ZodDefault<z.ZodNumber>;
23
+ delegation_gate: z.ZodDefault<z.ZodBoolean>;
24
+ delegation_max_chars: z.ZodDefault<z.ZodNumber>;
23
25
  }, z.core.$strip>;
24
26
  export type HooksConfig = z.infer<typeof HooksConfigSchema>;
25
27
  export declare const ScoringWeightsSchema: z.ZodObject<{
@@ -200,6 +202,8 @@ export declare const PluginConfigSchema: z.ZodObject<{
200
202
  agent_activity: z.ZodDefault<z.ZodBoolean>;
201
203
  delegation_tracker: z.ZodDefault<z.ZodBoolean>;
202
204
  agent_awareness_max_chars: z.ZodDefault<z.ZodNumber>;
205
+ delegation_gate: z.ZodDefault<z.ZodBoolean>;
206
+ delegation_max_chars: z.ZodDefault<z.ZodNumber>;
203
207
  }, z.core.$strip>>;
204
208
  context_budget: z.ZodOptional<z.ZodObject<{
205
209
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Delegation Gate Hook
3
+ *
4
+ * Warns the architect when coder delegations are too large or batched.
5
+ * Uses experimental.chat.messages.transform to provide non-blocking guidance.
6
+ */
7
+ import type { PluginConfig } from '../config';
8
+ interface MessageInfo {
9
+ role: string;
10
+ agent?: string;
11
+ sessionID?: string;
12
+ }
13
+ interface MessagePart {
14
+ type: string;
15
+ text?: string;
16
+ [key: string]: unknown;
17
+ }
18
+ interface MessageWithParts {
19
+ info: MessageInfo;
20
+ parts: MessagePart[];
21
+ }
22
+ /**
23
+ * Creates the experimental.chat.messages.transform hook for delegation gating.
24
+ * Inspects coder delegations and warns when tasks are oversized or batched.
25
+ */
26
+ export declare function createDelegationGateHook(config: PluginConfig): (input: Record<string, never>, output: {
27
+ messages?: MessageWithParts[];
28
+ }) => Promise<void>;
29
+ export {};
@@ -2,6 +2,7 @@ export { createAgentActivityHooks } from './agent-activity';
2
2
  export { createCompactionCustomizerHook } from './compaction-customizer';
3
3
  export { createContextBudgetHandler } from './context-budget';
4
4
  export { createDelegationTrackerHook } from './delegation-tracker';
5
+ export { createDelegationGateHook } from './delegation-gate';
5
6
  export { extractCurrentPhase, extractCurrentPhaseFromPlan, extractCurrentTask, extractCurrentTaskFromPlan, extractDecisions, extractIncompleteTasks, extractIncompleteTasksFromPlan, extractPatterns, } from './extractors';
6
7
  export { createGuardrailsHooks } from './guardrails';
7
8
  export { createPipelineTrackerHook } from './pipeline-tracker';
package/dist/index.js CHANGED
@@ -13578,7 +13578,9 @@ var HooksConfigSchema = exports_external.object({
13578
13578
  compaction: exports_external.boolean().default(true),
13579
13579
  agent_activity: exports_external.boolean().default(true),
13580
13580
  delegation_tracker: exports_external.boolean().default(false),
13581
- agent_awareness_max_chars: exports_external.number().min(50).max(2000).default(300)
13581
+ agent_awareness_max_chars: exports_external.number().min(50).max(2000).default(300),
13582
+ delegation_gate: exports_external.boolean().default(true),
13583
+ delegation_max_chars: exports_external.number().min(500).max(20000).default(4000)
13582
13584
  });
13583
13585
  var ScoringWeightsSchema = exports_external.object({
13584
13586
  phase: exports_external.number().min(0).max(5).default(1),
@@ -16350,6 +16352,70 @@ function createDelegationTrackerHook(config2) {
16350
16352
  }
16351
16353
  };
16352
16354
  }
16355
+ // src/hooks/delegation-gate.ts
16356
+ function createDelegationGateHook(config2) {
16357
+ const enabled = config2.hooks?.delegation_gate !== false;
16358
+ const delegationMaxChars = config2.hooks?.delegation_max_chars ?? 4000;
16359
+ if (!enabled) {
16360
+ return async (_input, _output) => {};
16361
+ }
16362
+ return async (_input, output) => {
16363
+ const messages = output?.messages;
16364
+ if (!messages || messages.length === 0)
16365
+ return;
16366
+ let lastUserMessageIndex = -1;
16367
+ for (let i = messages.length - 1;i >= 0; i--) {
16368
+ if (messages[i]?.info?.role === "user") {
16369
+ lastUserMessageIndex = i;
16370
+ break;
16371
+ }
16372
+ }
16373
+ if (lastUserMessageIndex === -1)
16374
+ return;
16375
+ const lastUserMessage = messages[lastUserMessageIndex];
16376
+ if (!lastUserMessage?.parts)
16377
+ return;
16378
+ const agent = lastUserMessage.info?.agent;
16379
+ const strippedAgent = agent ? stripKnownSwarmPrefix(agent) : undefined;
16380
+ if (strippedAgent && strippedAgent !== "architect")
16381
+ return;
16382
+ const textPartIndex = lastUserMessage.parts.findIndex((p) => p?.type === "text" && p.text !== undefined);
16383
+ if (textPartIndex === -1)
16384
+ return;
16385
+ const textPart = lastUserMessage.parts[textPartIndex];
16386
+ const text = textPart.text ?? "";
16387
+ const coderDelegationPattern = /(?:^|\n)\s*(?:\w+_)?coder\s*\n\s*TASK:/i;
16388
+ if (!coderDelegationPattern.test(text))
16389
+ return;
16390
+ const warnings = [];
16391
+ if (text.length > delegationMaxChars) {
16392
+ warnings.push(`Delegation exceeds recommended size (${text.length} chars, limit ${delegationMaxChars}). Consider splitting into smaller tasks.`);
16393
+ }
16394
+ const fileMatches = text.match(/^FILE:/gm);
16395
+ if (fileMatches && fileMatches.length > 1) {
16396
+ warnings.push(`Multiple FILE: directives detected (${fileMatches.length}). Each coder task should target ONE file.`);
16397
+ }
16398
+ const taskMatches = text.match(/^TASK:/gm);
16399
+ if (taskMatches && taskMatches.length > 1) {
16400
+ warnings.push(`Multiple TASK: sections detected (${taskMatches.length}). Send ONE task per coder call.`);
16401
+ }
16402
+ const batchingPattern = /\b(?:and also|then also|additionally|as well as|along with)\b/gi;
16403
+ const batchingMatches = text.match(batchingPattern);
16404
+ if (batchingMatches && batchingMatches.length > 0) {
16405
+ warnings.push("Batching language detected. Break compound objectives into separate coder calls.");
16406
+ }
16407
+ if (warnings.length === 0)
16408
+ return;
16409
+ const warningText = `[\u26A0\uFE0F DELEGATION GATE: Your coder delegation may be too complex. Issues:
16410
+ ${warnings.join(`
16411
+ `)}
16412
+ Split into smaller, atomic tasks for better results.]`;
16413
+ const originalText = textPart.text ?? "";
16414
+ textPart.text = `${warningText}
16415
+
16416
+ ${originalText}`;
16417
+ };
16418
+ }
16353
16419
  // src/hooks/guardrails.ts
16354
16420
  function createGuardrailsHooks(config2) {
16355
16421
  if (config2.enabled === false) {
@@ -16361,6 +16427,11 @@ function createGuardrailsHooks(config2) {
16361
16427
  }
16362
16428
  return {
16363
16429
  toolBefore: async (input, output) => {
16430
+ const rawActiveAgent = swarmState.activeAgent.get(input.sessionID);
16431
+ const strippedAgent = rawActiveAgent ? stripKnownSwarmPrefix(rawActiveAgent) : undefined;
16432
+ if (strippedAgent === ORCHESTRATOR_NAME) {
16433
+ return;
16434
+ }
16364
16435
  const agentName = swarmState.activeAgent.get(input.sessionID);
16365
16436
  const session = ensureAgentSession(input.sessionID, agentName);
16366
16437
  const agentConfig = resolveGuardrailsConfig(config2, session.agentName);
@@ -29522,6 +29593,7 @@ var OpenCodeSwarm = async (ctx) => {
29522
29593
  const commandHandler = createSwarmCommandHandler(ctx.directory, Object.fromEntries(agentDefinitions.map((agent) => [agent.name, agent])));
29523
29594
  const activityHooks = createAgentActivityHooks(config3, ctx.directory);
29524
29595
  const delegationHandler = createDelegationTrackerHook(config3);
29596
+ const delegationGateHandler = createDelegationGateHook(config3);
29525
29597
  const guardrailsConfig = GuardrailsConfigSchema.parse(config3.guardrails ?? {});
29526
29598
  const guardrailsHooks = createGuardrailsHooks(guardrailsConfig);
29527
29599
  log("Plugin initialized", {
@@ -29569,7 +29641,8 @@ var OpenCodeSwarm = async (ctx) => {
29569
29641
  "experimental.chat.messages.transform": composeHandlers(...[
29570
29642
  pipelineHook["experimental.chat.messages.transform"],
29571
29643
  contextBudgetHandler,
29572
- guardrailsHooks.messagesTransform
29644
+ guardrailsHooks.messagesTransform,
29645
+ delegationGateHandler
29573
29646
  ].filter((fn) => Boolean(fn))),
29574
29647
  "experimental.chat.system.transform": systemEnhancerHook["experimental.chat.system.transform"],
29575
29648
  "experimental.session.compacting": compactionHook["experimental.session.compacting"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "5.1.0",
3
+ "version": "5.1.1",
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",