helloagents 3.0.18 → 3.0.19
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +17 -9
- package/README_CN.md +17 -9
- package/bootstrap-lite.md +11 -11
- package/bootstrap.md +12 -15
- package/cli.mjs +2 -0
- package/gemini-extension.json +1 -1
- package/hooks/hooks-codex.json +40 -0
- package/package.json +1 -1
- package/scripts/cli-codex-config.mjs +143 -0
- package/scripts/cli-codex-goals.mjs +114 -0
- package/scripts/cli-codex.mjs +42 -0
- package/scripts/cli-doctor-codex.mjs +208 -0
- package/scripts/cli-doctor.mjs +3 -112
- package/scripts/cli-messages.mjs +5 -0
- package/scripts/notify-payload.mjs +58 -0
- package/scripts/notify.mjs +60 -14
- package/scripts/runtime-artifacts.mjs +20 -4
- package/scripts/runtime-context.mjs +60 -2
- package/scripts/runtime-scope.mjs +2 -1
- package/scripts/runtime-ttl.mjs +7 -0
- package/scripts/session-token.mjs +1 -0
- package/scripts/turn-state.mjs +1 -1
- package/scripts/workflow-plan-files.mjs +1 -1
- package/skills/commands/auto/SKILL.md +4 -2
- package/skills/commands/build/SKILL.md +3 -1
- package/skills/commands/loop/SKILL.md +3 -1
- package/skills/commands/plan/SKILL.md +4 -1
- package/skills/commands/prd/SKILL.md +1 -0
- package/skills/commands/test/SKILL.md +2 -1
- package/skills/commands/verify/SKILL.md +3 -1
- package/skills/helloagents/SKILL.md +1 -1
- package/templates/plans/tasks.md +3 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync } from 'node:fs'
|
|
4
|
+
import { homedir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
|
|
7
|
+
import { ensureTimestampedBackup } from './cli-codex-backup.mjs'
|
|
8
|
+
import {
|
|
9
|
+
CODEX_GOALS_FEATURE_KEY,
|
|
10
|
+
CODEX_MANAGED_TOML_COMMENT,
|
|
11
|
+
isManagedCodexGoalsFeature,
|
|
12
|
+
readCodexGoalsFeatureLine,
|
|
13
|
+
removeCodexGoalsFeatureConfig,
|
|
14
|
+
setCodexGoalsFeatureConfig,
|
|
15
|
+
} from './cli-codex-config.mjs'
|
|
16
|
+
import { ensureDir, safeRead, safeWrite } from './cli-utils.mjs'
|
|
17
|
+
|
|
18
|
+
const CODEX_CONFIG_BASENAME = 'config.toml'
|
|
19
|
+
|
|
20
|
+
function isJsonMode(args) {
|
|
21
|
+
return args.includes('--json')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readStatus(home) {
|
|
25
|
+
const configPath = join(home, '.codex', CODEX_CONFIG_BASENAME)
|
|
26
|
+
const toml = safeRead(configPath) || ''
|
|
27
|
+
const line = readCodexGoalsFeatureLine(toml)
|
|
28
|
+
const valueMatch = line.match(/^\s*goals\s*=\s*(true|false)\b/)
|
|
29
|
+
const configured = Boolean(valueMatch)
|
|
30
|
+
const enabled = valueMatch ? valueMatch[1] === 'true' : false
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
configPath,
|
|
34
|
+
configured,
|
|
35
|
+
enabled,
|
|
36
|
+
managed: isManagedCodexGoalsFeature(line),
|
|
37
|
+
line,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function printStatus(status, jsonMode) {
|
|
42
|
+
if (jsonMode) {
|
|
43
|
+
process.stdout.write(`${JSON.stringify({
|
|
44
|
+
feature: CODEX_GOALS_FEATURE_KEY,
|
|
45
|
+
enabled: status.enabled,
|
|
46
|
+
configured: status.configured,
|
|
47
|
+
managed: status.managed,
|
|
48
|
+
configPath: status.configPath,
|
|
49
|
+
}, null, 2)}\n`)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const state = status.enabled ? 'enabled' : 'disabled'
|
|
54
|
+
const source = status.configured ? (status.managed ? 'managed' : 'user') : 'default'
|
|
55
|
+
process.stdout.write(`Codex goals: ${state} (${source})\n`)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function updateGoals(home, enabled) {
|
|
59
|
+
const codexDir = join(home, '.codex')
|
|
60
|
+
const configPath = join(codexDir, CODEX_CONFIG_BASENAME)
|
|
61
|
+
ensureDir(codexDir)
|
|
62
|
+
ensureTimestampedBackup(configPath, CODEX_CONFIG_BASENAME)
|
|
63
|
+
|
|
64
|
+
const current = safeRead(configPath) || ''
|
|
65
|
+
const next = enabled
|
|
66
|
+
? setCodexGoalsFeatureConfig(current, true)
|
|
67
|
+
: removeCodexGoalsFeatureConfig(setCodexGoalsFeatureConfig(current, false))
|
|
68
|
+
|
|
69
|
+
if (next.trim()) safeWrite(configPath, next)
|
|
70
|
+
else safeWrite(configPath, '')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function printUsage() {
|
|
74
|
+
process.stdout.write([
|
|
75
|
+
'Usage: helloagents codex goals <status|enable|disable> [--json]',
|
|
76
|
+
'',
|
|
77
|
+
`This only manages [features].goals ${CODEX_MANAGED_TOML_COMMENT}.`,
|
|
78
|
+
].join('\n') + '\n')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function runCodexGoalsCli(args = process.argv.slice(2), { home = homedir() } = {}) {
|
|
82
|
+
const command = args.find((arg) => !arg.startsWith('--')) || 'status'
|
|
83
|
+
const jsonMode = isJsonMode(args)
|
|
84
|
+
|
|
85
|
+
if (!existsSync(join(home, '.codex')) && command === 'status') {
|
|
86
|
+
const status = {
|
|
87
|
+
configPath: join(home, '.codex', CODEX_CONFIG_BASENAME),
|
|
88
|
+
configured: false,
|
|
89
|
+
enabled: false,
|
|
90
|
+
managed: false,
|
|
91
|
+
line: '',
|
|
92
|
+
}
|
|
93
|
+
printStatus(status, jsonMode)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (command === 'status') {
|
|
98
|
+
printStatus(readStatus(home), jsonMode)
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (command === 'enable' || command === 'disable') {
|
|
103
|
+
updateGoals(home, command === 'enable')
|
|
104
|
+
printStatus(readStatus(home), jsonMode)
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
printUsage()
|
|
109
|
+
process.exitCode = 1
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (process.argv[1]?.endsWith('cli-codex-goals.mjs')) {
|
|
113
|
+
runCodexGoalsCli()
|
|
114
|
+
}
|
package/scripts/cli-codex.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
ensureDir, safeRead, safeWrite, removeIfExists,
|
|
5
5
|
readJsonOrThrow, copyEntries,
|
|
6
6
|
createLink, removeLink, injectMarkedContent, removeMarkedContent,
|
|
7
|
+
cleanSettingsHooks, loadHooksWithCliEntry, mergeSettingsHooks,
|
|
7
8
|
} from './cli-utils.mjs';
|
|
8
9
|
import { ensureTimestampedBackup, readCodexBackup, removeCodexBackup } from './cli-codex-backup.mjs';
|
|
9
10
|
import {
|
|
@@ -12,9 +13,16 @@ import {
|
|
|
12
13
|
CODEX_PLUGIN_CONFIG_HEADER,
|
|
13
14
|
installCodexManagedTopLevelConfig,
|
|
14
15
|
isManagedCodexBackupInstruction,
|
|
16
|
+
isManagedCodexGoalsFeature,
|
|
15
17
|
isManagedCodexModelInstruction,
|
|
16
18
|
isManagedCodexNotify,
|
|
19
|
+
isManagedLegacyCodexHooksFeature,
|
|
20
|
+
readCodexGoalsFeatureLine,
|
|
21
|
+
readLegacyCodexHooksFeatureLine,
|
|
22
|
+
removeCodexGoalsFeatureConfig,
|
|
23
|
+
removeLegacyManagedCodexHooksFeatureConfig,
|
|
17
24
|
removeCodexPluginConfig,
|
|
25
|
+
restoreCodexGoalsFeatureConfig,
|
|
18
26
|
restoreCodexTopLevelConfig,
|
|
19
27
|
upsertCodexPluginConfig,
|
|
20
28
|
} from './cli-codex-config.mjs';
|
|
@@ -31,6 +39,7 @@ export const CODEX_PLUGIN_KEY = `${CODEX_PLUGIN_NAME}@${CODEX_MARKETPLACE_NAME}`
|
|
|
31
39
|
export { CODEX_MANAGED_TOML_COMMENT, CODEX_PLUGIN_CONFIG_HEADER };
|
|
32
40
|
export const CODEX_RUNTIME_CARRIER = 'AGENTS.md';
|
|
33
41
|
const CODEX_CONFIG_BASENAME = 'config.toml';
|
|
42
|
+
const CODEX_HOOKS_BASENAME = 'hooks.json';
|
|
34
43
|
export const CODEX_RUNTIME_ENTRIES = [
|
|
35
44
|
'.codex-plugin',
|
|
36
45
|
'assets',
|
|
@@ -120,19 +129,40 @@ function writeCodexRuntimeCarrier(filePath, bootstrapPath, settings) {
|
|
|
120
129
|
return true;
|
|
121
130
|
}
|
|
122
131
|
|
|
132
|
+
function installCodexStandaloneHooks(home, pkgRoot) {
|
|
133
|
+
const hooksData = loadHooksWithCliEntry(pkgRoot, 'hooks-codex.json', '${PLUGIN_ROOT}');
|
|
134
|
+
if (!hooksData) return false;
|
|
135
|
+
mergeSettingsHooks(join(home, '.codex', CODEX_HOOKS_BASENAME), hooksData);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function cleanupCodexStandaloneHooks(home) {
|
|
140
|
+
cleanSettingsHooks(join(home, '.codex', CODEX_HOOKS_BASENAME));
|
|
141
|
+
}
|
|
142
|
+
|
|
123
143
|
function cleanupCodexManagedConfig(configPath, { removePluginConfig = false } = {}) {
|
|
124
144
|
const backupToml = readCodexBackup(configPath, CODEX_CONFIG_BASENAME);
|
|
125
145
|
let toml = safeRead(configPath) || '';
|
|
126
146
|
|
|
127
147
|
const currentModelInstructions = readTopLevelTomlLine(toml, 'model_instructions_file');
|
|
128
148
|
const currentNotify = readTopLevelTomlBlock(toml, 'notify');
|
|
149
|
+
const currentCodexGoalsFeature = readCodexGoalsFeatureLine(toml);
|
|
150
|
+
const currentLegacyCodexHooksFeature = readLegacyCodexHooksFeatureLine(toml);
|
|
129
151
|
|
|
130
152
|
const shouldRestoreModelInstructions = isManagedCodexModelInstruction(currentModelInstructions);
|
|
131
153
|
const shouldRestoreNotify = isManagedCodexNotify(currentNotify);
|
|
154
|
+
const shouldRestoreCodexGoalsFeature = isManagedCodexGoalsFeature(currentCodexGoalsFeature);
|
|
155
|
+
const shouldRemoveLegacyCodexHooksFeature = isManagedLegacyCodexHooksFeature(currentLegacyCodexHooksFeature);
|
|
132
156
|
|
|
133
157
|
if (removePluginConfig) {
|
|
134
158
|
toml = removeCodexPluginConfig(toml);
|
|
135
159
|
}
|
|
160
|
+
if (shouldRestoreCodexGoalsFeature) {
|
|
161
|
+
toml = removeCodexGoalsFeatureConfig(toml);
|
|
162
|
+
}
|
|
163
|
+
if (shouldRemoveLegacyCodexHooksFeature) {
|
|
164
|
+
toml = removeLegacyManagedCodexHooksFeatureConfig(toml);
|
|
165
|
+
}
|
|
136
166
|
if (shouldRestoreModelInstructions) {
|
|
137
167
|
toml = removeTopLevelTomlLines(toml, (line) =>
|
|
138
168
|
line.startsWith('model_instructions_file =') && isManagedCodexModelInstruction(line)).text;
|
|
@@ -144,6 +174,7 @@ function cleanupCodexManagedConfig(configPath, { removePluginConfig = false } =
|
|
|
144
174
|
|
|
145
175
|
const backupModelInstructions = readTopLevelTomlLine(backupToml, 'model_instructions_file');
|
|
146
176
|
const backupNotify = readTopLevelTomlBlock(backupToml, 'notify');
|
|
177
|
+
const backupCodexGoalsFeature = readCodexGoalsFeatureLine(backupToml);
|
|
147
178
|
|
|
148
179
|
toml = restoreCodexTopLevelConfig(toml, {
|
|
149
180
|
modelInstructionsLine: shouldRestoreModelInstructions && !isManagedCodexBackupInstruction(backupModelInstructions)
|
|
@@ -153,6 +184,11 @@ function cleanupCodexManagedConfig(configPath, { removePluginConfig = false } =
|
|
|
153
184
|
? backupNotify
|
|
154
185
|
: '',
|
|
155
186
|
});
|
|
187
|
+
toml = restoreCodexGoalsFeatureConfig(toml, {
|
|
188
|
+
codexGoalsLine: shouldRestoreCodexGoalsFeature && !isManagedCodexGoalsFeature(backupCodexGoalsFeature)
|
|
189
|
+
? backupCodexGoalsFeature
|
|
190
|
+
: '',
|
|
191
|
+
});
|
|
156
192
|
|
|
157
193
|
return toml;
|
|
158
194
|
}
|
|
@@ -173,7 +209,9 @@ export function installCodexStandby(home, pkgRoot) {
|
|
|
173
209
|
toml = installCodexManagedTopLevelConfig(toml, {
|
|
174
210
|
modelInstructionsPath: CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH,
|
|
175
211
|
});
|
|
212
|
+
toml = removeLegacyManagedCodexHooksFeatureConfig(toml);
|
|
176
213
|
safeWrite(configPath, toml);
|
|
214
|
+
installCodexStandaloneHooks(home, pkgRoot);
|
|
177
215
|
|
|
178
216
|
createLink(pkgRoot, join(codexDir, 'helloagents'));
|
|
179
217
|
return true;
|
|
@@ -210,6 +248,7 @@ export function uninstallCodexStandby(home) {
|
|
|
210
248
|
else removeIfExists(configPath);
|
|
211
249
|
changed = true;
|
|
212
250
|
removeCodexBackup(configPath, CODEX_CONFIG_BASENAME);
|
|
251
|
+
cleanupCodexStandaloneHooks(home);
|
|
213
252
|
removeLink(join(codexDir, 'helloagents'));
|
|
214
253
|
changed = true;
|
|
215
254
|
}
|
|
@@ -265,8 +304,10 @@ export function installCodexGlobal(home, pkgRoot) {
|
|
|
265
304
|
toml = installCodexManagedTopLevelConfig(toml, {
|
|
266
305
|
modelInstructionsPath: CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH,
|
|
267
306
|
});
|
|
307
|
+
toml = removeLegacyManagedCodexHooksFeatureConfig(toml);
|
|
268
308
|
toml = upsertCodexPluginConfig(toml);
|
|
269
309
|
safeWrite(configPath, toml);
|
|
310
|
+
installCodexStandaloneHooks(home, pkgRoot);
|
|
270
311
|
|
|
271
312
|
return true;
|
|
272
313
|
}
|
|
@@ -284,6 +325,7 @@ export function uninstallCodexGlobal(home) {
|
|
|
284
325
|
removeCodexMarketplaceEntry(marketplaceFile);
|
|
285
326
|
removeMarkedContent(join(codexDir, 'AGENTS.md'));
|
|
286
327
|
removeLink(join(codexDir, 'helloagents'));
|
|
328
|
+
cleanupCodexStandaloneHooks(home);
|
|
287
329
|
|
|
288
330
|
const toml = cleanupCodexManagedConfig(configPath, { removePluginConfig: true });
|
|
289
331
|
if (toml.trim()) safeWrite(configPath, toml);
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_CONFIG_HEADER, CODEX_PLUGIN_NAME } from './cli-codex.mjs'
|
|
5
|
+
import {
|
|
6
|
+
CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH,
|
|
7
|
+
CODEX_MANAGED_NOTIFY_VALUE,
|
|
8
|
+
readCodexGoalsFeatureLine,
|
|
9
|
+
readCodexHooksFeatureLine,
|
|
10
|
+
readLegacyCodexHooksFeatureLine,
|
|
11
|
+
} from './cli-codex-config.mjs'
|
|
12
|
+
import { buildRuntimeCarrier } from './cli-runtime-carrier.mjs'
|
|
13
|
+
import { readTopLevelTomlLine } from './cli-toml.mjs'
|
|
14
|
+
import { loadHooksWithCliEntry, safeJson, safeRead } from './cli-utils.mjs'
|
|
15
|
+
|
|
16
|
+
function safeRealTarget(linkPath) {
|
|
17
|
+
try {
|
|
18
|
+
return realpathSync(linkPath)
|
|
19
|
+
} catch {
|
|
20
|
+
return ''
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeText(text = '') {
|
|
25
|
+
return String(text || '').replace(/\r\n/g, '\n').trim()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizePath(value = '') {
|
|
29
|
+
return String(value || '').replace(/\\/g, '/')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function sortJson(value) {
|
|
33
|
+
if (Array.isArray(value)) return value.map(sortJson)
|
|
34
|
+
if (value && typeof value === 'object') {
|
|
35
|
+
return Object.keys(value).sort().reduce((acc, key) => {
|
|
36
|
+
acc[key] = sortJson(value[key])
|
|
37
|
+
return acc
|
|
38
|
+
}, {})
|
|
39
|
+
}
|
|
40
|
+
return value
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function pickManagedHooks(hooks) {
|
|
44
|
+
const next = {}
|
|
45
|
+
for (const [event, entries] of Object.entries(hooks || {})) {
|
|
46
|
+
if (!Array.isArray(entries)) continue
|
|
47
|
+
const managedEntries = entries.filter((entry) => JSON.stringify(entry).includes('helloagents'))
|
|
48
|
+
if (managedEntries.length > 0) next[event] = managedEntries
|
|
49
|
+
}
|
|
50
|
+
return next
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function managedHooksMatch(actualHooks, expectedHooks) {
|
|
54
|
+
return JSON.stringify(sortJson(pickManagedHooks(actualHooks || {})))
|
|
55
|
+
=== JSON.stringify(sortJson(expectedHooks || {}))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readExpectedHooks(runtime, hooksFile, pathVar) {
|
|
59
|
+
return pickManagedHooks(loadHooksWithCliEntry(runtime.pkgRoot, hooksFile, pathVar)?.hooks || {})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function readExpectedCarrierContent(runtime, fileName, settings) {
|
|
63
|
+
const bootstrap = safeRead(join(runtime.pkgRoot, fileName)) || ''
|
|
64
|
+
return normalizeText(buildRuntimeCarrier(bootstrap, settings))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildDoctorIssue(runtime, code, cn, en) {
|
|
68
|
+
return {
|
|
69
|
+
code,
|
|
70
|
+
message: runtime.msg(cn, en),
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function normalizeDoctorMode(mode = '') {
|
|
75
|
+
return mode || 'none'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function summarizeDoctorStatus(issues, { trackedMode, detectedMode } = {}) {
|
|
79
|
+
if (issues.length > 0) return 'drift'
|
|
80
|
+
if (detectedMode !== 'none') return 'ok'
|
|
81
|
+
if (trackedMode !== 'none') return 'drift'
|
|
82
|
+
return 'not-installed'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function suggestCodexDoctorFix(status, trackedMode) {
|
|
86
|
+
if (status === 'drift') {
|
|
87
|
+
return `helloagents update codex${trackedMode && trackedMode !== 'none' ? ` --${trackedMode}` : ''}`
|
|
88
|
+
}
|
|
89
|
+
if (status === 'not-installed') return 'helloagents install codex --standby'
|
|
90
|
+
return ''
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function appendCodexStandbyIssues(runtime, issues, checks) {
|
|
94
|
+
if (!checks.carrierMarker) issues.push(buildDoctorIssue(runtime, 'standby-carrier-missing', 'standby `~/.codex/AGENTS.md` 缺少 HelloAGENTS 标记', 'Standby `~/.codex/AGENTS.md` is missing the HELLOAGENTS marker'))
|
|
95
|
+
if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue(runtime, 'standby-carrier-drift', 'standby `~/.codex/AGENTS.md` 与当前 bootstrap-lite.md 不一致', 'Standby `~/.codex/AGENTS.md` differs from the current bootstrap-lite.md'))
|
|
96
|
+
if (!checks.homeLink) issues.push(buildDoctorIssue(runtime, 'standby-link-missing', 'standby `~/.codex/helloagents` 链接缺失或未指向稳定运行根目录', 'Standby `~/.codex/helloagents` link is missing or points to a different runtime root'))
|
|
97
|
+
if (!checks.modelInstructionsFile) issues.push(buildDoctorIssue(runtime, 'standby-model-instructions-missing', 'standby config 缺少受管 model_instructions_file', 'Standby config is missing the managed model_instructions_file'))
|
|
98
|
+
if (checks.modelInstructionsFile && !checks.modelInstructionsPathMatch) issues.push(buildDoctorIssue(runtime, 'standby-model-instructions-drift', 'standby model_instructions_file 未指向受管 `~/.codex/AGENTS.md`', 'Standby model_instructions_file does not point to the managed `~/.codex/AGENTS.md`'))
|
|
99
|
+
if (!checks.codexNotify) issues.push(buildDoctorIssue(runtime, 'standby-notify-missing', 'standby notify 配置缺失', 'Standby notify configuration is missing'))
|
|
100
|
+
if (checks.codexNotify && !checks.notifyPathMatch) issues.push(buildDoctorIssue(runtime, 'standby-notify-drift', 'standby notify 未使用受管命令入口', 'Standby notify does not use the managed command entrypoint'))
|
|
101
|
+
if (!checks.codexHooksFeature) issues.push(buildDoctorIssue(runtime, 'codex-hooks-feature-disabled', 'Codex hooks 功能被显式关闭', 'Codex hooks feature is explicitly disabled'))
|
|
102
|
+
if (!checks.standaloneHooks) issues.push(buildDoctorIssue(runtime, 'standby-hooks-missing', 'standby `~/.codex/hooks.json` 缺少 HelloAGENTS hooks', 'Standby `~/.codex/hooks.json` is missing HelloAGENTS hooks'))
|
|
103
|
+
if (checks.standaloneHooks && !checks.standaloneHooksMatch) issues.push(buildDoctorIssue(runtime, 'standby-hooks-drift', 'standby `~/.codex/hooks.json` 与当前 hooks-codex.json 不一致', 'Standby `~/.codex/hooks.json` differs from the current hooks-codex.json'))
|
|
104
|
+
if (checks.pluginRoot || checks.pluginCache || checks.marketplaceEntry || checks.pluginEnabled) {
|
|
105
|
+
issues.push(buildDoctorIssue(runtime, 'standby-global-residue', 'standby 模式下仍残留 global 插件文件或配置', 'Global plugin artifacts still remain while Codex is in standby mode'))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function appendCodexGlobalIssues(runtime, issues, checks, pluginVersion, cacheVersion) {
|
|
110
|
+
if (!checks.carrierMarker) issues.push(buildDoctorIssue(runtime, 'global-home-carrier-missing', 'global `~/.codex/AGENTS.md` 缺少 HelloAGENTS 规则内容', 'Global `~/.codex/AGENTS.md` is missing the HelloAGENTS carrier'))
|
|
111
|
+
if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue(runtime, 'global-home-carrier-drift', 'global `~/.codex/AGENTS.md` 与当前 bootstrap.md 不一致', 'Global `~/.codex/AGENTS.md` differs from the current bootstrap.md'))
|
|
112
|
+
if (!checks.globalHomeLink) issues.push(buildDoctorIssue(runtime, 'global-read-root-link-missing', 'global `~/.codex/helloagents` 链接缺失或未指向当前插件根目录', 'Global `~/.codex/helloagents` link is missing or does not point to the current plugin root'))
|
|
113
|
+
if (!checks.pluginRoot) issues.push(buildDoctorIssue(runtime, 'global-plugin-root-missing', 'global 插件根目录缺失', 'Global plugin root is missing'))
|
|
114
|
+
if (!checks.pluginCache) issues.push(buildDoctorIssue(runtime, 'global-plugin-cache-missing', 'global 插件缓存目录缺失', 'Global plugin cache directory is missing'))
|
|
115
|
+
if (checks.pluginRoot && !checks.pluginCarrierMatch) issues.push(buildDoctorIssue(runtime, 'global-plugin-carrier-drift', 'global 插件根目录中的 AGENTS.md 与当前 bootstrap.md 不一致', 'Global plugin AGENTS.md differs from the current bootstrap.md'))
|
|
116
|
+
if (checks.pluginCache && !checks.pluginCacheCarrierMatch) issues.push(buildDoctorIssue(runtime, 'global-plugin-cache-carrier-drift', 'global 插件缓存中的 AGENTS.md 与当前 bootstrap.md 不一致', 'Global plugin cache AGENTS.md differs from the current bootstrap.md'))
|
|
117
|
+
if (!checks.marketplaceEntry) issues.push(buildDoctorIssue(runtime, 'global-marketplace-missing', 'global marketplace 条目缺失', 'Global marketplace entry is missing'))
|
|
118
|
+
if (!checks.pluginEnabled) issues.push(buildDoctorIssue(runtime, 'global-plugin-disabled', 'global config 中缺少插件启用段', 'Global plugin enablement block is missing from config'))
|
|
119
|
+
if (!checks.modelInstructionsFile) issues.push(buildDoctorIssue(runtime, 'global-model-instructions-missing', 'global config 缺少受管 model_instructions_file', 'Global config is missing the managed model_instructions_file'))
|
|
120
|
+
if (checks.modelInstructionsFile && !checks.modelInstructionsPathMatch) issues.push(buildDoctorIssue(runtime, 'global-model-instructions-drift', 'global model_instructions_file 未指向受管 `~/.codex/AGENTS.md`', 'Global model_instructions_file does not point to the managed `~/.codex/AGENTS.md`'))
|
|
121
|
+
if (!checks.codexNotify) issues.push(buildDoctorIssue(runtime, 'global-notify-missing', 'global notify 配置缺失', 'Global notify configuration is missing'))
|
|
122
|
+
if (checks.codexNotify && !checks.globalNotifyPathMatch) issues.push(buildDoctorIssue(runtime, 'global-notify-drift', 'global notify 未使用受管命令入口', 'Global notify does not use the managed command entrypoint'))
|
|
123
|
+
if (!checks.codexHooksFeature) issues.push(buildDoctorIssue(runtime, 'codex-hooks-feature-disabled', 'Codex hooks 功能被显式关闭', 'Codex hooks feature is explicitly disabled'))
|
|
124
|
+
if (!checks.standaloneHooks) issues.push(buildDoctorIssue(runtime, 'global-hooks-missing', 'global `~/.codex/hooks.json` 缺少 HelloAGENTS hooks', 'Global `~/.codex/hooks.json` is missing HelloAGENTS hooks'))
|
|
125
|
+
if (checks.standaloneHooks && !checks.standaloneHooksMatch) issues.push(buildDoctorIssue(runtime, 'global-hooks-drift', 'global `~/.codex/hooks.json` 与当前 hooks-codex.json 不一致', 'Global `~/.codex/hooks.json` differs from the current hooks-codex.json'))
|
|
126
|
+
if (pluginVersion && !checks.pluginVersionMatch) issues.push(buildDoctorIssue(runtime, 'global-plugin-version-drift', 'global 插件根目录版本与当前包版本不一致', 'Global plugin root version does not match the current package version'))
|
|
127
|
+
if (cacheVersion && !checks.pluginCacheVersionMatch) issues.push(buildDoctorIssue(runtime, 'global-plugin-cache-version-drift', 'global 插件缓存版本与当前包版本不一致', 'Global plugin cache version does not match the current package version'))
|
|
128
|
+
if (checks.homeLink) issues.push(buildDoctorIssue(runtime, 'global-standby-link-residue', 'global 模式下仍残留 standby home 链接', 'Standby home link still remains while Codex is in global mode'))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function buildCodexChecks(runtime, settings, trackedMode, detectedMode) {
|
|
132
|
+
const codexDir = join(runtime.home, '.codex')
|
|
133
|
+
const codexConfig = safeRead(join(codexDir, 'config.toml')) || ''
|
|
134
|
+
const pluginRoot = join(runtime.home, 'plugins', CODEX_PLUGIN_NAME)
|
|
135
|
+
const pluginCacheRoot = join(codexDir, 'plugins', 'cache', CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME, 'local')
|
|
136
|
+
const homeLinkTarget = safeRealTarget(join(codexDir, 'helloagents'))
|
|
137
|
+
const expectedHomeCarrier = (detectedMode === 'global' || (detectedMode === 'none' && trackedMode === 'global'))
|
|
138
|
+
? 'bootstrap.md'
|
|
139
|
+
: 'bootstrap-lite.md'
|
|
140
|
+
const codexHooks = safeJson(join(codexDir, 'hooks.json')) || {}
|
|
141
|
+
const marketplace = safeJson(join(runtime.home, '.agents', 'plugins', 'marketplace.json')) || {}
|
|
142
|
+
const modelInstructionsLine = readTopLevelTomlLine(codexConfig, 'model_instructions_file')
|
|
143
|
+
const expectedHooks = readExpectedHooks(runtime, 'hooks-codex.json', '${PLUGIN_ROOT}')
|
|
144
|
+
const hooksFeatureLine = readCodexHooksFeatureLine(codexConfig)
|
|
145
|
+
const goalsFeatureLine = readCodexGoalsFeatureLine(codexConfig)
|
|
146
|
+
const legacyHooksFeatureLine = readLegacyCodexHooksFeatureLine(codexConfig)
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
checks: {
|
|
150
|
+
carrierMarker: (safeRead(join(codexDir, 'AGENTS.md')) || '').includes('HELLOAGENTS_START'),
|
|
151
|
+
carrierContentMatch: normalizeText((safeRead(join(codexDir, 'AGENTS.md')) || '').match(/<!-- HELLOAGENTS_START -->([\s\S]*?)<!-- HELLOAGENTS_END -->/)?.[1] || '')
|
|
152
|
+
=== readExpectedCarrierContent(runtime, expectedHomeCarrier, settings),
|
|
153
|
+
homeLink: homeLinkTarget === (safeRealTarget(runtime.pkgRoot) || normalizePath(runtime.pkgRoot)),
|
|
154
|
+
globalHomeLink: homeLinkTarget === (safeRealTarget(pluginRoot) || normalizePath(pluginRoot)),
|
|
155
|
+
modelInstructionsFile: !!modelInstructionsLine,
|
|
156
|
+
modelInstructionsPathMatch: !!modelInstructionsLine && normalizePath(modelInstructionsLine).includes(`"${CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH}"`),
|
|
157
|
+
codexNotify: codexConfig.includes('codex-notify'),
|
|
158
|
+
notifyPathMatch: codexConfig.includes(CODEX_MANAGED_NOTIFY_VALUE),
|
|
159
|
+
codexHooksFeature: !/^\s*hooks\s*=\s*false\b/.test(hooksFeatureLine)
|
|
160
|
+
&& !/^\s*codex_hooks\s*=\s*false\b/.test(legacyHooksFeatureLine),
|
|
161
|
+
codexGoalsFeature: /^\s*goals\s*=\s*true\b/.test(goalsFeatureLine),
|
|
162
|
+
legacyCodexHooksFeature: Boolean(legacyHooksFeatureLine),
|
|
163
|
+
standaloneHooks: JSON.stringify(codexHooks.hooks || {}).includes('helloagents'),
|
|
164
|
+
standaloneHooksMatch: managedHooksMatch(codexHooks.hooks || {}, expectedHooks),
|
|
165
|
+
pluginRoot: existsSync(pluginRoot),
|
|
166
|
+
pluginCache: existsSync(pluginCacheRoot),
|
|
167
|
+
pluginCarrierMatch: normalizeText(safeRead(join(pluginRoot, 'AGENTS.md')) || '') === readExpectedCarrierContent(runtime, 'bootstrap.md', settings),
|
|
168
|
+
pluginCacheCarrierMatch: normalizeText(safeRead(join(pluginCacheRoot, 'AGENTS.md')) || '') === readExpectedCarrierContent(runtime, 'bootstrap.md', settings),
|
|
169
|
+
marketplaceEntry: Array.isArray(marketplace.plugins) && marketplace.plugins.some((plugin) => plugin?.name === CODEX_PLUGIN_NAME),
|
|
170
|
+
pluginEnabled: codexConfig.includes(CODEX_PLUGIN_CONFIG_HEADER) && codexConfig.includes('enabled = true'),
|
|
171
|
+
globalNotifyPathMatch: codexConfig.includes(CODEX_MANAGED_NOTIFY_VALUE),
|
|
172
|
+
pluginVersionMatch: false,
|
|
173
|
+
pluginCacheVersionMatch: false,
|
|
174
|
+
},
|
|
175
|
+
pluginVersion: safeJson(join(pluginRoot, 'package.json'))?.version || '',
|
|
176
|
+
cacheVersion: safeJson(join(pluginCacheRoot, 'package.json'))?.version || '',
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function inspectCodexDoctor(runtime, settings) {
|
|
181
|
+
const host = 'codex'
|
|
182
|
+
const trackedMode = normalizeDoctorMode(runtime.getTrackedHostMode(settings, host))
|
|
183
|
+
const detectedMode = normalizeDoctorMode(runtime.detectHostMode(host))
|
|
184
|
+
const { checks, pluginVersion, cacheVersion } = buildCodexChecks(runtime, settings, trackedMode, detectedMode)
|
|
185
|
+
checks.pluginVersionMatch = pluginVersion ? pluginVersion === runtime.pkgVersion : false
|
|
186
|
+
checks.pluginCacheVersionMatch = cacheVersion ? cacheVersion === runtime.pkgVersion : false
|
|
187
|
+
|
|
188
|
+
const issues = []
|
|
189
|
+
const notes = []
|
|
190
|
+
if (trackedMode !== 'none' && detectedMode !== 'none' && trackedMode !== detectedMode) {
|
|
191
|
+
issues.push(buildDoctorIssue(runtime, 'tracked-mode-mismatch', '记录模式与检测模式不一致', 'Tracked mode does not match detected mode'))
|
|
192
|
+
}
|
|
193
|
+
if (detectedMode === 'standby') appendCodexStandbyIssues(runtime, issues, checks)
|
|
194
|
+
if (detectedMode === 'global') appendCodexGlobalIssues(runtime, issues, checks, pluginVersion, cacheVersion)
|
|
195
|
+
if (trackedMode === 'none' && detectedMode !== 'none') {
|
|
196
|
+
issues.push(buildDoctorIssue(runtime, 'untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
|
|
197
|
+
}
|
|
198
|
+
if (trackedMode !== 'none' && detectedMode === 'none') {
|
|
199
|
+
issues.push(buildDoctorIssue(runtime, 'tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应的受管文件或配置', 'Config says this CLI is installed, but no managed artifacts were detected'))
|
|
200
|
+
}
|
|
201
|
+
if (!checks.pluginVersionMatch && !pluginVersion && detectedMode === 'global') notes.push(runtime.msg('未读到 global 插件根目录版本信息', 'Global plugin root version was not readable'))
|
|
202
|
+
if (!checks.pluginCacheVersionMatch && !cacheVersion && detectedMode === 'global') notes.push(runtime.msg('未读到 global 插件缓存版本信息', 'Global plugin cache version was not readable'))
|
|
203
|
+
if (detectedMode !== 'none' && !checks.codexGoalsFeature) notes.push(runtime.msg('Codex /goal 未启用;如需长程执行,可运行 `helloagents codex goals enable`。', 'Codex /goal is not enabled; run `helloagents codex goals enable` if you need long-running goals.'))
|
|
204
|
+
if (detectedMode !== 'none' && checks.legacyCodexHooksFeature) notes.push(runtime.msg('检测到旧版 `codex_hooks`;HelloAGENTS 只兼容 Codex 最新版,建议移除旧 key。', 'Legacy `codex_hooks` was detected; HelloAGENTS targets latest Codex only, so remove the old key.'))
|
|
205
|
+
|
|
206
|
+
const status = summarizeDoctorStatus(issues, { trackedMode, detectedMode })
|
|
207
|
+
return { host, label: runtime.getHostLabel(host), trackedMode, detectedMode, status, checks, issues, notes, suggestedFix: suggestCodexDoctorFix(status, trackedMode) }
|
|
208
|
+
}
|
package/scripts/cli-doctor.mjs
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { realpathSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
|
|
4
|
-
import { CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_CONFIG_HEADER, CODEX_PLUGIN_NAME } from './cli-codex.mjs'
|
|
5
|
-
import {
|
|
6
|
-
CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH,
|
|
7
|
-
CODEX_MANAGED_NOTIFY_VALUE,
|
|
8
|
-
} from './cli-codex-config.mjs'
|
|
9
4
|
import { DEFAULTS } from './cli-config.mjs'
|
|
5
|
+
import { inspectCodexDoctor as inspectCodexDoctorImpl } from './cli-doctor-codex.mjs'
|
|
10
6
|
import { printDoctorText } from './cli-doctor-render.mjs'
|
|
11
7
|
import { buildRuntimeCarrier } from './cli-runtime-carrier.mjs'
|
|
12
8
|
import { readTopLevelTomlLine } from './cli-toml.mjs'
|
|
@@ -223,111 +219,6 @@ function inspectGeminiDoctor(settings) {
|
|
|
223
219
|
return { host, label: runtime.getHostLabel(host), trackedMode, detectedMode, status, checks, issues, notes, suggestedFix: suggestDoctorFix(host, status, trackedMode) }
|
|
224
220
|
}
|
|
225
221
|
|
|
226
|
-
function appendCodexStandbyIssues(issues, checks) {
|
|
227
|
-
if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 规则文件缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
|
|
228
|
-
if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby 规则文件内容与当前 bootstrap-lite.md 不一致', 'Standby carrier content differs from the current bootstrap-lite.md'))
|
|
229
|
-
if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby home 链接缺失或未指向稳定运行根目录', 'Standby home link is missing or points to a different runtime root'))
|
|
230
|
-
if (!checks.modelInstructionsFile) issues.push(buildDoctorIssue('standby-model-instructions-missing', 'standby config 缺少受管 model_instructions_file', 'Standby config is missing the managed model_instructions_file'))
|
|
231
|
-
if (checks.modelInstructionsFile && !checks.modelInstructionsPathMatch) issues.push(buildDoctorIssue('standby-model-instructions-drift', 'standby model_instructions_file 未指向受管 `~/.codex/AGENTS.md`', 'Standby model_instructions_file does not point to the managed `~/.codex/AGENTS.md`'))
|
|
232
|
-
if (!checks.codexNotify) issues.push(buildDoctorIssue('standby-notify-missing', 'standby notify 配置缺失', 'Standby notify configuration is missing'))
|
|
233
|
-
if (checks.codexNotify && !checks.notifyPathMatch) issues.push(buildDoctorIssue('standby-notify-drift', 'standby notify 未使用受管命令入口', 'Standby notify does not use the managed command entrypoint'))
|
|
234
|
-
if (checks.pluginRoot || checks.pluginCache || checks.marketplaceEntry || checks.pluginEnabled) {
|
|
235
|
-
issues.push(buildDoctorIssue('standby-global-residue', 'standby 模式下仍残留 global 插件文件或配置', 'Global plugin artifacts still remain while Codex is in standby mode'))
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function appendCodexGlobalIssues(issues, checks, pluginVersion, cacheVersion) {
|
|
240
|
-
if (!checks.carrierMarker) issues.push(buildDoctorIssue('global-home-carrier-missing', 'global `~/.codex/AGENTS.md` 缺少 HelloAGENTS 规则内容', 'Global `~/.codex/AGENTS.md` is missing the HelloAGENTS carrier'))
|
|
241
|
-
if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('global-home-carrier-drift', 'global `~/.codex/AGENTS.md` 与当前 bootstrap.md 不一致', 'Global `~/.codex/AGENTS.md` differs from the current bootstrap.md'))
|
|
242
|
-
if (!checks.globalHomeLink) issues.push(buildDoctorIssue('global-read-root-link-missing', 'global `~/.codex/helloagents` 链接缺失或未指向当前插件根目录', 'Global `~/.codex/helloagents` link is missing or does not point to the current plugin root'))
|
|
243
|
-
if (!checks.pluginRoot) issues.push(buildDoctorIssue('global-plugin-root-missing', 'global 插件根目录缺失', 'Global plugin root is missing'))
|
|
244
|
-
if (!checks.pluginCache) issues.push(buildDoctorIssue('global-plugin-cache-missing', 'global 插件缓存目录缺失', 'Global plugin cache directory is missing'))
|
|
245
|
-
if (checks.pluginRoot && !checks.pluginCarrierMatch) issues.push(buildDoctorIssue('global-plugin-carrier-drift', 'global 插件根目录中的 AGENTS.md 与当前 bootstrap.md 不一致', 'Global plugin AGENTS.md differs from the current bootstrap.md'))
|
|
246
|
-
if (checks.pluginCache && !checks.pluginCacheCarrierMatch) issues.push(buildDoctorIssue('global-plugin-cache-carrier-drift', 'global 插件缓存中的 AGENTS.md 与当前 bootstrap.md 不一致', 'Global plugin cache AGENTS.md differs from the current bootstrap.md'))
|
|
247
|
-
if (!checks.marketplaceEntry) issues.push(buildDoctorIssue('global-marketplace-missing', 'global marketplace 条目缺失', 'Global marketplace entry is missing'))
|
|
248
|
-
if (!checks.pluginEnabled) issues.push(buildDoctorIssue('global-plugin-disabled', 'global config 中缺少插件启用段', 'Global plugin enablement block is missing from config'))
|
|
249
|
-
if (!checks.modelInstructionsFile) issues.push(buildDoctorIssue('global-model-instructions-missing', 'global config 缺少受管 model_instructions_file', 'Global config is missing the managed model_instructions_file'))
|
|
250
|
-
if (checks.modelInstructionsFile && !checks.modelInstructionsPathMatch) issues.push(buildDoctorIssue('global-model-instructions-drift', 'global model_instructions_file 未指向受管 `~/.codex/AGENTS.md`', 'Global model_instructions_file does not point to the managed `~/.codex/AGENTS.md`'))
|
|
251
|
-
if (!checks.codexNotify) issues.push(buildDoctorIssue('global-notify-missing', 'global notify 配置缺失', 'Global notify configuration is missing'))
|
|
252
|
-
if (checks.codexNotify && !checks.globalNotifyPathMatch) issues.push(buildDoctorIssue('global-notify-drift', 'global notify 未使用受管命令入口', 'Global notify does not use the managed command entrypoint'))
|
|
253
|
-
if (pluginVersion && !checks.pluginVersionMatch) issues.push(buildDoctorIssue('global-plugin-version-drift', 'global 插件根目录版本与当前包版本不一致', 'Global plugin root version does not match the current package version'))
|
|
254
|
-
if (cacheVersion && !checks.pluginCacheVersionMatch) issues.push(buildDoctorIssue('global-plugin-cache-version-drift', 'global 插件缓存版本与当前包版本不一致', 'Global plugin cache version does not match the current package version'))
|
|
255
|
-
if (checks.homeLink) {
|
|
256
|
-
issues.push(buildDoctorIssue('global-standby-link-residue', 'global 模式下仍残留 standby home 链接', 'Standby home link still remains while Codex is in global mode'))
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function inspectCodexDoctor(settings) {
|
|
261
|
-
const host = 'codex'
|
|
262
|
-
const trackedMode = normalizeDoctorMode(runtime.getTrackedHostMode(settings, host))
|
|
263
|
-
const detectedMode = normalizeDoctorMode(runtime.detectHostMode(host))
|
|
264
|
-
const codexDir = join(runtime.home, '.codex')
|
|
265
|
-
const codexConfig = safeRead(join(codexDir, 'config.toml')) || ''
|
|
266
|
-
const pluginRoot = join(runtime.home, 'plugins', CODEX_PLUGIN_NAME)
|
|
267
|
-
const pluginCacheRoot = join(codexDir, 'plugins', 'cache', CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME, 'local')
|
|
268
|
-
const marketplace = safeJson(join(runtime.home, '.agents', 'plugins', 'marketplace.json')) || {}
|
|
269
|
-
const pluginVersion = safeJson(join(pluginRoot, 'package.json'))?.version || ''
|
|
270
|
-
const cacheVersion = safeJson(join(pluginCacheRoot, 'package.json'))?.version || ''
|
|
271
|
-
const homeLinkTarget = safeRealTarget(join(codexDir, 'helloagents'))
|
|
272
|
-
const pkgRootTarget = safeRealTarget(runtime.pkgRoot) || normalizePath(runtime.pkgRoot)
|
|
273
|
-
const pluginRootTarget = safeRealTarget(pluginRoot) || normalizePath(pluginRoot)
|
|
274
|
-
const modelInstructionsLine = readTopLevelTomlLine(codexConfig, 'model_instructions_file')
|
|
275
|
-
const expectedHomeCarrier = (detectedMode === 'global' || (detectedMode === 'none' && trackedMode === 'global'))
|
|
276
|
-
? 'bootstrap.md'
|
|
277
|
-
: 'bootstrap-lite.md'
|
|
278
|
-
const checks = {
|
|
279
|
-
carrierMarker: (safeRead(join(codexDir, 'AGENTS.md')) || '').includes('HELLOAGENTS_START'),
|
|
280
|
-
carrierContentMatch: extractManagedCarrierContent(join(codexDir, 'AGENTS.md'))
|
|
281
|
-
=== readExpectedCarrierContent(expectedHomeCarrier, settings),
|
|
282
|
-
homeLink: homeLinkTarget === pkgRootTarget,
|
|
283
|
-
globalHomeLink: homeLinkTarget === pluginRootTarget,
|
|
284
|
-
modelInstructionsFile: !!modelInstructionsLine,
|
|
285
|
-
modelInstructionsPathMatch: !!modelInstructionsLine
|
|
286
|
-
&& normalizePath(modelInstructionsLine).includes(`"${CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH}"`),
|
|
287
|
-
codexNotify: codexConfig.includes('codex-notify'),
|
|
288
|
-
notifyPathMatch: codexConfig.includes(CODEX_MANAGED_NOTIFY_VALUE),
|
|
289
|
-
pluginRoot: existsSync(pluginRoot),
|
|
290
|
-
pluginCache: existsSync(pluginCacheRoot),
|
|
291
|
-
pluginCarrierMatch: normalizeText(safeRead(join(pluginRoot, 'AGENTS.md')) || '')
|
|
292
|
-
=== readExpectedCarrierContent('bootstrap.md', settings),
|
|
293
|
-
pluginCacheCarrierMatch: normalizeText(safeRead(join(pluginCacheRoot, 'AGENTS.md')) || '')
|
|
294
|
-
=== readExpectedCarrierContent('bootstrap.md', settings),
|
|
295
|
-
marketplaceEntry: Array.isArray(marketplace.plugins) && marketplace.plugins.some((plugin) => plugin?.name === CODEX_PLUGIN_NAME),
|
|
296
|
-
pluginEnabled: codexConfig.includes(CODEX_PLUGIN_CONFIG_HEADER) && codexConfig.includes('enabled = true'),
|
|
297
|
-
globalNotifyPath: codexConfig.includes('codex-notify'),
|
|
298
|
-
globalNotifyPathMatch: codexConfig.includes(CODEX_MANAGED_NOTIFY_VALUE),
|
|
299
|
-
pluginVersionMatch: pluginVersion ? pluginVersion === runtime.pkgVersion : false,
|
|
300
|
-
pluginCacheVersionMatch: cacheVersion ? cacheVersion === runtime.pkgVersion : false,
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const issues = []
|
|
304
|
-
const notes = []
|
|
305
|
-
if (trackedMode !== 'none' && detectedMode !== 'none' && trackedMode !== detectedMode) {
|
|
306
|
-
issues.push(buildDoctorIssue('tracked-mode-mismatch', '记录模式与检测模式不一致', 'Tracked mode does not match detected mode'))
|
|
307
|
-
}
|
|
308
|
-
if (detectedMode === 'standby') {
|
|
309
|
-
appendCodexStandbyIssues(issues, checks)
|
|
310
|
-
}
|
|
311
|
-
if (detectedMode === 'global') {
|
|
312
|
-
appendCodexGlobalIssues(issues, checks, pluginVersion, cacheVersion)
|
|
313
|
-
}
|
|
314
|
-
if (trackedMode === 'none' && detectedMode !== 'none') {
|
|
315
|
-
issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
|
|
316
|
-
}
|
|
317
|
-
if (trackedMode !== 'none' && detectedMode === 'none') {
|
|
318
|
-
issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应的受管文件或配置', 'Config says this CLI is installed, but no managed artifacts were detected'))
|
|
319
|
-
}
|
|
320
|
-
if (!checks.pluginVersionMatch && !pluginVersion && detectedMode === 'global') {
|
|
321
|
-
notes.push(runtime.msg('未读到 global 插件根目录版本信息', 'Global plugin root version was not readable'))
|
|
322
|
-
}
|
|
323
|
-
if (!checks.pluginCacheVersionMatch && !cacheVersion && detectedMode === 'global') {
|
|
324
|
-
notes.push(runtime.msg('未读到 global 插件缓存版本信息', 'Global plugin cache version was not readable'))
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
const status = summarizeDoctorStatus(issues, { host, trackedMode, detectedMode })
|
|
328
|
-
return { host, label: runtime.getHostLabel(host), trackedMode, detectedMode, status, checks, issues, notes, suggestedFix: suggestDoctorFix(host, status, trackedMode) }
|
|
329
|
-
}
|
|
330
|
-
|
|
331
222
|
function parseDoctorArgs(args) {
|
|
332
223
|
const wantsJson = args.includes('--json')
|
|
333
224
|
const unknownFlags = args.filter((arg) => arg.startsWith('--') && arg !== '--json' && arg !== '--all')
|
|
@@ -348,7 +239,7 @@ function parseDoctorArgs(args) {
|
|
|
348
239
|
function inspectDoctorHost(host, settings) {
|
|
349
240
|
if (host === 'claude') return inspectClaudeDoctor(settings)
|
|
350
241
|
if (host === 'gemini') return inspectGeminiDoctor(settings)
|
|
351
|
-
return
|
|
242
|
+
return inspectCodexDoctorImpl(runtime, settings)
|
|
352
243
|
}
|
|
353
244
|
|
|
354
245
|
function buildDoctorReport(host) {
|
package/scripts/cli-messages.mjs
CHANGED
|
@@ -112,6 +112,11 @@ ${msg('诊断', 'Diagnostics')}:
|
|
|
112
112
|
helloagents doctor codex --json
|
|
113
113
|
${msg('检查 carrier、链接、hooks、配置注入、Codex 插件安装、受管 model_instructions_file 指向与版本漂移', 'Checks carriers, links, hooks, config injections, Codex plugin installation, managed model_instructions_file targeting, and version drift')}
|
|
114
114
|
|
|
115
|
+
${msg('Codex /goal', 'Codex /goal')}:
|
|
116
|
+
helloagents codex goals status
|
|
117
|
+
helloagents codex goals enable
|
|
118
|
+
${msg('仅显式管理 Codex 最新版 [features].goals,不替代 /goal', 'Explicitly manages only latest Codex [features].goals; does not replace /goal')}
|
|
119
|
+
|
|
115
120
|
${msg('卸载', 'Uninstall')}:
|
|
116
121
|
helloagents cleanup ${msg('(推荐先执行,显式清理所有 CLI 注入/链接)', '(recommended first, explicitly cleans CLI injections/links)')}
|
|
117
122
|
npm uninstall -g helloagents
|