@whyshu/argent 0.1.22 → 0.1.24

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/bin/argent.mjs CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  DSH_EXACT_VERSION, PROFILE_NAME, PROD_BUNDLES, buildLaunchdPlist, buildProfile, dshHomeOf, runnerScriptPath,
19
19
  depsInstalled, health, installDeps, loginWithPhone, pairRunner, probeDshVersion, readAuthToken, repoRoot, runnerConfigPath, sendLoginCode, versionMatches,
20
20
  engineRoot, materializeRuntime, checkForUpdate, compareVersions, launcherVersion,
21
- PUBLISHED_BUNDLE_VERSIONS, resolveDshBin, updateProfilePluginVersions,
21
+ PUBLISHED_BUNDLE_VERSIONS, resolveDshBin, updateProfilePluginVersions, writeEngineContract,
22
22
  } from '../lib/index.mjs';
23
23
 
24
24
  const argv = process.argv.slice(2);
@@ -218,6 +218,19 @@ function runCli(bin, args, { cwd = '', env = {} } = {}) {
218
218
 
219
219
  function inherit_stdout() { return !JSON_OUT }
220
220
 
221
+
222
+ /** desktop profile 的依赖安装器:ARGENT_PNPM → 宿主自带 pnpm → PATH 上的 pnpm */
223
+ function resolvePnpm() {
224
+ const cands = [
225
+ process.env.ARGENT_PNPM,
226
+ '/Applications/DeepSeek Harness.app/Contents/Resources/runtime/primary-runtime/dependencies/pnpm/bin/pnpm.mjs',
227
+ ].filter(Boolean)
228
+ for (const c of cands) { if (existsSync(c)) return { cmd: process.execPath, args: [c] } }
229
+ const which = spawnSync('pnpm', ['--version'], { encoding: 'utf8' })
230
+ if (which.status === 0) return { cmd: 'pnpm', args: [] }
231
+ return null
232
+ }
233
+
221
234
  async function updateCommand(sub) {
222
235
  if (sub !== 'check' && sub !== 'run') {
223
236
  console.error('用法:argent update check|run [--to <version>] [--yes] [--registry <url>] [--dsh-home <dir>] [--target <dir>] [--json] [--dry-run]');
@@ -252,7 +265,7 @@ async function updateCommand(sub) {
252
265
  const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx'
253
266
  const common = ['--yes', `@whyshu/argent@${targetVersion}`]
254
267
  const installArgs = [...common, 'runtime', 'install', '--target', opts.target, '--skills-target', opts.skillsTarget]
255
- const healthArgs = [...common, 'health', '--dsh-home', opts.dshHome, '--json', '--dsh-bin', opts.dshBin]
268
+ const healthArgs = [...common, 'health', '--dsh-home', opts.dshHome, '--profile', opts.profile, '--json', '--dsh-bin', opts.dshBin]
256
269
  if (DRY_RUN) {
257
270
  print({ ok: true, dryRun: true, current, targetVersion, dshBin: opts.dshBin, commands: [
258
271
  [npx, ...installArgs].join(' '),
@@ -277,17 +290,32 @@ async function updateCommand(sub) {
277
290
  const profileDir = join(opts.dshHome, 'profiles', opts.profile)
278
291
  const backupDir = join(opts.dshHome, 'argent', 'backup', `update-${stamp}`)
279
292
  mkdirSync(backupDir, { recursive: true })
280
- for (const f of ['package.json', 'cordis.patch.yml', 'pnpm-lock.yaml', 'pnpm-workspace.yaml']) {
293
+ for (const f of ['package.json', 'cordis.patch.yml', 'pnpm-lock.yaml', 'pnpm-workspace.yaml', 'argent.json']) {
281
294
  const src = join(profileDir, f)
282
295
  if (existsSync(src)) { mkdirSync(dirname(join(backupDir, f)), { recursive: true }); cpSync(src, join(backupDir, f)) }
283
296
  }
284
297
  const env = { NPM_CONFIG_REGISTRY: opts.registry }
298
+ let eng = null
285
299
  try {
286
300
  runCli(npx, installArgs, { env })
287
301
  // 不调用 setup:setup 会重建 profile,可能丢掉客户现有 bundles/依赖结构(2026-10-22 实测)。
288
302
  const prof = updateProfilePluginVersions({ dshHome: opts.dshHome, profile: opts.profile, versions: PUBLISHED_BUNDLE_VERSIONS })
289
303
  if (!prof.ok) throw new Error(`更新 profile 插件版本失败:${prof.message}`)
290
- runCli(opts.dshBin, ['plugin', '--profile', opts.profile, 'install'], { env: { ...env, DSH_HOME: opts.dshHome }, cwd: opts.dshHome })
304
+ // 方案 C · P3:runtime 换完必须重写契约文件(否则壳仍按旧 engineDir/版本读)
305
+ eng = writeEngineContract(profileDir, { repo: opts.repo, target: opts.target })
306
+ // ⚠️ dsh CLI **拒绝管理 desktop profile**("managed exclusively by the Electron application")
307
+ // ⇒ desktop 必须由宿主自己的 pnpm 装依赖(2026-11-02 实测:`dsh plugin --profile desktop install` 直接 exit 1)。
308
+ if (opts.profile === 'desktop') {
309
+ const ws = join(profileDir, 'pnpm-workspace.yaml')
310
+ if (!existsSync(ws)) {
311
+ writeFileSync(ws, 'packages:\n - .\nnodeLinker: hoisted\nautoInstallPeers: false\nallowBuilds:\n protobufjs: true\n')
312
+ }
313
+ const pnpm = resolvePnpm()
314
+ if (!pnpm) throw new Error('desktop profile 需要 pnpm:设置 ARGENT_PNPM,或确保 PATH 上有 pnpm')
315
+ runCli(pnpm.cmd, pnpm.args.concat(['install', '--reporter=append-only']), { cwd: profileDir, env })
316
+ } else {
317
+ runCli(opts.dshBin, ['plugin', '--profile', opts.profile, 'install'], { env: { ...env, DSH_HOME: opts.dshHome }, cwd: opts.dshHome })
318
+ }
291
319
  runCli(npx, healthArgs, { env })
292
320
  } catch (e) {
293
321
  console.error(`❌ 更新失败:${e.message}`)
@@ -295,7 +323,11 @@ async function updateCommand(sub) {
295
323
  console.error(' 可分步重试:npx -y @whyshu/argent@' + targetVersion + ' runtime install,再按 dsh plugin install 重试;必要时用备份恢复 package.json/patch。')
296
324
  process.exit(2)
297
325
  }
298
- print({ ok: true, from: current, to: targetVersion, runtimeTarget: opts.target, profile: profileDir, backupDir, restartRequired: true, next: '空闲窗口重启 dsh web(service install/launchd 或现有重启脚本)' })
326
+ print({
327
+ ok: true, from: current, to: targetVersion, runtimeTarget: opts.target, profile: profileDir, backupDir,
328
+ contract: { engineDir: eng.engineDir, engineVersion: eng.engineVersion, contract: eng.contract, source: eng.source },
329
+ restartRequired: true, next: '空闲窗口重启 dsh web(service install/launchd 或现有重启脚本)',
330
+ })
299
331
  }
300
332
 
301
333
  function serviceCommand(sub) {
package/lib/index.mjs CHANGED
@@ -31,6 +31,11 @@ export const PROD_BUNDLES = [
31
31
  ];
32
32
  export const RUNNER_LABEL = 'com.whyshu.argent-runner';
33
33
 
34
+ /** 启动器自身版本(写进 argent.json 的 writtenBy,便于事后追溯是谁写的契约) */
35
+ export const LAUNCHER_VERSION = (() => {
36
+ try { return JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8')).version } catch { return '' }
37
+ })();
38
+
34
39
  /** 仓库根:launcher/lib → ../../(本地联调);发布包内由 ARGENT_REPO 覆盖 */
35
40
  export function repoRoot({ repo } = {}) {
36
41
  if (repo) return resolve(repo);
@@ -73,6 +78,8 @@ export function pluginDirs({ repo = repoRoot() } = {}) {
73
78
 
74
79
  /** 已发布插件的版本(发布模式装配 profile 用;与 npm 上实际可见版本对齐) */
75
80
  export const PUBLISHED_BUNDLE_VERSIONS = {
81
+ // ⚠️ 每个包**只允许出现一次**(2026-11-02 修:argent-desktop 曾同时写 0.2.2/0.2.3,
82
+ // 后者静默覆盖前者 ⇒ 改上面那行的人会以为生效了)
76
83
  '@whyshu/argent-presets': '0.1.2',
77
84
  '@whyshu/argent-core': '0.1.6',
78
85
  '@whyshu/argent-dsh': '0.1.10',
@@ -80,10 +87,9 @@ export const PUBLISHED_BUNDLE_VERSIONS = {
80
87
  '@whyshu/argent-gateway': '0.2.3',
81
88
  '@whyshu/argent-flow': '0.1.1',
82
89
  '@whyshu/argent-ml': '0.1.1',
83
- '@whyshu/argent-desktop': '0.2.2',
84
90
  '@michengai/dsh-im-connect': '1.0.2',
85
91
  '@linxin666/dsh-client-ui-task-board': '0.4.7',
86
- '@whyshu/argent-desktop': '0.2.3',
92
+ '@whyshu/argent-desktop': '0.2.4',
87
93
  };
88
94
 
89
95
 
@@ -125,6 +131,57 @@ export function engineRoot({ env = process.env, launcherLib = dirname(fileURLToP
125
131
  return '';
126
132
  }
127
133
 
134
+ /**
135
+ * 引擎定位(方案 C 契约的**写入侧**)。
136
+ *
137
+ * 判据与 engineRoot 一致但不猜:`<dir>/VERSION` + `<dir>/core/lib/argent-datadir.mjs`。
138
+ * 优先级:本地仓库(开发联调)→ npm 包 `engine/` → 已物化 runtime(~/.argent-dsh)。
139
+ * 找不到就返回 ok:false —— 由调用方决定写空值还是报错,⛔ 不要在这里编一个路径。
140
+ */
141
+ export function engineInfo({ repo = repoRoot(), target = process.env.ARGENT_RUNTIME_TARGET, env = process.env } = {}) {
142
+ const valid = (d) => Boolean(d) && existsSync(join(d, 'VERSION')) && existsSync(join(d, 'core', 'lib', 'argent-datadir.mjs'));
143
+ const cands = [];
144
+ // ① 显式 runtime 目标(update run --target):客户机上**唯一稳定**的引擎位置
145
+ if (valid(target)) cands.push({ dir: target, source: 'target' });
146
+ // ② 本地仓库(开发联调):必须真的是 git 检出,⛔ 不要把 npx 缓存里的包当"仓库"
147
+ if (valid(repo) && existsSync(join(repo, '.git'))) cands.push({ dir: repo, source: 'repo' });
148
+ // ③ 已物化 runtime(默认 ~/.argent-dsh)
149
+ const runtime = join(homedir(), '.argent-dsh');
150
+ if (valid(runtime)) cands.push({ dir: runtime, source: 'runtime' });
151
+ // ④ 最后才是 npm 包(npx 缓存路径,随时会被清 —— 只作兜底)
152
+ const pkg = engineRoot({ env });
153
+ if (valid(pkg)) cands.push({ dir: pkg, source: 'package' });
154
+ for (const c of cands) {
155
+ let version = '';
156
+ try { version = readFileSync(join(c.dir, 'VERSION'), 'utf8').trim(); } catch { /* 保留空串 */ }
157
+ return { ok: true, dir: c.dir, source: c.source, version };
158
+ }
159
+ return { ok: false, dir: '', source: 'none', version: '' };
160
+ }
161
+
162
+ /** 读引擎契约版本(唯一真源 = `<engine>/core/lib/contract.mjs`;读不到返回 null,⛔ 不猜) */
163
+ export function engineContract(dir) {
164
+ try {
165
+ const m = readFileSync(join(dir, 'core', 'lib', 'contract.mjs'), 'utf8').match(/export const CONTRACT\s*=\s*(\d+)/);
166
+ return m ? Number(m[1]) : null;
167
+ } catch { return null; }
168
+ }
169
+
170
+ /** 写 `<profile>/argent.json`:宿主壳只读这个文件,不再猜引擎在哪(方案 C · P1) */
171
+ export function writeEngineContract(dir, { repo = repoRoot(), target = process.env.ARGENT_RUNTIME_TARGET, env = process.env } = {}) {
172
+ const info = engineInfo({ repo, target, env });
173
+ const payload = {
174
+ engineDir: info.ok ? info.dir : '',
175
+ engineVersion: info.ok ? info.version : '',
176
+ contract: info.ok ? engineContract(info.dir) : null,
177
+ source: info.source,
178
+ writtenBy: LAUNCHER_VERSION ? `@whyshu/argent@${LAUNCHER_VERSION}` : '@whyshu/argent',
179
+ writtenAt: new Date().toISOString(),
180
+ };
181
+ writeFileSync(join(dir, 'argent.json'), JSON.stringify(payload, null, 2) + '\n');
182
+ return payload;
183
+ }
184
+
128
185
  function walkFiles(dir, base = dir) {
129
186
  const out = [];
130
187
  for (const e of readdirSync(dir)) {
@@ -205,6 +262,8 @@ export function buildProfile({ dshHome = dshHomeOf(), profile = PROFILE_NAME, re
205
262
  dsh: { profile: { bundles } },
206
263
  };
207
264
  writeFileSync(join(dir, 'package.json'), JSON.stringify(pkg, null, 2) + '\n');
265
+ // 方案 C · P1:把"引擎在哪、什么版本、契约几"写成机器可读的契约文件(宿主壳只读它)
266
+ const engine = writeEngineContract(dir, { repo });
208
267
  const patchLines = [
209
268
  '# @whyshu/argent 生成(幂等;手工改动会在此文件重生成时被覆盖)',
210
269
  '- id: agent-preset-registry',
@@ -218,7 +277,7 @@ export function buildProfile({ dshHome = dshHomeOf(), profile = PROFILE_NAME, re
218
277
  writeFileSync(join(dir, 'cordis.patch.yml'), patch);
219
278
  // pnpm 配置必须与现有 web profile 同口径:否则 pnpm 会尝试从 registry 自动装 peer(含未发布版本)⇒ 安装失败
220
279
  writeFileSync(join(dir, 'pnpm-workspace.yaml'), 'packages:\n - .\nnodeLinker: hoisted\nautoInstallPeers: false\nallowBuilds:\n protobufjs: true\n');
221
- return { ok: true, dir, plugins: Object.keys(plugins), bundles: [...bundles], preset: DEFAULT_PRESET };
280
+ return { ok: true, dir, plugins: Object.keys(plugins), bundles: [...bundles], preset: DEFAULT_PRESET, engine };
222
281
  }
223
282
 
224
283
  export function depsInstalled(dshHome = dshHomeOf(), profile = PROFILE_NAME) {
@@ -316,6 +375,36 @@ export function buildLaunchdPlist({ dshHome = dshHomeOf(), repo = repoRoot(), no
316
375
  }
317
376
 
318
377
  /** 状态自检(不启动任何东西;JSON 友好) */
378
+ /**
379
+ * 两构件对齐检查(方案 C · P3):`<profile>/argent.json` ⇄ 实际引擎 ⇄ 插件声明。
380
+ * ⛔ 只报告,不修;`problems` 为空才算对齐。
381
+ */
382
+ export function contractStatus({ dshHome = dshHomeOf(), profile = PROFILE_NAME, repo = repoRoot() } = {}) {
383
+ const dir = profileDir(dshHome, profile);
384
+ const file = join(dir, 'argent.json');
385
+ let contract = null;
386
+ try { contract = JSON.parse(readFileSync(file, 'utf8')); } catch { /* 缺文件/坏 JSON 都算未建立 */ }
387
+ const info = engineInfo({ repo });
388
+ const expected = info.ok ? engineContract(info.dir) : null;
389
+ let plugin = null;
390
+ try {
391
+ const pkg = JSON.parse(readFileSync(join(dir, 'node_modules', '@whyshu', 'argent-desktop', 'package.json'), 'utf8'));
392
+ plugin = { version: pkg.version || '', requires: pkg.argentEngine || '', contract: pkg.argentContract ?? null };
393
+ } catch { /* 未装插件(web profile)属正常 */ }
394
+ const problems = [];
395
+ if (!contract) problems.push(`缺少 ${file}(运行 argent setup 生成)`);
396
+ else {
397
+ if (contract.contract == null) problems.push('argent.json 缺 contract');
398
+ else if (expected !== null && contract.contract !== expected) problems.push(`契约不符:argent.json=${contract.contract},引擎=${expected}`);
399
+ if (!contract.engineDir) problems.push('argent.json 未指向 engineDir');
400
+ else if (info.ok && contract.engineVersion !== info.version) problems.push(`引擎版本漂移:argent.json=${contract.engineVersion},实际=${info.version}`);
401
+ }
402
+ if (plugin && plugin.contract != null && expected !== null && plugin.contract !== expected) {
403
+ problems.push(`插件要求契约 ${plugin.contract},引擎提供 ${expected}`);
404
+ }
405
+ return { ok: problems.length === 0, file, contract, engine: info, plugin, expectedContract: expected, problems };
406
+ }
407
+
319
408
  export function health({ dshHome = dshHomeOf(), profile = PROFILE_NAME, repo = repoRoot(), dshBin = 'dsh', port = 3080 } = {}) {
320
409
  const version = probeDshVersion({ dshBin });
321
410
  const dir = profileDir(dshHome, profile);
@@ -330,6 +419,7 @@ export function health({ dshHome = dshHomeOf(), profile = PROFILE_NAME, repo = r
330
419
  profile: { dir, packageJson: existsSync(profilePackage), patch: existsSync(profilePatch) },
331
420
  runner: { configPath: runnerCfg, configPresent: existsSync(runnerCfg), mode: runnerCfgMode },
332
421
  webUrl: `http://127.0.0.1:${port}`,
422
+ contract: contractStatus({ dshHome, profile, repo }),
333
423
  repo,
334
424
  };
335
425
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whyshu/argent",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "Argent 启动器:一条命令启动「打包好的 dsh + Argent 插件/角色预设」,并管理本机 runner 常驻。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@deepseek-ai/dsh": "0.2.1-rc.2",
18
- "@whyshu/argent-engine": "0.8.0-rc.8"
18
+ "@whyshu/argent-engine": "0.8.0-rc.9"
19
19
  },
20
20
  "files": [
21
21
  "bin/",