opencode-swarm 6.45.0 → 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/agents/explorer.d.ts +1 -1
- 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 +210 -88
- 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*"]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentDefinition } from './architect';
|
|
2
2
|
export declare const CURATOR_INIT_PROMPT = "## IDENTITY\nYou are Explorer in CURATOR_INIT mode. You consolidate prior session knowledge into an architect briefing.\nDO NOT use the Task tool to delegate. You ARE the agent that does the work.\n\nINPUT FORMAT:\nTASK: CURATOR_INIT\nPRIOR_SUMMARY: [JSON or \"none\"]\nKNOWLEDGE_ENTRIES: [JSON array of high-confidence entries]\nPROJECT_CONTEXT: [context.md excerpt]\n\nACTIONS:\n- Read the prior summary to understand session history\n- Cross-reference knowledge entries against project context\n- Identify contradictions (knowledge says X, project state shows Y)\n- Produce a concise briefing for the architect\n\nRULES:\n- Output under 2000 chars\n- No code modifications\n- Flag contradictions explicitly with CONTRADICTION: prefix\n- If no prior summary exists, state \"First session \u2014 no prior context\"\n\nOUTPUT FORMAT:\nBRIEFING:\n[concise summary of prior session state, key decisions, active blockers]\n\nCONTRADICTIONS:\n- [entry_id]: [description] (or \"None detected\")\n\nKNOWLEDGE_STATS:\n- Entries reviewed: [N]\n- Prior phases covered: [N]\n";
|
|
3
|
-
export declare const CURATOR_PHASE_PROMPT = "## IDENTITY\nYou are Explorer in CURATOR_PHASE mode. You consolidate a completed phase into a digest.\nDO NOT use the Task tool to delegate. You ARE the agent that does the work.\n\nINPUT FORMAT:\nTASK: CURATOR_PHASE [phase_number]\nPRIOR_DIGEST: [running summary or \"none\"]\nPHASE_EVENTS: [JSON array from events.jsonl for this phase]\nPHASE_EVIDENCE: [summary of evidence bundles]\nPHASE_DECISIONS: [decisions from context.md]\nAGENTS_DISPATCHED: [list]\nAGENTS_EXPECTED: [list from config]\n\nACTIONS:\n- Extend the prior digest with this phase's outcomes (do NOT regenerate from scratch)\n- Identify workflow deviations: missing reviewer, missing retro, skipped test_engineer\n- Recommend knowledge updates: entries to promote, archive, or flag as contradicted\n- Summarize key decisions and blockers resolved\n\nRULES:\n- Output under 2000 chars\n- No code modifications\n- Compliance observations are READ-ONLY \u2014 report, do not enforce\n- Extend the digest, never replace it\n\nOUTPUT FORMAT:\nPHASE_DIGEST:\nphase: [N]\nsummary: [what was accomplished]\nagents_used: [list]\ntasks_completed: [N]/[total]\nkey_decisions: [list]\nblockers_resolved: [list]\n\nCOMPLIANCE:\n- [type]: [description] (or \"No deviations observed\")\n\nKNOWLEDGE_UPDATES:\n- [action]
|
|
3
|
+
export declare const CURATOR_PHASE_PROMPT = "## IDENTITY\nYou are Explorer in CURATOR_PHASE mode. You consolidate a completed phase into a digest.\nDO NOT use the Task tool to delegate. You ARE the agent that does the work.\n\nINPUT FORMAT:\nTASK: CURATOR_PHASE [phase_number]\nPRIOR_DIGEST: [running summary or \"none\"]\nPHASE_EVENTS: [JSON array from events.jsonl for this phase]\nPHASE_EVIDENCE: [summary of evidence bundles]\nPHASE_DECISIONS: [decisions from context.md]\nAGENTS_DISPATCHED: [list]\nAGENTS_EXPECTED: [list from config]\n\nACTIONS:\n- Extend the prior digest with this phase's outcomes (do NOT regenerate from scratch)\n- Identify workflow deviations: missing reviewer, missing retro, skipped test_engineer\n- Recommend knowledge updates: entries to promote, archive, or flag as contradicted\n- Summarize key decisions and blockers resolved\n\nRULES:\n- Output under 2000 chars\n- No code modifications\n- Compliance observations are READ-ONLY \u2014 report, do not enforce\n- Extend the digest, never replace it\n\nOUTPUT FORMAT:\nPHASE_DIGEST:\nphase: [N]\nsummary: [what was accomplished]\nagents_used: [list]\ntasks_completed: [N]/[total]\nkey_decisions: [list]\nblockers_resolved: [list]\n\nCOMPLIANCE:\n- [type]: [description] (or \"No deviations observed\")\n\nKNOWLEDGE_UPDATES:\n- [action] new: [reason] (or \"No recommendations\")\nNOTE: Always use \"new\" as the token \u2014 existing entry IDs (UUID v4) are not available in this context. Any non-UUID token is treated as \"new\" by the parser. Only \"promote new:\" creates a new entry; \"archive new:\" and \"flag_contradiction new:\" are silently skipped because those actions require an existing entry to operate on.\n\nEXTENDED_DIGEST:\n[the full running digest with this phase appended]\n";
|
|
4
4
|
export declare function createExplorerAgent(model: string, customPrompt?: string, customAppendPrompt?: string): AgentDefinition;
|
|
5
5
|
export declare function createExplorerCuratorAgent(model: string, mode: 'CURATOR_INIT' | 'CURATOR_PHASE', customAppendPrompt?: string): AgentDefinition;
|
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(),
|
|
@@ -32519,7 +32531,7 @@ __export(exports_co_change_analyzer, {
|
|
|
32519
32531
|
buildCoChangeMatrix: () => buildCoChangeMatrix
|
|
32520
32532
|
});
|
|
32521
32533
|
import * as child_process2 from "child_process";
|
|
32522
|
-
import { randomUUID } from "crypto";
|
|
32534
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
32523
32535
|
import { readdir as readdir2, readFile as readFile4, stat } from "fs/promises";
|
|
32524
32536
|
import * as path19 from "path";
|
|
32525
32537
|
import { promisify } from "util";
|
|
@@ -32779,7 +32791,7 @@ function darkMatterToKnowledgeEntries(pairs, projectName) {
|
|
|
32779
32791
|
}
|
|
32780
32792
|
const confidence = Math.min(0.3 + 0.2 * Math.min(pair.coChangeCount / 10, 1), 0.5);
|
|
32781
32793
|
entries.push({
|
|
32782
|
-
id:
|
|
32794
|
+
id: randomUUID2(),
|
|
32783
32795
|
tier: "swarm",
|
|
32784
32796
|
lesson,
|
|
32785
32797
|
category: "architecture",
|
|
@@ -36214,17 +36226,17 @@ function getTestFilesFromConvention(sourceFiles) {
|
|
|
36214
36226
|
const testFiles = [];
|
|
36215
36227
|
for (const file3 of sourceFiles) {
|
|
36216
36228
|
const normalizedPath = file3.replace(/\\/g, "/");
|
|
36217
|
-
const
|
|
36229
|
+
const basename5 = path28.basename(file3);
|
|
36218
36230
|
const dirname12 = path28.dirname(file3);
|
|
36219
|
-
if (hasCompoundTestExtension(
|
|
36231
|
+
if (hasCompoundTestExtension(basename5) || basename5.includes(".spec.") || basename5.includes(".test.") || normalizedPath.includes("/__tests__/") || normalizedPath.includes("/tests/") || normalizedPath.includes("/test/")) {
|
|
36220
36232
|
if (!testFiles.includes(file3)) {
|
|
36221
36233
|
testFiles.push(file3);
|
|
36222
36234
|
}
|
|
36223
36235
|
continue;
|
|
36224
36236
|
}
|
|
36225
36237
|
for (const _pattern of TEST_PATTERNS) {
|
|
36226
|
-
const nameWithoutExt =
|
|
36227
|
-
const ext = path28.extname(
|
|
36238
|
+
const nameWithoutExt = basename5.replace(/\.[^.]+$/, "");
|
|
36239
|
+
const ext = path28.extname(basename5);
|
|
36228
36240
|
const possibleTestFiles = [
|
|
36229
36241
|
path28.join(dirname12, `${nameWithoutExt}.spec${ext}`),
|
|
36230
36242
|
path28.join(dirname12, `${nameWithoutExt}.test${ext}`),
|
|
@@ -38054,17 +38066,17 @@ function normalizeSeparators(filePath) {
|
|
|
38054
38066
|
}
|
|
38055
38067
|
function matchesDocPattern(filePath, patterns) {
|
|
38056
38068
|
const normalizedPath = normalizeSeparators(filePath);
|
|
38057
|
-
const
|
|
38069
|
+
const basename6 = path42.basename(filePath);
|
|
38058
38070
|
for (const pattern of patterns) {
|
|
38059
38071
|
if (!pattern.includes("/") && !pattern.includes("\\")) {
|
|
38060
|
-
if (
|
|
38072
|
+
if (basename6 === pattern) {
|
|
38061
38073
|
return true;
|
|
38062
38074
|
}
|
|
38063
38075
|
continue;
|
|
38064
38076
|
}
|
|
38065
38077
|
if (pattern.startsWith("**/")) {
|
|
38066
38078
|
const filenamePattern = pattern.slice(3);
|
|
38067
|
-
if (
|
|
38079
|
+
if (basename6 === filenamePattern) {
|
|
38068
38080
|
return true;
|
|
38069
38081
|
}
|
|
38070
38082
|
continue;
|
|
@@ -44118,7 +44130,8 @@ COMPLIANCE:
|
|
|
44118
44130
|
- [type]: [description] (or "No deviations observed")
|
|
44119
44131
|
|
|
44120
44132
|
KNOWLEDGE_UPDATES:
|
|
44121
|
-
- [action]
|
|
44133
|
+
- [action] new: [reason] (or "No recommendations")
|
|
44134
|
+
NOTE: Always use "new" as the token \u2014 existing entry IDs (UUID v4) are not available in this context. Any non-UUID token is treated as "new" by the parser. Only "promote new:" creates a new entry; "archive new:" and "flag_contradiction new:" are silently skipped because those actions require an existing entry to operate on.
|
|
44122
44135
|
|
|
44123
44136
|
EXTENDED_DIGEST:
|
|
44124
44137
|
[the full running digest with this phase appended]
|
|
@@ -48202,6 +48215,7 @@ async function handleConfigCommand(directory, _args) {
|
|
|
48202
48215
|
init_schema();
|
|
48203
48216
|
|
|
48204
48217
|
// src/hooks/curator.ts
|
|
48218
|
+
import { randomUUID } from "crypto";
|
|
48205
48219
|
import * as fs12 from "fs";
|
|
48206
48220
|
import * as path18 from "path";
|
|
48207
48221
|
init_event_bus();
|
|
@@ -48223,7 +48237,8 @@ function parseKnowledgeRecommendations(llmOutput) {
|
|
|
48223
48237
|
const match = trimmed.match(/^-\s+(promote|archive|flag_contradiction)\s+(\S+):\s+(.+)$/i);
|
|
48224
48238
|
if (match) {
|
|
48225
48239
|
const action = match[1].toLowerCase();
|
|
48226
|
-
const
|
|
48240
|
+
const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
48241
|
+
const entryId = match[2] === "new" || !UUID_V4.test(match[2]) ? undefined : match[2];
|
|
48227
48242
|
const reason = match[3].trim();
|
|
48228
48243
|
recommendations.push({
|
|
48229
48244
|
action,
|
|
@@ -48737,6 +48752,43 @@ async function applyCuratorKnowledgeUpdates(directory, recommendations, _knowled
|
|
|
48737
48752
|
if (modified) {
|
|
48738
48753
|
await rewriteKnowledge(knowledgePath, updatedEntries);
|
|
48739
48754
|
}
|
|
48755
|
+
for (const rec of recommendations) {
|
|
48756
|
+
if (rec.entry_id !== undefined)
|
|
48757
|
+
continue;
|
|
48758
|
+
if (rec.action !== "promote") {
|
|
48759
|
+
skipped++;
|
|
48760
|
+
continue;
|
|
48761
|
+
}
|
|
48762
|
+
const lesson = rec.lesson?.trim() ?? "";
|
|
48763
|
+
if (lesson.length < 15) {
|
|
48764
|
+
skipped++;
|
|
48765
|
+
continue;
|
|
48766
|
+
}
|
|
48767
|
+
const now = new Date().toISOString();
|
|
48768
|
+
const newEntry = {
|
|
48769
|
+
id: randomUUID(),
|
|
48770
|
+
tier: "swarm",
|
|
48771
|
+
lesson: lesson.slice(0, 280),
|
|
48772
|
+
category: "other",
|
|
48773
|
+
tags: [],
|
|
48774
|
+
scope: "global",
|
|
48775
|
+
confidence: 0.5,
|
|
48776
|
+
status: "candidate",
|
|
48777
|
+
confirmed_by: [],
|
|
48778
|
+
retrieval_outcomes: {
|
|
48779
|
+
applied_count: 0,
|
|
48780
|
+
succeeded_after_count: 0,
|
|
48781
|
+
failed_after_count: 0
|
|
48782
|
+
},
|
|
48783
|
+
schema_version: 1,
|
|
48784
|
+
created_at: now,
|
|
48785
|
+
updated_at: now,
|
|
48786
|
+
auto_generated: true,
|
|
48787
|
+
project_name: path18.basename(directory)
|
|
48788
|
+
};
|
|
48789
|
+
await appendKnowledge(knowledgePath, newEntry);
|
|
48790
|
+
applied++;
|
|
48791
|
+
}
|
|
48740
48792
|
return { applied, skipped };
|
|
48741
48793
|
}
|
|
48742
48794
|
|
|
@@ -50416,7 +50468,7 @@ init_schema();
|
|
|
50416
50468
|
|
|
50417
50469
|
// src/hooks/knowledge-migrator.ts
|
|
50418
50470
|
init_knowledge_store();
|
|
50419
|
-
import { randomUUID as
|
|
50471
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
50420
50472
|
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
|
|
50421
50473
|
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
50422
50474
|
import * as path23 from "path";
|
|
@@ -50486,7 +50538,7 @@ async function migrateContextToKnowledge(directory, config3) {
|
|
|
50486
50538
|
}
|
|
50487
50539
|
const inferredTags = inferTags(raw.text);
|
|
50488
50540
|
const entry = {
|
|
50489
|
-
id:
|
|
50541
|
+
id: randomUUID3(),
|
|
50490
50542
|
tier: "swarm",
|
|
50491
50543
|
lesson: truncateLesson(raw.text),
|
|
50492
50544
|
category: raw.categoryHint ?? inferCategoryFromText(raw.text),
|
|
@@ -53518,7 +53570,7 @@ function isInDeclaredScope(filePath, scopeEntries, cwd) {
|
|
|
53518
53570
|
return rel.length > 0 && !rel.startsWith("..") && !path35.isAbsolute(rel);
|
|
53519
53571
|
});
|
|
53520
53572
|
}
|
|
53521
|
-
function createGuardrailsHooks(directory, directoryOrConfig, config3) {
|
|
53573
|
+
function createGuardrailsHooks(directory, directoryOrConfig, config3, authorityConfig) {
|
|
53522
53574
|
let guardrailsConfig;
|
|
53523
53575
|
if (directory && typeof directory === "object" && "enabled" in directory) {
|
|
53524
53576
|
console.warn("[guardrails] Legacy call without directory, falling back to process.cwd()");
|
|
@@ -53536,6 +53588,7 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
|
|
|
53536
53588
|
messagesTransform: async () => {}
|
|
53537
53589
|
};
|
|
53538
53590
|
}
|
|
53591
|
+
const precomputedAuthorityRules = buildEffectiveRules(authorityConfig);
|
|
53539
53592
|
const cfg = guardrailsConfig;
|
|
53540
53593
|
const requiredQaGates = cfg.qa_gates?.required_tools ?? [
|
|
53541
53594
|
"diff",
|
|
@@ -53630,7 +53683,7 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
|
|
|
53630
53683
|
if (typeof delegTargetPath === "string" && delegTargetPath.length > 0) {
|
|
53631
53684
|
const agentName = swarmState.activeAgent.get(sessionID) ?? "unknown";
|
|
53632
53685
|
const cwd = effectiveDirectory;
|
|
53633
|
-
const authorityCheck =
|
|
53686
|
+
const authorityCheck = checkFileAuthorityWithRules(agentName, delegTargetPath, cwd, precomputedAuthorityRules);
|
|
53634
53687
|
if (!authorityCheck.allowed) {
|
|
53635
53688
|
throw new Error(`WRITE BLOCKED: Agent "${agentName}" is not authorised to write "${delegTargetPath}". Reason: ${authorityCheck.reason}`);
|
|
53636
53689
|
}
|
|
@@ -53639,6 +53692,19 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
|
|
|
53639
53692
|
}
|
|
53640
53693
|
}
|
|
53641
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
|
+
}
|
|
53642
53708
|
} else if (isArchitect(sessionID)) {
|
|
53643
53709
|
const coderDelegArgs = args2;
|
|
53644
53710
|
const rawSubagentType = coderDelegArgs?.subagent_type;
|
|
@@ -53707,6 +53773,55 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
|
|
|
53707
53773
|
}
|
|
53708
53774
|
}
|
|
53709
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
|
+
}
|
|
53710
53825
|
function handlePlanAndScopeProtection(sessionID, tool3, args2) {
|
|
53711
53826
|
const toolArgs = args2;
|
|
53712
53827
|
const targetPath = toolArgs?.filePath ?? toolArgs?.path ?? toolArgs?.file ?? toolArgs?.target;
|
|
@@ -53719,68 +53834,25 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
|
|
|
53719
53834
|
}
|
|
53720
53835
|
}
|
|
53721
53836
|
if (!targetPath && (tool3 === "apply_patch" || tool3 === "patch")) {
|
|
53722
|
-
const
|
|
53723
|
-
|
|
53724
|
-
const
|
|
53725
|
-
const
|
|
53726
|
-
|
|
53727
|
-
|
|
53728
|
-
|
|
53729
|
-
|
|
53730
|
-
|
|
53731
|
-
|
|
53732
|
-
|
|
53733
|
-
|
|
53734
|
-
|
|
53735
|
-
|
|
53736
|
-
|
|
53737
|
-
|
|
53738
|
-
|
|
53739
|
-
if (aPath !== "/dev/null")
|
|
53740
|
-
paths.add(aPath);
|
|
53741
|
-
if (bPath !== "/dev/null")
|
|
53742
|
-
paths.add(bPath);
|
|
53743
|
-
}
|
|
53744
|
-
const minusPathPattern = /^---\s+a\/(.+)$/gm;
|
|
53745
|
-
for (const match of patchText.matchAll(minusPathPattern)) {
|
|
53746
|
-
const p = match[1].trim();
|
|
53747
|
-
if (p !== "/dev/null")
|
|
53748
|
-
paths.add(p);
|
|
53749
|
-
}
|
|
53750
|
-
const traditionalMinusPattern = /^---\s+([^\s].+?)(?:\t.*)?$/gm;
|
|
53751
|
-
const traditionalPlusPattern = /^\+\+\+\s+([^\s].+?)(?:\t.*)?$/gm;
|
|
53752
|
-
for (const match of patchText.matchAll(traditionalMinusPattern)) {
|
|
53753
|
-
const p = match[1].trim();
|
|
53754
|
-
if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/")) {
|
|
53755
|
-
paths.add(p);
|
|
53756
|
-
}
|
|
53757
|
-
}
|
|
53758
|
-
for (const match of patchText.matchAll(traditionalPlusPattern)) {
|
|
53759
|
-
const p = match[1].trim();
|
|
53760
|
-
if (p !== "/dev/null" && !p.startsWith("a/") && !p.startsWith("b/")) {
|
|
53761
|
-
paths.add(p);
|
|
53762
|
-
}
|
|
53763
|
-
}
|
|
53764
|
-
for (const p of paths) {
|
|
53765
|
-
const resolvedP = path35.resolve(effectiveDirectory, p);
|
|
53766
|
-
const planMdPath = path35.resolve(effectiveDirectory, ".swarm", "plan.md").toLowerCase();
|
|
53767
|
-
const planJsonPath = path35.resolve(effectiveDirectory, ".swarm", "plan.json").toLowerCase();
|
|
53768
|
-
if (resolvedP.toLowerCase() === planMdPath || resolvedP.toLowerCase() === planJsonPath) {
|
|
53769
|
-
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.");
|
|
53770
|
-
}
|
|
53771
|
-
if (isOutsideSwarmDir(p, effectiveDirectory) && (isSourceCodePath(p) || hasTraversalSegments(p))) {
|
|
53772
|
-
const session = swarmState.agentSessions.get(sessionID);
|
|
53773
|
-
if (session) {
|
|
53774
|
-
session.architectWriteCount++;
|
|
53775
|
-
warn("Architect direct code edit detected via apply_patch", {
|
|
53776
|
-
tool: tool3,
|
|
53777
|
-
sessionID,
|
|
53778
|
-
targetPath: p,
|
|
53779
|
-
writeCount: session.architectWriteCount
|
|
53780
|
-
});
|
|
53781
|
-
}
|
|
53782
|
-
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
|
+
});
|
|
53783
53854
|
}
|
|
53855
|
+
break;
|
|
53784
53856
|
}
|
|
53785
53857
|
}
|
|
53786
53858
|
}
|
|
@@ -53877,6 +53949,24 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
|
|
|
53877
53949
|
handleTestSuiteBlocking(input.tool, output.args);
|
|
53878
53950
|
if (isArchitect(input.sessionID) && isWriteTool(input.tool)) {
|
|
53879
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
|
+
}
|
|
53880
53970
|
}
|
|
53881
53971
|
const resolved = resolveSessionAndWindow(input.sessionID);
|
|
53882
53972
|
if (!resolved)
|
|
@@ -54371,7 +54461,7 @@ function hashArgs(args2) {
|
|
|
54371
54461
|
return 0;
|
|
54372
54462
|
}
|
|
54373
54463
|
}
|
|
54374
|
-
var
|
|
54464
|
+
var DEFAULT_AGENT_AUTHORITY_RULES = {
|
|
54375
54465
|
architect: {
|
|
54376
54466
|
blockedExact: [".swarm/plan.md", ".swarm/plan.json"],
|
|
54377
54467
|
blockedZones: ["generated"]
|
|
@@ -54412,12 +54502,38 @@ var AGENT_AUTHORITY_RULES = {
|
|
|
54412
54502
|
blockedZones: ["generated"]
|
|
54413
54503
|
}
|
|
54414
54504
|
};
|
|
54415
|
-
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) {
|
|
54416
54531
|
const normalizedAgent = agentName.toLowerCase();
|
|
54532
|
+
const strippedAgent = stripKnownSwarmPrefix(agentName).toLowerCase();
|
|
54417
54533
|
const dir = cwd || process.cwd();
|
|
54418
54534
|
const resolved = path35.resolve(dir, filePath);
|
|
54419
54535
|
const normalizedPath = path35.relative(dir, resolved).replace(/\\/g, "/");
|
|
54420
|
-
const rules =
|
|
54536
|
+
const rules = effectiveRules[normalizedAgent] ?? effectiveRules[strippedAgent];
|
|
54421
54537
|
if (!rules) {
|
|
54422
54538
|
return { allowed: false, reason: `Unknown agent: ${agentName}` };
|
|
54423
54539
|
}
|
|
@@ -54444,8 +54560,8 @@ function checkFileAuthority(agentName, filePath, cwd) {
|
|
|
54444
54560
|
}
|
|
54445
54561
|
}
|
|
54446
54562
|
}
|
|
54447
|
-
if (rules.allowedPrefix
|
|
54448
|
-
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;
|
|
54449
54565
|
if (!isAllowed) {
|
|
54450
54566
|
return {
|
|
54451
54567
|
allowed: false,
|
|
@@ -59858,7 +59974,7 @@ function countCodeLines(content) {
|
|
|
59858
59974
|
return lines.length;
|
|
59859
59975
|
}
|
|
59860
59976
|
function isTestFile(filePath) {
|
|
59861
|
-
const
|
|
59977
|
+
const basename8 = path52.basename(filePath);
|
|
59862
59978
|
const _ext = path52.extname(filePath).toLowerCase();
|
|
59863
59979
|
const testPatterns = [
|
|
59864
59980
|
".test.",
|
|
@@ -59874,7 +59990,7 @@ function isTestFile(filePath) {
|
|
|
59874
59990
|
".spec.jsx"
|
|
59875
59991
|
];
|
|
59876
59992
|
for (const pattern of testPatterns) {
|
|
59877
|
-
if (
|
|
59993
|
+
if (basename8.includes(pattern)) {
|
|
59878
59994
|
return true;
|
|
59879
59995
|
}
|
|
59880
59996
|
}
|
|
@@ -60540,7 +60656,12 @@ var curator_analyze = createSwarmTool({
|
|
|
60540
60656
|
let applied = 0;
|
|
60541
60657
|
let skipped = 0;
|
|
60542
60658
|
if (typedArgs.recommendations && typedArgs.recommendations.length > 0) {
|
|
60543
|
-
const
|
|
60659
|
+
const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
60660
|
+
const sanitizedRecs = typedArgs.recommendations.map((rec) => ({
|
|
60661
|
+
...rec,
|
|
60662
|
+
entry_id: rec.entry_id === undefined || UUID_V4.test(rec.entry_id) ? rec.entry_id : undefined
|
|
60663
|
+
}));
|
|
60664
|
+
const result = await applyCuratorKnowledgeUpdates(directory, sanitizedRecs, knowledgeConfig);
|
|
60544
60665
|
applied = result.applied;
|
|
60545
60666
|
skipped = result.skipped;
|
|
60546
60667
|
}
|
|
@@ -62218,7 +62339,7 @@ var imports = createSwarmTool({
|
|
|
62218
62339
|
init_dist();
|
|
62219
62340
|
init_config();
|
|
62220
62341
|
init_knowledge_store();
|
|
62221
|
-
import { randomUUID as
|
|
62342
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
62222
62343
|
init_manager2();
|
|
62223
62344
|
init_create_tool();
|
|
62224
62345
|
var VALID_CATEGORIES2 = [
|
|
@@ -62293,7 +62414,7 @@ var knowledgeAdd = createSwarmTool({
|
|
|
62293
62414
|
project_name = plan?.title ?? "";
|
|
62294
62415
|
} catch {}
|
|
62295
62416
|
const entry = {
|
|
62296
|
-
id:
|
|
62417
|
+
id: randomUUID6(),
|
|
62297
62418
|
tier: "swarm",
|
|
62298
62419
|
lesson,
|
|
62299
62420
|
category,
|
|
@@ -69968,7 +70089,8 @@ var OpenCodeSwarm = async (ctx) => {
|
|
|
69968
70089
|
console.warn("");
|
|
69969
70090
|
}
|
|
69970
70091
|
const delegationHandler = createDelegationTrackerHook(config3, guardrailsConfig.enabled);
|
|
69971
|
-
const
|
|
70092
|
+
const authorityConfig = AuthorityConfigSchema.parse(config3.authority ?? {});
|
|
70093
|
+
const guardrailsHooks = createGuardrailsHooks(ctx.directory, undefined, guardrailsConfig, authorityConfig);
|
|
69972
70094
|
const watchdogConfig = WatchdogConfigSchema.parse(config3.watchdog ?? {});
|
|
69973
70095
|
const advisoryInjector = (sessionId, message) => {
|
|
69974
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",
|