@whyshu/argent 0.2.7 → 0.2.9

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/mcp/gateway.mjs CHANGED
@@ -32,6 +32,16 @@ import { fileURLToPath } from 'node:url'
32
32
 
33
33
  const HERE = dirname(fileURLToPath(import.meta.url))
34
34
  const PKG_ROOT = resolve(HERE, '..')
35
+ /**
36
+ * 引擎根(**可选**):只认宿主显式传入的 `ARGENT_ENGINE_DIR`(`@whyshu/argent` 注册时会给),
37
+ * ⛔ 不按仓库相对路径猜 —— 插件安装后没有"仓库根",这条口径由
38
+ * `scripts/tests/argent-mcp-vendor-sync.test.mjs` 钉住。
39
+ * 2026-11-08 修:本文件从 `core/mcp/gateway.mjs` 抄来时把原来的 `REPO` 丢了、只剩 `PKG_ROOT`,
40
+ * 而 ml 分支还在用 `REPO`(`tools/agentic/mcp_server.py` / `scripts`)⇒ 只要
41
+ * `ARGENT_GATEWAY_ML=1` 就抛 `ReferenceError: REPO is not defined`,**而且死在装 stdio 前端之前**
42
+ * ⇒ 宿主侧 `dsh-mcp-client` 永远等不到 initialize 响应(表现为"MCP 网关握手挂起")。
43
+ */
44
+ const ENGINE_DIR = process.env.ARGENT_ENGINE_DIR || ''
35
45
  const DATA_DIR = process.env.ARGENT_DATA_DIR || join(homedir(), '.dsh', 'argent')
36
46
  const GATEWAY_DIR = join(DATA_DIR, 'gateway')
37
47
  const NO_PREFIX = process.env.ARGENT_GATEWAY_NOPREFIX === '1'
@@ -147,15 +157,16 @@ export class Gateway {
147
157
 
148
158
  static defaultUpstreams() {
149
159
  // 引擎上游可选:插件独立安装(没有 engine 包)时只挂 erp.*,不因缺引擎而整个网关启动失败
150
- const engineDir = process.env.ARGENT_ENGINE_DIR || ''
160
+ const engineDir = ENGINE_DIR
151
161
  const engineServer = engineDir ? join(engineDir, 'core', 'mcp', 'server.mjs') : ''
152
162
  const ups = []
153
163
  if (engineServer && existsSync(engineServer)) ups.push(new Upstream('engine', process.execPath, [engineServer]))
154
164
  ups.push(new Upstream('erp', process.execPath, [join(HERE, 'erp-server.mjs')]))
155
165
  if (process.env.ARGENT_GATEWAY_ML === '1') {
166
+ // ml 上游在**引擎根**下面(tools/agentic/mcp_server.py + scripts);没有引擎根就跳过,⛔ 不猜路径
156
167
  const py = process.env.ARGENT_ENGINE_PY || join(DATA_DIR, 'venv', 'bin', 'python3')
157
- const srv = join(REPO, 'tools', 'agentic', 'mcp_server.py')
158
- if (existsSync(py) && existsSync(srv)) ups.push(new Upstream('ml', py, [srv], { PYTHONPATH: join(REPO, 'scripts') }))
168
+ const srv = engineDir ? join(engineDir, 'tools', 'agentic', 'mcp_server.py') : ''
169
+ if (py && srv && existsSync(py) && existsSync(srv)) ups.push(new Upstream('ml', py, [srv], { PYTHONPATH: join(engineDir, 'scripts') }))
159
170
  }
160
171
  return ups
161
172
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whyshu/argent",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "type": "module",
5
5
  "description": "Argent 单 bundle(dsh web / DSH Desktop 同一插件):ERP 连接器 + 问述代理 + 登录/续期 + MCP 网关 + 伞伞桌宠(含积分)",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -227,8 +227,13 @@ export function apply(ctx) {
227
227
  }
228
228
  }
229
229
 
230
- // ⛔ 2026-11-07 实测:启动阶段 ctx.plugin(dsh-mcp-client) 会挂起 dsh 启动(端口 bound 但 HTTP 不响应),
231
- // 用户侧表现为「dsh 启动失败」;只有删插件才能启动。故:默认延后注册,且可用 ARGENT_MCP_DISABLE=1 关闭。
230
+ // 真根因(2026-11-08 用 V8 inspector 抓栈确诊):**不是** dsh-mcp-client/网关握手挂起,而是
231
+ // MCP 工具在 boot 之后才注册 ⇒ 触发 `@whyshu/argent-erp` 的 office-guard 在
232
+ // 「dispose 旧 mask → restrict 新 mask」过程中的**同步重入风暴**(主线程 100% CPU + 堆 OOM,
233
+ // 端口 bound 但 HTTP 不响应)。已在 office-guard 加 reconcile 重入闸修好;见
234
+ // scripts/tests/argent-office-guard-reentrancy.test.mjs。
235
+ // 这里仍保留「延后注册 + ARGENT_MCP_DISABLE + 握手 20s 上限」作为兜底:宿主侧第三方 listeners
236
+ // 若还有同类重入,最多拖慢 MCP 自身,不把 boot 与 HTTP 服务拖下水。
232
237
  if (process.env.ARGENT_MCP_DISABLE === '1') {
233
238
  console.log('[argent] MCP 网关已禁用(ARGENT_MCP_DISABLE=1)')
234
239
  } else setTimeout(() => { void (async () => {
package/src/tunnel.mjs CHANGED
@@ -530,18 +530,33 @@ export function startTunnel(options = {}) {
530
530
  }
531
531
 
532
532
  // ── dsh 准入 cookie(仅内存;401/403 重换一次)──────────────────────────
533
+ // dsh 准入 token 的三条来源(优先级:env → 启动脚本写出的 token 文件 → 本机 /api/argent-state/access)
534
+ // 为什么需要文件来源:`/api/argent-state/access` 由 argent-core 插件提供;单插件(@whyshu/argent)装配下
535
+ // 该端点 401 ⇒ 隧道拿不到 token ⇒ App 侧表现为 access-unavailable(2026-11-07 实测)。
536
+ const readTokenFile = () => {
537
+ try {
538
+ const t = readFileSync(join(String(options.dataDir || ''), 'dsh-web.token'), 'utf8').trim()
539
+ return t || ''
540
+ } catch { return '' }
541
+ }
533
542
  const acquireCookie = async ({ refresh = false } = {}) => {
534
543
  if (!refresh && dsh.cookie) return dsh.cookie
535
- let access
536
- try {
537
- access = await fetchImpl(`${dshBase}${DSH_ACCESS_PATH}`, {
538
- headers: { accept: 'application/json' },
539
- signal: AbortSignal.timeout(5000),
540
- })
541
- } catch { throw err('access-unreachable') }
542
544
  let body = null
543
- try { body = await access.json() } catch { body = null }
544
- if (!access.ok || !body || body.ok !== true || !body.token) throw err('access-unavailable', `HTTP ${access.status}`)
545
+ const envToken = String(env.ARGENT_DSH_TOKEN || '').trim()
546
+ const fileToken = envToken ? '' : readTokenFile()
547
+ if (envToken || fileToken) {
548
+ body = { ok: true, token: envToken || fileToken }
549
+ } else {
550
+ let access
551
+ try {
552
+ access = await fetchImpl(`${dshBase}${DSH_ACCESS_PATH}`, {
553
+ headers: { accept: 'application/json' },
554
+ signal: AbortSignal.timeout(5000),
555
+ })
556
+ } catch { throw err('access-unreachable') }
557
+ try { body = await access.json() } catch { body = null }
558
+ if (!access.ok || !body || body.ok !== true || !body.token) throw err('access-unavailable', `HTTP ${access.status}`)
559
+ }
545
560
  let res
546
561
  try {
547
562
  res = await fetchImpl(`${dshBase}/?token=${encodeURIComponent(String(body.token))}`, {