Phase 005: vendor ArkScope, land the Arco style baseline, and fix bridge interpreter resolution
- Vendor ArkScope decorative layers (ScrambleTitle / IsometricWave) with provenance notes; decorative-only per AGENTS.md red lines - Migrate theme tokens to Arco, register the Arco theme, and add hook adapter composables (useMessage / useDialog / useNotification) with matching signatures - Sample pages: LoginView and AppSidebar on the new baseline; docs/arco-mapping.md and docs/ui-baseline.md land - agent-bridge: prefer the PM-selected venv from facts.json over legacy in-tree venvs (legacy lacks the mcp extra, so every MCP server showed 0 tools / not connected) - agent-bridge python (vendored, deliberate deviation): MCP discovery import fallback and pyyaml -> hermes_yaml so plugins and MCP work under the PM env; install the mcp extra via `hermes pm install --extra mcp` - Chat-chain fragment: docs/chat-chain-changes/2026-10-17-phase005-arkscope-baseline.md Verified: harness:check, test (2020 passed), test:e2e (30 passed), build all green; manual: plugins 100/0 warnings, MCP 6/6 servers connected with 104 tools.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { execFileSync, spawn, type ChildProcess } from 'child_process'
|
||||
import { existsSync, readFileSync } from 'fs'
|
||||
import { createHash } from 'crypto'
|
||||
import { existsSync, readFileSync, realpathSync } from 'fs'
|
||||
import { createConnection, createServer } from 'net'
|
||||
import { dirname, isAbsolute, join, resolve } from 'path'
|
||||
import { logger } from '../../logger'
|
||||
@@ -103,18 +104,22 @@ export function buildAgentBridgeProcessEnv(endpoint: string, hermesHome: string
|
||||
|
||||
function pathCandidates(agentRoot?: string): string[] {
|
||||
if (!agentRoot) return []
|
||||
// Legacy in-tree virtualenvs. PM-managed installs live under
|
||||
// `<hermesHome>/installs/<key>/environments/.../venv` (see
|
||||
// pmSelectedVenvPython below); these trees predate PM and may be a bare
|
||||
// interpreter, so they are only a fallback.
|
||||
return process.platform === 'win32'
|
||||
? [
|
||||
join(agentRoot, 'venv', 'Scripts', 'python.exe'),
|
||||
join(agentRoot, 'venv', 'Scripts', 'python3.exe'),
|
||||
join(agentRoot, '.venv', 'Scripts', 'python.exe'),
|
||||
join(agentRoot, '.venv', 'Scripts', 'python3.exe'),
|
||||
join(agentRoot, 'venv', 'Scripts', 'python.exe'),
|
||||
join(agentRoot, 'venv', 'Scripts', 'python3.exe'),
|
||||
]
|
||||
: [
|
||||
join(agentRoot, 'venv', 'bin', 'python3'),
|
||||
join(agentRoot, 'venv', 'bin', 'python'),
|
||||
join(agentRoot, '.venv', 'bin', 'python3'),
|
||||
join(agentRoot, '.venv', 'bin', 'python'),
|
||||
join(agentRoot, 'venv', 'bin', 'python3'),
|
||||
join(agentRoot, 'venv', 'bin', 'python'),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -218,6 +223,41 @@ function firstExistingExecutable(candidates: string[]): string | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// PM (hermes's package manager) records the selected venv in
|
||||
// `<hermesHome>/installs/<installKey>/facts.json`, where installKey is
|
||||
// sha256(resolved agentRoot)[:16] (see pm/environments.py). That venv carries
|
||||
// hermes's extras (mcp, ruamel, ...); the in-tree `.venv`/`venv` trees predate
|
||||
// PM and often lack them.
|
||||
function pmSelectedVenvPython(agentRoot: string | undefined, hermesHome: string): string | undefined {
|
||||
if (!agentRoot) return undefined
|
||||
let resolvedRoot = agentRoot
|
||||
try {
|
||||
resolvedRoot = realpathSync(agentRoot)
|
||||
} catch {}
|
||||
const installKey = createHash('sha256').update(resolve(resolvedRoot)).digest('hex').slice(0, 16)
|
||||
const factsCandidates = [
|
||||
join(hermesHome, 'installs', installKey, 'facts.json'),
|
||||
join(process.env.HERMES_HOME?.trim() || join(process.env.HOME || '', '.hermes'), 'installs', installKey, 'facts.json'),
|
||||
]
|
||||
for (const factsPath of factsCandidates) {
|
||||
try {
|
||||
if (!existsSync(factsPath)) continue
|
||||
const facts = JSON.parse(readFileSync(factsPath, 'utf8')) as {
|
||||
packages?: { venv?: { environment?: string } }
|
||||
}
|
||||
const environment = facts?.packages?.venv?.environment
|
||||
if (!environment) continue
|
||||
const python = firstExistingExecutable(
|
||||
process.platform === 'win32'
|
||||
? [join(environment, 'Scripts', 'python.exe'), join(environment, 'Scripts', 'python3.exe')]
|
||||
: [join(environment, 'bin', 'python3'), join(environment, 'bin', 'python')],
|
||||
)
|
||||
if (python) return python
|
||||
} catch {}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function resolveAgentRoot(explicit?: string, hermesHome = detectHermesHome()): string | undefined {
|
||||
const candidates = [
|
||||
explicit,
|
||||
@@ -242,6 +282,11 @@ export function resolveAgentBridgeCommand(options: AgentBridgeManagerOptions = {
|
||||
return { command: explicitPython, argsPrefix: [], agentRoot, hermesHome }
|
||||
}
|
||||
|
||||
const pmPython = pmSelectedVenvPython(agentRoot, hermesHome)
|
||||
if (pmPython) {
|
||||
return { command: pmPython, argsPrefix: [], agentRoot, hermesHome }
|
||||
}
|
||||
|
||||
const venvPython = firstExistingExecutable(pathCandidates(agentRoot))
|
||||
if (venvPython) {
|
||||
return { command: venvPython, argsPrefix: [], agentRoot, hermesHome }
|
||||
|
||||
Reference in New Issue
Block a user