opencode-swarm 6.1.0 → 6.1.2

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/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  <p align="center">
2
- <img src="https://img.shields.io/badge/version-6.0.0-blue" alt="Version">
2
+ <img src="https://img.shields.io/badge/version-6.1.2-blue" alt="Version">
3
3
  <img src="https://img.shields.io/badge/license-MIT-green" alt="License">
4
4
  <img src="https://img.shields.io/badge/opencode-plugin-purple" alt="OpenCode Plugin">
5
- <img src="https://img.shields.io/badge/agents-7-orange" alt="Agents">
6
- <img src="https://img.shields.io/badge/tests-1188-brightgreen" alt="Tests">
5
+ <img src="https://img.shields.io/badge/agents-9-orange" alt="Agents">
6
+ <img src="https://img.shields.io/badge/tests-1280-brightgreen" alt="Tests">
7
7
  </p>
8
8
 
9
9
  <h1 align="center">🐝 OpenCode Swarm</h1>
@@ -343,6 +343,23 @@ bunx opencode-swarm uninstall --clean
343
343
 
344
344
  ## What's New
345
345
 
346
+ ### v6.1.2 — Guardrails Remediation
347
+ - **Fail-safe config validation** — Config validation failures now disable guardrails as a safety precaution (previously Zod defaults could silently re-enable them).
348
+ - **Architect exemption fix** — Architect/orchestrator sessions can no longer inherit 30-minute base limits during delegation race conditions.
349
+ - **Explicit disable always wins** — `guardrails.enabled: false` in config is now always honored, even when the config was loaded from file.
350
+ - **Internal map synchronization** — `startAgentSession()` now keeps `activeAgent` and `agentSessions` maps in sync for consistent state tracking.
351
+
352
+ ### v6.1.1 — Security Fix & Tech Debt
353
+ - **Security hardening (`_loadedFromFile`)** — Fixed a critical vulnerability where an internal loader flag could be injected via JSON config to bypass guardrails. The flag is now purely internal and no longer part of the public schema.
354
+ - **TOCTOU protection** — Added atomic-style content checks in the config loader to prevent race conditions during file reads.
355
+ - **`retrieve_summary` tool** — Properly registered the retrieval tool, allowing agents to fetch full content from auto-summarized tool outputs.
356
+ - **92 new tests** — 1280 total tests across 57+ files (up from 1188 in v6.0.0).
357
+
358
+ ### v6.1.0 — Docs & Design Agents
359
+ - **`docs` agent** — Dedicated documentation synthesizer that automatically updates READMEs, API docs, and guides during Phase 6.
360
+ - **`designer` agent** — UI/UX specification agent that generates component scaffolds before coding begins on UI-heavy tasks.
361
+ - **Heterogeneous model defaults** — Updated default models for new agents to use optimized Gemini models for speed and cost.
362
+
346
363
  ### v6.0.0 — Core QA & Security Gates
347
364
  - **Dual-pass security reviewer** — After the general reviewer APPROVES, the architect automatically triggers a second security-only review pass when the changed file matches security-sensitive paths (`auth`, `crypto`, `session`, `token`, `middleware`, `api`, `security`) or the coder's output contains security keywords. Configurable via `review_passes` config.
348
365
  - **Adversarial testing** — After verification tests PASS, the test engineer is re-delegated with adversarial-only framing: attack vectors, boundary violations, and injection attempts. Pure prompt engineering, no new infrastructure.
@@ -411,6 +428,11 @@ All features are opt-in via configuration. See [Installation Guide](docs/install
411
428
  |-------|------|
412
429
  | `explorer` | Fast codebase scanner. Identifies structure, languages, frameworks, key files. |
413
430
 
431
+ ### 🎨 Design
432
+ | Agent | Role |
433
+ |-------|------|
434
+ | `designer` | UI/UX specification agent. Generates component scaffolds and design tokens before coding begins on UI-heavy tasks. |
435
+
414
436
  ### 🧠 Domain Expert
415
437
  | Agent | Role |
416
438
  |-------|------|
@@ -428,6 +450,11 @@ All features are opt-in via configuration. See [Installation Guide](docs/install
428
450
  | `reviewer` | Dual-pass review: correctness review first, then automatic security-only pass for security-sensitive files. The architect specifies CHECK dimensions per call. OWASP Top 10 categories built in. |
429
451
  | `critic` | Plan review gate. Reviews the architect's plan BEFORE implementation — checks completeness, feasibility, scope, dependencies, and flags AI-slop. |
430
452
 
453
+ ### 📝 Documentation
454
+ | Agent | Role |
455
+ |-------|------|
456
+ | `docs` | Documentation synthesizer. Automatically updates READMEs, API docs, and guides based on implementation changes during Phase 6. |
457
+
431
458
  ---
432
459
 
433
460
  ## Slash Commands
@@ -462,7 +489,9 @@ Create `~/.config/opencode/opencode-swarm.json`:
462
489
  "sme": { "model": "google/gemini-2.0-flash" },
463
490
  "reviewer": { "model": "openai/gpt-4o" },
464
491
  "critic": { "model": "google/gemini-2.0-flash" },
465
- "test_engineer": { "model": "google/gemini-2.0-flash" }
492
+ "test_engineer": { "model": "google/gemini-2.0-flash" },
493
+ "docs": { "model": "google/gemini-2.0-flash" },
494
+ "designer": { "model": "google/gemini-2.0-flash" }
466
495
  }
467
496
  }
468
497
  ```
@@ -630,7 +659,7 @@ bun test
630
659
  bun test tests/unit/config/schema.test.ts
631
660
  ```
632
661
 
633
- 1188 tests across 53+ files covering config, tools, agents, hooks, commands, state, guardrails, evidence, plan schemas, circuit breaker race conditions, invocation windows, multi-invocation isolation, security categories, review/integration schemas, and diff tool. Uses Bun's built-in test runner — zero additional test dependencies.
662
+ 1280 tests across 57+ files covering config, tools, agents, hooks, commands, state, guardrails, evidence, plan schemas, circuit breaker race conditions, invocation windows, multi-invocation isolation, security categories, review/integration schemas, and diff tool. Uses Bun's built-in test runner — zero additional test dependencies.
634
663
 
635
664
  ## Troubleshooting
636
665
 
@@ -55,8 +55,8 @@ export declare const ReviewEvidenceSchema: z.ZodObject<{
55
55
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
56
56
  type: z.ZodLiteral<"review">;
57
57
  risk: z.ZodEnum<{
58
- medium: "medium";
59
58
  low: "low";
59
+ medium: "medium";
60
60
  high: "high";
61
61
  critical: "critical";
62
62
  }>;
@@ -162,8 +162,8 @@ export declare const EvidenceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
162
162
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
163
163
  type: z.ZodLiteral<"review">;
164
164
  risk: z.ZodEnum<{
165
- medium: "medium";
166
165
  low: "low";
166
+ medium: "medium";
167
167
  high: "high";
168
168
  critical: "critical";
169
169
  }>;
@@ -264,8 +264,8 @@ export declare const EvidenceBundleSchema: z.ZodObject<{
264
264
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
265
265
  type: z.ZodLiteral<"review">;
266
266
  risk: z.ZodEnum<{
267
- medium: "medium";
268
267
  low: "low";
268
+ medium: "medium";
269
269
  high: "high";
270
270
  critical: "critical";
271
271
  }>;
@@ -1,9 +1,9 @@
1
1
  export type { AgentName, PipelineAgentName, QAAgentName, } from './constants';
2
2
  export { ALL_AGENT_NAMES, ALL_SUBAGENT_NAMES, DEFAULT_MODELS, isQAAgent, isSubagent, ORCHESTRATOR_NAME, PIPELINE_AGENTS, QA_AGENTS, } from './constants';
3
- export { loadAgentPrompt, loadPluginConfig, } from './loader';
4
- export type { MigrationStatus, Phase, PhaseStatus, Plan, Task, TaskSize, TaskStatus, } from './plan-schema';
5
- export { MigrationStatusSchema, PhaseSchema, PhaseStatusSchema, PlanSchema, TaskSchema, TaskSizeSchema, TaskStatusSchema, } from './plan-schema';
6
3
  export type { ApprovalEvidence, BaseEvidence, DiffEvidence, Evidence, EvidenceBundle, EvidenceType, EvidenceVerdict, NoteEvidence, ReviewEvidence, TestEvidence, } from './evidence-schema';
7
4
  export { ApprovalEvidenceSchema, BaseEvidenceSchema, DiffEvidenceSchema, EVIDENCE_MAX_JSON_BYTES, EVIDENCE_MAX_PATCH_BYTES, EVIDENCE_MAX_TASK_BYTES, EvidenceBundleSchema, EvidenceSchema, EvidenceTypeSchema, EvidenceVerdictSchema, NoteEvidenceSchema, ReviewEvidenceSchema, TestEvidenceSchema, } from './evidence-schema';
5
+ export { loadAgentPrompt, loadPluginConfig, loadPluginConfigWithMeta, } from './loader';
6
+ export type { MigrationStatus, Phase, PhaseStatus, Plan, Task, TaskSize, TaskStatus, } from './plan-schema';
7
+ export { MigrationStatusSchema, PhaseSchema, PhaseStatusSchema, PlanSchema, TaskSchema, TaskSizeSchema, TaskStatusSchema, } from './plan-schema';
8
8
  export type { AgentOverrideConfig, PluginConfig, SwarmConfig, } from './schema';
9
9
  export { AgentOverrideConfigSchema, PluginConfigSchema, SwarmConfigSchema, } from './schema';
@@ -1,10 +1,6 @@
1
1
  import { type PluginConfig } from './schema';
2
2
  export declare const MAX_CONFIG_FILE_BYTES = 102400;
3
- export declare const MAX_MERGE_DEPTH = 10;
4
- /**
5
- * Deep merge two objects, with override values taking precedence.
6
- */
7
- export declare function deepMerge<T extends Record<string, unknown>>(base?: T, override?: T): T | undefined;
3
+ export { deepMerge, MAX_MERGE_DEPTH } from '../utils/merge';
8
4
  /**
9
5
  * Load plugin configuration from user and project config files.
10
6
  *
@@ -17,6 +13,15 @@ export declare function deepMerge<T extends Record<string, unknown>>(base?: T, o
17
13
  * Zod defaults don't override explicit user values.
18
14
  */
19
15
  export declare function loadPluginConfig(directory: string): PluginConfig;
16
+ /**
17
+ * Internal variant of loadPluginConfig that also returns loader metadata.
18
+ * Used only by src/index.ts to determine guardrails fallback behavior.
19
+ * NOT part of the public API — use loadPluginConfig() for all other callers.
20
+ */
21
+ export declare function loadPluginConfigWithMeta(directory: string): {
22
+ config: PluginConfig;
23
+ loadedFromFile: boolean;
24
+ };
20
25
  /**
21
26
  * Load custom prompt for an agent from the prompts directory.
22
27
  * Checks for {agent}.md (replaces default) and {agent}_append.md (appends).
@@ -7,8 +7,8 @@ export declare const TaskStatusSchema: z.ZodEnum<{
7
7
  }>;
8
8
  export type TaskStatus = z.infer<typeof TaskStatusSchema>;
9
9
  export declare const TaskSizeSchema: z.ZodEnum<{
10
- small: "small";
11
10
  medium: "medium";
11
+ small: "small";
12
12
  large: "large";
13
13
  }>;
14
14
  export type TaskSize = z.infer<typeof TaskSizeSchema>;
@@ -35,8 +35,8 @@ export declare const TaskSchema: z.ZodObject<{
35
35
  blocked: "blocked";
36
36
  }>>;
37
37
  size: z.ZodDefault<z.ZodEnum<{
38
- small: "small";
39
38
  medium: "medium";
39
+ small: "small";
40
40
  large: "large";
41
41
  }>>;
42
42
  description: z.ZodString;
@@ -66,8 +66,8 @@ export declare const PhaseSchema: z.ZodObject<{
66
66
  blocked: "blocked";
67
67
  }>>;
68
68
  size: z.ZodDefault<z.ZodEnum<{
69
- small: "small";
70
69
  medium: "medium";
70
+ small: "small";
71
71
  large: "large";
72
72
  }>>;
73
73
  description: z.ZodString;
@@ -103,8 +103,8 @@ export declare const PlanSchema: z.ZodObject<{
103
103
  blocked: "blocked";
104
104
  }>>;
105
105
  size: z.ZodDefault<z.ZodEnum<{
106
- small: "small";
107
106
  medium: "medium";
107
+ small: "small";
108
108
  large: "large";
109
109
  }>>;
110
110
  description: z.ZodString;
@@ -318,7 +318,6 @@ export declare const PluginConfigSchema: z.ZodObject<{
318
318
  trigger_paths: z.ZodDefault<z.ZodArray<z.ZodString>>;
319
319
  trigger_keywords: z.ZodDefault<z.ZodArray<z.ZodString>>;
320
320
  }, z.core.$strip>>;
321
- _loadedFromFile: z.ZodDefault<z.ZodBoolean>;
322
321
  }, z.core.$strip>;
323
322
  export type PluginConfig = z.infer<typeof PluginConfigSchema>;
324
323
  export type { AgentName, PipelineAgentName, QAAgentName, } from './constants';
package/dist/index.js CHANGED
@@ -26,11 +26,83 @@ var __export = (target, all) => {
26
26
  };
27
27
  var __require = import.meta.require;
28
28
 
29
- // src/config/loader.ts
30
- import * as fs from "fs";
31
- import * as os from "os";
32
- import * as path from "path";
29
+ // src/utils/merge.ts
30
+ var MAX_MERGE_DEPTH = 10;
31
+ function deepMergeInternal(base, override, depth) {
32
+ if (depth >= MAX_MERGE_DEPTH) {
33
+ throw new Error(`deepMerge exceeded maximum depth of ${MAX_MERGE_DEPTH}`);
34
+ }
35
+ const result = { ...base };
36
+ for (const key of Object.keys(override)) {
37
+ const baseVal = base[key];
38
+ const overrideVal = override[key];
39
+ if (typeof baseVal === "object" && baseVal !== null && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(baseVal) && !Array.isArray(overrideVal)) {
40
+ result[key] = deepMergeInternal(baseVal, overrideVal, depth + 1);
41
+ } else {
42
+ result[key] = overrideVal;
43
+ }
44
+ }
45
+ return result;
46
+ }
47
+ function deepMerge(base, override) {
48
+ if (!base)
49
+ return override;
50
+ if (!override)
51
+ return base;
52
+ return deepMergeInternal(base, override, 0);
53
+ }
33
54
 
55
+ // src/config/constants.ts
56
+ var QA_AGENTS = ["reviewer", "critic"];
57
+ var PIPELINE_AGENTS = ["explorer", "coder", "test_engineer"];
58
+ var ORCHESTRATOR_NAME = "architect";
59
+ var ALL_SUBAGENT_NAMES = [
60
+ "sme",
61
+ "docs",
62
+ "designer",
63
+ ...QA_AGENTS,
64
+ ...PIPELINE_AGENTS
65
+ ];
66
+ var ALL_AGENT_NAMES = [
67
+ ORCHESTRATOR_NAME,
68
+ ...ALL_SUBAGENT_NAMES
69
+ ];
70
+ var DEFAULT_MODELS = {
71
+ architect: "anthropic/claude-sonnet-4-5",
72
+ explorer: "google/gemini-2.0-flash",
73
+ coder: "anthropic/claude-sonnet-4-5",
74
+ test_engineer: "google/gemini-2.0-flash",
75
+ sme: "google/gemini-2.0-flash",
76
+ reviewer: "google/gemini-2.0-flash",
77
+ critic: "google/gemini-2.0-flash",
78
+ docs: "google/gemini-2.0-flash",
79
+ designer: "google/gemini-2.0-flash",
80
+ default: "google/gemini-2.0-flash"
81
+ };
82
+ var DEFAULT_SCORING_CONFIG = {
83
+ enabled: false,
84
+ max_candidates: 100,
85
+ weights: {
86
+ phase: 1,
87
+ current_task: 2,
88
+ blocked_task: 1.5,
89
+ recent_failure: 2.5,
90
+ recent_success: 0.5,
91
+ evidence_presence: 1,
92
+ decision_recency: 1.5,
93
+ dependency_proximity: 1
94
+ },
95
+ decision_decay: {
96
+ mode: "exponential",
97
+ half_life_hours: 24
98
+ },
99
+ token_ratios: {
100
+ prose: 0.25,
101
+ code: 0.4,
102
+ markdown: 0.3,
103
+ json: 0.35
104
+ }
105
+ };
34
106
  // node_modules/zod/v4/classic/external.js
35
107
  var exports_external = {};
36
108
  __export(exports_external, {
@@ -13563,6 +13635,85 @@ function date4(params) {
13563
13635
 
13564
13636
  // node_modules/zod/v4/classic/external.js
13565
13637
  config(en_default());
13638
+ // src/config/evidence-schema.ts
13639
+ var EVIDENCE_MAX_JSON_BYTES = 500 * 1024;
13640
+ var EVIDENCE_MAX_PATCH_BYTES = 5 * 1024 * 1024;
13641
+ var EVIDENCE_MAX_TASK_BYTES = 20 * 1024 * 1024;
13642
+ var EvidenceTypeSchema = exports_external.enum([
13643
+ "review",
13644
+ "test",
13645
+ "diff",
13646
+ "approval",
13647
+ "note"
13648
+ ]);
13649
+ var EvidenceVerdictSchema = exports_external.enum([
13650
+ "pass",
13651
+ "fail",
13652
+ "approved",
13653
+ "rejected",
13654
+ "info"
13655
+ ]);
13656
+ var BaseEvidenceSchema = exports_external.object({
13657
+ task_id: exports_external.string().min(1),
13658
+ type: EvidenceTypeSchema,
13659
+ timestamp: exports_external.string().datetime(),
13660
+ agent: exports_external.string().min(1),
13661
+ verdict: EvidenceVerdictSchema,
13662
+ summary: exports_external.string().min(1),
13663
+ metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
13664
+ });
13665
+ var ReviewEvidenceSchema = BaseEvidenceSchema.extend({
13666
+ type: exports_external.literal("review"),
13667
+ risk: exports_external.enum(["low", "medium", "high", "critical"]),
13668
+ issues: exports_external.array(exports_external.object({
13669
+ severity: exports_external.enum(["error", "warning", "info"]),
13670
+ message: exports_external.string().min(1),
13671
+ file: exports_external.string().optional(),
13672
+ line: exports_external.number().int().optional()
13673
+ })).default([])
13674
+ });
13675
+ var TestEvidenceSchema = BaseEvidenceSchema.extend({
13676
+ type: exports_external.literal("test"),
13677
+ tests_passed: exports_external.number().int().min(0),
13678
+ tests_failed: exports_external.number().int().min(0),
13679
+ test_file: exports_external.string().optional(),
13680
+ failures: exports_external.array(exports_external.object({
13681
+ name: exports_external.string().min(1),
13682
+ message: exports_external.string().min(1)
13683
+ })).default([])
13684
+ });
13685
+ var DiffEvidenceSchema = BaseEvidenceSchema.extend({
13686
+ type: exports_external.literal("diff"),
13687
+ files_changed: exports_external.array(exports_external.string()).default([]),
13688
+ additions: exports_external.number().int().min(0).default(0),
13689
+ deletions: exports_external.number().int().min(0).default(0),
13690
+ patch_path: exports_external.string().optional()
13691
+ });
13692
+ var ApprovalEvidenceSchema = BaseEvidenceSchema.extend({
13693
+ type: exports_external.literal("approval")
13694
+ });
13695
+ var NoteEvidenceSchema = BaseEvidenceSchema.extend({
13696
+ type: exports_external.literal("note")
13697
+ });
13698
+ var EvidenceSchema = exports_external.discriminatedUnion("type", [
13699
+ ReviewEvidenceSchema,
13700
+ TestEvidenceSchema,
13701
+ DiffEvidenceSchema,
13702
+ ApprovalEvidenceSchema,
13703
+ NoteEvidenceSchema
13704
+ ]);
13705
+ var EvidenceBundleSchema = exports_external.object({
13706
+ schema_version: exports_external.literal("1.0.0"),
13707
+ task_id: exports_external.string().min(1),
13708
+ entries: exports_external.array(EvidenceSchema).default([]),
13709
+ created_at: exports_external.string().datetime(),
13710
+ updated_at: exports_external.string().datetime()
13711
+ });
13712
+ // src/config/loader.ts
13713
+ import * as fs from "fs";
13714
+ import * as os from "os";
13715
+ import * as path from "path";
13716
+
13566
13717
  // src/config/schema.ts
13567
13718
  var AgentOverrideConfigSchema = exports_external.object({
13568
13719
  model: exports_external.string().optional(),
@@ -13798,8 +13949,7 @@ var PluginConfigSchema = exports_external.object({
13798
13949
  review_passes: ReviewPassesConfigSchema.optional(),
13799
13950
  integration_analysis: IntegrationAnalysisConfigSchema.optional(),
13800
13951
  docs: DocsConfigSchema.optional(),
13801
- ui_review: UIReviewConfigSchema.optional(),
13802
- _loadedFromFile: exports_external.boolean().default(false)
13952
+ ui_review: UIReviewConfigSchema.optional()
13803
13953
  });
13804
13954
 
13805
13955
  // src/config/loader.ts
@@ -13818,6 +13968,11 @@ function loadRawConfigFromPath(configPath) {
13818
13968
  return null;
13819
13969
  }
13820
13970
  const content = fs.readFileSync(configPath, "utf-8");
13971
+ if (content.length > MAX_CONFIG_FILE_BYTES) {
13972
+ console.warn(`[opencode-swarm] Config file too large after read (max 100 KB): ${configPath}`);
13973
+ console.warn("[opencode-swarm] \u26A0\uFE0F Guardrails will be DISABLED as a safety precaution. Fix the config file to restore normal operation.");
13974
+ return null;
13975
+ }
13821
13976
  const rawConfig = JSON.parse(content);
13822
13977
  if (typeof rawConfig !== "object" || rawConfig === null || Array.isArray(rawConfig)) {
13823
13978
  console.warn(`[opencode-swarm] Invalid config at ${configPath}: expected an object`);
@@ -13833,23 +13988,6 @@ function loadRawConfigFromPath(configPath) {
13833
13988
  return null;
13834
13989
  }
13835
13990
  }
13836
- var MAX_MERGE_DEPTH = 10;
13837
- function deepMergeInternal(base, override, depth) {
13838
- if (depth >= MAX_MERGE_DEPTH) {
13839
- throw new Error(`deepMerge exceeded maximum depth of ${MAX_MERGE_DEPTH}`);
13840
- }
13841
- const result = { ...base };
13842
- for (const key of Object.keys(override)) {
13843
- const baseVal = base[key];
13844
- const overrideVal = override[key];
13845
- if (typeof baseVal === "object" && baseVal !== null && typeof overrideVal === "object" && overrideVal !== null && !Array.isArray(baseVal) && !Array.isArray(overrideVal)) {
13846
- result[key] = deepMergeInternal(baseVal, overrideVal, depth + 1);
13847
- } else {
13848
- result[key] = overrideVal;
13849
- }
13850
- }
13851
- return result;
13852
- }
13853
13991
  function loadPluginConfig(directory) {
13854
13992
  const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
13855
13993
  const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
@@ -13858,7 +13996,7 @@ function loadPluginConfig(directory) {
13858
13996
  const loadedFromFile = rawUserConfig !== null || rawProjectConfig !== null;
13859
13997
  let mergedRaw = rawUserConfig ?? {};
13860
13998
  if (rawProjectConfig) {
13861
- mergedRaw = deepMergeInternal(mergedRaw, rawProjectConfig, 0);
13999
+ mergedRaw = deepMerge(mergedRaw, rawProjectConfig);
13862
14000
  }
13863
14001
  const result = PluginConfigSchema.safeParse(mergedRaw);
13864
14002
  if (!result.success) {
@@ -13866,20 +14004,26 @@ function loadPluginConfig(directory) {
13866
14004
  const userResult = PluginConfigSchema.safeParse(rawUserConfig);
13867
14005
  if (userResult.success) {
13868
14006
  console.warn("[opencode-swarm] Project config ignored due to validation errors. Using user config.");
13869
- return { ...userResult.data, _loadedFromFile: true };
14007
+ return userResult.data;
13870
14008
  }
13871
14009
  }
13872
14010
  console.warn("[opencode-swarm] Merged config validation failed:");
13873
14011
  console.warn(result.error.format());
13874
14012
  console.warn("[opencode-swarm] \u26A0\uFE0F Guardrails will be DISABLED as a safety precaution. Fix the config file to restore normal operation.");
13875
- return {
13876
- max_iterations: 5,
13877
- qa_retry_limit: 3,
13878
- inject_phase_reminders: true,
13879
- _loadedFromFile: false
13880
- };
14013
+ return PluginConfigSchema.parse({
14014
+ guardrails: { enabled: false }
14015
+ });
13881
14016
  }
13882
- return { ...result.data, _loadedFromFile: loadedFromFile };
14017
+ return result.data;
14018
+ }
14019
+ function loadPluginConfigWithMeta(directory) {
14020
+ const userConfigPath = path.join(getUserConfigDir(), "opencode", CONFIG_FILENAME);
14021
+ const projectConfigPath = path.join(directory, ".opencode", CONFIG_FILENAME);
14022
+ const rawUserConfig = loadRawConfigFromPath(userConfigPath);
14023
+ const rawProjectConfig = loadRawConfigFromPath(projectConfigPath);
14024
+ const loadedFromFile = rawUserConfig !== null || rawProjectConfig !== null;
14025
+ const config2 = loadPluginConfig(directory);
14026
+ return { config: config2, loadedFromFile };
13883
14027
  }
13884
14028
  function loadAgentPrompt(agentName) {
13885
14029
  const promptsDir = path.join(getUserConfigDir(), "opencode", PROMPTS_DIR_NAME);
@@ -13902,58 +14046,6 @@ function loadAgentPrompt(agentName) {
13902
14046
  }
13903
14047
  return result;
13904
14048
  }
13905
-
13906
- // src/config/constants.ts
13907
- var QA_AGENTS = ["reviewer", "critic"];
13908
- var PIPELINE_AGENTS = ["explorer", "coder", "test_engineer"];
13909
- var ORCHESTRATOR_NAME = "architect";
13910
- var ALL_SUBAGENT_NAMES = [
13911
- "sme",
13912
- "docs",
13913
- "designer",
13914
- ...QA_AGENTS,
13915
- ...PIPELINE_AGENTS
13916
- ];
13917
- var ALL_AGENT_NAMES = [
13918
- ORCHESTRATOR_NAME,
13919
- ...ALL_SUBAGENT_NAMES
13920
- ];
13921
- var DEFAULT_MODELS = {
13922
- architect: "anthropic/claude-sonnet-4-5",
13923
- explorer: "google/gemini-2.0-flash",
13924
- coder: "anthropic/claude-sonnet-4-5",
13925
- test_engineer: "google/gemini-2.0-flash",
13926
- sme: "google/gemini-2.0-flash",
13927
- reviewer: "google/gemini-2.0-flash",
13928
- critic: "google/gemini-2.0-flash",
13929
- docs: "google/gemini-2.0-flash",
13930
- designer: "google/gemini-2.0-flash",
13931
- default: "google/gemini-2.0-flash"
13932
- };
13933
- var DEFAULT_SCORING_CONFIG = {
13934
- enabled: false,
13935
- max_candidates: 100,
13936
- weights: {
13937
- phase: 1,
13938
- current_task: 2,
13939
- blocked_task: 1.5,
13940
- recent_failure: 2.5,
13941
- recent_success: 0.5,
13942
- evidence_presence: 1,
13943
- decision_recency: 1.5,
13944
- dependency_proximity: 1
13945
- },
13946
- decision_decay: {
13947
- mode: "exponential",
13948
- half_life_hours: 24
13949
- },
13950
- token_ratios: {
13951
- prose: 0.25,
13952
- code: 0.4,
13953
- markdown: 0.3,
13954
- json: 0.35
13955
- }
13956
- };
13957
14049
  // src/config/plan-schema.ts
13958
14050
  var TaskStatusSchema = exports_external.enum([
13959
14051
  "pending",
@@ -13999,80 +14091,6 @@ var PlanSchema = exports_external.object({
13999
14091
  phases: exports_external.array(PhaseSchema).min(1),
14000
14092
  migration_status: MigrationStatusSchema.optional()
14001
14093
  });
14002
- // src/config/evidence-schema.ts
14003
- var EVIDENCE_MAX_JSON_BYTES = 500 * 1024;
14004
- var EVIDENCE_MAX_PATCH_BYTES = 5 * 1024 * 1024;
14005
- var EVIDENCE_MAX_TASK_BYTES = 20 * 1024 * 1024;
14006
- var EvidenceTypeSchema = exports_external.enum([
14007
- "review",
14008
- "test",
14009
- "diff",
14010
- "approval",
14011
- "note"
14012
- ]);
14013
- var EvidenceVerdictSchema = exports_external.enum([
14014
- "pass",
14015
- "fail",
14016
- "approved",
14017
- "rejected",
14018
- "info"
14019
- ]);
14020
- var BaseEvidenceSchema = exports_external.object({
14021
- task_id: exports_external.string().min(1),
14022
- type: EvidenceTypeSchema,
14023
- timestamp: exports_external.string().datetime(),
14024
- agent: exports_external.string().min(1),
14025
- verdict: EvidenceVerdictSchema,
14026
- summary: exports_external.string().min(1),
14027
- metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
14028
- });
14029
- var ReviewEvidenceSchema = BaseEvidenceSchema.extend({
14030
- type: exports_external.literal("review"),
14031
- risk: exports_external.enum(["low", "medium", "high", "critical"]),
14032
- issues: exports_external.array(exports_external.object({
14033
- severity: exports_external.enum(["error", "warning", "info"]),
14034
- message: exports_external.string().min(1),
14035
- file: exports_external.string().optional(),
14036
- line: exports_external.number().int().optional()
14037
- })).default([])
14038
- });
14039
- var TestEvidenceSchema = BaseEvidenceSchema.extend({
14040
- type: exports_external.literal("test"),
14041
- tests_passed: exports_external.number().int().min(0),
14042
- tests_failed: exports_external.number().int().min(0),
14043
- test_file: exports_external.string().optional(),
14044
- failures: exports_external.array(exports_external.object({
14045
- name: exports_external.string().min(1),
14046
- message: exports_external.string().min(1)
14047
- })).default([])
14048
- });
14049
- var DiffEvidenceSchema = BaseEvidenceSchema.extend({
14050
- type: exports_external.literal("diff"),
14051
- files_changed: exports_external.array(exports_external.string()).default([]),
14052
- additions: exports_external.number().int().min(0).default(0),
14053
- deletions: exports_external.number().int().min(0).default(0),
14054
- patch_path: exports_external.string().optional()
14055
- });
14056
- var ApprovalEvidenceSchema = BaseEvidenceSchema.extend({
14057
- type: exports_external.literal("approval")
14058
- });
14059
- var NoteEvidenceSchema = BaseEvidenceSchema.extend({
14060
- type: exports_external.literal("note")
14061
- });
14062
- var EvidenceSchema = exports_external.discriminatedUnion("type", [
14063
- ReviewEvidenceSchema,
14064
- TestEvidenceSchema,
14065
- DiffEvidenceSchema,
14066
- ApprovalEvidenceSchema,
14067
- NoteEvidenceSchema
14068
- ]);
14069
- var EvidenceBundleSchema = exports_external.object({
14070
- schema_version: exports_external.literal("1.0.0"),
14071
- task_id: exports_external.string().min(1),
14072
- entries: exports_external.array(EvidenceSchema).default([]),
14073
- created_at: exports_external.string().datetime(),
14074
- updated_at: exports_external.string().datetime()
14075
- });
14076
14094
  // src/agents/architect.ts
14077
14095
  var ARCHITECT_PROMPT = `You are Architect - orchestrator of a multi-agent swarm.
14078
14096
 
@@ -15434,6 +15452,7 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000) {
15434
15452
  windows: {}
15435
15453
  };
15436
15454
  swarmState.agentSessions.set(sessionId, sessionState);
15455
+ swarmState.activeAgent.set(sessionId, agentName);
15437
15456
  }
15438
15457
  function ensureAgentSession(sessionId, agentName) {
15439
15458
  const now = Date.now();
@@ -17252,7 +17271,7 @@ function createGuardrailsHooks(config2) {
17252
17271
  return;
17253
17272
  }
17254
17273
  }
17255
- const agentName = swarmState.activeAgent.get(input.sessionID);
17274
+ const agentName = swarmState.activeAgent.get(input.sessionID) ?? ORCHESTRATOR_NAME;
17256
17275
  const session = ensureAgentSession(input.sessionID, agentName);
17257
17276
  const resolvedName = stripKnownSwarmPrefix(session.agentName);
17258
17277
  if (resolvedName === ORCHESTRATOR_NAME) {
@@ -30766,9 +30785,39 @@ var gitingest = tool({
30766
30785
  return fetchGitingest(args);
30767
30786
  }
30768
30787
  });
30788
+ // src/tools/retrieve-summary.ts
30789
+ var RETRIEVE_MAX_BYTES = 10 * 1024 * 1024;
30790
+ var retrieve_summary = tool({
30791
+ description: "Retrieve the full content of a stored tool output summary by its ID (e.g. S1, S2). Use this when a prior tool output was summarized and you need the full content.",
30792
+ args: {
30793
+ id: tool.schema.string().describe("The summary ID to retrieve (e.g. S1, S2, S99). Must match pattern S followed by digits.")
30794
+ },
30795
+ async execute(args, context) {
30796
+ const directory = context.directory;
30797
+ let sanitizedId;
30798
+ try {
30799
+ sanitizedId = sanitizeSummaryId(args.id);
30800
+ } catch {
30801
+ return "Error: invalid summary ID format. Expected format: S followed by digits (e.g. S1, S2, S99).";
30802
+ }
30803
+ let fullOutput;
30804
+ try {
30805
+ fullOutput = await loadFullOutput(directory, sanitizedId);
30806
+ } catch {
30807
+ return "Error: failed to retrieve summary.";
30808
+ }
30809
+ if (fullOutput === null) {
30810
+ return `Summary \`${sanitizedId}\` not found. Use a valid summary ID (e.g. S1, S2).`;
30811
+ }
30812
+ if (fullOutput.length > RETRIEVE_MAX_BYTES) {
30813
+ return `Error: summary content exceeds maximum size limit (10 MB).`;
30814
+ }
30815
+ return fullOutput;
30816
+ }
30817
+ });
30769
30818
  // src/index.ts
30770
30819
  var OpenCodeSwarm = async (ctx) => {
30771
- const config3 = loadPluginConfig(ctx.directory);
30820
+ const { config: config3, loadedFromFile } = loadPluginConfigWithMeta(ctx.directory);
30772
30821
  const agents = getAgentConfigs(config3);
30773
30822
  const agentDefinitions = createAgents(config3);
30774
30823
  const pipelineHook = createPipelineTrackerHook(config3);
@@ -30778,7 +30827,7 @@ var OpenCodeSwarm = async (ctx) => {
30778
30827
  const commandHandler = createSwarmCommandHandler(ctx.directory, Object.fromEntries(agentDefinitions.map((agent) => [agent.name, agent])));
30779
30828
  const activityHooks = createAgentActivityHooks(config3, ctx.directory);
30780
30829
  const delegationGateHandler = createDelegationGateHook(config3);
30781
- const guardrailsFallback = config3._loadedFromFile ? config3.guardrails ?? {} : { ...config3.guardrails, enabled: false };
30830
+ const guardrailsFallback = config3.guardrails?.enabled === false ? { ...config3.guardrails, enabled: false } : loadedFromFile ? config3.guardrails ?? {} : { ...config3.guardrails, enabled: false };
30782
30831
  const guardrailsConfig = GuardrailsConfigSchema.parse(guardrailsFallback);
30783
30832
  const delegationHandler = createDelegationTrackerHook(config3, guardrailsConfig.enabled);
30784
30833
  const guardrailsHooks = createGuardrailsHooks(guardrailsConfig);
@@ -30808,7 +30857,8 @@ var OpenCodeSwarm = async (ctx) => {
30808
30857
  detect_domains,
30809
30858
  extract_code_blocks,
30810
30859
  gitingest,
30811
- diff
30860
+ diff,
30861
+ retrieve_summary
30812
30862
  },
30813
30863
  config: async (opencodeConfig) => {
30814
30864
  if (!opencodeConfig.agent) {
@@ -2,3 +2,4 @@ export { type DiffErrorResult, type DiffResult, diff } from './diff';
2
2
  export { detect_domains } from './domain-detector';
3
3
  export { extract_code_blocks } from './file-extractor';
4
4
  export { fetchGitingest, type GitingestArgs, gitingest } from './gitingest';
5
+ export { retrieve_summary } from './retrieve-summary';
@@ -0,0 +1,2 @@
1
+ import { tool } from '@opencode-ai/plugin';
2
+ export declare const retrieve_summary: ReturnType<typeof tool>;
@@ -1,2 +1,3 @@
1
1
  export { CLIError, ConfigError, HookError, SwarmError, ToolError, } from './errors';
2
2
  export { error, log, warn } from './logger';
3
+ export { deepMerge, MAX_MERGE_DEPTH } from './merge';
@@ -0,0 +1,5 @@
1
+ export declare const MAX_MERGE_DEPTH = 10;
2
+ /**
3
+ * Deep merge two objects, with override values taking precedence.
4
+ */
5
+ export declare function deepMerge<T extends Record<string, unknown>>(base?: T, override?: T): T | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "6.1.0",
3
+ "version": "6.1.2",
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",