opencode-swarm 6.45.1 → 6.46.0
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 +121 -0
- package/dist/cli/index.js +12 -0
- package/dist/config/evidence-schema.d.ts +2 -2
- package/dist/config/schema.d.ts +50 -0
- package/dist/hooks/guardrails.d.ts +12 -3
- package/dist/hooks/index.d.ts +1 -1
- package/dist/index.js +147 -70
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -601,6 +601,117 @@ Per-agent overrides:
|
|
|
601
601
|
|
|
602
602
|
</details>
|
|
603
603
|
|
|
604
|
+
<details>
|
|
605
|
+
<summary><strong>File Authority (Per-Agent Write Permissions)</strong></summary>
|
|
606
|
+
|
|
607
|
+
Swarm enforces per-agent file write authority — each agent can only write to specific paths. By default, these rules are hardcoded, but you can override them via config.
|
|
608
|
+
|
|
609
|
+
### Default Rules
|
|
610
|
+
|
|
611
|
+
| Agent | Can Write | Blocked | Zones |
|
|
612
|
+
|-------|-----------|---------|-------|
|
|
613
|
+
| `architect` | Everything (except plan files) | `.swarm/plan.md`, `.swarm/plan.json` | `generated` |
|
|
614
|
+
| `coder` | `src/`, `tests/`, `docs/`, `scripts/` | `.swarm/` (entire directory) | `generated`, `config` |
|
|
615
|
+
| `reviewer` | `.swarm/evidence/`, `.swarm/outputs/` | `src/`, `.swarm/plan.md`, `.swarm/plan.json` | `generated` |
|
|
616
|
+
| `test_engineer` | `tests/`, `.swarm/evidence/` | `src/`, `.swarm/plan.md`, `.swarm/plan.json` | `generated` |
|
|
617
|
+
| `explorer` | Read-only | Everything | — |
|
|
618
|
+
| `sme` | Read-only | Everything | — |
|
|
619
|
+
| `docs` | `docs/`, `.swarm/outputs/` | — | `generated` |
|
|
620
|
+
| `designer` | `docs/`, `.swarm/outputs/` | — | `generated` |
|
|
621
|
+
| `critic` | `.swarm/evidence/` | — | `generated` |
|
|
622
|
+
|
|
623
|
+
### Prefixed Agents
|
|
624
|
+
|
|
625
|
+
Prefixed agents (e.g., `paid_coder`, `mega_reviewer`, `local_architect`) inherit defaults from their canonical base agent via `stripKnownSwarmPrefix`. The lookup order is:
|
|
626
|
+
|
|
627
|
+
1. Exact match for the prefixed name (if explicitly defined in user config)
|
|
628
|
+
2. Fall back to the canonical agent's defaults (e.g., `paid_coder` → `coder`)
|
|
629
|
+
|
|
630
|
+
```json
|
|
631
|
+
{
|
|
632
|
+
"authority": {
|
|
633
|
+
"rules": {
|
|
634
|
+
"coder": { "allowedPrefix": ["src/", "lib/"] },
|
|
635
|
+
"paid_coder": { "allowedPrefix": ["vendor/", "plugins/"] }
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
In this example, `paid_coder` gets its own explicit rule, while other prefixed coders (e.g., `mega_coder`) fall back to `coder`.
|
|
642
|
+
|
|
643
|
+
### Runtime Enforcement
|
|
644
|
+
|
|
645
|
+
Architect direct writes are enforced at runtime via `toolBefore` hook. This tracks writes to source code paths outside `.swarm/` and protects `.swarm/plan.md` and `.swarm/plan.json` from direct modification.
|
|
646
|
+
|
|
647
|
+
### Configuration
|
|
648
|
+
|
|
649
|
+
Override default rules in `.opencode/opencode-swarm.json`:
|
|
650
|
+
|
|
651
|
+
```json
|
|
652
|
+
{
|
|
653
|
+
"authority": {
|
|
654
|
+
"enabled": true,
|
|
655
|
+
"rules": {
|
|
656
|
+
"coder": {
|
|
657
|
+
"allowedPrefix": ["src/", "lib/", "scripts/"],
|
|
658
|
+
"blockedPrefix": [".swarm/"],
|
|
659
|
+
"blockedZones": ["generated"]
|
|
660
|
+
},
|
|
661
|
+
"explorer": {
|
|
662
|
+
"readOnly": false,
|
|
663
|
+
"allowedPrefix": ["notes/", "scratch/"]
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### Rule Fields
|
|
671
|
+
|
|
672
|
+
| Field | Type | Description |
|
|
673
|
+
|-------|------|-------------|
|
|
674
|
+
| `readOnly` | boolean | If `true`, agent cannot write anywhere |
|
|
675
|
+
| `blockedExact` | string[] | Exact file paths that are blocked |
|
|
676
|
+
| `blockedPrefix` | string[] | Path prefixes that are blocked (e.g., `.swarm/`) |
|
|
677
|
+
| `allowedPrefix` | string[] | Only these path prefixes are allowed. Omit to remove restriction; set `[]` to deny all |
|
|
678
|
+
| `blockedZones` | string[] | File zones to block: `production`, `test`, `config`, `generated`, `docs`, `build` |
|
|
679
|
+
|
|
680
|
+
### Merge Behavior
|
|
681
|
+
|
|
682
|
+
- User rules **override** hardcoded defaults for the specified agent
|
|
683
|
+
- Scalar fields (`readOnly`) — user value replaces default
|
|
684
|
+
- Array fields (`blockedPrefix`, `allowedPrefix`, etc.) — user array **replaces** entirely (not merged)
|
|
685
|
+
- If a field is omitted in the user rule for a **known agent** (one with hardcoded defaults), the default value for that field is preserved
|
|
686
|
+
- If a field is omitted in the user rule for a **custom agent** (not in the defaults list), that field is `undefined` — there are no defaults to inherit
|
|
687
|
+
- `allowedPrefix: []` explicitly denies all writes; omitting `allowedPrefix` entirely means no allowlist restriction is applied (all paths are evaluated against blocklist rules only)
|
|
688
|
+
- Setting `enabled: false` ignores all custom rules and uses hardcoded defaults
|
|
689
|
+
|
|
690
|
+
### Custom Agents
|
|
691
|
+
|
|
692
|
+
Custom agents (not in the defaults list) start with no rules. Their write authority depends entirely on what you configure:
|
|
693
|
+
|
|
694
|
+
- **Not in config at all** — agent is denied with `Unknown agent` (no rule exists; this is not the same as "blocked from all writes")
|
|
695
|
+
- **In config without `allowedPrefix`** — no allowlist restriction applies; only any `blockedPrefix`, `blockedZones`, or `readOnly` rules you explicitly set will enforce limits
|
|
696
|
+
- **In config with `allowedPrefix: []`** — all writes are denied
|
|
697
|
+
|
|
698
|
+
To safely restrict a custom agent, always set `allowedPrefix` explicitly:
|
|
699
|
+
|
|
700
|
+
```json
|
|
701
|
+
{
|
|
702
|
+
"authority": {
|
|
703
|
+
"rules": {
|
|
704
|
+
"my_custom_agent": {
|
|
705
|
+
"allowedPrefix": ["plugins/", "extensions/"],
|
|
706
|
+
"blockedZones": ["generated"]
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
</details>
|
|
714
|
+
|
|
604
715
|
<details>
|
|
605
716
|
<summary><strong>Context Budget Guard</strong></summary>
|
|
606
717
|
|
|
@@ -780,6 +891,16 @@ Config file location: `~/.config/opencode/opencode-swarm.json` (global) or `.ope
|
|
|
780
891
|
"coder": { "max_tool_calls": 500 }
|
|
781
892
|
}
|
|
782
893
|
},
|
|
894
|
+
"authority": {
|
|
895
|
+
"enabled": true,
|
|
896
|
+
"rules": {
|
|
897
|
+
"coder": {
|
|
898
|
+
"allowedPrefix": ["src/", "lib/"],
|
|
899
|
+
"blockedPrefix": [".swarm/"],
|
|
900
|
+
"blockedZones": ["generated"]
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
},
|
|
783
904
|
"review_passes": {
|
|
784
905
|
"always_security_review": false,
|
|
785
906
|
"security_globs": ["**/*auth*", "**/*crypto*", "**/*session*"]
|
package/dist/cli/index.js
CHANGED
|
@@ -18649,6 +18649,17 @@ var CompactionConfigSchema = exports_external.object({
|
|
|
18649
18649
|
emergencyThreshold: exports_external.number().min(1).max(99).default(80),
|
|
18650
18650
|
preserveLastNTurns: exports_external.number().int().min(1).default(5)
|
|
18651
18651
|
});
|
|
18652
|
+
var AgentAuthorityRuleSchema = exports_external.object({
|
|
18653
|
+
readOnly: exports_external.boolean().optional(),
|
|
18654
|
+
blockedExact: exports_external.array(exports_external.string()).optional(),
|
|
18655
|
+
blockedPrefix: exports_external.array(exports_external.string()).optional(),
|
|
18656
|
+
allowedPrefix: exports_external.array(exports_external.string()).optional(),
|
|
18657
|
+
blockedZones: exports_external.array(exports_external.enum(["production", "test", "config", "generated", "docs", "build"])).optional()
|
|
18658
|
+
});
|
|
18659
|
+
var AuthorityConfigSchema = exports_external.object({
|
|
18660
|
+
enabled: exports_external.boolean().default(true),
|
|
18661
|
+
rules: exports_external.record(exports_external.string(), AgentAuthorityRuleSchema).default({})
|
|
18662
|
+
});
|
|
18652
18663
|
var PluginConfigSchema = exports_external.object({
|
|
18653
18664
|
agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
|
|
18654
18665
|
swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
|
|
@@ -18665,6 +18676,7 @@ var PluginConfigSchema = exports_external.object({
|
|
|
18665
18676
|
watchdog: WatchdogConfigSchema.optional(),
|
|
18666
18677
|
self_review: SelfReviewConfigSchema.optional(),
|
|
18667
18678
|
tool_filter: ToolFilterConfigSchema.optional(),
|
|
18679
|
+
authority: AuthorityConfigSchema.optional(),
|
|
18668
18680
|
plan_cursor: PlanCursorConfigSchema.optional(),
|
|
18669
18681
|
evidence: EvidenceConfigSchema.optional(),
|
|
18670
18682
|
summaries: SummaryConfigSchema.optional(),
|
|
@@ -8,6 +8,7 @@ export declare const EvidenceTypeSchema: z.ZodEnum<{
|
|
|
8
8
|
quality_budget: "quality_budget";
|
|
9
9
|
placeholder: "placeholder";
|
|
10
10
|
test: "test";
|
|
11
|
+
build: "build";
|
|
11
12
|
review: "review";
|
|
12
13
|
approval: "approval";
|
|
13
14
|
note: "note";
|
|
@@ -15,7 +16,6 @@ export declare const EvidenceTypeSchema: z.ZodEnum<{
|
|
|
15
16
|
syntax: "syntax";
|
|
16
17
|
sast: "sast";
|
|
17
18
|
sbom: "sbom";
|
|
18
|
-
build: "build";
|
|
19
19
|
}>;
|
|
20
20
|
export type EvidenceType = z.infer<typeof EvidenceTypeSchema>;
|
|
21
21
|
export declare const EvidenceVerdictSchema: z.ZodEnum<{
|
|
@@ -34,6 +34,7 @@ export declare const BaseEvidenceSchema: z.ZodObject<{
|
|
|
34
34
|
quality_budget: "quality_budget";
|
|
35
35
|
placeholder: "placeholder";
|
|
36
36
|
test: "test";
|
|
37
|
+
build: "build";
|
|
37
38
|
review: "review";
|
|
38
39
|
approval: "approval";
|
|
39
40
|
note: "note";
|
|
@@ -41,7 +42,6 @@ export declare const BaseEvidenceSchema: z.ZodObject<{
|
|
|
41
42
|
syntax: "syntax";
|
|
42
43
|
sast: "sast";
|
|
43
44
|
sbom: "sbom";
|
|
44
|
-
build: "build";
|
|
45
45
|
}>;
|
|
46
46
|
timestamp: z.ZodString;
|
|
47
47
|
agent: z.ZodString;
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -468,6 +468,39 @@ export declare const CompactionConfigSchema: z.ZodObject<{
|
|
|
468
468
|
preserveLastNTurns: z.ZodDefault<z.ZodNumber>;
|
|
469
469
|
}, z.core.$strip>;
|
|
470
470
|
export type CompactionConfig = z.infer<typeof CompactionConfigSchema>;
|
|
471
|
+
export declare const AgentAuthorityRuleSchema: z.ZodObject<{
|
|
472
|
+
readOnly: z.ZodOptional<z.ZodBoolean>;
|
|
473
|
+
blockedExact: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
474
|
+
blockedPrefix: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
475
|
+
allowedPrefix: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
476
|
+
blockedZones: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
477
|
+
docs: "docs";
|
|
478
|
+
production: "production";
|
|
479
|
+
test: "test";
|
|
480
|
+
config: "config";
|
|
481
|
+
generated: "generated";
|
|
482
|
+
build: "build";
|
|
483
|
+
}>>>;
|
|
484
|
+
}, z.core.$strip>;
|
|
485
|
+
export type AgentAuthorityRule = z.infer<typeof AgentAuthorityRuleSchema>;
|
|
486
|
+
export declare const AuthorityConfigSchema: z.ZodObject<{
|
|
487
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
488
|
+
rules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
489
|
+
readOnly: z.ZodOptional<z.ZodBoolean>;
|
|
490
|
+
blockedExact: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
491
|
+
blockedPrefix: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
492
|
+
allowedPrefix: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
493
|
+
blockedZones: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
494
|
+
docs: "docs";
|
|
495
|
+
production: "production";
|
|
496
|
+
test: "test";
|
|
497
|
+
config: "config";
|
|
498
|
+
generated: "generated";
|
|
499
|
+
build: "build";
|
|
500
|
+
}>>>;
|
|
501
|
+
}, z.core.$strip>>>;
|
|
502
|
+
}, z.core.$strip>;
|
|
503
|
+
export type AuthorityConfig = z.infer<typeof AuthorityConfigSchema>;
|
|
471
504
|
export declare const PluginConfigSchema: z.ZodObject<{
|
|
472
505
|
agents: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
473
506
|
model: z.ZodOptional<z.ZodString>;
|
|
@@ -627,6 +660,23 @@ export declare const PluginConfigSchema: z.ZodObject<{
|
|
|
627
660
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
628
661
|
overrides: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
629
662
|
}, z.core.$strip>>;
|
|
663
|
+
authority: z.ZodOptional<z.ZodObject<{
|
|
664
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
665
|
+
rules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
666
|
+
readOnly: z.ZodOptional<z.ZodBoolean>;
|
|
667
|
+
blockedExact: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
668
|
+
blockedPrefix: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
669
|
+
allowedPrefix: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
670
|
+
blockedZones: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
671
|
+
docs: "docs";
|
|
672
|
+
production: "production";
|
|
673
|
+
test: "test";
|
|
674
|
+
config: "config";
|
|
675
|
+
generated: "generated";
|
|
676
|
+
build: "build";
|
|
677
|
+
}>>>;
|
|
678
|
+
}, z.core.$strip>>>;
|
|
679
|
+
}, z.core.$strip>>;
|
|
630
680
|
plan_cursor: z.ZodOptional<z.ZodObject<{
|
|
631
681
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
632
682
|
max_tokens: z.ZodDefault<z.ZodNumber>;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - Layer 1 (Soft Warning @ warning_threshold): Sets warning flag for messagesTransform to inject warning
|
|
7
7
|
* - Layer 2 (Hard Block @ 100%): Throws error in toolBefore to block further calls, injects STOP message
|
|
8
8
|
*/
|
|
9
|
-
import { type GuardrailsConfig } from '../config/schema';
|
|
9
|
+
import { type AuthorityConfig, type GuardrailsConfig } from '../config/schema';
|
|
10
10
|
import { type FileZone } from '../context/zone-classifier';
|
|
11
11
|
/**
|
|
12
12
|
* Retrieves stored input args for a given callID.
|
|
@@ -34,7 +34,7 @@ export declare function deleteStoredInputArgs(callID: string): void;
|
|
|
34
34
|
* @param config Guardrails configuration (optional)
|
|
35
35
|
* @returns Tool before/after hooks and messages transform hook
|
|
36
36
|
*/
|
|
37
|
-
export declare function createGuardrailsHooks(directory: string, directoryOrConfig?: string | GuardrailsConfig, config?: GuardrailsConfig): {
|
|
37
|
+
export declare function createGuardrailsHooks(directory: string, directoryOrConfig?: string | GuardrailsConfig, config?: GuardrailsConfig, authorityConfig?: AuthorityConfig): {
|
|
38
38
|
toolBefore: (input: {
|
|
39
39
|
tool: string;
|
|
40
40
|
sessionID: string;
|
|
@@ -103,13 +103,22 @@ export declare function validateAndRecordAttestation(dir: string, findingId: str
|
|
|
103
103
|
valid: false;
|
|
104
104
|
reason: string;
|
|
105
105
|
}>;
|
|
106
|
+
type AgentRule = {
|
|
107
|
+
readOnly?: boolean;
|
|
108
|
+
blockedExact?: string[];
|
|
109
|
+
blockedPrefix?: string[];
|
|
110
|
+
allowedPrefix?: string[];
|
|
111
|
+
blockedZones?: FileZone[];
|
|
112
|
+
};
|
|
113
|
+
export declare const DEFAULT_AGENT_AUTHORITY_RULES: Record<string, AgentRule>;
|
|
106
114
|
/**
|
|
107
115
|
* Checks whether the given agent is authorised to write to the given file path.
|
|
108
116
|
*/
|
|
109
|
-
export declare function checkFileAuthority(agentName: string, filePath: string, cwd: string): {
|
|
117
|
+
export declare function checkFileAuthority(agentName: string, filePath: string, cwd: string, authorityConfig?: AuthorityConfig): {
|
|
110
118
|
allowed: true;
|
|
111
119
|
} | {
|
|
112
120
|
allowed: false;
|
|
113
121
|
reason: string;
|
|
114
122
|
zone?: FileZone;
|
|
115
123
|
};
|
|
124
|
+
export {};
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { createDelegationGateHook } from './delegation-gate';
|
|
|
6
6
|
export { createDelegationSanitizerHook } from './delegation-sanitizer';
|
|
7
7
|
export { createDelegationTrackerHook } from './delegation-tracker';
|
|
8
8
|
export { extractCurrentPhase, extractCurrentPhaseFromPlan, extractCurrentTask, extractCurrentTaskFromPlan, extractDecisions, extractIncompleteTasks, extractIncompleteTasksFromPlan, extractPatterns, } from './extractors';
|
|
9
|
-
export { createGuardrailsHooks } from './guardrails';
|
|
9
|
+
export { checkFileAuthority, createGuardrailsHooks, DEFAULT_AGENT_AUTHORITY_RULES, } from './guardrails';
|
|
10
10
|
export { classifyMessage, classifyMessages, containsPlanContent, isDuplicateToolRead, isStaleError, isToolResult, MessagePriority, type MessagePriorityType, type MessageWithParts, } from './message-priority';
|
|
11
11
|
export { consolidateSystemMessages } from './messages-transform';
|
|
12
12
|
export { extractModelInfo, NATIVE_MODEL_LIMITS, PROVIDER_CAPS, resolveModelLimit, } from './model-limits';
|
package/dist/index.js
CHANGED
|
@@ -14573,7 +14573,7 @@ function resolveGuardrailsConfig(config2, agentName) {
|
|
|
14573
14573
|
};
|
|
14574
14574
|
return resolved;
|
|
14575
14575
|
}
|
|
14576
|
-
var KNOWN_SWARM_PREFIXES, SEPARATORS, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, WatchdogConfigSchema, SelfReviewConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, CuratorConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, PluginConfigSchema;
|
|
14576
|
+
var KNOWN_SWARM_PREFIXES, SEPARATORS, AgentOverrideConfigSchema, SwarmConfigSchema, HooksConfigSchema, ScoringWeightsSchema, DecisionDecaySchema, TokenRatiosSchema, ScoringConfigSchema, ContextBudgetConfigSchema, EvidenceConfigSchema, GateFeatureSchema, PlaceholderScanConfigSchema, QualityBudgetConfigSchema, GateConfigSchema, PipelineConfigSchema, PhaseCompleteConfigSchema, SummaryConfigSchema, ReviewPassesConfigSchema, AdversarialDetectionConfigSchema, AdversarialTestingConfigSchemaBase, AdversarialTestingConfigSchema, IntegrationAnalysisConfigSchema, DocsConfigSchema, UIReviewConfigSchema, CompactionAdvisoryConfigSchema, LintConfigSchema, SecretscanConfigSchema, GuardrailsProfileSchema, DEFAULT_AGENT_PROFILES, DEFAULT_ARCHITECT_PROFILE, GuardrailsConfigSchema, WatchdogConfigSchema, SelfReviewConfigSchema, ToolFilterConfigSchema, PlanCursorConfigSchema, CheckpointConfigSchema, AutomationModeSchema, AutomationCapabilitiesSchema, AutomationConfigSchemaBase, AutomationConfigSchema, KnowledgeConfigSchema, CuratorConfigSchema, SlopDetectorConfigSchema, IncrementalVerifyConfigSchema, CompactionConfigSchema, AgentAuthorityRuleSchema, AuthorityConfigSchema, PluginConfigSchema;
|
|
14577
14577
|
var init_schema = __esm(() => {
|
|
14578
14578
|
init_zod();
|
|
14579
14579
|
init_constants();
|
|
@@ -15047,6 +15047,17 @@ var init_schema = __esm(() => {
|
|
|
15047
15047
|
emergencyThreshold: exports_external.number().min(1).max(99).default(80),
|
|
15048
15048
|
preserveLastNTurns: exports_external.number().int().min(1).default(5)
|
|
15049
15049
|
});
|
|
15050
|
+
AgentAuthorityRuleSchema = exports_external.object({
|
|
15051
|
+
readOnly: exports_external.boolean().optional(),
|
|
15052
|
+
blockedExact: exports_external.array(exports_external.string()).optional(),
|
|
15053
|
+
blockedPrefix: exports_external.array(exports_external.string()).optional(),
|
|
15054
|
+
allowedPrefix: exports_external.array(exports_external.string()).optional(),
|
|
15055
|
+
blockedZones: exports_external.array(exports_external.enum(["production", "test", "config", "generated", "docs", "build"])).optional()
|
|
15056
|
+
});
|
|
15057
|
+
AuthorityConfigSchema = exports_external.object({
|
|
15058
|
+
enabled: exports_external.boolean().default(true),
|
|
15059
|
+
rules: exports_external.record(exports_external.string(), AgentAuthorityRuleSchema).default({})
|
|
15060
|
+
});
|
|
15050
15061
|
PluginConfigSchema = exports_external.object({
|
|
15051
15062
|
agents: exports_external.record(exports_external.string(), AgentOverrideConfigSchema).optional(),
|
|
15052
15063
|
swarms: exports_external.record(exports_external.string(), SwarmConfigSchema).optional(),
|
|
@@ -15063,6 +15074,7 @@ var init_schema = __esm(() => {
|
|
|
15063
15074
|
watchdog: WatchdogConfigSchema.optional(),
|
|
15064
15075
|
self_review: SelfReviewConfigSchema.optional(),
|
|
15065
15076
|
tool_filter: ToolFilterConfigSchema.optional(),
|
|
15077
|
+
authority: AuthorityConfigSchema.optional(),
|
|
15066
15078
|
plan_cursor: PlanCursorConfigSchema.optional(),
|
|
15067
15079
|
evidence: EvidenceConfigSchema.optional(),
|
|
15068
15080
|
summaries: SummaryConfigSchema.optional(),
|
|
@@ -53558,7 +53570,7 @@ function isInDeclaredScope(filePath, scopeEntries, cwd) {
|
|
|
53558
53570
|
return rel.length > 0 && !rel.startsWith("..") && !path35.isAbsolute(rel);
|
|
53559
53571
|
});
|
|
53560
53572
|
}
|
|
53561
|
-
function createGuardrailsHooks(directory, directoryOrConfig, config3) {
|
|
53573
|
+
function createGuardrailsHooks(directory, directoryOrConfig, config3, authorityConfig) {
|
|
53562
53574
|
let guardrailsConfig;
|
|
53563
53575
|
if (directory && typeof directory === "object" && "enabled" in directory) {
|
|
53564
53576
|
console.warn("[guardrails] Legacy call without directory, falling back to process.cwd()");
|
|
@@ -53576,6 +53588,7 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
|
|
|
53576
53588
|
messagesTransform: async () => {}
|
|
53577
53589
|
};
|
|
53578
53590
|
}
|
|
53591
|
+
const precomputedAuthorityRules = buildEffectiveRules(authorityConfig);
|
|
53579
53592
|
const cfg = guardrailsConfig;
|
|
53580
53593
|
const requiredQaGates = cfg.qa_gates?.required_tools ?? [
|
|
53581
53594
|
"diff",
|
|
@@ -53670,7 +53683,7 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
|
|
|
53670
53683
|
if (typeof delegTargetPath === "string" && delegTargetPath.length > 0) {
|
|
53671
53684
|
const agentName = swarmState.activeAgent.get(sessionID) ?? "unknown";
|
|
53672
53685
|
const cwd = effectiveDirectory;
|
|
53673
|
-
const authorityCheck =
|
|
53686
|
+
const authorityCheck = checkFileAuthorityWithRules(agentName, delegTargetPath, cwd, precomputedAuthorityRules);
|
|
53674
53687
|
if (!authorityCheck.allowed) {
|
|
53675
53688
|
throw new Error(`WRITE BLOCKED: Agent "${agentName}" is not authorised to write "${delegTargetPath}". Reason: ${authorityCheck.reason}`);
|
|
53676
53689
|
}
|
|
@@ -53679,6 +53692,19 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
|
|
|
53679
53692
|
}
|
|
53680
53693
|
}
|
|
53681
53694
|
}
|
|
53695
|
+
if (tool3 === "apply_patch" || tool3 === "patch") {
|
|
53696
|
+
const agentName = swarmState.activeAgent.get(sessionID) ?? "unknown";
|
|
53697
|
+
const cwd = effectiveDirectory;
|
|
53698
|
+
for (const p of extractPatchTargetPaths(tool3, args2)) {
|
|
53699
|
+
const authorityCheck = checkFileAuthorityWithRules(agentName, p, cwd, precomputedAuthorityRules);
|
|
53700
|
+
if (!authorityCheck.allowed) {
|
|
53701
|
+
throw new Error(`WRITE BLOCKED: Agent "${agentName}" is not authorised to write "${p}" (via patch). Reason: ${authorityCheck.reason}`);
|
|
53702
|
+
}
|
|
53703
|
+
if (!currentSession.modifiedFilesThisCoderTask.includes(p)) {
|
|
53704
|
+
currentSession.modifiedFilesThisCoderTask.push(p);
|
|
53705
|
+
}
|
|
53706
|
+
}
|
|
53707
|
+
}
|
|
53682
53708
|
} else if (isArchitect(sessionID)) {
|
|
53683
53709
|
const coderDelegArgs = args2;
|
|
53684
53710
|
const rawSubagentType = coderDelegArgs?.subagent_type;
|
|
@@ -53747,6 +53773,55 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
|
|
|
53747
53773
|
}
|
|
53748
53774
|
}
|
|
53749
53775
|
}
|
|
53776
|
+
function extractPatchTargetPaths(tool3, args2) {
|
|
53777
|
+
if (tool3 !== "apply_patch" && tool3 !== "patch")
|
|
53778
|
+
return [];
|
|
53779
|
+
const toolArgs = args2;
|
|
53780
|
+
const patchText = toolArgs?.input ?? toolArgs?.patch ?? (Array.isArray(toolArgs?.cmd) ? toolArgs.cmd[1] : undefined);
|
|
53781
|
+
if (typeof patchText !== "string")
|
|
53782
|
+
return [];
|
|
53783
|
+
if (patchText.length > 1e6) {
|
|
53784
|
+
throw new Error("WRITE BLOCKED: Patch payload exceeds 1 MB \u2014 authority cannot be verified for all modified paths. Split into smaller patches.");
|
|
53785
|
+
}
|
|
53786
|
+
const paths = new Set;
|
|
53787
|
+
const patchPathPattern = /\*\*\*\s+(?:Update|Add|Delete)\s+File:\s*(.+)/gi;
|
|
53788
|
+
const diffPathPattern = /\+\+\+\s+b\/(.+)/gm;
|
|
53789
|
+
const gitDiffPathPattern = /^diff --git a\/(.+?) b\/(.+?)$/gm;
|
|
53790
|
+
const minusPathPattern = /^---\s+a\/(.+)$/gm;
|
|
53791
|
+
const traditionalMinusPattern = /^---\s+([^\s].+?)(?:\t.*)?$/gm;
|
|
53792
|
+
const traditionalPlusPattern = /^\+\+\+\s+([^\s].+?)(?:\t.*)?$/gm;
|
|
53793
|
+
for (const match of patchText.matchAll(patchPathPattern))
|
|
53794
|
+
paths.add(match[1].trim());
|
|
53795
|
+
for (const match of patchText.matchAll(diffPathPattern)) {
|
|
53796
|
+
const p = match[1].trim();
|
|
53797
|
+
if (p !== "/dev/null")
|
|
53798
|
+
paths.add(p);
|
|
53799
|
+
}
|
|
53800
|
+
for (const match of patchText.matchAll(gitDiffPathPattern)) {
|
|
53801
|
+
const aPath = match[1].trim();
|
|
53802
|
+
const bPath = match[2].trim();
|
|
53803
|
+
if (aPath !== "/dev/null")
|
|
53804
|
+
paths.add(aPath);
|
|
53805
|
+
if (bPath !== "/dev/null")
|
|
53806
|
+
paths.add(bPath);
|
|
53807
|
+
}
|
|
53808
|
+
for (const match of patchText.matchAll(minusPathPattern)) {
|
|
53809
|
+
const p = match[1].trim();
|
|
53810
|
+
if (p !== "/dev/null")
|
|
53811
|
+
paths.add(p);
|
|
53812
|
+
}
|
|
53813
|
+
for (const match of patchText.matchAll(traditionalMinusPattern)) {
|
|
53814
|
+
const p = match[1].trim();
|
|
53815
|
+
if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/"))
|
|
53816
|
+
paths.add(p);
|
|
53817
|
+
}
|
|
53818
|
+
for (const match of patchText.matchAll(traditionalPlusPattern)) {
|
|
53819
|
+
const p = match[1].trim();
|
|
53820
|
+
if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/"))
|
|
53821
|
+
paths.add(p);
|
|
53822
|
+
}
|
|
53823
|
+
return Array.from(paths);
|
|
53824
|
+
}
|
|
53750
53825
|
function handlePlanAndScopeProtection(sessionID, tool3, args2) {
|
|
53751
53826
|
const toolArgs = args2;
|
|
53752
53827
|
const targetPath = toolArgs?.filePath ?? toolArgs?.path ?? toolArgs?.file ?? toolArgs?.target;
|
|
@@ -53759,68 +53834,25 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
|
|
|
53759
53834
|
}
|
|
53760
53835
|
}
|
|
53761
53836
|
if (!targetPath && (tool3 === "apply_patch" || tool3 === "patch")) {
|
|
53762
|
-
const
|
|
53763
|
-
|
|
53764
|
-
const
|
|
53765
|
-
const
|
|
53766
|
-
|
|
53767
|
-
|
|
53768
|
-
|
|
53769
|
-
|
|
53770
|
-
|
|
53771
|
-
|
|
53772
|
-
|
|
53773
|
-
|
|
53774
|
-
|
|
53775
|
-
|
|
53776
|
-
|
|
53777
|
-
|
|
53778
|
-
|
|
53779
|
-
if (aPath !== "/dev/null")
|
|
53780
|
-
paths.add(aPath);
|
|
53781
|
-
if (bPath !== "/dev/null")
|
|
53782
|
-
paths.add(bPath);
|
|
53783
|
-
}
|
|
53784
|
-
const minusPathPattern = /^---\s+a\/(.+)$/gm;
|
|
53785
|
-
for (const match of patchText.matchAll(minusPathPattern)) {
|
|
53786
|
-
const p = match[1].trim();
|
|
53787
|
-
if (p !== "/dev/null")
|
|
53788
|
-
paths.add(p);
|
|
53789
|
-
}
|
|
53790
|
-
const traditionalMinusPattern = /^---\s+([^\s].+?)(?:\t.*)?$/gm;
|
|
53791
|
-
const traditionalPlusPattern = /^\+\+\+\s+([^\s].+?)(?:\t.*)?$/gm;
|
|
53792
|
-
for (const match of patchText.matchAll(traditionalMinusPattern)) {
|
|
53793
|
-
const p = match[1].trim();
|
|
53794
|
-
if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/")) {
|
|
53795
|
-
paths.add(p);
|
|
53796
|
-
}
|
|
53797
|
-
}
|
|
53798
|
-
for (const match of patchText.matchAll(traditionalPlusPattern)) {
|
|
53799
|
-
const p = match[1].trim();
|
|
53800
|
-
if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/")) {
|
|
53801
|
-
paths.add(p);
|
|
53802
|
-
}
|
|
53803
|
-
}
|
|
53804
|
-
for (const p of paths) {
|
|
53805
|
-
const resolvedP = path35.resolve(effectiveDirectory, p);
|
|
53806
|
-
const planMdPath = path35.resolve(effectiveDirectory, ".swarm", "plan.md").toLowerCase();
|
|
53807
|
-
const planJsonPath = path35.resolve(effectiveDirectory, ".swarm", "plan.json").toLowerCase();
|
|
53808
|
-
if (resolvedP.toLowerCase() === planMdPath || resolvedP.toLowerCase() === planJsonPath) {
|
|
53809
|
-
throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md and .swarm/plan.json are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
|
|
53810
|
-
}
|
|
53811
|
-
if (isOutsideSwarmDir(p, effectiveDirectory) && (isSourceCodePath(p) || hasTraversalSegments(p))) {
|
|
53812
|
-
const session = swarmState.agentSessions.get(sessionID);
|
|
53813
|
-
if (session) {
|
|
53814
|
-
session.architectWriteCount++;
|
|
53815
|
-
warn("Architect direct code edit detected via apply_patch", {
|
|
53816
|
-
tool: tool3,
|
|
53817
|
-
sessionID,
|
|
53818
|
-
targetPath: p,
|
|
53819
|
-
writeCount: session.architectWriteCount
|
|
53820
|
-
});
|
|
53821
|
-
}
|
|
53822
|
-
break;
|
|
53837
|
+
for (const p of extractPatchTargetPaths(tool3, args2)) {
|
|
53838
|
+
const resolvedP = path35.resolve(effectiveDirectory, p);
|
|
53839
|
+
const planMdPath = path35.resolve(effectiveDirectory, ".swarm", "plan.md").toLowerCase();
|
|
53840
|
+
const planJsonPath = path35.resolve(effectiveDirectory, ".swarm", "plan.json").toLowerCase();
|
|
53841
|
+
if (resolvedP.toLowerCase() === planMdPath || resolvedP.toLowerCase() === planJsonPath) {
|
|
53842
|
+
throw new Error("PLAN STATE VIOLATION: Direct writes to .swarm/plan.md and .swarm/plan.json are blocked. " + "plan.md is auto-regenerated from plan.json by PlanSyncWorker. " + "Use update_task_status() to mark tasks complete, " + "phase_complete() for phase transitions, or " + "save_plan to create/restructure plans.");
|
|
53843
|
+
}
|
|
53844
|
+
if (isOutsideSwarmDir(p, effectiveDirectory) && (isSourceCodePath(p) || hasTraversalSegments(p))) {
|
|
53845
|
+
const session = swarmState.agentSessions.get(sessionID);
|
|
53846
|
+
if (session) {
|
|
53847
|
+
session.architectWriteCount++;
|
|
53848
|
+
warn("Architect direct code edit detected via apply_patch", {
|
|
53849
|
+
tool: tool3,
|
|
53850
|
+
sessionID,
|
|
53851
|
+
targetPath: p,
|
|
53852
|
+
writeCount: session.architectWriteCount
|
|
53853
|
+
});
|
|
53823
53854
|
}
|
|
53855
|
+
break;
|
|
53824
53856
|
}
|
|
53825
53857
|
}
|
|
53826
53858
|
}
|
|
@@ -53917,6 +53949,24 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
|
|
|
53917
53949
|
handleTestSuiteBlocking(input.tool, output.args);
|
|
53918
53950
|
if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
|
|
53919
53951
|
handlePlanAndScopeProtection(input.sessionID, input.tool, output.args);
|
|
53952
|
+
const toolArgs = output.args;
|
|
53953
|
+
const targetPath = toolArgs?.filePath ?? toolArgs?.path ?? toolArgs?.file ?? toolArgs?.target;
|
|
53954
|
+
if (typeof targetPath === "string" && targetPath.length > 0) {
|
|
53955
|
+
const agentName = swarmState.activeAgent.get(input.sessionID) ?? "architect";
|
|
53956
|
+
const authorityCheck = checkFileAuthorityWithRules(agentName, targetPath, effectiveDirectory, precomputedAuthorityRules);
|
|
53957
|
+
if (!authorityCheck.allowed) {
|
|
53958
|
+
throw new Error(`WRITE BLOCKED: Agent "${agentName}" is not authorised to write "${targetPath}". Reason: ${authorityCheck.reason}`);
|
|
53959
|
+
}
|
|
53960
|
+
}
|
|
53961
|
+
}
|
|
53962
|
+
if (input.tool === "apply_patch" || input.tool === "patch") {
|
|
53963
|
+
const agentName = swarmState.activeAgent.get(input.sessionID) ?? "architect";
|
|
53964
|
+
for (const p of extractPatchTargetPaths(input.tool, output.args)) {
|
|
53965
|
+
const authorityCheck = checkFileAuthorityWithRules(agentName, p, effectiveDirectory, precomputedAuthorityRules);
|
|
53966
|
+
if (!authorityCheck.allowed) {
|
|
53967
|
+
throw new Error(`WRITE BLOCKED: Agent "${agentName}" is not authorised to write "${p}" (via patch). Reason: ${authorityCheck.reason}`);
|
|
53968
|
+
}
|
|
53969
|
+
}
|
|
53920
53970
|
}
|
|
53921
53971
|
const resolved = resolveSessionAndWindow(input.sessionID);
|
|
53922
53972
|
if (!resolved)
|
|
@@ -54411,7 +54461,7 @@ function hashArgs(args2) {
|
|
|
54411
54461
|
return 0;
|
|
54412
54462
|
}
|
|
54413
54463
|
}
|
|
54414
|
-
var
|
|
54464
|
+
var DEFAULT_AGENT_AUTHORITY_RULES = {
|
|
54415
54465
|
architect: {
|
|
54416
54466
|
blockedExact: [".swarm/plan.md", ".swarm/plan.json"],
|
|
54417
54467
|
blockedZones: ["generated"]
|
|
@@ -54452,12 +54502,38 @@ var AGENT_AUTHORITY_RULES = {
|
|
|
54452
54502
|
blockedZones: ["generated"]
|
|
54453
54503
|
}
|
|
54454
54504
|
};
|
|
54455
|
-
function
|
|
54505
|
+
function buildEffectiveRules(authorityConfig) {
|
|
54506
|
+
if (authorityConfig?.enabled === false || !authorityConfig?.rules) {
|
|
54507
|
+
return DEFAULT_AGENT_AUTHORITY_RULES;
|
|
54508
|
+
}
|
|
54509
|
+
const entries = Object.entries(authorityConfig.rules);
|
|
54510
|
+
if (entries.length === 0) {
|
|
54511
|
+
return DEFAULT_AGENT_AUTHORITY_RULES;
|
|
54512
|
+
}
|
|
54513
|
+
const merged = {
|
|
54514
|
+
...DEFAULT_AGENT_AUTHORITY_RULES
|
|
54515
|
+
};
|
|
54516
|
+
for (const [agent, userRule] of entries) {
|
|
54517
|
+
const normalizedRuleKey = agent.toLowerCase();
|
|
54518
|
+
const existing = merged[normalizedRuleKey] ?? {};
|
|
54519
|
+
merged[normalizedRuleKey] = {
|
|
54520
|
+
...existing,
|
|
54521
|
+
...userRule,
|
|
54522
|
+
blockedExact: userRule.blockedExact ?? existing.blockedExact,
|
|
54523
|
+
blockedPrefix: userRule.blockedPrefix ?? existing.blockedPrefix,
|
|
54524
|
+
allowedPrefix: userRule.allowedPrefix ?? existing.allowedPrefix,
|
|
54525
|
+
blockedZones: userRule.blockedZones ?? existing.blockedZones
|
|
54526
|
+
};
|
|
54527
|
+
}
|
|
54528
|
+
return merged;
|
|
54529
|
+
}
|
|
54530
|
+
function checkFileAuthorityWithRules(agentName, filePath, cwd, effectiveRules) {
|
|
54456
54531
|
const normalizedAgent = agentName.toLowerCase();
|
|
54532
|
+
const strippedAgent = stripKnownSwarmPrefix(agentName).toLowerCase();
|
|
54457
54533
|
const dir = cwd || process.cwd();
|
|
54458
54534
|
const resolved = path35.resolve(dir, filePath);
|
|
54459
54535
|
const normalizedPath = path35.relative(dir, resolved).replace(/\\/g, "/");
|
|
54460
|
-
const rules =
|
|
54536
|
+
const rules = effectiveRules[normalizedAgent] ?? effectiveRules[strippedAgent];
|
|
54461
54537
|
if (!rules) {
|
|
54462
54538
|
return { allowed: false, reason: `Unknown agent: ${agentName}` };
|
|
54463
54539
|
}
|
|
@@ -54484,8 +54560,8 @@ function checkFileAuthority(agentName, filePath, cwd) {
|
|
|
54484
54560
|
}
|
|
54485
54561
|
}
|
|
54486
54562
|
}
|
|
54487
|
-
if (rules.allowedPrefix
|
|
54488
|
-
const isAllowed = rules.allowedPrefix.some((prefix) => normalizedPath.startsWith(prefix));
|
|
54563
|
+
if (rules.allowedPrefix != null) {
|
|
54564
|
+
const isAllowed = rules.allowedPrefix.length > 0 ? rules.allowedPrefix.some((prefix) => normalizedPath.startsWith(prefix)) : false;
|
|
54489
54565
|
if (!isAllowed) {
|
|
54490
54566
|
return {
|
|
54491
54567
|
allowed: false,
|
|
@@ -70013,7 +70089,8 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
70013
70089
|
console.warn("");
|
|
70014
70090
|
}
|
|
70015
70091
|
const delegationHandler = createDelegationTrackerHook(config3, guardrailsConfig.enabled);
|
|
70016
|
-
const
|
|
70092
|
+
const authorityConfig = AuthorityConfigSchema.parse(config3.authority ?? {});
|
|
70093
|
+
const guardrailsHooks = createGuardrailsHooks(ctx.directory, undefined, guardrailsConfig, authorityConfig);
|
|
70017
70094
|
const watchdogConfig = WatchdogConfigSchema.parse(config3.watchdog ?? {});
|
|
70018
70095
|
const advisoryInjector = (sessionId, message) => {
|
|
70019
70096
|
const s = swarmState.agentSessions.get(sessionId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.46.0",
|
|
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",
|