@whyshu/argent 0.2.1 → 0.2.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/package.json +4 -1
- package/src/index.js +8 -0
- package/src/office-runtime.js +23 -1
- package/src/runtime-sync.mjs +113 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whyshu/argent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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.12"
|
|
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) {
|
package/src/office-runtime.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|