@whyshu/argent 0.1.21 → 0.1.23

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);
@@ -277,7 +277,7 @@ async function updateCommand(sub) {
277
277
  const profileDir = join(opts.dshHome, 'profiles', opts.profile)
278
278
  const backupDir = join(opts.dshHome, 'argent', 'backup', `update-${stamp}`)
279
279
  mkdirSync(backupDir, { recursive: true })
280
- for (const f of ['package.json', 'cordis.patch.yml', 'pnpm-lock.yaml', 'pnpm-workspace.yaml']) {
280
+ for (const f of ['package.json', 'cordis.patch.yml', 'pnpm-lock.yaml', 'pnpm-workspace.yaml', 'argent.json']) {
281
281
  const src = join(profileDir, f)
282
282
  if (existsSync(src)) { mkdirSync(dirname(join(backupDir, f)), { recursive: true }); cpSync(src, join(backupDir, f)) }
283
283
  }
@@ -287,6 +287,8 @@ async function updateCommand(sub) {
287
287
  // 不调用 setup:setup 会重建 profile,可能丢掉客户现有 bundles/依赖结构(2026-10-22 实测)。
288
288
  const prof = updateProfilePluginVersions({ dshHome: opts.dshHome, profile: opts.profile, versions: PUBLISHED_BUNDLE_VERSIONS })
289
289
  if (!prof.ok) throw new Error(`更新 profile 插件版本失败:${prof.message}`)
290
+ // 方案 C · P3:runtime 换完必须重写契约文件(否则壳仍按旧 engineDir/版本读)
291
+ const eng = writeEngineContract(profileDir, { repo: opts.repo })
290
292
  runCli(opts.dshBin, ['plugin', '--profile', opts.profile, 'install'], { env: { ...env, DSH_HOME: opts.dshHome }, cwd: opts.dshHome })
291
293
  runCli(npx, healthArgs, { env })
292
294
  } catch (e) {
@@ -295,7 +297,11 @@ async function updateCommand(sub) {
295
297
  console.error(' 可分步重试:npx -y @whyshu/argent@' + targetVersion + ' runtime install,再按 dsh plugin install 重试;必要时用备份恢复 package.json/patch。')
296
298
  process.exit(2)
297
299
  }
298
- print({ ok: true, from: current, to: targetVersion, runtimeTarget: opts.target, profile: profileDir, backupDir, restartRequired: true, next: '空闲窗口重启 dsh web(service install/launchd 或现有重启脚本)' })
300
+ print({
301
+ ok: true, from: current, to: targetVersion, runtimeTarget: opts.target, profile: profileDir, backupDir,
302
+ contract: { engineDir: eng.engineDir, engineVersion: eng.engineVersion, contract: eng.contract, source: eng.source },
303
+ restartRequired: true, next: '空闲窗口重启 dsh web(service install/launchd 或现有重启脚本)',
304
+ })
299
305
  }
300
306
 
301
307
  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,52 @@ 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(), 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
+ if (valid(repo)) cands.push({ dir: repo, source: 'repo' });
145
+ const pkg = engineRoot({ env });
146
+ if (valid(pkg)) cands.push({ dir: pkg, source: 'package' });
147
+ const runtime = env.ARGENT_RUNTIME_TARGET || join(homedir(), '.argent-dsh');
148
+ if (valid(runtime)) cands.push({ dir: runtime, source: 'runtime' });
149
+ for (const c of cands) {
150
+ let version = '';
151
+ try { version = readFileSync(join(c.dir, 'VERSION'), 'utf8').trim(); } catch { /* 保留空串 */ }
152
+ return { ok: true, dir: c.dir, source: c.source, version };
153
+ }
154
+ return { ok: false, dir: '', source: 'none', version: '' };
155
+ }
156
+
157
+ /** 读引擎契约版本(唯一真源 = `<engine>/core/lib/contract.mjs`;读不到返回 null,⛔ 不猜) */
158
+ export function engineContract(dir) {
159
+ try {
160
+ const m = readFileSync(join(dir, 'core', 'lib', 'contract.mjs'), 'utf8').match(/export const CONTRACT\s*=\s*(\d+)/);
161
+ return m ? Number(m[1]) : null;
162
+ } catch { return null; }
163
+ }
164
+
165
+ /** 写 `<profile>/argent.json`:宿主壳只读这个文件,不再猜引擎在哪(方案 C · P1) */
166
+ export function writeEngineContract(dir, { repo = repoRoot(), env = process.env } = {}) {
167
+ const info = engineInfo({ repo, env });
168
+ const payload = {
169
+ engineDir: info.ok ? info.dir : '',
170
+ engineVersion: info.ok ? info.version : '',
171
+ contract: info.ok ? engineContract(info.dir) : null,
172
+ source: info.source,
173
+ writtenBy: LAUNCHER_VERSION ? `@whyshu/argent@${LAUNCHER_VERSION}` : '@whyshu/argent',
174
+ writtenAt: new Date().toISOString(),
175
+ };
176
+ writeFileSync(join(dir, 'argent.json'), JSON.stringify(payload, null, 2) + '\n');
177
+ return payload;
178
+ }
179
+
128
180
  function walkFiles(dir, base = dir) {
129
181
  const out = [];
130
182
  for (const e of readdirSync(dir)) {
@@ -205,6 +257,8 @@ export function buildProfile({ dshHome = dshHomeOf(), profile = PROFILE_NAME, re
205
257
  dsh: { profile: { bundles } },
206
258
  };
207
259
  writeFileSync(join(dir, 'package.json'), JSON.stringify(pkg, null, 2) + '\n');
260
+ // 方案 C · P1:把"引擎在哪、什么版本、契约几"写成机器可读的契约文件(宿主壳只读它)
261
+ const engine = writeEngineContract(dir, { repo });
208
262
  const patchLines = [
209
263
  '# @whyshu/argent 生成(幂等;手工改动会在此文件重生成时被覆盖)',
210
264
  '- id: agent-preset-registry',
@@ -218,7 +272,7 @@ export function buildProfile({ dshHome = dshHomeOf(), profile = PROFILE_NAME, re
218
272
  writeFileSync(join(dir, 'cordis.patch.yml'), patch);
219
273
  // pnpm 配置必须与现有 web profile 同口径:否则 pnpm 会尝试从 registry 自动装 peer(含未发布版本)⇒ 安装失败
220
274
  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 };
275
+ return { ok: true, dir, plugins: Object.keys(plugins), bundles: [...bundles], preset: DEFAULT_PRESET, engine };
222
276
  }
223
277
 
224
278
  export function depsInstalled(dshHome = dshHomeOf(), profile = PROFILE_NAME) {
@@ -316,6 +370,36 @@ export function buildLaunchdPlist({ dshHome = dshHomeOf(), repo = repoRoot(), no
316
370
  }
317
371
 
318
372
  /** 状态自检(不启动任何东西;JSON 友好) */
373
+ /**
374
+ * 两构件对齐检查(方案 C · P3):`<profile>/argent.json` ⇄ 实际引擎 ⇄ 插件声明。
375
+ * ⛔ 只报告,不修;`problems` 为空才算对齐。
376
+ */
377
+ export function contractStatus({ dshHome = dshHomeOf(), profile = PROFILE_NAME, repo = repoRoot() } = {}) {
378
+ const dir = profileDir(dshHome, profile);
379
+ const file = join(dir, 'argent.json');
380
+ let contract = null;
381
+ try { contract = JSON.parse(readFileSync(file, 'utf8')); } catch { /* 缺文件/坏 JSON 都算未建立 */ }
382
+ const info = engineInfo({ repo });
383
+ const expected = info.ok ? engineContract(info.dir) : null;
384
+ let plugin = null;
385
+ try {
386
+ const pkg = JSON.parse(readFileSync(join(dir, 'node_modules', '@whyshu', 'argent-desktop', 'package.json'), 'utf8'));
387
+ plugin = { version: pkg.version || '', requires: pkg.argentEngine || '', contract: pkg.argentContract ?? null };
388
+ } catch { /* 未装插件(web profile)属正常 */ }
389
+ const problems = [];
390
+ if (!contract) problems.push(`缺少 ${file}(运行 argent setup 生成)`);
391
+ else {
392
+ if (contract.contract == null) problems.push('argent.json 缺 contract');
393
+ else if (expected !== null && contract.contract !== expected) problems.push(`契约不符:argent.json=${contract.contract},引擎=${expected}`);
394
+ if (!contract.engineDir) problems.push('argent.json 未指向 engineDir');
395
+ else if (info.ok && contract.engineVersion !== info.version) problems.push(`引擎版本漂移:argent.json=${contract.engineVersion},实际=${info.version}`);
396
+ }
397
+ if (plugin && plugin.contract != null && expected !== null && plugin.contract !== expected) {
398
+ problems.push(`插件要求契约 ${plugin.contract},引擎提供 ${expected}`);
399
+ }
400
+ return { ok: problems.length === 0, file, contract, engine: info, plugin, expectedContract: expected, problems };
401
+ }
402
+
319
403
  export function health({ dshHome = dshHomeOf(), profile = PROFILE_NAME, repo = repoRoot(), dshBin = 'dsh', port = 3080 } = {}) {
320
404
  const version = probeDshVersion({ dshBin });
321
405
  const dir = profileDir(dshHome, profile);
@@ -330,6 +414,7 @@ export function health({ dshHome = dshHomeOf(), profile = PROFILE_NAME, repo = r
330
414
  profile: { dir, packageJson: existsSync(profilePackage), patch: existsSync(profilePatch) },
331
415
  runner: { configPath: runnerCfg, configPresent: existsSync(runnerCfg), mode: runnerCfgMode },
332
416
  webUrl: `http://127.0.0.1:${port}`,
417
+ contract: contractStatus({ dshHome, profile, repo }),
333
418
  repo,
334
419
  };
335
420
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whyshu/argent",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
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.7"
18
+ "@whyshu/argent-engine": "0.8.0-rc.9"
19
19
  },
20
20
  "files": [
21
21
  "bin/",