pantheon-opencode 1.6.0-beta.3 → 1.6.0-beta.5
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/AGENTS.md +24 -5
- package/CHANGELOG.md +49 -0
- package/README.md +51 -19
- package/README.pt-BR.md +32 -13
- package/bin/pantheon-init.mjs +26 -3
- package/package.json +12 -11
- package/plugin.json +1 -1
- package/pyproject.toml +6 -6
- package/scripts/{code_mode_server.py → code_mode.py} +3 -3
- package/scripts/cost.mjs +244 -41
- package/scripts/doctor.mjs +151 -1
- package/scripts/install/agents-md.mjs +2 -2
- package/scripts/install/config-migration.mjs +80 -2
- package/scripts/install/health-check.mjs +21 -19
- package/scripts/install/opencode-version.mjs +246 -4
- package/scripts/install/opencode.mjs +250 -119
- package/scripts/install/shared.mjs +5 -5
- package/scripts/install-mcp.mjs +28 -9
- package/scripts/{mcp_persistence_server.py → mcp_persistence.py} +2 -2
- package/{src/mcp/mcp_resources_server.py → scripts/mcp_resources.py} +2 -2
- package/scripts/{memory_mcp_server.py → memory_mcp.py} +2 -2
- package/scripts/probe-context-rehydrate.mjs +8 -2
- package/scripts/probe-pantheon-cost.mjs +71 -15
- package/scripts/sync-artifacts.mjs +5 -5
- package/scripts/test-opencode-v2-sandbox.sh +21 -21
- package/scripts/validate-routing.mjs +3 -3
- package/skills-lock.json +1 -1
- package/src/agents/apollo.md +7 -2
- package/src/agents/zeus.md +29 -1
- package/src/instructions/memory-protocol.instructions.md +15 -4
- package/src/instructions/zeus-anti-stall.instructions.md +9 -1
- package/src/mcp/{code_mode_server.py → code_mode.py} +3 -3
- package/src/mcp/eval_store.py +2 -2
- package/src/mcp/{mcp_persistence_server.py → mcp_persistence.py} +2 -2
- package/{scripts/mcp_resources_server.py → src/mcp/mcp_resources.py} +2 -2
- package/src/mcp/{memory_mcp_server.py → memory_mcp.py} +2 -2
- package/src/mcp/{pantheon_vision_server.py → pantheon_vision.py} +1 -1
- package/src/mcp/requirements-mcp.txt +6 -4
- package/src/mcp/requirements-vision.txt +2 -2
- package/src/pantheon/cost-command.ts +399 -101
- package/src/pantheon/presets.mjs +3 -3
- package/src/pantheon/v2-unsupported.d.mts +11 -0
- package/src/pantheon/v2-unsupported.mjs +113 -0
- package/src/plugin-v2.ts +269 -100
- package/src/plugins/tui/dist/server.d.ts +1 -2
- package/src/plugins/tui/dist/server.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.d.ts +74 -74
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +17 -20
- package/src/plugins/tui/package-lock.json +364 -317
- package/src/plugins/tui/package.json +8 -5
- package/src/plugins/tui/tsconfig.build.json +5 -0
- package/src/plugins/tui/tsdown.config.ts +3 -0
- package/src/skills/context-compression/SKILL.md +1 -1
- package/opencode.json +0 -58
|
@@ -7,7 +7,7 @@ Provides MCP resources for the Pantheon agent framework:
|
|
|
7
7
|
plans/status, memory-bank files
|
|
8
8
|
|
|
9
9
|
Usage:
|
|
10
|
-
python scripts/
|
|
10
|
+
python scripts/mcp_resources.py
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
@@ -19,7 +19,7 @@ from typing import Any
|
|
|
19
19
|
import yaml
|
|
20
20
|
from _pantheon_paths import has_symlink_component, pantheon_home, pantheon_project
|
|
21
21
|
from eval_store import get_latest_eval, list_evals
|
|
22
|
-
from
|
|
22
|
+
from fastmcp import FastMCP
|
|
23
23
|
|
|
24
24
|
# ── Path Resolution ──────────────────────────────────────────────────────────
|
|
25
25
|
_PANTHEON_HOME: Path = pantheon_home()
|
|
@@ -19,7 +19,7 @@ Tools:
|
|
|
19
19
|
memory_stats — DB statistics
|
|
20
20
|
|
|
21
21
|
Usage:
|
|
22
|
-
python scripts/
|
|
22
|
+
python scripts/memory_mcp.py
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
from __future__ import annotations
|
|
@@ -33,7 +33,7 @@ from pathlib import Path
|
|
|
33
33
|
from typing import Annotated, Any
|
|
34
34
|
|
|
35
35
|
from _pantheon_paths import pantheon_home
|
|
36
|
-
from
|
|
36
|
+
from fastmcp import FastMCP
|
|
37
37
|
from pydantic import BeforeValidator, Field
|
|
38
38
|
|
|
39
39
|
# ── Paths ─────────────────────────────────────────────────────────────────────
|
|
@@ -15,7 +15,7 @@ import { fileURLToPath } from 'node:url'
|
|
|
15
15
|
|
|
16
16
|
const requestedVersion = valueFor('--version')
|
|
17
17
|
const jsonOutput = process.argv.includes('--json')
|
|
18
|
-
const runtime = fileURLToPath(new URL('./
|
|
18
|
+
const runtime = fileURLToPath(new URL('./mcp_persistence.py', import.meta.url))
|
|
19
19
|
|
|
20
20
|
function valueFor(name) {
|
|
21
21
|
const index = process.argv.indexOf(name)
|
|
@@ -63,6 +63,12 @@ spec.loader.exec_module(module)
|
|
|
63
63
|
|
|
64
64
|
async def call(name, arguments):
|
|
65
65
|
result = await module.mcp.call_tool(name, arguments)
|
|
66
|
+
structured = getattr(result, "structured_content", None)
|
|
67
|
+
if structured is not None:
|
|
68
|
+
metadata = getattr(result, "meta", None) or {}
|
|
69
|
+
if metadata.get("fastmcp", {}).get("wrap_result"):
|
|
70
|
+
return structured.get("result") if isinstance(structured, dict) else structured
|
|
71
|
+
return structured
|
|
66
72
|
if isinstance(result, tuple):
|
|
67
73
|
structured = result[1]
|
|
68
74
|
if structured is not None:
|
|
@@ -186,7 +192,7 @@ async function main() {
|
|
|
186
192
|
return
|
|
187
193
|
}
|
|
188
194
|
if (!existsSync(runtime)) {
|
|
189
|
-
unavailable('installed package does not include
|
|
195
|
+
unavailable('installed package does not include mcp_persistence.py')
|
|
190
196
|
return
|
|
191
197
|
}
|
|
192
198
|
|
|
@@ -56,11 +56,48 @@ function setEnvironment(values) {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Write the rows in the family the requested host generation actually uses.
|
|
61
|
+
*
|
|
62
|
+
* Call sites describe an assistant turn the way they always have
|
|
63
|
+
* (`role: 'assistant'`, plus a `phase`). This function translates that
|
|
64
|
+
* descriptor into what is on disk: for V2, `role` becomes the `type` COLUMN and
|
|
65
|
+
* `phase` is dropped, because a real V2 payload carries neither — measured, the
|
|
66
|
+
* `role` key is absent from 40052 of 40052 rows on a host 2.0.22 database.
|
|
67
|
+
*
|
|
68
|
+
* Keeping that translation in ONE place is the point: a fixture that wrote
|
|
69
|
+
* `role` into a session_message row asserts the V1 format under a V2 name, which
|
|
70
|
+
* is exactly how the stale V1 predicate in the CLI mirror stayed green — the
|
|
71
|
+
* synthetic database agreed with the bug instead of contradicting it.
|
|
72
|
+
*/
|
|
59
73
|
function createSyntheticDb(DatabaseSync, path, rows) {
|
|
60
74
|
const db = new DatabaseSync(path)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
75
|
+
if (requestedVersion === 'v2') {
|
|
76
|
+
db.exec(
|
|
77
|
+
'CREATE TABLE session_message (id TEXT, session_id TEXT, type TEXT, seq INTEGER, time_created INTEGER, time_updated INTEGER, data TEXT)',
|
|
78
|
+
)
|
|
79
|
+
const insert = db.prepare(
|
|
80
|
+
'INSERT INTO session_message (id, session_id, type, seq, time_created, time_updated, data) VALUES (?,?,?,?,?,?,?)',
|
|
81
|
+
)
|
|
82
|
+
rows.forEach((row, index) => {
|
|
83
|
+
const payload = { ...row }
|
|
84
|
+
delete payload.role
|
|
85
|
+
delete payload.phase
|
|
86
|
+
insert.run(
|
|
87
|
+
`msg_${index + 1}`,
|
|
88
|
+
'ses_probe',
|
|
89
|
+
row.role,
|
|
90
|
+
index + 1,
|
|
91
|
+
Date.now(),
|
|
92
|
+
Date.now(),
|
|
93
|
+
JSON.stringify(payload),
|
|
94
|
+
)
|
|
95
|
+
})
|
|
96
|
+
} else {
|
|
97
|
+
db.exec('CREATE TABLE message (data TEXT NOT NULL, time_created INTEGER NOT NULL)')
|
|
98
|
+
const insert = db.prepare('INSERT INTO message (data, time_created) VALUES (?, ?)')
|
|
99
|
+
for (const data of rows) insert.run(JSON.stringify(data), Date.now())
|
|
100
|
+
}
|
|
64
101
|
db.close()
|
|
65
102
|
}
|
|
66
103
|
|
|
@@ -129,7 +166,10 @@ async function main() {
|
|
|
129
166
|
const root = mkdtempSync(join(tmpdir(), 'pantheon-cost-probe-'))
|
|
130
167
|
const dataHome = join(root, 'data')
|
|
131
168
|
const opencodeDir = join(dataHome, 'opencode')
|
|
132
|
-
|
|
169
|
+
// Every real installation writes `opencode.db`; the v1/v2 distinction is the
|
|
170
|
+
// SCHEMA of that file, never its name. Deriving `opencode-v2.db` here made the
|
|
171
|
+
// probe assert a path that exists on no real machine.
|
|
172
|
+
const selectedDb = join(opencodeDir, 'opencode.db')
|
|
133
173
|
const otherDb = join(root, 'other.db')
|
|
134
174
|
const envDb = join(root, 'env.db')
|
|
135
175
|
const explicitDb = join(root, 'explicit.db')
|
|
@@ -139,7 +179,9 @@ async function main() {
|
|
|
139
179
|
try {
|
|
140
180
|
mkdirSync(opencodeDir, { recursive: true })
|
|
141
181
|
// These rows intentionally include a monetary-looking field to prove it is
|
|
142
|
-
// ignored; only input/output/total tokens may appear in the report.
|
|
182
|
+
// ignored; only input/output/total tokens may appear in the report. Every
|
|
183
|
+
// V2 phase reads back as `unknown` because the V2 reader emits that literal:
|
|
184
|
+
// a V2 payload carries no phase field for a reader to recover.
|
|
143
185
|
const selectedRows =
|
|
144
186
|
requestedVersion === 'v2'
|
|
145
187
|
? [
|
|
@@ -224,8 +266,8 @@ async function main() {
|
|
|
224
266
|
selectedOutput,
|
|
225
267
|
requestedVersion === 'v2'
|
|
226
268
|
? [
|
|
227
|
-
{ agent: 'aphrodite', phase: '
|
|
228
|
-
{ agent: 'zeus', phase: '
|
|
269
|
+
{ agent: 'aphrodite', phase: 'unknown', input: 8, output: 10, total: 18 },
|
|
270
|
+
{ agent: 'zeus', phase: 'unknown', input: 4, output: 6, total: 10 },
|
|
229
271
|
]
|
|
230
272
|
: [
|
|
231
273
|
{ agent: 'hermes', phase: 'plan', input: 13, output: 24, total: 37 },
|
|
@@ -250,7 +292,12 @@ async function main() {
|
|
|
250
292
|
{},
|
|
251
293
|
{ sessionID: 'cost-probe' },
|
|
252
294
|
)
|
|
253
|
-
|
|
295
|
+
// V2 has no phase to report, so the same row reads back as 'unknown'.
|
|
296
|
+
requireOutput(
|
|
297
|
+
output,
|
|
298
|
+
`| env-agent | ${requestedVersion === 'v2' ? 'unknown' : 'override'} | 1 | 2 | 3 |`,
|
|
299
|
+
'PANTHEON_COST_DB',
|
|
300
|
+
)
|
|
254
301
|
if (output.includes('wrong-version') || output.includes('env-agent') === false) {
|
|
255
302
|
throw new Error('PANTHEON_COST_DB did not override the version-selected database')
|
|
256
303
|
}
|
|
@@ -269,7 +316,11 @@ async function main() {
|
|
|
269
316
|
{},
|
|
270
317
|
{ sessionID: 'cost-probe' },
|
|
271
318
|
)
|
|
272
|
-
requireOutput(
|
|
319
|
+
requireOutput(
|
|
320
|
+
output,
|
|
321
|
+
`| explicit-agent | ${requestedVersion === 'v2' ? 'unknown' : 'explicit'} | 9 | 1 | 10 |`,
|
|
322
|
+
'explicit dbPath',
|
|
323
|
+
)
|
|
273
324
|
if (output.includes('env-agent')) throw new Error('explicit dbPath did not take precedence')
|
|
274
325
|
checks.push('explicit dbPath precedence: PASS')
|
|
275
326
|
} finally {
|
|
@@ -287,11 +338,7 @@ async function main() {
|
|
|
287
338
|
{ sessionID: 'cost-probe' },
|
|
288
339
|
)
|
|
289
340
|
requireOutput(output, 'not found', 'missing DB')
|
|
290
|
-
requireOutput(
|
|
291
|
-
output,
|
|
292
|
-
requestedVersion === 'v2' ? 'opencode-v2.db' : 'opencode.db',
|
|
293
|
-
'selected missing DB path',
|
|
294
|
-
)
|
|
341
|
+
requireOutput(output, 'opencode.db', 'selected missing DB path')
|
|
295
342
|
checks.push('missing DB: PASS')
|
|
296
343
|
} finally {
|
|
297
344
|
missingRestore()
|
|
@@ -302,7 +349,16 @@ async function main() {
|
|
|
302
349
|
}).pantheon_cost.execute({}, { sessionID: 'cost-probe' })
|
|
303
350
|
requireOutput(incompatibleOutput, 'incompatible opencode.db schema', 'incompatible schema')
|
|
304
351
|
checks.push('incompatible schema: PASS')
|
|
305
|
-
|
|
352
|
+
// Names the synthetic fixture that was built, because the previous wording
|
|
353
|
+
// ("no real DB was read") was true of every run and therefore true of the run
|
|
354
|
+
// in which the V2 ledger was read as V1 and silently dropped. A verdict that
|
|
355
|
+
// cannot fail is not a verdict: it stayed green across the exact defect it
|
|
356
|
+
// was meant to cover.
|
|
357
|
+
emit(
|
|
358
|
+
'PASS',
|
|
359
|
+
`${checks.length} synthetic ${requestedVersion} checks passed against a temporary opencode.db (no user database was read)`,
|
|
360
|
+
checks,
|
|
361
|
+
)
|
|
306
362
|
} catch (error) {
|
|
307
363
|
emit('FAIL', diagnostic(error), checks)
|
|
308
364
|
} finally {
|
|
@@ -31,13 +31,13 @@ import {
|
|
|
31
31
|
|
|
32
32
|
/** Canonical MCP script sources — mirrors installOpenCode phase 2.10. */
|
|
33
33
|
const MCP_SCRIPTS = {
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
34
|
+
'mcp_resources.py': () => join(ROOT, 'src', 'mcp', 'mcp_resources.py'),
|
|
35
|
+
'code_mode.py': () => join(ROOT, 'src', 'mcp', 'code_mode.py'),
|
|
36
|
+
'memory_mcp.py': () => join(ROOT, 'src', 'mcp', 'memory_mcp.py'),
|
|
37
|
+
'mcp_persistence.py': () => join(ROOT, 'src', 'mcp', 'mcp_persistence.py'),
|
|
38
38
|
'_pantheon_paths.py': () => join(ROOT, 'src', 'mcp', '_pantheon_paths.py'),
|
|
39
39
|
'mcp_codemap_module.py': () => join(ROOT, 'src', 'mcp', 'mcp_codemap_module.py'),
|
|
40
|
-
'
|
|
40
|
+
'pantheon_vision.py': () => join(ROOT, 'src', 'mcp', 'pantheon_vision.py'),
|
|
41
41
|
'eval_store.py': () => join(ROOT, 'src', 'mcp', 'eval_store.py'),
|
|
42
42
|
'scrub-secrets.py': () => join(ROOT, 'scripts', 'scrub-secrets.py'),
|
|
43
43
|
}
|
|
@@ -241,11 +241,11 @@ try:
|
|
|
241
241
|
except OSError:
|
|
242
242
|
raise SystemExit(1)
|
|
243
243
|
expected = {
|
|
244
|
-
"
|
|
245
|
-
"
|
|
246
|
-
"
|
|
247
|
-
"
|
|
248
|
-
"
|
|
244
|
+
"code_mode.py",
|
|
245
|
+
"memory_mcp.py",
|
|
246
|
+
"mcp_persistence.py",
|
|
247
|
+
"mcp_resources.py",
|
|
248
|
+
"pantheon_vision.py",
|
|
249
249
|
}
|
|
250
250
|
pattern = re.compile(
|
|
251
251
|
r"spawning process \{\s*"
|
|
@@ -515,11 +515,11 @@ try:
|
|
|
515
515
|
except OSError:
|
|
516
516
|
raise SystemExit(1)
|
|
517
517
|
expected = {
|
|
518
|
-
"
|
|
519
|
-
"
|
|
520
|
-
"
|
|
521
|
-
"
|
|
522
|
-
"
|
|
518
|
+
"code_mode.py",
|
|
519
|
+
"memory_mcp.py",
|
|
520
|
+
"mcp_persistence.py",
|
|
521
|
+
"mcp_resources.py",
|
|
522
|
+
"pantheon_vision.py",
|
|
523
523
|
}
|
|
524
524
|
pattern = re.compile(
|
|
525
525
|
r"spawning process \{\s*"
|
|
@@ -642,11 +642,11 @@ config_path = Path(os.environ["CONFIG_PATH"])
|
|
|
642
642
|
runtime_dir = os.environ["RUNTIME_DIR"]
|
|
643
643
|
python_bin = os.environ["MCP_PYTHON"]
|
|
644
644
|
managed = {
|
|
645
|
-
"pantheon-code-mode": "
|
|
646
|
-
"pantheon-memory": "
|
|
647
|
-
"pantheon-persistence": "
|
|
648
|
-
"pantheon-resources": "
|
|
649
|
-
"pantheon-vision": "
|
|
645
|
+
"pantheon-code-mode": "code_mode.py",
|
|
646
|
+
"pantheon-memory": "memory_mcp.py",
|
|
647
|
+
"pantheon-persistence": "mcp_persistence.py",
|
|
648
|
+
"pantheon-resources": "mcp_resources.py",
|
|
649
|
+
"pantheon-vision": "pantheon_vision.py",
|
|
650
650
|
}
|
|
651
651
|
try:
|
|
652
652
|
config = json.loads(config_path.read_text(encoding="utf-8"))
|
|
@@ -829,7 +829,7 @@ else
|
|
|
829
829
|
fi
|
|
830
830
|
|
|
831
831
|
echo "--- Gate (b): pantheon://agents CONTENT check ---"
|
|
832
|
-
RESOURCES_SRV="$PANTHEON_GLOBAL/scripts/
|
|
832
|
+
RESOURCES_SRV="$PANTHEON_GLOBAL/scripts/mcp_resources.py"
|
|
833
833
|
if [ ! -f "$RESOURCES_SRV" ]; then
|
|
834
834
|
record_status "pantheon://agents resource" "FAIL" "resources server not found at $RESOURCES_SRV"
|
|
835
835
|
else
|
|
@@ -1013,11 +1013,11 @@ config_path = Path(os.environ["CONFIG_PATH"])
|
|
|
1013
1013
|
runtime_dir = os.environ["RUNTIME_DIR"]
|
|
1014
1014
|
python_bin = os.environ["MCP_PYTHON"]
|
|
1015
1015
|
managed = {
|
|
1016
|
-
"pantheon-code-mode": "
|
|
1017
|
-
"pantheon-memory": "
|
|
1018
|
-
"pantheon-persistence": "
|
|
1019
|
-
"pantheon-resources": "
|
|
1020
|
-
"pantheon-vision": "
|
|
1016
|
+
"pantheon-code-mode": "code_mode.py",
|
|
1017
|
+
"pantheon-memory": "memory_mcp.py",
|
|
1018
|
+
"pantheon-persistence": "mcp_persistence.py",
|
|
1019
|
+
"pantheon-resources": "mcp_resources.py",
|
|
1020
|
+
"pantheon-vision": "pantheon_vision.py",
|
|
1021
1021
|
}
|
|
1022
1022
|
|
|
1023
1023
|
try:
|
|
@@ -19,7 +19,7 @@ import { createHash } from 'node:crypto'
|
|
|
19
19
|
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
20
20
|
import { dirname, join } from 'node:path'
|
|
21
21
|
import { fileURLToPath } from 'node:url'
|
|
22
|
-
import
|
|
22
|
+
import { load } from 'js-yaml'
|
|
23
23
|
import { validatePresetDefs } from '../src/pantheon/presets.mjs'
|
|
24
24
|
|
|
25
25
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
@@ -52,7 +52,7 @@ if (!existsSync(routingPath)) {
|
|
|
52
52
|
console.error('❌ routing.yml not found')
|
|
53
53
|
process.exit(1)
|
|
54
54
|
}
|
|
55
|
-
const routing =
|
|
55
|
+
const routing = load(readFileSync(routingPath, 'utf8'))
|
|
56
56
|
|
|
57
57
|
console.log('🔍 Validating routing.yml...\n')
|
|
58
58
|
|
|
@@ -253,7 +253,7 @@ function parseFrontmatter(filePath) {
|
|
|
253
253
|
const match = content.match(FRONTMATTER_RE)
|
|
254
254
|
if (!match) return null
|
|
255
255
|
try {
|
|
256
|
-
return
|
|
256
|
+
return load(match[1])
|
|
257
257
|
} catch {
|
|
258
258
|
return null
|
|
259
259
|
}
|
package/skills-lock.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"src/skills/clonedeps/SKILL.md": "3e0b43cf96da98c83699dc48db3fd58fbfda84b9022bb70d3747875c81c3e124",
|
|
6
6
|
"src/skills/code-review-checklist/SKILL.md": "0e59367c65ef4608db80a0674279f32cfc3f67dca7236f233b61a47fe506579f",
|
|
7
7
|
"src/skills/codemap/SKILL.md": "87af5271e1576e154cadff313eab16264b726e43d094520736b7a5b03b409a4a",
|
|
8
|
-
"src/skills/context-compression/SKILL.md": "
|
|
8
|
+
"src/skills/context-compression/SKILL.md": "9c65c132beb0dc7bd20335cfd7f41c589f5168a72dcf5d51740bc66f2865e027",
|
|
9
9
|
"src/skills/git-workflow-and-versioning/SKILL.md": "685a94041f1b4ce2a430301f517b89102055696267c369a7e8fb191606659276",
|
|
10
10
|
"src/skills/incremental-implementation/SKILL.md": "7065ddb45da3c6b6751de07f290e92e6cb072e949a2e3aaae13ee7762f0a70fc",
|
|
11
11
|
"src/skills/loop-engineering/SKILL.md": "f804e545821b9cca8fe0ec517307ed902446f28c2ca8ff9194c0f987cf7229e3",
|
package/src/agents/apollo.md
CHANGED
|
@@ -38,7 +38,10 @@ mcp_tools:
|
|
|
38
38
|
- Generate structured summaries (not raw dumps)
|
|
39
39
|
|
|
40
40
|
### 2. External Research
|
|
41
|
-
- Web search
|
|
41
|
+
- Web search through whatever web-search MCP the USER has configured — Pantheon
|
|
42
|
+
bundles none and does not assume one. If no search tool is available, skip
|
|
43
|
+
external search and say so; never present an unverified recollection as a
|
|
44
|
+
citation.
|
|
42
45
|
- Context7 for library documentation
|
|
43
46
|
- Read URLs with webfetch for known resource URLs
|
|
44
47
|
|
|
@@ -50,7 +53,9 @@ mcp_tools:
|
|
|
50
53
|
## TOOLS NOT AVAILABLE
|
|
51
54
|
- bash - forbidden (cannot run commands)
|
|
52
55
|
- edit - forbidden (read-only agent)
|
|
53
|
-
-
|
|
56
|
+
- Web search by default - no search MCP is bundled or pre-configured. Use one
|
|
57
|
+
ONLY if the user's own config exposes it; otherwise state that external search
|
|
58
|
+
was unavailable instead of improvising.
|
|
54
59
|
|
|
55
60
|
## MCP Security
|
|
56
61
|
- Never embed credentials in URLs (grep for token=, key=, secret=)
|
package/src/agents/zeus.md
CHANGED
|
@@ -284,7 +284,35 @@ in `task-result-guard.ts` (plugin `tool.execute.after` chain): an
|
|
|
284
284
|
empty/whitespace-only result is converted into an explicit error so a silent
|
|
285
285
|
child failure is never mistaken for success. No manual retry helper is required.
|
|
286
286
|
|
|
287
|
-
### /cost — pantheon_cost tool (
|
|
287
|
+
### /cost — pantheon_cost tool (disponivel com o version gate corrigido)
|
|
288
|
+
`pantheon_cost` volta a existir assim que o version gate corrigido estiver
|
|
289
|
+
instalado **e após o usuário re-rodar `init`**: em host 2.x, `auto` resolve
|
|
290
|
+
para `v2` e registra o entry de diretorio `src/plugin-v2`, que expoe
|
|
291
|
+
exatamente 3 tools — `hashline_edit`, `pantheon_cost` e `pantheon_model`
|
|
292
|
+
(V2 nao registra as 3 goal tools; ver `docs/INSTALLATION.md` ->
|
|
293
|
+
"OpenCode V1/V2 — contrato de plugin").
|
|
294
|
+
**Caveat**: a tool so reaparece para quem re-rodar `init` depois do fix. O
|
|
295
|
+
`postinstall` nao re-executa `init`, entao ate la o config existente continua
|
|
296
|
+
resolvendo para V1 e a tool segue ausente — nesse estado, tratar `/cost` como
|
|
297
|
+
indisponivel.
|
|
298
|
+
|
|
299
|
+
Causa raiz como era na tag `1.6.0-beta.4` (registro para quem ainda tem um
|
|
300
|
+
install quebrado): o host `opencode v2.0.22` rejeita caminhos de arquivo `.ts` nos
|
|
301
|
+
arrays de plugin do config (recusa `configured plugin path must be a directory`,
|
|
302
|
+
medida em runtime nesse host; o repo so registra a parafrase `must be a
|
|
303
|
+
directory`), e a selecao de geracao caia sempre em V1 — a geracao V1 escreve
|
|
304
|
+
entradas `.ts` sob a chave singular `plugin` (`config.plugin`, o bloco de merge
|
|
305
|
+
em `scripts/install/opencode.mjs`) e o host descarta todas, logo nenhum
|
|
306
|
+
modulo Pantheon entra no processo Node. **O que mudou**: `resolveOpenCodeVersion`
|
|
307
|
+
agora tem default `'auto'` nos dois call sites e no proprio signature, e `auto`
|
|
308
|
+
consulta o host. Precedencia `auto`: flag explicito > `OPENCODE_VERSION` >
|
|
309
|
+
basename `opencode2` > probe `--version` do host (major >= 2 => v2) > soft-fail
|
|
310
|
+
para V1 com warning visivel. Precedencia e criterios do probe estao em
|
|
311
|
+
`docs/INSTALLATION.md` -> "OpenCode V1/V2 — contrato de plugin".
|
|
312
|
+
|
|
313
|
+
Comportamento da tool — **NAO VERIFICADO em runtime**: em 1.6.0-beta.4 o modulo
|
|
314
|
+
(`src/pantheon/cost-command.ts`) nunca era carregado no processo, e o fix nao foi
|
|
315
|
+
exercitado end-to-end, entao este descritivo permanece nao verificado.
|
|
288
316
|
`pantheon_cost({ days?: number })` le o `opencode.db` READ-ONLY usando o backend
|
|
289
317
|
unico `node:sqlite`; se indisponivel, retorna `UNSUPPORTED`, sem fallback. Devolve tabela markdown de custo + tokens por
|
|
290
318
|
agente nos ultimos N dias (default 7). Uso: quando Nyx pedir visibilidade de
|
|
@@ -58,8 +58,14 @@ Para otimizar decisoes de delegacao e reduzir gasto de tokens:
|
|
|
58
58
|
2. Se score > 0.85 → reutiliza agente + background_mode do cache
|
|
59
59
|
3. Se score ≤ 0.85 → aplica regras estaticas e memory_store() com:
|
|
60
60
|
- key: deleg:<task_type>
|
|
61
|
-
- value: {agent, background, pattern}
|
|
62
|
-
- metadata: {type: "decision", score: N}
|
|
61
|
+
- value: JSON.stringify({agent, background, pattern})
|
|
62
|
+
- metadata: JSON.stringify({type: "decision", score: N})
|
|
63
|
+
|
|
64
|
+
`value` E `metadata` sao JSON-encoded strings — o runtime exige que o
|
|
65
|
+
chamador serialize os dois. Objeto cru em `metadata` e rejeitado pelo
|
|
66
|
+
`BeforeValidator` do servidor com `metadata must be a JSON object encoded
|
|
67
|
+
as a string (got object). json.dumps it first. Example: metadata='{"type":
|
|
68
|
+
"decision", "score": 0.9}'`.
|
|
63
69
|
|
|
64
70
|
4. **kv_store("deleg:<pattern>", ...)** para padroes recorrentes de delegacao
|
|
65
71
|
5. **kv_get("deleg:<pattern>")** para reusar decisoes ja tomadas
|
|
@@ -88,14 +94,19 @@ memory_store({
|
|
|
88
94
|
precedent_used: false,
|
|
89
95
|
timestamp: "<ISO-8601>"
|
|
90
96
|
},
|
|
91
|
-
metadata: {
|
|
97
|
+
metadata: JSON.stringify({
|
|
92
98
|
type: "council_decision",
|
|
93
99
|
specialist_count: N,
|
|
94
100
|
model_tier_used: "premium|default|fast"
|
|
95
|
-
}
|
|
101
|
+
})
|
|
96
102
|
})
|
|
97
103
|
```
|
|
98
104
|
`value` is JSON-serialized before storing (the MCP `memory_store.value` argument is a string).
|
|
105
|
+
`metadata` works the same way — it is a JSON object encoded as a **string**, so pass
|
|
106
|
+
`JSON.stringify({...})`. A raw object is rejected with `metadata must be a JSON object
|
|
107
|
+
encoded as a string (got object). json.dumps it first. Example: metadata='{"type":
|
|
108
|
+
"decision", "score": 0.9}'` (enforced by a `BeforeValidator` in
|
|
109
|
+
`memory_mcp.py`). **Both `value` and `metadata` must be serialized by the caller.**
|
|
99
110
|
|
|
100
111
|
### Read Path (Precedent Fast-Path)
|
|
101
112
|
Before dispatching a new council, Zeus runs:
|
|
@@ -66,9 +66,17 @@ No file I/O, no checkpoint_session.py — TTL (4h) handles cleanup automatically
|
|
|
66
66
|
Before ANY delegate dispatch, save a checkpoint:
|
|
67
67
|
```
|
|
68
68
|
context_save(slug, "phase:N", json({
|
|
69
|
-
"phase":
|
|
69
|
+
"phase": {"current": N, "total": M, "name": "..."},
|
|
70
|
+
"turn_count": N, "agent": "...", "summary": "..."
|
|
70
71
|
}), session_id=SESSION_ID)
|
|
71
72
|
```
|
|
73
|
+
**`content.phase` must be an OBJECT, not a bare number.** `{"phase": 1}` is rejected with
|
|
74
|
+
`context_save: content.phase must be an object (got number)`. The server reads `current`
|
|
75
|
+
and `total` (non-negative ints) and `name` (string, max 256 **bytes** — the guard
|
|
76
|
+
measures `len(value.encode())`, so multi-byte characters count as more than one). That
|
|
77
|
+
set is not closed: any other key inside `phase` is accepted and ignored, never an
|
|
78
|
+
error. The `"phase:N"` slot KEY above is unrelated to `content.phase` and stays a plain
|
|
79
|
+
string.
|
|
72
80
|
All checkpoints auto-expire after 4h (TTL=14400).
|
|
73
81
|
|
|
74
82
|
### Gatilho de Pré-Compactação (Anti-perda de estado)
|
|
@@ -5,12 +5,12 @@ Provides a confined execution environment for orchestration scripts
|
|
|
5
5
|
via MCP tools and resources.
|
|
6
6
|
|
|
7
7
|
Usage:
|
|
8
|
-
python scripts/
|
|
8
|
+
python scripts/code_mode.py
|
|
9
9
|
|
|
10
10
|
Or via MCP client (stdio transport):
|
|
11
11
|
pantheon-code-mode:
|
|
12
12
|
command: python
|
|
13
|
-
args: ["scripts/
|
|
13
|
+
args: ["scripts/code_mode.py"]
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
@@ -36,7 +36,7 @@ from typing import Any
|
|
|
36
36
|
|
|
37
37
|
import yaml
|
|
38
38
|
from _pantheon_paths import pantheon_home, pantheon_project
|
|
39
|
-
from
|
|
39
|
+
from fastmcp import FastMCP
|
|
40
40
|
|
|
41
41
|
# ── Constants ─────────────────────────────────────────────────────────────────
|
|
42
42
|
ALLOWED_EXTENSIONS: frozenset[str] = frozenset({".sh", ".py"})
|
package/src/mcp/eval_store.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""Plugin eval certification storage — ``plugin_eval`` memory namespace.
|
|
3
3
|
|
|
4
4
|
Thin stdlib-only wrapper around the memory SQLite DB (same schema as
|
|
5
|
-
``src/mcp/
|
|
5
|
+
``src/mcp/memory_mcp.py``) for plugin-eval certification results.
|
|
6
6
|
|
|
7
7
|
Namespace: ``plugin_eval``
|
|
8
8
|
Key format: ``eval:<name>:<date>`` (e.g. ``eval:tdd-with-agents:2026-08-21``)
|
|
@@ -30,7 +30,7 @@ from typing import Any
|
|
|
30
30
|
|
|
31
31
|
NAMESPACE = "plugin_eval"
|
|
32
32
|
|
|
33
|
-
# Minimal schema — matches
|
|
33
|
+
# Minimal schema — matches memory_mcp.SCHEMA_SQL for the memories
|
|
34
34
|
# table (vec/FTS tables are initialized by the memory MCP server on first
|
|
35
35
|
# run; eval entries remain readable via list/recall regardless).
|
|
36
36
|
_SCHEMA_SQL = """
|
|
@@ -5,7 +5,7 @@ Key-Value store with FTS5 full-text search, TTL-based expiration,
|
|
|
5
5
|
and namespace isolation. Uses SQLite with zero external dependencies.
|
|
6
6
|
|
|
7
7
|
Usage:
|
|
8
|
-
python scripts/
|
|
8
|
+
python scripts/mcp_persistence.py
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
@@ -25,7 +25,7 @@ from pathlib import Path
|
|
|
25
25
|
from typing import Annotated
|
|
26
26
|
|
|
27
27
|
from _pantheon_paths import pantheon_home, pantheon_project
|
|
28
|
-
from
|
|
28
|
+
from fastmcp import FastMCP
|
|
29
29
|
from pydantic import Field
|
|
30
30
|
|
|
31
31
|
# ── Schema ──────────────────────────────────────────────────────────────────────
|
|
@@ -7,7 +7,7 @@ Provides MCP resources for the Pantheon agent framework:
|
|
|
7
7
|
plans/status, memory-bank files
|
|
8
8
|
|
|
9
9
|
Usage:
|
|
10
|
-
python scripts/
|
|
10
|
+
python scripts/mcp_resources.py
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
@@ -19,7 +19,7 @@ from typing import Any
|
|
|
19
19
|
import yaml
|
|
20
20
|
from _pantheon_paths import has_symlink_component, pantheon_home, pantheon_project
|
|
21
21
|
from eval_store import get_latest_eval, list_evals
|
|
22
|
-
from
|
|
22
|
+
from fastmcp import FastMCP
|
|
23
23
|
|
|
24
24
|
# ── Path Resolution ──────────────────────────────────────────────────────────
|
|
25
25
|
_PANTHEON_HOME: Path = pantheon_home()
|
|
@@ -19,7 +19,7 @@ Tools:
|
|
|
19
19
|
memory_stats — DB statistics
|
|
20
20
|
|
|
21
21
|
Usage:
|
|
22
|
-
python scripts/
|
|
22
|
+
python scripts/memory_mcp.py
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
from __future__ import annotations
|
|
@@ -34,7 +34,7 @@ from pathlib import Path
|
|
|
34
34
|
from typing import Annotated, Any
|
|
35
35
|
|
|
36
36
|
from _pantheon_paths import pantheon_home
|
|
37
|
-
from
|
|
37
|
+
from fastmcp import FastMCP
|
|
38
38
|
from pydantic import BeforeValidator, Field
|
|
39
39
|
|
|
40
40
|
try:
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# Pantheon MCP runtime dependencies — EXACT pins (issue #21)
|
|
2
|
-
#
|
|
3
|
-
# Update deliberately via: pip install -U <pkg> && pip freeze | grep -iE '^(mcp|fastmcp|pyyaml)'
|
|
2
|
+
# Coordinated MCP SDK/FastMCP major migration (Dependabot PRs #140 and #188).
|
|
3
|
+
# Update deliberately via: pip install -U <pkg> && pip freeze | grep -iE '^(mcp|fastmcp|pyyaml|httpx)=='
|
|
4
4
|
# Memory search is FTS5 (SQLite stdlib): sqlite-vec and fastembed were removed
|
|
5
5
|
# with the vector pipeline — the server no longer imports either.
|
|
6
|
-
mcp==
|
|
7
|
-
fastmcp==
|
|
6
|
+
mcp==2.2.0
|
|
7
|
+
fastmcp==4.0.10
|
|
8
8
|
pydantic==2.12.5
|
|
9
9
|
pydantic-settings==2.14.2
|
|
10
10
|
pyyaml==6.0.3
|
|
11
|
+
# Vision imports httpx; MCP 2.2.0 provides httpx2, a different package.
|
|
12
|
+
httpx==0.28.1
|