@heretek-ai/epistemic-swarm 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/install.sh +88 -0
- package/package.json +6 -1
- package/plugins/antigravity/plugin.json +1 -1
- package/plugins/gemini/gemini-extension.json +1 -1
- package/plugins/opencode/index.js +318 -17
- package/plugins/opencode/tui.js +247 -0
- 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__/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/tests/__pycache__/test_auction_order.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_domain_packs.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_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/test_opencode_ux.py +393 -0
- package/runner/tests/test_swarm.py +19 -3
- 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/__pycache__/__init__.cpython-311.pyc +0 -0
- package/skills/swarm_config/__pycache__/configure.cpython-311.pyc +0 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Tests for the OpenCode v2 end-user UX surface (goal Phase A).
|
|
3
|
+
|
|
4
|
+
Covers the slash-command catalog, the server object-form contract, and
|
|
5
|
+
install.sh OpenCode handling (idempotency + bare-object repair).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import tempfile
|
|
13
|
+
import unittest
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
17
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
18
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
19
|
+
|
|
20
|
+
EXPECTED_COMMANDS = [
|
|
21
|
+
"swarm",
|
|
22
|
+
"grill",
|
|
23
|
+
"swarm-config",
|
|
24
|
+
"audit",
|
|
25
|
+
"scout",
|
|
26
|
+
"brainstorming",
|
|
27
|
+
"brainstorm",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def run_node(code):
|
|
32
|
+
return subprocess.run(
|
|
33
|
+
["node", "--input-type=module", "-e", code],
|
|
34
|
+
capture_output=True,
|
|
35
|
+
text=True,
|
|
36
|
+
cwd=str(PROJECT_ROOT),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def last_json_object(stdout):
|
|
41
|
+
lines = [l.strip() for l in stdout.strip().split("\n") if l.strip().startswith("{")]
|
|
42
|
+
return json.loads(lines[-1])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TestOpenCodeCommandCatalog(unittest.TestCase):
|
|
46
|
+
def test_catalog_shape_and_registration(self):
|
|
47
|
+
res = run_node(
|
|
48
|
+
"""
|
|
49
|
+
import plugin, { OPENCODE_COMMANDS, registerOpenCodeCommands } from "./plugins/opencode/index.js";
|
|
50
|
+
const shell = await plugin.server();
|
|
51
|
+
const cfg = {};
|
|
52
|
+
shell.config(cfg);
|
|
53
|
+
console.log(JSON.stringify({
|
|
54
|
+
catalog: OPENCODE_COMMANDS.map(c => ({name: c.name, hasDesc: !!c.description, hasArgs: c.template.includes("$ARGUMENTS")})),
|
|
55
|
+
registered: Object.keys(cfg.command || {}),
|
|
56
|
+
templates: Object.fromEntries(Object.entries(cfg.command || {}).map(([k, v]) => [k, !!v.description && !!v.template]))
|
|
57
|
+
}));
|
|
58
|
+
"""
|
|
59
|
+
)
|
|
60
|
+
self.assertEqual(
|
|
61
|
+
res.returncode, 0, f"command catalog test failed: {res.stderr}"
|
|
62
|
+
)
|
|
63
|
+
data = last_json_object(res.stdout)
|
|
64
|
+
names = [c["name"] for c in data["catalog"]]
|
|
65
|
+
for c in EXPECTED_COMMANDS:
|
|
66
|
+
self.assertIn(c, names)
|
|
67
|
+
for entry in data["catalog"]:
|
|
68
|
+
self.assertTrue(
|
|
69
|
+
entry["hasDesc"], f"command lacks description: {entry['name']}"
|
|
70
|
+
)
|
|
71
|
+
self.assertTrue(
|
|
72
|
+
entry["hasArgs"], f"template lacks $ARGUMENTS: {entry['name']}"
|
|
73
|
+
)
|
|
74
|
+
for c in EXPECTED_COMMANDS:
|
|
75
|
+
self.assertIn(c, data["registered"])
|
|
76
|
+
self.assertTrue(data["templates"][c])
|
|
77
|
+
|
|
78
|
+
def test_registration_never_overwrites(self):
|
|
79
|
+
res = run_node(
|
|
80
|
+
"""
|
|
81
|
+
import { registerOpenCodeCommands } from "./plugins/opencode/index.js";
|
|
82
|
+
const cfg = {command: {swarm: {description: "mine", template: "mine"}}};
|
|
83
|
+
registerOpenCodeCommands(cfg);
|
|
84
|
+
console.log(JSON.stringify({swarm: cfg.command.swarm, count: Object.keys(cfg.command).length}));
|
|
85
|
+
"""
|
|
86
|
+
)
|
|
87
|
+
self.assertEqual(res.returncode, 0, f"no-overwrite test failed: {res.stderr}")
|
|
88
|
+
data = last_json_object(res.stdout)
|
|
89
|
+
self.assertEqual(data["swarm"], {"description": "mine", "template": "mine"})
|
|
90
|
+
self.assertEqual(data["count"], len(EXPECTED_COMMANDS))
|
|
91
|
+
|
|
92
|
+
def test_tool_map_shape(self):
|
|
93
|
+
res = run_node(
|
|
94
|
+
"""
|
|
95
|
+
import plugin, { IUMBTEMS_TOOL_NAMES } from "./plugins/opencode/index.js";
|
|
96
|
+
const shell = await plugin.server();
|
|
97
|
+
const entries = Object.entries(shell.tool);
|
|
98
|
+
console.log(JSON.stringify({
|
|
99
|
+
keys: entries.map(([k]) => k),
|
|
100
|
+
canonical: IUMBTEMS_TOOL_NAMES,
|
|
101
|
+
ok: entries.map(([k, t]) => (!!t.description && !!t.input && typeof t.execute === "function" && t.options && t.options.codemode === false))
|
|
102
|
+
}));
|
|
103
|
+
"""
|
|
104
|
+
)
|
|
105
|
+
self.assertEqual(res.returncode, 0, f"tool map test failed: {res.stderr}")
|
|
106
|
+
data = last_json_object(res.stdout)
|
|
107
|
+
self.assertEqual(sorted(data["keys"]), sorted(data["canonical"]))
|
|
108
|
+
self.assertEqual(len(data["keys"]), 13)
|
|
109
|
+
self.assertTrue(all(data["ok"]))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class TestInstallOpenCode(unittest.TestCase):
|
|
113
|
+
def _run_install(self, home):
|
|
114
|
+
env = dict(os.environ, HOME=str(home))
|
|
115
|
+
return subprocess.run(
|
|
116
|
+
["bash", str(PROJECT_ROOT / "install.sh")],
|
|
117
|
+
capture_output=True,
|
|
118
|
+
text=True,
|
|
119
|
+
cwd=str(PROJECT_ROOT),
|
|
120
|
+
env=env,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def _seed_malformed(self, home):
|
|
124
|
+
oc = home / ".config" / "opencode"
|
|
125
|
+
oc.mkdir(parents=True, exist_ok=True)
|
|
126
|
+
# Exact malformation seen in the wild: bare options object after the string.
|
|
127
|
+
oc.joinpath("opencode.json").write_text(
|
|
128
|
+
json.dumps(
|
|
129
|
+
{
|
|
130
|
+
"$schema": "https://opencode.ai/config.json",
|
|
131
|
+
"plugin": [
|
|
132
|
+
"@prevalentware/opencode-goal-plugin",
|
|
133
|
+
"@heretek-ai/epistemic-swarm",
|
|
134
|
+
{
|
|
135
|
+
"search_engine": "duckduckgo",
|
|
136
|
+
"max_iterations": 10,
|
|
137
|
+
"mode": "research",
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
}
|
|
141
|
+
),
|
|
142
|
+
encoding="utf-8",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def test_idempotent_and_repairs_bare_object(self):
|
|
146
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
147
|
+
home = Path(tmp)
|
|
148
|
+
self._seed_malformed(home)
|
|
149
|
+
first = self._run_install(home)
|
|
150
|
+
self.assertEqual(first.returncode, 0, f"install failed: {first.stderr}")
|
|
151
|
+
oc_path = home / ".config" / "opencode" / "opencode.json"
|
|
152
|
+
cfg = json.loads(oc_path.read_text(encoding="utf-8"))
|
|
153
|
+
# Bare object repaired into tuple form, preserving user options.
|
|
154
|
+
tuples = [
|
|
155
|
+
p
|
|
156
|
+
for p in cfg["plugin"]
|
|
157
|
+
if isinstance(p, list) and p and p[0] == "@heretek-ai/epistemic-swarm"
|
|
158
|
+
]
|
|
159
|
+
self.assertEqual(len(tuples), 1)
|
|
160
|
+
self.assertEqual(
|
|
161
|
+
tuples[0][1],
|
|
162
|
+
{
|
|
163
|
+
"search_engine": "duckduckgo",
|
|
164
|
+
"max_iterations": 10,
|
|
165
|
+
"mode": "research",
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
self.assertFalse(any(isinstance(p, dict) for p in cfg["plugin"]))
|
|
169
|
+
# Agents merged from the snippet, MCP server absolute.
|
|
170
|
+
for agent in [
|
|
171
|
+
"code-auditor",
|
|
172
|
+
"oss-scout",
|
|
173
|
+
"alpha-thesis",
|
|
174
|
+
"beta-redteam",
|
|
175
|
+
"epistemic-auditor",
|
|
176
|
+
"brainstormer",
|
|
177
|
+
]:
|
|
178
|
+
self.assertIn(agent, cfg.get("agent", {}))
|
|
179
|
+
self.assertIn("iumbtems", cfg.get("mcp", {}))
|
|
180
|
+
cmd = cfg["mcp"]["iumbtems"]["command"]
|
|
181
|
+
self.assertTrue(Path(cmd[-1]).is_absolute())
|
|
182
|
+
self.assertEqual(cmd[-1], str(PROJECT_ROOT / "runner" / "mcp_server.py"))
|
|
183
|
+
# Second run changes nothing.
|
|
184
|
+
before = oc_path.read_bytes()
|
|
185
|
+
second = self._run_install(home)
|
|
186
|
+
self.assertEqual(second.returncode, 0, f"reinstall failed: {second.stderr}")
|
|
187
|
+
self.assertEqual(oc_path.read_bytes(), before)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class TestOpenCodeTui(unittest.TestCase):
|
|
191
|
+
def test_module_load_and_setup(self):
|
|
192
|
+
res = run_node(
|
|
193
|
+
"""
|
|
194
|
+
import plugin, { readSwarmStatus, setupTui } from "./plugins/opencode/tui.js";
|
|
195
|
+
const calls = [];
|
|
196
|
+
const mock = {
|
|
197
|
+
theme: {}, directory: "/tmp/oc-tui-empty",
|
|
198
|
+
ui: { slot: (...a) => { calls.push(a.length); return () => {}; }, toast: { show: () => {} }, router: { current: () => ({type: "other"}) } },
|
|
199
|
+
keymap: { layer: () => {} }
|
|
200
|
+
};
|
|
201
|
+
const cleanup = setupTui(mock);
|
|
202
|
+
const empty = readSwarmStatus("/tmp/oc-tui-empty");
|
|
203
|
+
console.log(JSON.stringify({id: plugin.id, hasTui: !!plugin.tui, setupFn: typeof plugin.setup, slots: calls, cleanup: typeof cleanup, emptyWorkspace: empty.workspace}));
|
|
204
|
+
"""
|
|
205
|
+
)
|
|
206
|
+
self.assertEqual(res.returncode, 0, f"tui load test failed: {res.stderr}")
|
|
207
|
+
data = last_json_object(res.stdout)
|
|
208
|
+
self.assertEqual(data["id"], "heretek.iumbtems.epistemic-swarm.tui")
|
|
209
|
+
self.assertTrue(data["hasTui"])
|
|
210
|
+
self.assertEqual(data["setupFn"], "function")
|
|
211
|
+
self.assertEqual(data["slots"], [1, 1])
|
|
212
|
+
self.assertEqual(data["cleanup"], "function")
|
|
213
|
+
self.assertFalse(data["emptyWorkspace"])
|
|
214
|
+
|
|
215
|
+
def test_status_snapshot_counts(self):
|
|
216
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
217
|
+
root = Path(tmp)
|
|
218
|
+
research = root / ".research"
|
|
219
|
+
(research / "ledger").mkdir(parents=True)
|
|
220
|
+
research.joinpath("config.json").write_text(
|
|
221
|
+
json.dumps(
|
|
222
|
+
{
|
|
223
|
+
"mode": "research",
|
|
224
|
+
"search_engine": "duckduckgo",
|
|
225
|
+
"max_iterations": 2,
|
|
226
|
+
}
|
|
227
|
+
),
|
|
228
|
+
encoding="utf-8",
|
|
229
|
+
)
|
|
230
|
+
research.joinpath("final_synthesis.md").write_text(
|
|
231
|
+
"# synthesis\n", encoding="utf-8"
|
|
232
|
+
)
|
|
233
|
+
research.joinpath("frontier.json").write_text(
|
|
234
|
+
json.dumps({"nodes": [{"status": "open"}, {"status": "settled"}]}),
|
|
235
|
+
encoding="utf-8",
|
|
236
|
+
)
|
|
237
|
+
research.joinpath("ledger", "claim_status.json").write_text(
|
|
238
|
+
json.dumps(
|
|
239
|
+
[
|
|
240
|
+
{
|
|
241
|
+
"scope_id": "s",
|
|
242
|
+
"claim_id": "a",
|
|
243
|
+
"from_status": "UNVERIFIED",
|
|
244
|
+
"to_status": "VERIFIED",
|
|
245
|
+
"reason": "",
|
|
246
|
+
"at": "t1",
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"scope_id": "s",
|
|
250
|
+
"claim_id": "a",
|
|
251
|
+
"from_status": "VERIFIED",
|
|
252
|
+
"to_status": "STALE",
|
|
253
|
+
"reason": "retracted",
|
|
254
|
+
"at": "t2",
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"scope_id": "s",
|
|
258
|
+
"claim_id": "b",
|
|
259
|
+
"from_status": "VERIFIED",
|
|
260
|
+
"to_status": "SUSPECT",
|
|
261
|
+
"reason": "",
|
|
262
|
+
"at": "t3",
|
|
263
|
+
},
|
|
264
|
+
]
|
|
265
|
+
),
|
|
266
|
+
encoding="utf-8",
|
|
267
|
+
)
|
|
268
|
+
research.joinpath("requeue.json").write_text(
|
|
269
|
+
json.dumps([{"scope_id": "s1"}, {"scope_id": "s2"}]), encoding="utf-8"
|
|
270
|
+
)
|
|
271
|
+
res = run_node(
|
|
272
|
+
f"""
|
|
273
|
+
import {{ readSwarmStatus }} from "./plugins/opencode/tui.js";
|
|
274
|
+
console.log(JSON.stringify(readSwarmStatus({str(root)!r})));
|
|
275
|
+
"""
|
|
276
|
+
)
|
|
277
|
+
self.assertEqual(res.returncode, 0, f"snapshot test failed: {res.stderr}")
|
|
278
|
+
data = last_json_object(res.stdout)
|
|
279
|
+
self.assertTrue(data["workspace"])
|
|
280
|
+
self.assertEqual(data["mode"], "research")
|
|
281
|
+
self.assertEqual(data["searchEngine"], "duckduckgo")
|
|
282
|
+
self.assertEqual(len(data["reports"]), 1)
|
|
283
|
+
self.assertEqual(data["frontierOpen"], 1)
|
|
284
|
+
self.assertEqual(data["stale"], 1)
|
|
285
|
+
self.assertEqual(data["suspect"], 1)
|
|
286
|
+
self.assertEqual(data["requeued"], 2)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class TestOpenCodeLifecycle(unittest.TestCase):
|
|
290
|
+
@staticmethod
|
|
291
|
+
def _fixture(tmp):
|
|
292
|
+
root = Path(tmp)
|
|
293
|
+
research = root / ".research"
|
|
294
|
+
(research / "ledger").mkdir(parents=True)
|
|
295
|
+
research.joinpath("config.json").write_text(
|
|
296
|
+
json.dumps(
|
|
297
|
+
{"mode": "audit", "search_engine": "brave", "max_iterations": 3}
|
|
298
|
+
),
|
|
299
|
+
encoding="utf-8",
|
|
300
|
+
)
|
|
301
|
+
research.joinpath("ledger", "claim_status.json").write_text(
|
|
302
|
+
json.dumps(
|
|
303
|
+
[
|
|
304
|
+
{
|
|
305
|
+
"scope_id": "s",
|
|
306
|
+
"claim_id": "x1",
|
|
307
|
+
"from_status": "VERIFIED",
|
|
308
|
+
"to_status": "STALE",
|
|
309
|
+
"reason": "retracted",
|
|
310
|
+
"at": "t1",
|
|
311
|
+
},
|
|
312
|
+
]
|
|
313
|
+
),
|
|
314
|
+
encoding="utf-8",
|
|
315
|
+
)
|
|
316
|
+
research.joinpath("requeue.json").write_text(
|
|
317
|
+
json.dumps([{"scope_id": "s9"}]), encoding="utf-8"
|
|
318
|
+
)
|
|
319
|
+
return str(root)
|
|
320
|
+
|
|
321
|
+
def test_idle_detection(self):
|
|
322
|
+
res = run_node(
|
|
323
|
+
"""
|
|
324
|
+
import { isIdleEvent } from "./plugins/opencode/index.js";
|
|
325
|
+
console.log(JSON.stringify({
|
|
326
|
+
idle: isIdleEvent({type: "session.idle"}),
|
|
327
|
+
statusIdle: isIdleEvent({type: "session.status", properties: {status: {type: "idle"}}}),
|
|
328
|
+
busy: isIdleEvent({type: "session.status", properties: {status: {type: "busy"}}}),
|
|
329
|
+
other: isIdleEvent({type: "message.updated"}),
|
|
330
|
+
empty: isIdleEvent(null)
|
|
331
|
+
}));
|
|
332
|
+
"""
|
|
333
|
+
)
|
|
334
|
+
self.assertEqual(res.returncode, 0, f"idle test failed: {res.stderr}")
|
|
335
|
+
data = last_json_object(res.stdout)
|
|
336
|
+
self.assertTrue(data["idle"])
|
|
337
|
+
self.assertTrue(data["statusIdle"])
|
|
338
|
+
self.assertFalse(data["busy"])
|
|
339
|
+
self.assertFalse(data["other"])
|
|
340
|
+
self.assertFalse(data["empty"])
|
|
341
|
+
|
|
342
|
+
def test_compaction_hook_pushes_state(self):
|
|
343
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
344
|
+
root = self._fixture(tmp)
|
|
345
|
+
res = run_node(
|
|
346
|
+
f"""
|
|
347
|
+
import plugin from "./plugins/opencode/index.js";
|
|
348
|
+
const shell = await plugin.server();
|
|
349
|
+
const hook = shell["experimental.session.compacting"];
|
|
350
|
+
const out = {{context: []}};
|
|
351
|
+
await hook({{directory: {root!r}}}, out);
|
|
352
|
+
const empty = {{context: []}};
|
|
353
|
+
await hook({{directory: "/tmp/oc-lifecycle-empty"}}, empty);
|
|
354
|
+
console.log(JSON.stringify({{hook: typeof hook, pushed: out.context.length, text: out.context[0] || "", skipped: empty.context.length}}));
|
|
355
|
+
"""
|
|
356
|
+
)
|
|
357
|
+
self.assertEqual(res.returncode, 0, f"compaction test failed: {res.stderr}")
|
|
358
|
+
data = last_json_object(res.stdout)
|
|
359
|
+
self.assertEqual(data["hook"], "function")
|
|
360
|
+
self.assertEqual(data["pushed"], 1)
|
|
361
|
+
self.assertIn("mode=audit", data["text"])
|
|
362
|
+
self.assertIn("x1", data["text"])
|
|
363
|
+
self.assertIn("s9", data["text"])
|
|
364
|
+
self.assertEqual(data["skipped"], 0)
|
|
365
|
+
|
|
366
|
+
def test_nudge_once_per_ledger_state(self):
|
|
367
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
368
|
+
root = self._fixture(tmp)
|
|
369
|
+
res = run_node(
|
|
370
|
+
f"""
|
|
371
|
+
import {{ handleIdleEvent }} from "./plugins/opencode/index.js";
|
|
372
|
+
const prompts = [];
|
|
373
|
+
const host = {{session: {{prompt: async (p) => {{ prompts.push(p); return {{}}; }}}}}};
|
|
374
|
+
const state = {{}};
|
|
375
|
+
const evt = {{type: "session.idle", properties: {{sessionID: "ses_1"}}}};
|
|
376
|
+
const first = handleIdleEvent(host, {root!r}, state, evt);
|
|
377
|
+
await new Promise(r => setTimeout(r, 20));
|
|
378
|
+
const second = handleIdleEvent(host, {root!r}, state, evt);
|
|
379
|
+
const busy = handleIdleEvent(host, {root!r}, state, {{type: "message.updated"}});
|
|
380
|
+
console.log(JSON.stringify({{first, second, busy, prompts: prompts.length, sid: prompts[0]?.sessionID || null}}));
|
|
381
|
+
"""
|
|
382
|
+
)
|
|
383
|
+
self.assertEqual(res.returncode, 0, f"nudge test failed: {res.stderr}")
|
|
384
|
+
data = last_json_object(res.stdout)
|
|
385
|
+
self.assertTrue(data["first"])
|
|
386
|
+
self.assertFalse(data["second"])
|
|
387
|
+
self.assertFalse(data["busy"])
|
|
388
|
+
self.assertEqual(data["prompts"], 1)
|
|
389
|
+
self.assertEqual(data["sid"], "ses_1")
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
if __name__ == "__main__":
|
|
393
|
+
unittest.main()
|
|
@@ -318,15 +318,20 @@ In our experiments, the 70B parameter model was trained on 15.0 trillion tokens.
|
|
|
318
318
|
|
|
319
319
|
# 1. Test OpenCode Plugin registration (A2b: thin forwarders + both
|
|
320
320
|
# name exports — canonical IUMBTEMS_TOOL_NAMES and the deprecated
|
|
321
|
-
# IUMBEMS_TOOL_NAMES alias).
|
|
321
|
+
# IUMBEMS_TOOL_NAMES alias). Object form: server() resolves to
|
|
322
|
+
# {config, tool} with the tool map keyed by name.
|
|
322
323
|
node_code_oc = """
|
|
323
324
|
import plugin, { IUMBTEMS_TOOL_NAMES, IUMBEMS_TOOL_NAMES } from "./plugins/opencode/index.js";
|
|
324
|
-
const
|
|
325
|
+
const shell = await plugin.server();
|
|
326
|
+
const tools = Object.values(shell.tool);
|
|
325
327
|
const names = tools.map(t => t.name);
|
|
328
|
+
const cfg = {};
|
|
329
|
+
shell.config(cfg);
|
|
326
330
|
console.log(JSON.stringify({
|
|
327
331
|
names,
|
|
328
332
|
canonical: IUMBTEMS_TOOL_NAMES,
|
|
329
|
-
alias: IUMBEMS_TOOL_NAMES
|
|
333
|
+
alias: IUMBEMS_TOOL_NAMES,
|
|
334
|
+
commands: Object.keys(cfg.command || {})
|
|
330
335
|
}));
|
|
331
336
|
"""
|
|
332
337
|
res_oc = subprocess.run(
|
|
@@ -365,6 +370,17 @@ In our experiments, the 70B parameter model was trained on 15.0 trillion tokens.
|
|
|
365
370
|
]
|
|
366
371
|
for t in expected_tools:
|
|
367
372
|
self.assertIn(t, tools)
|
|
373
|
+
# Slash-command catalog registered via the server config hook.
|
|
374
|
+
for c in [
|
|
375
|
+
"swarm",
|
|
376
|
+
"grill",
|
|
377
|
+
"swarm-config",
|
|
378
|
+
"audit",
|
|
379
|
+
"scout",
|
|
380
|
+
"brainstorming",
|
|
381
|
+
"brainstorm",
|
|
382
|
+
]:
|
|
383
|
+
self.assertIn(c, data_oc["commands"])
|
|
368
384
|
|
|
369
385
|
# 2. Test Pi Extension registration
|
|
370
386
|
node_code_pi = """
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|