@whyshu/argent 0.2.1 → 0.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whyshu/argent",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "description": "Argent 单 bundle(dsh web / DSH Desktop 同一插件):ERP 连接器 + 问述代理 + 登录/续期 + MCP 网关 + 伞伞桌宠(含积分)",
6
6
  "main": "src/index.js",
@@ -36,5 +36,8 @@
36
36
  "plugin": {
37
37
  "displayName": "Argent(ERP + 问述代理 + 伞伞)"
38
38
  }
39
+ },
40
+ "dependencies": {
41
+ "@whyshu/argent-engine": "0.8.0-rc.13"
39
42
  }
40
43
  }
package/src/index.js CHANGED
@@ -24,6 +24,7 @@ import { registerWhyshuAuth } from './whyshu-auth.mjs'
24
24
  import { acquireLease, heartbeatLease, releaseLease, leaseHolder } from './lease.mjs'
25
25
  import { resolveAccountToken } from './account-token.mjs'
26
26
  import { installMachineSessionReporter } from './machine-session.mjs'
27
+ import { syncRuntimeIfNeeded } from './runtime-sync.mjs'
27
28
 
28
29
  const PKG_ROOT = dirname(dirname(fileURLToPath(import.meta.url)))
29
30
  const PKG_VERSION = (() => {
@@ -178,6 +179,13 @@ export function apply(ctx) {
178
179
 
179
180
  // ⑤ 内核解析 → 租约 → MCP 网关 → ERP 连接器(全部在异步块里,串行避免 dataDir 竞态)
180
181
  void (async () => {
182
+ // 运行时跟随插件:插件 pin 的 engine 版本若新于 ~/.argent-dsh,先同步(失败只告警)
183
+ try {
184
+ const r = syncRuntimeIfNeeded({ log: (m) => console.log(m) })
185
+ if (r && r.action !== 'up_to_date' && r.action !== 'installed_newer') console.log('[argent] runtime sync:', JSON.stringify(r))
186
+ } catch (e) {
187
+ console.error('[argent] 运行时同步失败(继续启动):', e?.message || e)
188
+ }
181
189
  try {
182
190
  const k = await resolveKernel().catch(() => null)
183
191
  if (k?.mod?.resolveDataDir) {
@@ -414,6 +414,17 @@ export function startOfficeRuntime(ctx, { dataDir } = {}) {
414
414
  const CHAT_HEARTBEAT_MS = 30 * 1000
415
415
  const TASK_MAX_MS = 45 * 60 * 1000
416
416
 
417
+ // ── 办公室角色(P2 机器对话/新建会话)────────────────────────────────────
418
+ // 与 `plugins/argent-erp/presets/argent-office.patch.yml` 对齐:
419
+ // · ⚠️ **不**把 create_session 的缺省 preset 改成 argent-office —— ERP「AI 团队」是问述内部用的;
420
+ // · 只有 ERP **显式**选到 argent-office 时,才把该会话模型钉死为问述代理(走问述积分)。
421
+ // 均可由 env 覆盖,交给客户机按部署改,不必改代码。
422
+ const OFFICE_PRESET = process.env.ARGENT_OFFICE_PRESET || 'argent-office'
423
+ const OFFICE_MODEL = {
424
+ provider: process.env.ARGENT_OFFICE_MODEL_PROVIDER || 'whyshu',
425
+ model: process.env.ARGENT_OFFICE_MODEL || 'deepseek-flash',
426
+ }
427
+
417
428
  ctx.effect(() => {
418
429
  const off = ctx.on('session/event', (session, event) => {
419
430
  const sid = String(session?.id || '')
@@ -864,9 +875,20 @@ export function startOfficeRuntime(ctx, { dataDir } = {}) {
864
875
  try {
865
876
  const sc = ctx.get('sessionController') || ctx.sessionController
866
877
  if (!sc || typeof sc.create !== 'function') throw new Error('sessionController.create 不可用(该 dsh 版本不支持)')
867
- const created = await sc.create({ agentPreset: String(command.preset || '') || undefined })
878
+ // ⚠️ ERP「AI 团队」是问述内部用的,不要把它缺省接管成六禾园办公室角色;
879
+ // 缺省仍是宿主默认(standard),只有 ERP 显式指定 preset 时才按指定来。
880
+ const preset = String(command.preset || '') || undefined
881
+ const created = await sc.create({ agentPreset: preset })
868
882
  resultSessionId = String(created?.sessionId || '')
869
883
  if (!resultSessionId) throw new Error('create 未返回 sessionId')
884
+ // 办公室角色:把模型钉成问述代理(失败不致命,会话照常可用)
885
+ if (preset === OFFICE_PRESET && typeof sc.selectModel === 'function') {
886
+ try {
887
+ await sc.selectModel({ sessionId: resultSessionId, ...OFFICE_MODEL })
888
+ } catch (e) {
889
+ log('warn', `会话模型钉选失败(不影响新建):${trunc(e?.message || e, 160)}`)
890
+ }
891
+ }
870
892
  } catch (e) {
871
893
  status = 'failed'
872
894
  error = trunc(e?.message || e, 300)
@@ -0,0 +1,113 @@
1
+ /**
2
+ * 运行时跟随插件(2026-11-07):插件启动时把依赖的 @whyshu/argent-engine 同步到 ~/.argent-dsh。
3
+ *
4
+ * 目标:以后 UI 升级 @whyshu/argent 插件,pnpm 会同时装上插件 pin 的 engine 依赖;
5
+ * 插件启动时把 engine 文件刷到运行时目录 ⇒ 不再需要启动器/单独 runtime install。
6
+ *
7
+ * 安全边界:
8
+ * · 只写引擎文件(scripts/core/verticals/configs/agents/skills/tools/agentic + VERSION);
9
+ * ⛔ 不动 venv、数据、台账、agent-report、license;
10
+ * · 只在 bundled 版本比已装新(或未装)时同步;已装更新则跳过(防降级);
11
+ * · 覆盖前备份差异文件到 <target>/.runtime-backup/<ts>/;失败只告警不阻断插件启动。
12
+ */
13
+ import { createRequire } from 'node:module'
14
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, statSync, chmodSync, readdirSync } from 'node:fs'
15
+ import { dirname, join, relative, sep } from 'node:path'
16
+ import { homedir } from 'node:os'
17
+ import { fileURLToPath } from 'node:url'
18
+ import { createHash } from 'node:crypto'
19
+
20
+ const sha = (buf) => createHash('sha256').update(buf).digest('hex')
21
+
22
+ /** 版本比较(支持 0.8.0-rc.11 / 0.8.0 / 1.2.3);无法解析时按字符串比较。 */
23
+ export function compareVersions(a, b) {
24
+ const parse = (v) => {
25
+ const m = String(v || '').trim().match(/^(\d+)\.(\d+)\.(\d+)(?:-([a-z]+)\.?(\d+)?)?/i)
26
+ if (!m) return null
27
+ return [Number(m[1]), Number(m[2]), Number(m[3]), m[4] || '', Number(m[5] || 0)]
28
+ }
29
+ const x = parse(a); const y = parse(b)
30
+ if (!x || !y) return String(a || '').localeCompare(String(b || ''))
31
+ for (let i = 0; i < 3; i += 1) if (x[i] !== y[i]) return x[i] < y[i] ? -1 : 1
32
+ // 正式版 > 预发布(0.8.0 > 0.8.0-rc.12)
33
+ if (!x[3] && y[3]) return 1
34
+ if (x[3] && !y[3]) return -1
35
+ if (x[3] !== y[3]) return String(x[3]).localeCompare(String(y[3]))
36
+ if (x[4] !== y[4]) return x[4] < y[4] ? -1 : 1
37
+ return 0
38
+ }
39
+
40
+ /** 解析插件依赖的 engine 包目录(未安装返回 '')。 */
41
+ export function resolveBundledEngineDir() {
42
+ try {
43
+ const require = createRequire(import.meta.url)
44
+ const pkgJson = require.resolve('@whyshu/argent-engine/package.json')
45
+ return dirname(pkgJson)
46
+ } catch {
47
+ return ''
48
+ }
49
+ }
50
+
51
+ export function installedRuntimeVersion(target = join(homedir(), '.argent-dsh')) {
52
+ try { return String(readFileSync(join(target, 'VERSION'), 'utf8')).trim() } catch { return '' }
53
+ }
54
+
55
+ function listFiles(dir, base = dir) {
56
+ const out = []
57
+ for (const e of readdirSync(dir)) {
58
+ const p = join(dir, e)
59
+ if (statSync(p).isDirectory()) out.push(...listFiles(p, base))
60
+ else out.push(relative(base, p).split(sep).join('/'))
61
+ }
62
+ return out
63
+ }
64
+
65
+ /**
66
+ * 同步运行时(bundle → target)。
67
+ * @returns {{ok:boolean, action:'no_bundle'|'up_to_date'|'installed_newer'|'synced', from?:string, to?:string, files?:number, backupDir?:string}}
68
+ */
69
+ export function syncRuntimeIfNeeded({
70
+ target = join(homedir(), '.argent-dsh'),
71
+ bundledDir = resolveBundledEngineDir(),
72
+ log = () => {},
73
+ } = {}) {
74
+ if (!bundledDir || !existsSync(join(bundledDir, 'engine'))) return { ok: false, action: 'no_bundle' }
75
+ const src = join(bundledDir, 'engine')
76
+ const bundledVersion = (() => { try { return readFileSync(join(src, 'VERSION'), 'utf8').trim() } catch { return '' } })()
77
+ if (!bundledVersion) return { ok: false, action: 'no_bundle' }
78
+ const current = installedRuntimeVersion(target)
79
+ if (current && compareVersions(bundledVersion, current) < 0) {
80
+ return { ok: true, action: 'installed_newer', from: current, to: bundledVersion }
81
+ }
82
+ if (current && compareVersions(bundledVersion, current) === 0) {
83
+ return { ok: true, action: 'up_to_date', from: current, to: bundledVersion }
84
+ }
85
+ const manifestPath = join(bundledDir, 'MANIFEST.json')
86
+ let manifest = null
87
+ try { manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) } catch { manifest = null }
88
+ const files = listFiles(src)
89
+ const ts = new Date().toISOString().replace(/[:.]/g, '-')
90
+ const backup = join(target, '.runtime-backup', `plugin-sync-${ts}`)
91
+ mkdirSync(backup, { recursive: true })
92
+ let copied = 0
93
+ for (const rel of files) {
94
+ const s = join(src, rel)
95
+ const d = join(target, rel)
96
+ if (existsSync(d)) {
97
+ try {
98
+ if (sha(readFileSync(d)) !== sha(readFileSync(s))) {
99
+ const b = join(backup, rel)
100
+ mkdirSync(dirname(b), { recursive: true })
101
+ copyFileSync(d, b)
102
+ }
103
+ } catch { /* 备份失败继续 */ }
104
+ }
105
+ mkdirSync(dirname(d), { recursive: true })
106
+ copyFileSync(s, d)
107
+ chmodSync(d, rel.endsWith('.sh') ? 0o755 : 0o644)
108
+ copied += 1
109
+ }
110
+ writeFileSync(join(target, 'VERSION'), `${bundledVersion}\n`)
111
+ log(`[argent] 运行时已跟随插件更新:${current || '(未安装)'} → ${bundledVersion}(${copied} 个文件,备份 ${backup})`)
112
+ return { ok: true, action: 'synced', from: current || '', to: bundledVersion, files: copied, backupDir: backup, manifest: Boolean(manifest) }
113
+ }