@heretek-ai/epistemic-swarm 0.7.6 → 0.7.8
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/.claude-plugin/plugin.json +1 -1
- package/config/opencode-snippet.json +102 -149
- package/install.sh +81 -58
- package/package.json +1 -1
- package/plugins/opencode/index.js +72 -8
- package/runner/__pycache__/__init__.cpython-311.pyc +0 -0
- package/runner/__pycache__/auctioneer.cpython-311.pyc +0 -0
- package/runner/__pycache__/auditor_engine.cpython-311.pyc +0 -0
- package/runner/__pycache__/claim_store.cpython-311.pyc +0 -0
- package/runner/__pycache__/claim_witness.cpython-311.pyc +0 -0
- package/runner/__pycache__/living_dossiers.cpython-311.pyc +0 -0
- package/runner/__pycache__/mcp_protocol.cpython-311.pyc +0 -0
- package/runner/__pycache__/mcp_server.cpython-311.pyc +0 -0
- package/runner/__pycache__/path_safety.cpython-311.pyc +0 -0
- package/runner/__pycache__/pcrb.cpython-311.pyc +0 -0
- package/runner/__pycache__/pcrb_verify.cpython-311.pyc +0 -0
- package/runner/__pycache__/refinement.cpython-311.pyc +0 -0
- package/runner/__pycache__/research_swarm.cpython-311.pyc +0 -0
- package/runner/__pycache__/state_machine.cpython-311.pyc +0 -0
- package/runner/mcp_server.py +7 -1
- package/runner/research_swarm.py +65 -3
- package/runner/tests/__pycache__/test_auction_order.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_backends.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_bet1_spike.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_claim_store.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_claim_witness.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_claude_plugin.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_domain_packs.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_factory.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_fleet_seam.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_living_dossiers.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_mcp_server.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_opencode_ux.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_opencode_v2_registrars.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_pcrb.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_refinement.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_swarm.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_swarm_config.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_sweep_regressions.cpython-311.pyc +0 -0
- package/runner/tests/__pycache__/test_webcache.cpython-311.pyc +0 -0
- package/runner/tests/test_factory.py +15 -7
- package/runner/tests/test_opencode_ux.py +37 -27
- package/runner/tests/test_opencode_v2_registrars.py +113 -16
- package/runner/tests/test_swarm_config.py +263 -0
- package/scripts/__pycache__/bet1_advisory_spike.cpython-311.pyc +0 -0
- package/scripts/__pycache__/build_adapters.cpython-311.pyc +0 -0
- package/scripts/__pycache__/divergence_experiment.cpython-311.pyc +0 -0
- package/skills/epistemic_search/scripts/__pycache__/search.cpython-311.pyc +0 -0
- package/skills/research_cache/__pycache__/__init__.cpython-311.pyc +0 -0
- package/skills/research_cache/__pycache__/hasher.cpython-311.pyc +0 -0
- package/skills/swarm_config/__init__.py +15 -2
- package/skills/swarm_config/__pycache__/__init__.cpython-311.pyc +0 -0
- package/skills/swarm_config/__pycache__/configure.cpython-311.pyc +0 -0
- package/skills/swarm_config/configure.py +103 -6
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://opencode.ai/config.json",
|
|
3
3
|
"mcp": {
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
"servers": {
|
|
5
|
+
"iumbtems": {
|
|
6
|
+
"type": "local",
|
|
7
|
+
"command": ["python3", "<IUMBTEMS_ROOT>/runner/mcp_server.py"],
|
|
8
|
+
"enabled": true
|
|
9
|
+
}
|
|
8
10
|
}
|
|
9
11
|
},
|
|
10
12
|
"plugins": [
|
|
@@ -17,187 +19,138 @@
|
|
|
17
19
|
}
|
|
18
20
|
}
|
|
19
21
|
],
|
|
20
|
-
"
|
|
22
|
+
"agents": {
|
|
21
23
|
"code-auditor": {
|
|
22
24
|
"description": "IUMBTEMS Codebase Auditor: Dialectic architecture mapping and vulnerability red-teaming with line-level proof.",
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
"grep":
|
|
27
|
-
"glob":
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
25
|
+
"mode": "all",
|
|
26
|
+
"permissions": [
|
|
27
|
+
{ "action": "read", "resource": "*", "effect": "allow" },
|
|
28
|
+
{ "action": "grep", "resource": "*", "effect": "allow" },
|
|
29
|
+
{ "action": "glob", "resource": "*", "effect": "allow" },
|
|
30
|
+
{ "action": "edit", "resource": "*", "effect": "allow" },
|
|
31
|
+
{ "action": "shell", "resource": "*", "effect": "ask" }
|
|
32
|
+
]
|
|
31
33
|
},
|
|
32
34
|
"oss-scout": {
|
|
33
35
|
"description": "IUMBTEMS Open Source Scout: Discovers mature OSS packages, audits copyleft licensing, and generates clean-room blueprints.",
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
"grep":
|
|
38
|
-
"glob":
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
36
|
+
"mode": "all",
|
|
37
|
+
"permissions": [
|
|
38
|
+
{ "action": "read", "resource": "*", "effect": "allow" },
|
|
39
|
+
{ "action": "grep", "resource": "*", "effect": "allow" },
|
|
40
|
+
{ "action": "glob", "resource": "*", "effect": "allow" },
|
|
41
|
+
{ "action": "edit", "resource": "*", "effect": "allow" },
|
|
42
|
+
{ "action": "shell", "resource": "*", "effect": "ask" }
|
|
43
|
+
]
|
|
42
44
|
},
|
|
43
45
|
"alpha-thesis": {
|
|
44
46
|
"description": "IUMBTEMS Proponent Agent: Gathers corroborating primary sources, benchmark results, and empirical proofs.",
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
"read":
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
"iumbtems_config": true
|
|
55
|
-
}
|
|
47
|
+
"mode": "all",
|
|
48
|
+
"permissions": [
|
|
49
|
+
{ "action": "read", "resource": "*", "effect": "allow" },
|
|
50
|
+
{ "action": "grep", "resource": "*", "effect": "allow" },
|
|
51
|
+
{ "action": "glob", "resource": "*", "effect": "allow" },
|
|
52
|
+
{ "action": "edit", "resource": "*", "effect": "allow" },
|
|
53
|
+
{ "action": "shell", "resource": "*", "effect": "ask" },
|
|
54
|
+
{ "action": "subagent", "resource": "*", "effect": "allow" }
|
|
55
|
+
]
|
|
56
56
|
},
|
|
57
57
|
"beta-redteam": {
|
|
58
58
|
"description": "IUMBTEMS Adversary Agent: Active falsification, red-teaming, hunting for counter-benchmarks, and edge-case failure modes.",
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
"read":
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
}
|
|
59
|
+
"mode": "all",
|
|
60
|
+
"permissions": [
|
|
61
|
+
{ "action": "read", "resource": "*", "effect": "allow" },
|
|
62
|
+
{ "action": "grep", "resource": "*", "effect": "allow" },
|
|
63
|
+
{ "action": "glob", "resource": "*", "effect": "allow" },
|
|
64
|
+
{ "action": "edit", "resource": "*", "effect": "allow" },
|
|
65
|
+
{ "action": "shell", "resource": "*", "effect": "ask" },
|
|
66
|
+
{ "action": "subagent", "resource": "*", "effect": "allow" }
|
|
67
|
+
]
|
|
69
68
|
},
|
|
70
69
|
"epistemic-auditor": {
|
|
71
70
|
"description": "IUMBTEMS Synthesizer & Auditor: Verifies verbatim source quotes, downgrades unverified claims, and calculates divergence.",
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
"
|
|
75
|
-
"grep":
|
|
76
|
-
"glob":
|
|
77
|
-
"
|
|
78
|
-
|
|
71
|
+
"mode": "all",
|
|
72
|
+
"permissions": [
|
|
73
|
+
{ "action": "read", "resource": "*", "effect": "allow" },
|
|
74
|
+
{ "action": "grep", "resource": "*", "effect": "allow" },
|
|
75
|
+
{ "action": "glob", "resource": "*", "effect": "allow" },
|
|
76
|
+
{ "action": "edit", "resource": "*", "effect": "allow" }
|
|
77
|
+
]
|
|
79
78
|
},
|
|
80
79
|
"brainstormer": {
|
|
81
80
|
"description": "IUMBTEMS Lateral Brainstormer: divergent what-if features, paradigm inversions, falsifiable spikes (never bug fixes).",
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
"
|
|
85
|
-
"grep":
|
|
86
|
-
"glob":
|
|
87
|
-
"
|
|
88
|
-
|
|
89
|
-
}
|
|
81
|
+
"mode": "all",
|
|
82
|
+
"permissions": [
|
|
83
|
+
{ "action": "read", "resource": "*", "effect": "allow" },
|
|
84
|
+
{ "action": "grep", "resource": "*", "effect": "allow" },
|
|
85
|
+
{ "action": "glob", "resource": "*", "effect": "allow" },
|
|
86
|
+
{ "action": "edit", "resource": "*", "effect": "allow" }
|
|
87
|
+
]
|
|
90
88
|
},
|
|
91
89
|
"darkharvester": {
|
|
92
90
|
"description": "IUMBTEMS Competitor Teardown: seed plus expand, product plus code matrix, per-feature harvest verdicts with SPDX attribution.",
|
|
93
|
-
"
|
|
94
|
-
|
|
95
|
-
"
|
|
96
|
-
"grep":
|
|
97
|
-
"glob":
|
|
98
|
-
"
|
|
99
|
-
|
|
100
|
-
"iumbtems_config": true
|
|
101
|
-
}
|
|
91
|
+
"mode": "all",
|
|
92
|
+
"permissions": [
|
|
93
|
+
{ "action": "read", "resource": "*", "effect": "allow" },
|
|
94
|
+
{ "action": "grep", "resource": "*", "effect": "allow" },
|
|
95
|
+
{ "action": "glob", "resource": "*", "effect": "allow" },
|
|
96
|
+
{ "action": "edit", "resource": "*", "effect": "allow" }
|
|
97
|
+
]
|
|
102
98
|
},
|
|
103
99
|
"manager": {
|
|
104
100
|
"description": "IUMBTEMS Factory Manager: grill-gated phased builds. Owns gates, swarm dispatch, roadmap synthesis, QA tiebreaks. Never writes code.",
|
|
105
101
|
"mode": "primary",
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
"edit": "deny",
|
|
109
|
-
"
|
|
110
|
-
"
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"darkharvester": "allow"
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
"tools": {
|
|
121
|
-
"read": true,
|
|
122
|
-
"write": true,
|
|
123
|
-
"grep": true,
|
|
124
|
-
"glob": true,
|
|
125
|
-
"task": true,
|
|
126
|
-
"iumbtems_brainstorm": true,
|
|
127
|
-
"iumbtems_darkharvest": true,
|
|
128
|
-
"iumbtems_verify_quote": true,
|
|
129
|
-
"iumbtems_socratic_frontier": true,
|
|
130
|
-
"iumbtems_config": true
|
|
131
|
-
}
|
|
102
|
+
"system": "You are the IUMBTEMS Factory Manager. Own gate discipline: grill until the frontier is settled, run the brainstorm and darkharvest swarms, synthesize .roadmap phase dossiers, spawn the programmer per phase, run qa-a and qa-b, and tiebreak their disagreements. Never write implementation code. Use the iumbtems_factory tool for run state. Explicit user approval advances each gate.",
|
|
103
|
+
"permissions": [
|
|
104
|
+
{ "action": "edit", "resource": "*", "effect": "deny" },
|
|
105
|
+
{ "action": "shell", "resource": "*", "effect": "ask" },
|
|
106
|
+
{ "action": "subagent", "resource": "*", "effect": "deny" },
|
|
107
|
+
{ "action": "subagent", "resource": "programmer", "effect": "allow" },
|
|
108
|
+
{ "action": "subagent", "resource": "qa-a", "effect": "allow" },
|
|
109
|
+
{ "action": "subagent", "resource": "qa-b", "effect": "allow" },
|
|
110
|
+
{ "action": "subagent", "resource": "brainstormer", "effect": "allow" },
|
|
111
|
+
{ "action": "subagent", "resource": "darkharvester", "effect": "allow" }
|
|
112
|
+
]
|
|
132
113
|
},
|
|
133
114
|
"programmer": {
|
|
134
115
|
"description": "IUMBTEMS Factory Programmer: implements exactly one phase brief per spawn. Cites phase evidence hashes. Never invokes swarms or other programmers.",
|
|
135
116
|
"mode": "subagent",
|
|
136
|
-
"
|
|
137
|
-
"
|
|
138
|
-
"edit": "allow",
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
"tools": {
|
|
145
|
-
"read": true,
|
|
146
|
-
"write": true,
|
|
147
|
-
"bash": true,
|
|
148
|
-
"grep": true,
|
|
149
|
-
"glob": true,
|
|
150
|
-
"iumbtems_verify_quote": true
|
|
151
|
-
}
|
|
117
|
+
"system": "You are the IUMBTEMS Factory Programmer. Implement exactly one phase brief per spawn. Cite phase evidence hashes. Never invoke swarms or other programmers. If the brief is ambiguous or untestable, stop and ask the manager.",
|
|
118
|
+
"permissions": [
|
|
119
|
+
{ "action": "edit", "resource": "*", "effect": "allow" },
|
|
120
|
+
{ "action": "shell", "resource": "*", "effect": "ask" },
|
|
121
|
+
{ "action": "subagent", "resource": "*", "effect": "deny" }
|
|
122
|
+
]
|
|
152
123
|
},
|
|
153
124
|
"qa-a": {
|
|
154
125
|
"description": "IUMBTEMS Factory QA (functional): verifies phase acceptance criteria pass on the real surface. Read-only plus test execution. Diverged prompt from qa-b.",
|
|
155
126
|
"mode": "subagent",
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
-
"edit": "deny",
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
"tools": {
|
|
165
|
-
"read": true,
|
|
166
|
-
"bash": true,
|
|
167
|
-
"grep": true,
|
|
168
|
-
"glob": true
|
|
169
|
-
}
|
|
127
|
+
"system": "You are the IUMBTEMS Factory functional QA. Verify each phase acceptance criterion on the real surface with tests and inspection. Read-only plus test execution; never edit code. Return pass|fail(reason)|conditional(note).",
|
|
128
|
+
"permissions": [
|
|
129
|
+
{ "action": "edit", "resource": "*", "effect": "deny" },
|
|
130
|
+
{ "action": "shell", "resource": "*", "effect": "ask" },
|
|
131
|
+
{ "action": "subagent", "resource": "*", "effect": "deny" }
|
|
132
|
+
]
|
|
170
133
|
},
|
|
171
134
|
"qa-b": {
|
|
172
135
|
"description": "IUMBTEMS Factory QA (adversarial): hunts edge cases, regressions, and acceptance loopholes the functional pass missed. Read-only plus test execution. Diverged prompt from qa-a.",
|
|
173
136
|
"mode": "subagent",
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
"edit": "deny",
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
},
|
|
182
|
-
"tools": {
|
|
183
|
-
"read": true,
|
|
184
|
-
"bash": true,
|
|
185
|
-
"grep": true,
|
|
186
|
-
"glob": true
|
|
187
|
-
}
|
|
137
|
+
"system": "You are the IUMBTEMS Factory adversarial QA. Attack the phase: edge cases, regressions, vacuous acceptance criteria, error paths, resource limits. Read-only plus test execution; never edit code. Return pass|fail(reason)|conditional(note) with reproductions.",
|
|
138
|
+
"permissions": [
|
|
139
|
+
{ "action": "edit", "resource": "*", "effect": "deny" },
|
|
140
|
+
{ "action": "shell", "resource": "*", "effect": "ask" },
|
|
141
|
+
{ "action": "subagent", "resource": "*", "effect": "deny" }
|
|
142
|
+
]
|
|
188
143
|
}
|
|
189
144
|
},
|
|
190
|
-
"skills":
|
|
191
|
-
"
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
]
|
|
202
|
-
}
|
|
145
|
+
"skills": [
|
|
146
|
+
"./skills/grilling",
|
|
147
|
+
"./skills/research_cache",
|
|
148
|
+
"./skills/epistemic_search",
|
|
149
|
+
"./skills/swarm_config",
|
|
150
|
+
"./skills/code_audit",
|
|
151
|
+
"./skills/oss_scout",
|
|
152
|
+
"./skills/brainstorming",
|
|
153
|
+
"./skills/darkharvest",
|
|
154
|
+
"./skills/factory"
|
|
155
|
+
]
|
|
203
156
|
}
|
package/install.sh
CHANGED
|
@@ -109,7 +109,7 @@ except Exception as e:
|
|
|
109
109
|
EOF
|
|
110
110
|
fi
|
|
111
111
|
|
|
112
|
-
# 5. OpenCode V2 integration (
|
|
112
|
+
# 5. OpenCode V2 integration (plugins/agents/skills/mcp.servers — merge only)
|
|
113
113
|
OPENCODE_JSON="$HOME/.config/opencode/opencode.json"
|
|
114
114
|
echo "🔧 Merging OpenCode V2 configuration into $OPENCODE_JSON..."
|
|
115
115
|
python3 - <<EOF
|
|
@@ -124,91 +124,114 @@ OPTION_KEYS = {"search_engine", "max_iterations", "mode", "divergence_threshold"
|
|
|
124
124
|
oc_path.parent.mkdir(parents=True, exist_ok=True)
|
|
125
125
|
cfg = json.loads(oc_path.read_text(encoding="utf-8")) if oc_path.exists() else {}
|
|
126
126
|
|
|
127
|
-
# ---
|
|
128
|
-
plugins = cfg.get("
|
|
127
|
+
# --- plugins (V2): object form {"package", "options"}; migrate legacy `plugin` ---
|
|
128
|
+
plugins = cfg.get("plugins")
|
|
129
129
|
if not isinstance(plugins, list):
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
plugins = []
|
|
131
|
+
|
|
132
|
+
# Migrate any legacy V1 `plugin` entries: bare strings, [pkg, options] tuples,
|
|
133
|
+
# and the "wild" malformation where a bare options object trails our package.
|
|
134
|
+
legacy = cfg.get("plugin")
|
|
135
|
+
if isinstance(legacy, list):
|
|
134
136
|
i = 0
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
item
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
merged = {**(repaired[-1][1] if len(repaired[-1]) > 1 else {}), **item}
|
|
152
|
-
repaired[-1] = [PKG, merged]
|
|
153
|
-
changed = True
|
|
154
|
-
print(" ~ Folded stray options into existing tuple entry")
|
|
137
|
+
while i < len(legacy):
|
|
138
|
+
item = legacy[i]
|
|
139
|
+
if isinstance(item, str):
|
|
140
|
+
if item == PKG and i + 1 < len(legacy) and isinstance(legacy[i + 1], dict) and (set(legacy[i + 1]) & OPTION_KEYS):
|
|
141
|
+
plugins.append({"package": PKG, "options": legacy[i + 1]})
|
|
142
|
+
i += 2
|
|
143
|
+
continue
|
|
144
|
+
plugins.append(item)
|
|
145
|
+
elif isinstance(item, list) and item:
|
|
146
|
+
opts = item[1] if len(item) > 1 and isinstance(item[1], dict) else {}
|
|
147
|
+
plugins.append({"package": item[0], "options": opts})
|
|
148
|
+
elif isinstance(item, dict):
|
|
149
|
+
if "package" in item:
|
|
150
|
+
plugins.append(item)
|
|
151
|
+
elif set(item) & OPTION_KEYS:
|
|
152
|
+
plugins.append({"package": PKG, "options": item})
|
|
155
153
|
else:
|
|
156
|
-
|
|
157
|
-
changed = True
|
|
158
|
-
print(" ~ Attached stray options object to new tuple entry")
|
|
159
|
-
else:
|
|
160
|
-
repaired.append(item)
|
|
154
|
+
plugins.append(item)
|
|
161
155
|
i += 1
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
156
|
+
cfg.pop("plugin", None)
|
|
157
|
+
print(" ~ Migrated legacy 'plugin' entries to V2 'plugins' (object form)")
|
|
158
|
+
|
|
159
|
+
# Normalize our entry to the V2 object form and ensure default options exist.
|
|
160
|
+
DEFAULT_OPTIONS = {"search_engine": "duckduckgo", "max_iterations": 2, "mode": "research"}
|
|
161
|
+
our = None
|
|
162
|
+
for idx, entry in enumerate(plugins):
|
|
163
|
+
if entry == PKG:
|
|
164
|
+
our = {"package": PKG, "options": {}}
|
|
165
|
+
plugins[idx] = our
|
|
166
|
+
elif isinstance(entry, dict) and entry.get("package") == PKG:
|
|
167
|
+
our = entry
|
|
168
|
+
entry["options"] = {**DEFAULT_OPTIONS, **(entry.get("options") or {})}
|
|
169
|
+
if our is None:
|
|
170
|
+
our = {"package": PKG, "options": dict(DEFAULT_OPTIONS)}
|
|
171
|
+
plugins.append(our)
|
|
172
|
+
print(f" + Added plugin entry: {PKG}")
|
|
173
|
+
else:
|
|
174
|
+
print(f" ℹ️ Plugin entry already configured: {PKG}")
|
|
175
|
+
cfg["plugins"] = plugins
|
|
176
|
+
|
|
177
|
+
# --- agents (V2 plural): merge snippet definitions, never overwrite users ---
|
|
171
178
|
try:
|
|
172
179
|
snippet = json.loads((repo / "config" / "opencode-snippet.json").read_text(encoding="utf-8"))
|
|
173
|
-
agents = cfg.setdefault("
|
|
174
|
-
|
|
180
|
+
agents = cfg.setdefault("agents", {})
|
|
181
|
+
legacy_agents = cfg.get("agent")
|
|
182
|
+
if isinstance(legacy_agents, dict):
|
|
183
|
+
for name, definition in legacy_agents.items():
|
|
184
|
+
agents.setdefault(name, definition)
|
|
185
|
+
cfg.pop("agent", None)
|
|
186
|
+
print(" ~ Migrated legacy 'agent' entries to V2 'agents'")
|
|
187
|
+
for name, definition in (snippet.get("agents") or snippet.get("agent") or {}).items():
|
|
175
188
|
if name not in agents:
|
|
176
189
|
agents[name] = definition
|
|
177
190
|
print(f" + Added agent: {name}")
|
|
178
191
|
else:
|
|
179
192
|
print(f" ℹ️ Agent already configured: {name}")
|
|
180
193
|
|
|
181
|
-
# Skills:
|
|
182
|
-
# from ~/.config — merge as absolute paths
|
|
194
|
+
# Skills: V2 is a list of directories/URLs. Snippet paths are repo-relative
|
|
195
|
+
# ("./skills/x") which breaks from ~/.config — merge as absolute paths and
|
|
196
|
+
# repair a legacy `{"paths": [...]}` object or relative entries in place.
|
|
183
197
|
import os as _os
|
|
184
198
|
wanted = []
|
|
185
|
-
for sp in snippet.get("skills"
|
|
199
|
+
for sp in snippet.get("skills") or []:
|
|
186
200
|
wanted.append(sp if _os.path.isabs(sp) else str(repo / sp.lstrip("./")))
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
201
|
+
skills = cfg.get("skills")
|
|
202
|
+
if isinstance(skills, dict):
|
|
203
|
+
skills = cfg["skills"] = list(skills.get("paths") or [])
|
|
204
|
+
print(" ~ Migrated legacy skills.paths to the V2 skills list")
|
|
205
|
+
elif not isinstance(skills, list):
|
|
206
|
+
skills = cfg["skills"] = []
|
|
207
|
+
for i, existing in enumerate(list(skills)):
|
|
208
|
+
if existing in wanted:
|
|
209
|
+
continue
|
|
191
210
|
for abs_sp in wanted:
|
|
192
|
-
if existing == abs_sp:
|
|
193
|
-
break
|
|
194
211
|
rel = "./" + str(Path(abs_sp).relative_to(repo)) if str(abs_sp).startswith(str(repo)) else None
|
|
195
212
|
if rel and existing == rel:
|
|
196
|
-
|
|
213
|
+
skills[i] = abs_sp
|
|
197
214
|
print(f" ~ Repaired relative skill path: {rel} -> {abs_sp}")
|
|
198
215
|
break
|
|
199
216
|
for abs_sp in wanted:
|
|
200
|
-
if abs_sp not in
|
|
201
|
-
|
|
217
|
+
if abs_sp not in skills:
|
|
218
|
+
skills.append(abs_sp)
|
|
202
219
|
print(f" + Added skill path: {abs_sp}")
|
|
203
220
|
else:
|
|
204
221
|
print(f" ℹ️ Skill path already configured: {abs_sp}")
|
|
205
222
|
except Exception as e:
|
|
206
223
|
print(f" ⚠️ Non-critical warning merging agents: {e}")
|
|
207
224
|
|
|
208
|
-
# --- MCP server: absolute runner path
|
|
225
|
+
# --- MCP server (V2 `mcp.servers`): absolute runner path ---
|
|
209
226
|
mcp = cfg.setdefault("mcp", {})
|
|
210
|
-
|
|
211
|
-
|
|
227
|
+
servers = mcp.setdefault("servers", {})
|
|
228
|
+
legacy_mcp = mcp.get("iumbtems")
|
|
229
|
+
if isinstance(legacy_mcp, dict):
|
|
230
|
+
servers.setdefault("iumbtems", legacy_mcp)
|
|
231
|
+
mcp.pop("iumbtems", None)
|
|
232
|
+
print(" ~ Migrated legacy mcp.iumbtems to V2 mcp.servers.iumbtems")
|
|
233
|
+
if "iumbtems" not in servers:
|
|
234
|
+
servers["iumbtems"] = {
|
|
212
235
|
"type": "local",
|
|
213
236
|
"command": ["python3", str(repo / "runner" / "mcp_server.py")],
|
|
214
237
|
"enabled": True,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heretek-ai/epistemic-swarm",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
4
4
|
"description": "IUMBTEMS: I Use My Brain To Express My Self — High-Integrity Dialectic Research Agent Harness for Claude Code, OpenCode V2, Pi, OMP (oh-my-pi), Gemini CLI, Codex CLI, and AntiGravity",
|
|
5
5
|
"main": "bin/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -920,7 +920,8 @@ async function registerHostCommands(host) {
|
|
|
920
920
|
await host.session.prompt({
|
|
921
921
|
...prompt,
|
|
922
922
|
sessionID: input?.sessionID,
|
|
923
|
-
|
|
923
|
+
// No `agent` field here: V2's SessionPromptInput has none (agent
|
|
924
|
+
// mentions only). Pinning happens via switchAgent above.
|
|
924
925
|
text: cmd.template.split('$ARGUMENTS').join(String(args).trim()),
|
|
925
926
|
delivery: input?.delivery,
|
|
926
927
|
});
|
|
@@ -1028,7 +1029,9 @@ async function registerHostAgents(host) {
|
|
|
1028
1029
|
// every step of a factory session executed as `build`, not `manager`).
|
|
1029
1030
|
if (typeof host?.agent?.transform !== 'function') return [];
|
|
1030
1031
|
const snippet = readPkgJson(path.join(PKG_ROOT, 'config', 'opencode-snippet.json'));
|
|
1031
|
-
|
|
1032
|
+
// V2 config key is `agents` (plural); accept the legacy `agent` key for one
|
|
1033
|
+
// release so older snippet copies keep working.
|
|
1034
|
+
const agentDefs = (snippet && (snippet.agents || snippet.agent)) || {};
|
|
1032
1035
|
if (Object.keys(agentDefs).length === 0) return [];
|
|
1033
1036
|
|
|
1034
1037
|
let existing = new Set();
|
|
@@ -1046,7 +1049,7 @@ async function registerHostAgents(host) {
|
|
|
1046
1049
|
if (claimed.has(name)) continue;
|
|
1047
1050
|
claimed.add(name);
|
|
1048
1051
|
added.push(name);
|
|
1049
|
-
draft
|
|
1052
|
+
registerAgent(draft, name, def);
|
|
1050
1053
|
}
|
|
1051
1054
|
log(host, 'debug', 'agent.transform pass', { added });
|
|
1052
1055
|
});
|
|
@@ -1071,18 +1074,49 @@ const FACTORY_SYSTEM = {
|
|
|
1071
1074
|
'You are the IUMBTEMS Factory adversarial QA. Attack the phase: edge cases, regressions, vacuous acceptance criteria, error paths, resource limits. Read-only plus test execution; never edit code. Return pass|fail(reason)|conditional(note) with reproductions.',
|
|
1072
1075
|
};
|
|
1073
1076
|
|
|
1074
|
-
/**
|
|
1077
|
+
/** V1 permission/tool action names -> V2 permission action names. */
|
|
1078
|
+
const ACTION_ALIASES = { bash: 'shell', task: 'subagent', patch: 'edit', write: 'edit' };
|
|
1079
|
+
|
|
1080
|
+
function normalizeAction(action) {
|
|
1081
|
+
const key = String(action || '').trim();
|
|
1082
|
+
return ACTION_ALIASES[key] || key;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function normalizeEffect(effect) {
|
|
1086
|
+
return effect === 'deny' || effect === 'ask' ? effect : 'allow';
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* Map one snippet agent definition to an Agent.Info shape.
|
|
1091
|
+
*
|
|
1092
|
+
* Accepts the V2 config form (`permissions: [{action,resource,effect}]`) and
|
|
1093
|
+
* still understands the legacy V1 form (`tools` map + `permission` map) so an
|
|
1094
|
+
* older snippet copy is not silently dropped.
|
|
1095
|
+
*/
|
|
1075
1096
|
function toAgentInfo(name, def) {
|
|
1076
1097
|
const permissions = [];
|
|
1098
|
+
for (const rule of def?.permissions || []) {
|
|
1099
|
+
if (rule && typeof rule === 'object' && typeof rule.action === 'string') {
|
|
1100
|
+
permissions.push({
|
|
1101
|
+
action: normalizeAction(rule.action),
|
|
1102
|
+
resource: typeof rule.resource === 'string' ? rule.resource : '*',
|
|
1103
|
+
effect: normalizeEffect(rule.effect),
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1077
1107
|
for (const [tool, enabled] of Object.entries(def?.tools || {})) {
|
|
1078
|
-
permissions.push({
|
|
1108
|
+
permissions.push({
|
|
1109
|
+
action: normalizeAction(tool),
|
|
1110
|
+
resource: '*',
|
|
1111
|
+
effect: enabled ? 'allow' : 'deny',
|
|
1112
|
+
});
|
|
1079
1113
|
}
|
|
1080
1114
|
for (const [key, value] of Object.entries(def?.permission || {})) {
|
|
1081
1115
|
if (typeof value === 'string') {
|
|
1082
|
-
permissions.push({ action: key, resource: '*', effect: value });
|
|
1116
|
+
permissions.push({ action: normalizeAction(key), resource: '*', effect: normalizeEffect(value) });
|
|
1083
1117
|
} else if (value && typeof value === 'object') {
|
|
1084
1118
|
for (const [resource, effect] of Object.entries(value)) {
|
|
1085
|
-
permissions.push({ action: key, resource, effect });
|
|
1119
|
+
permissions.push({ action: normalizeAction(key), resource, effect: normalizeEffect(effect) });
|
|
1086
1120
|
}
|
|
1087
1121
|
}
|
|
1088
1122
|
}
|
|
@@ -1097,11 +1131,41 @@ function toAgentInfo(name, def) {
|
|
|
1097
1131
|
};
|
|
1098
1132
|
if (def?.steps) info.steps = def.steps;
|
|
1099
1133
|
if (def?.color) info.color = def.color;
|
|
1100
|
-
|
|
1134
|
+
// The canonical short role prompt wins; fall back to a snippet-provided one.
|
|
1135
|
+
const system = FACTORY_SYSTEM[name] || def?.system;
|
|
1101
1136
|
if (system) info.system = system;
|
|
1102
1137
|
return info;
|
|
1103
1138
|
}
|
|
1104
1139
|
|
|
1140
|
+
/**
|
|
1141
|
+
* Register one agent profile through a V2 `AgentEditor`.
|
|
1142
|
+
*
|
|
1143
|
+
* V2's AgentEditor has NO `add()` — only `list/get/default/update/remove`
|
|
1144
|
+
* (verified against @opencode/plugin@2.0.18 dist/promise/agent.d.ts). Calling
|
|
1145
|
+
* `draft.add(...)` threw `draft.add is not a function` and the host disabled
|
|
1146
|
+
* the whole plugin ("disabled plugin after transform failure", state=agent).
|
|
1147
|
+
*
|
|
1148
|
+
* `update(id, mutate)` upserts: when the id is new the host seeds the draft
|
|
1149
|
+
* from `Agent.Info.default(id)` plus global permission rules, then hands it to
|
|
1150
|
+
* the callback (opencode v2.0.18 agent registry:
|
|
1151
|
+
* `c = agents.get(d) ?? {...default(d), permissions:[...default.permissions, ...I]}`).
|
|
1152
|
+
* The first-party `opencode.config.agent` plugin registers config agents the
|
|
1153
|
+
* same way, so this is the supported path for creating profile agents.
|
|
1154
|
+
*
|
|
1155
|
+
* Seeded permissions are kept and ours appended: rule matching is
|
|
1156
|
+
* last-match-wins, so an explicit `edit: deny` still beats the default allow.
|
|
1157
|
+
*/
|
|
1158
|
+
function registerAgent(editor, name, def) {
|
|
1159
|
+
const info = toAgentInfo(name, def);
|
|
1160
|
+
const rules = info.permissions || [];
|
|
1161
|
+
const profile = { ...info };
|
|
1162
|
+
delete profile.permissions;
|
|
1163
|
+
editor.update(name, (agent) => {
|
|
1164
|
+
Object.assign(agent, profile);
|
|
1165
|
+
agent.permissions = [...(agent.permissions || []), ...rules];
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1105
1169
|
/** Canonical skills the plugin registers so agents never hunt the filesystem. */
|
|
1106
1170
|
const BUNDLED_SKILLS = [
|
|
1107
1171
|
'factory',
|
|
Binary file
|
|
Binary file
|
|
Binary file
|