Skip to content

Commit 3fa2505

Browse files
committed
fix: keep Grok registration on no-cli workspace install
1 parent 27a302d commit 3fa2505

3 files changed

Lines changed: 46 additions & 2 deletions

File tree

changelogs/unreleased.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ WorkspaceDataAbsorptionScopeGate · DocsSiteVisualAcceptanceGate · OmissionOnly
5757
- 拆分包开发与 Claude 安装态 MCP 契约:源码根 `.mcp.json` 改为指向实际存在的包内 `mcp/*`,CLI 仍为业务项目生成 `.claude/mcp/*`,避免 Grok 在源码仓发现通用 manifest 时启动不存在路径。
5858
- 补齐 `uninstall --host grok` 生命周期:复用 `HostAdapterScopeV1`,官方卸载用户插件并只移除 DevCodex 受管配置,保留 workspace source、未知键/注释与重复卸载幂等性。
5959
- 关闭诊断与 launcher 的作用域旁路:从子项目执行 `status/doctor` 也检查工作区 owner;`devcodex grok` 先消费官方 `--cwd`,拒绝 system-prompt override/重复 cwd,并在 root kernel 缺失、nested workspace 或 Windows 路径大小写变体下保持 fail-closed/同一身份。
60+
- 修复无 Grok CLI 环境下 workspace plugin 安装降级路径:仍写入 canonical `plugins.enabled` registration 与 migration receipt,但 installation 保持 `unavailable`,避免 CI/Linux 无 CLI 时 status/doctor 与 host-installation 契约漂移。
6061

6162
### 内部完整交付与用户可见输出契约(2026-07-19)
6263

scripts/lib/host-adapter-scope.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,16 +585,33 @@ function syncGrokWorkspacePluginInstallation({
585585

586586
const probe = spawnSync('grok', ['version'], { encoding: 'utf8', windowsHide: true, env })
587587
if (probe.error?.code === 'ENOENT') {
588-
return {
588+
const configBase = configSnapshot.existed
589+
? configSnapshot.content
590+
: (fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '')
591+
const merge = mergeGrokPluginRegistration(configBase, canonical, { legacyPluginPaths: legacy })
592+
if (merge.changed || !configSnapshot.existed) writeTextAtomic(configPath, merge.desired)
593+
const configAfter = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : ''
594+
const receipt = {
589595
schemaVersion: 'GrokWorkspacePluginMigrationReceiptV1',
590596
status: 'unavailable',
591597
reason: 'grok-cli-not-found-legacy-source-retained',
592598
pluginPath: portable(canonical),
593599
legacyPluginPaths: legacy.map(portable),
594600
legacySources: legacySources.map(portable),
595601
backupPaths: [],
602+
configPath,
603+
configBeforeDigest: contentDigest(configSnapshot.content),
604+
configAfterDigest: contentDigest(configAfter),
596605
dryRun: false
597606
}
607+
if (activeRoot) {
608+
const receiptFile = path.join(activeRoot, 'managed', 'grok-plugin-migration.json')
609+
writeTextAtomic(receiptFile, JSON.stringify({ ...receipt, recordedAt: new Date().toISOString() }, null, 2) + '\n')
610+
receipt.receiptFile = receiptFile
611+
}
612+
return {
613+
...receipt
614+
}
598615
}
599616
if (probe.status !== 0) {
600617
const error = new Error(`GROK_PLUGIN_CLI_UNAVAILABLE: ${String(probe.stderr || probe.stdout).trim()}`)

scripts/test-host-installation.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ const path = require('path')
88
const { spawnSync } = require('child_process')
99
const { buildDeploymentDescriptors } = require('./lib/deployment-descriptors')
1010
const { buildCliHostUtils } = require('./lib/cli-host-utils')
11-
const { mergeGrokPluginRegistration, removeGrokPluginRegistration } = require('./lib/host-adapter-scope')
11+
const {
12+
mergeGrokPluginRegistration,
13+
removeGrokPluginRegistration,
14+
syncGrokWorkspacePluginInstallation
15+
} = require('./lib/host-adapter-scope')
1216
const { buildGrokLaunchPlan } = require('./lib/grok-workspace-launcher')
1317

1418
const ROOT = path.resolve(__dirname, '..')
@@ -122,6 +126,28 @@ assert.throws(
122126
'an explicit user disable must fail closed without being overwritten'
123127
)
124128

129+
const noCliWorkspace = path.join(FIXTURE_ROOT, 'no-cli-workspace')
130+
const noCliActiveRoot = path.join(noCliWorkspace, '.devcodex', 'workspace')
131+
const noCliGrokHome = path.join(FIXTURE_ROOT, 'no-cli-grok-home')
132+
const noCliPlugin = path.join(noCliWorkspace, '.grok', 'devcodex', 'plugins', 'devcodex-workspace')
133+
const noCliLegacy = path.join(noCliWorkspace, '.grok', 'plugins', 'devcodex-workspace')
134+
fs.mkdirSync(noCliPlugin, { recursive: true })
135+
fs.mkdirSync(noCliLegacy, { recursive: true })
136+
fs.mkdirSync(noCliGrokHome, { recursive: true })
137+
fs.writeFileSync(path.join(noCliPlugin, 'plugin.json'), '{"name":"devcodex-workspace"}\n', 'utf8')
138+
fs.writeFileSync(path.join(noCliLegacy, 'plugin.json'), '{"name":"devcodex-workspace-legacy"}\n', 'utf8')
139+
fs.writeFileSync(path.join(noCliGrokHome, 'config.toml'), '[plugins]\nenabled = ["project-owned"]\n', 'utf8')
140+
const noCliReceipt = syncGrokWorkspacePluginInstallation({
141+
pluginPath: noCliPlugin,
142+
legacyPluginPaths: [noCliLegacy],
143+
activeRoot: noCliActiveRoot,
144+
env: { ...process.env, GROK_HOME: noCliGrokHome, PATH: '' }
145+
})
146+
assert.strictEqual(noCliReceipt.status, 'unavailable')
147+
assert.match(fs.readFileSync(path.join(noCliGrokHome, 'config.toml'), 'utf8'), /enabled = \["project-owned", "devcodex-workspace"\]/)
148+
assert(fs.existsSync(noCliLegacy), 'no-CLI migration must retain the legacy source')
149+
assert(fs.existsSync(path.join(noCliActiveRoot, 'managed', 'grok-plugin-migration.json')), 'no-CLI migration receipt must be recorded')
150+
125151
const hostUtils = buildCliHostUtils({
126152
fs,
127153
path,

0 commit comments

Comments
 (0)