helloagents 3.0.7 → 3.0.9-beta.1
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/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +6 -6
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +65 -58
- package/README_CN.md +60 -53
- package/bootstrap-lite.md +46 -28
- package/bootstrap.md +51 -34
- package/gemini-extension.json +1 -1
- package/package.json +12 -2
- package/scripts/capability-registry.mjs +9 -9
- package/scripts/cli-codex-config.mjs +49 -55
- package/scripts/cli-codex.mjs +69 -77
- package/scripts/cli-doctor.mjs +26 -18
- package/scripts/cli-host-detect.mjs +18 -2
- package/scripts/cli-messages.mjs +1 -1
- package/scripts/cli-toml.mjs +30 -0
- package/scripts/delivery-gate.mjs +5 -4
- package/scripts/guard-rules.mjs +26 -1
- package/scripts/guard.mjs +43 -14
- package/scripts/notify-context.mjs +30 -33
- package/scripts/notify-route.mjs +5 -2
- package/scripts/notify-source.mjs +3 -60
- package/scripts/notify.mjs +43 -11
- package/scripts/project-storage.mjs +107 -15
- package/scripts/session-token.mjs +73 -0
- package/scripts/turn-state.mjs +173 -0
- package/scripts/workflow-core.mjs +19 -11
- package/scripts/workflow-plan-files.mjs +17 -6
- package/scripts/workflow-recommendation.mjs +14 -14
- package/scripts/workflow-state.mjs +14 -14
- package/skills/_meta/SKILL.md +1 -1
- package/skills/commands/auto/SKILL.md +24 -9
- package/skills/commands/build/SKILL.md +4 -4
- package/skills/commands/clean/SKILL.md +3 -3
- package/skills/commands/commit/SKILL.md +1 -1
- package/skills/commands/help/SKILL.md +3 -3
- package/skills/commands/idea/SKILL.md +2 -2
- package/skills/commands/init/SKILL.md +13 -8
- package/skills/commands/loop/SKILL.md +4 -4
- package/skills/commands/plan/SKILL.md +13 -11
- package/skills/commands/prd/SKILL.md +10 -8
- package/skills/commands/verify/SKILL.md +5 -5
- package/skills/commands/wiki/SKILL.md +9 -11
- package/skills/hello-review/SKILL.md +1 -1
- package/skills/hello-subagent/SKILL.md +3 -2
- package/skills/hello-ui/SKILL.md +13 -13
- package/skills/hello-verify/SKILL.md +6 -5
- package/skills/helloagents/SKILL.md +17 -12
package/scripts/cli-codex.mjs
CHANGED
|
@@ -7,21 +7,19 @@ import {
|
|
|
7
7
|
} from './cli-utils.mjs';
|
|
8
8
|
import { ensureTimestampedBackup, readCodexBackup, removeCodexBackup } from './cli-codex-backup.mjs';
|
|
9
9
|
import {
|
|
10
|
-
CODEX_DEVELOPER_INSTRUCTIONS,
|
|
11
10
|
CODEX_MANAGED_TOML_COMMENT,
|
|
12
11
|
CODEX_PLUGIN_CONFIG_HEADER,
|
|
13
|
-
|
|
12
|
+
isManagedCodexHooks,
|
|
13
|
+
installCodexManagedTopLevelConfig,
|
|
14
14
|
isManagedCodexBackupInstruction,
|
|
15
15
|
isManagedCodexModelInstruction,
|
|
16
16
|
isManagedCodexNotify,
|
|
17
17
|
removeCodexPluginConfig,
|
|
18
|
-
|
|
18
|
+
restoreCodexTopLevelConfig,
|
|
19
19
|
upsertCodexPluginConfig,
|
|
20
20
|
} from './cli-codex-config.mjs';
|
|
21
21
|
import {
|
|
22
|
-
upsertTopLevelTomlKey,
|
|
23
22
|
readTopLevelTomlLine,
|
|
24
|
-
ensureTopLevelTomlLine,
|
|
25
23
|
readTomlKeyInSection,
|
|
26
24
|
removeTomlKeyInSection,
|
|
27
25
|
ensureTomlKeyInSection,
|
|
@@ -31,7 +29,7 @@ import {
|
|
|
31
29
|
export const CODEX_MARKETPLACE_NAME = 'local-plugins';
|
|
32
30
|
export const CODEX_PLUGIN_NAME = 'helloagents';
|
|
33
31
|
export const CODEX_PLUGIN_KEY = `${CODEX_PLUGIN_NAME}@${CODEX_MARKETPLACE_NAME}`;
|
|
34
|
-
export {
|
|
32
|
+
export { CODEX_MANAGED_TOML_COMMENT, CODEX_PLUGIN_CONFIG_HEADER };
|
|
35
33
|
export const CODEX_RUNTIME_CARRIER = 'AGENTS.md';
|
|
36
34
|
const CODEX_CONFIG_BASENAME = 'config.toml';
|
|
37
35
|
export const CODEX_RUNTIME_ENTRIES = [
|
|
@@ -108,10 +106,6 @@ function removeCodexMarketplaceEntry(marketplaceFile) {
|
|
|
108
106
|
return true;
|
|
109
107
|
}
|
|
110
108
|
|
|
111
|
-
function normalizePath(path) {
|
|
112
|
-
return path.replace(/\\/g, '/');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
109
|
function buildCodexRuntimeCarrier(bootstrapContent) {
|
|
116
110
|
const normalized = String(bootstrapContent || '').trim();
|
|
117
111
|
return normalized ? `${normalized}\n` : '';
|
|
@@ -131,11 +125,55 @@ function writeCodexRuntimeCarrier(filePath, bootstrapPath) {
|
|
|
131
125
|
return true;
|
|
132
126
|
}
|
|
133
127
|
|
|
134
|
-
function
|
|
135
|
-
|
|
128
|
+
function cleanupCodexManagedConfig(configPath, { removePluginConfig = false } = {}) {
|
|
129
|
+
const backupToml = readCodexBackup(configPath, CODEX_CONFIG_BASENAME);
|
|
130
|
+
let toml = safeRead(configPath) || '';
|
|
131
|
+
|
|
132
|
+
const currentModelInstructions = readTopLevelTomlLine(toml, 'model_instructions_file');
|
|
133
|
+
const currentNotify = readTopLevelTomlLine(toml, 'notify');
|
|
134
|
+
const currentCodexHooks = readTomlKeyInSection(toml, '[features]', 'codex_hooks');
|
|
135
|
+
|
|
136
|
+
const shouldRestoreModelInstructions = isManagedCodexModelInstruction(currentModelInstructions);
|
|
137
|
+
const shouldRestoreNotify = isManagedCodexNotify(currentNotify);
|
|
138
|
+
const shouldRestoreCodexHooks = isManagedCodexHooks(currentCodexHooks);
|
|
139
|
+
|
|
140
|
+
if (removePluginConfig) {
|
|
141
|
+
toml = removeCodexPluginConfig(toml);
|
|
142
|
+
}
|
|
143
|
+
if (shouldRestoreModelInstructions) {
|
|
144
|
+
toml = removeTopLevelTomlLines(toml, (line) =>
|
|
145
|
+
line.startsWith('model_instructions_file =') && isManagedCodexModelInstruction(line)).text;
|
|
146
|
+
}
|
|
147
|
+
if (shouldRestoreNotify) {
|
|
148
|
+
toml = removeTopLevelTomlLines(toml, (line) =>
|
|
149
|
+
line.startsWith('notify =') && isManagedCodexNotify(line)).text;
|
|
150
|
+
}
|
|
151
|
+
if (shouldRestoreCodexHooks) {
|
|
152
|
+
toml = removeTomlKeyInSection(toml, '[features]', 'codex_hooks');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const backupModelInstructions = readTopLevelTomlLine(backupToml, 'model_instructions_file');
|
|
156
|
+
const backupNotify = readTopLevelTomlLine(backupToml, 'notify');
|
|
157
|
+
const backupCodexHooks = readTomlKeyInSection(backupToml, '[features]', 'codex_hooks');
|
|
158
|
+
|
|
159
|
+
toml = restoreCodexTopLevelConfig(toml, {
|
|
160
|
+
modelInstructionsLine: shouldRestoreModelInstructions && !isManagedCodexBackupInstruction(backupModelInstructions)
|
|
161
|
+
? backupModelInstructions
|
|
162
|
+
: '',
|
|
163
|
+
notifyLine: shouldRestoreNotify && !isManagedCodexNotify(backupNotify)
|
|
164
|
+
? backupNotify
|
|
165
|
+
: '',
|
|
166
|
+
});
|
|
167
|
+
toml = ensureTomlKeyInSection(
|
|
136
168
|
toml,
|
|
137
|
-
|
|
138
|
-
|
|
169
|
+
'[features]',
|
|
170
|
+
'codex_hooks',
|
|
171
|
+
shouldRestoreCodexHooks && !isManagedCodexHooks(backupCodexHooks)
|
|
172
|
+
? backupCodexHooks
|
|
173
|
+
: '',
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
return toml;
|
|
139
177
|
}
|
|
140
178
|
|
|
141
179
|
export function installCodexStandby(home, pkgRoot) {
|
|
@@ -150,9 +188,10 @@ export function installCodexStandby(home, pkgRoot) {
|
|
|
150
188
|
let toml = safeRead(configPath) || '';
|
|
151
189
|
ensureTimestampedBackup(configPath, CODEX_CONFIG_BASENAME);
|
|
152
190
|
|
|
153
|
-
toml =
|
|
154
|
-
|
|
155
|
-
|
|
191
|
+
toml = installCodexManagedTopLevelConfig(toml, {
|
|
192
|
+
modelInstructionsPath: codexAgentsPath,
|
|
193
|
+
notifyScriptPath: join(pkgRoot, 'scripts', 'notify.mjs'),
|
|
194
|
+
});
|
|
156
195
|
safeWrite(configPath, toml);
|
|
157
196
|
|
|
158
197
|
createLink(pkgRoot, join(codexDir, 'helloagents'));
|
|
@@ -167,34 +206,10 @@ export function uninstallCodexStandby(home) {
|
|
|
167
206
|
removeMarkedContent(join(codexDir, 'AGENTS.md'));
|
|
168
207
|
|
|
169
208
|
const configPath = join(codexDir, 'config.toml');
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (!line) return false;
|
|
175
|
-
if (line.startsWith('model_instructions_file =') && isManagedCodexModelInstruction(line)) return true;
|
|
176
|
-
if (line.startsWith('notify =') && line.includes('codex-notify')) return true;
|
|
177
|
-
return false;
|
|
178
|
-
}).text;
|
|
179
|
-
toml = uninstallCodexDeveloperInstructions(configPath, toml);
|
|
180
|
-
toml = removeTomlKeyInSection(toml, '[features]', 'codex_hooks');
|
|
181
|
-
const backupModelInstructions = readTopLevelTomlLine(backupToml, 'model_instructions_file');
|
|
182
|
-
const backupNotify = readTopLevelTomlLine(backupToml, 'notify');
|
|
183
|
-
toml = ensureTopLevelTomlLine(
|
|
184
|
-
toml,
|
|
185
|
-
'model_instructions_file',
|
|
186
|
-
isManagedCodexBackupInstruction(backupModelInstructions) ? '' : backupModelInstructions,
|
|
187
|
-
);
|
|
188
|
-
toml = ensureTopLevelTomlLine(
|
|
189
|
-
toml,
|
|
190
|
-
'notify',
|
|
191
|
-
isManagedCodexNotify(backupNotify) ? '' : backupNotify,
|
|
192
|
-
);
|
|
193
|
-
toml = ensureTomlKeyInSection(toml, '[features]', 'codex_hooks', readTomlKeyInSection(backupToml, '[features]', 'codex_hooks'));
|
|
194
|
-
if (toml.trim()) safeWrite(configPath, toml);
|
|
195
|
-
else removeIfExists(configPath);
|
|
196
|
-
changed = true;
|
|
197
|
-
}
|
|
209
|
+
const toml = cleanupCodexManagedConfig(configPath);
|
|
210
|
+
if (toml.trim()) safeWrite(configPath, toml);
|
|
211
|
+
else removeIfExists(configPath);
|
|
212
|
+
changed = true;
|
|
198
213
|
removeCodexBackup(configPath, CODEX_CONFIG_BASENAME);
|
|
199
214
|
removeIfExists(join(codexDir, 'hooks.json'));
|
|
200
215
|
removeLink(join(codexDir, 'helloagents'));
|
|
@@ -233,6 +248,7 @@ export function installCodexGlobal(home, pkgRoot) {
|
|
|
233
248
|
|
|
234
249
|
copyEntries(pkgRoot, pluginRoot, CODEX_RUNTIME_ENTRIES);
|
|
235
250
|
copyEntries(pkgRoot, installedPluginRoot, CODEX_RUNTIME_ENTRIES);
|
|
251
|
+
createLink(pluginRoot, join(codexDir, 'helloagents'));
|
|
236
252
|
writeCodexRuntimeCarrier(
|
|
237
253
|
join(pluginRoot, CODEX_RUNTIME_CARRIER),
|
|
238
254
|
join(pluginRoot, 'bootstrap.md'),
|
|
@@ -241,21 +257,19 @@ export function installCodexGlobal(home, pkgRoot) {
|
|
|
241
257
|
join(installedPluginRoot, CODEX_RUNTIME_CARRIER),
|
|
242
258
|
join(installedPluginRoot, 'bootstrap.md'),
|
|
243
259
|
);
|
|
244
|
-
|
|
260
|
+
const homeCarrierPath = join(codexDir, CODEX_RUNTIME_CARRIER);
|
|
261
|
+
injectCodexRuntimeCarrier(homeCarrierPath, join(pkgRoot, 'bootstrap.md'));
|
|
245
262
|
|
|
246
263
|
ensureDir(join(home, '.agents', 'plugins'));
|
|
247
264
|
updateCodexMarketplace(marketplaceFile);
|
|
248
265
|
|
|
249
266
|
let toml = safeRead(configPath) || '';
|
|
250
267
|
ensureTimestampedBackup(configPath, CODEX_CONFIG_BASENAME);
|
|
251
|
-
toml =
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
`["node", "${normalizePath(join(pluginRoot, 'scripts', 'notify.mjs'))}", "codex-notify"]`,
|
|
256
|
-
);
|
|
268
|
+
toml = installCodexManagedTopLevelConfig(toml, {
|
|
269
|
+
modelInstructionsPath: homeCarrierPath,
|
|
270
|
+
notifyScriptPath: join(pluginRoot, 'scripts', 'notify.mjs'),
|
|
271
|
+
});
|
|
257
272
|
toml = upsertCodexPluginConfig(toml);
|
|
258
|
-
toml = installCodexDeveloperInstructions(configPath, toml);
|
|
259
273
|
safeWrite(configPath, toml);
|
|
260
274
|
|
|
261
275
|
return true;
|
|
@@ -273,31 +287,9 @@ export function uninstallCodexGlobal(home) {
|
|
|
273
287
|
removeIfExists(pluginCacheRoot);
|
|
274
288
|
removeCodexMarketplaceEntry(marketplaceFile);
|
|
275
289
|
removeMarkedContent(join(codexDir, 'AGENTS.md'));
|
|
290
|
+
removeLink(join(codexDir, 'helloagents'));
|
|
276
291
|
|
|
277
|
-
const
|
|
278
|
-
let toml = safeRead(configPath) || '';
|
|
279
|
-
toml = removeCodexPluginConfig(toml);
|
|
280
|
-
toml = uninstallCodexDeveloperInstructions(configPath, toml);
|
|
281
|
-
toml = removeTomlKeyInSection(toml, '[features]', 'codex_hooks');
|
|
282
|
-
toml = removeTopLevelTomlLines(toml, (line) =>
|
|
283
|
-
line.startsWith('model_instructions_file =')
|
|
284
|
-
&& isManagedCodexModelInstruction(line)).text;
|
|
285
|
-
toml = removeTopLevelTomlLines(toml, (line) =>
|
|
286
|
-
line.startsWith('notify =')
|
|
287
|
-
&& line.includes('/plugins/helloagents/scripts/notify.mjs')).text;
|
|
288
|
-
const backupModelInstructions = readTopLevelTomlLine(backupToml, 'model_instructions_file');
|
|
289
|
-
const backupNotify = readTopLevelTomlLine(backupToml, 'notify');
|
|
290
|
-
toml = ensureTopLevelTomlLine(
|
|
291
|
-
toml,
|
|
292
|
-
'model_instructions_file',
|
|
293
|
-
isManagedCodexBackupInstruction(backupModelInstructions) ? '' : backupModelInstructions,
|
|
294
|
-
);
|
|
295
|
-
toml = ensureTopLevelTomlLine(
|
|
296
|
-
toml,
|
|
297
|
-
'notify',
|
|
298
|
-
isManagedCodexNotify(backupNotify) ? '' : backupNotify,
|
|
299
|
-
);
|
|
300
|
-
toml = ensureTomlKeyInSection(toml, '[features]', 'codex_hooks', readTomlKeyInSection(backupToml, '[features]', 'codex_hooks'));
|
|
292
|
+
const toml = cleanupCodexManagedConfig(configPath, { removePluginConfig: true });
|
|
301
293
|
if (toml.trim()) safeWrite(configPath, toml);
|
|
302
294
|
else removeIfExists(configPath);
|
|
303
295
|
removeCodexBackup(configPath, CODEX_CONFIG_BASENAME);
|
package/scripts/cli-doctor.mjs
CHANGED
|
@@ -140,8 +140,8 @@ function inspectClaudeDoctor(settings) {
|
|
|
140
140
|
issues.push(buildDoctorIssue('tracked-mode-mismatch', '记录模式与检测模式不一致', 'Tracked mode does not match detected mode'))
|
|
141
141
|
}
|
|
142
142
|
if (detectedMode === 'standby') {
|
|
143
|
-
if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby
|
|
144
|
-
if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby
|
|
143
|
+
if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 规则文件缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
|
|
144
|
+
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'))
|
|
145
145
|
if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby home 链接缺失或未指向当前包根目录', 'Standby home link is missing or points to a different package root'))
|
|
146
146
|
if (!checks.settingsHooks) issues.push(buildDoctorIssue('standby-hooks-missing', 'standby settings hooks 缺失', 'Standby settings hooks are missing'))
|
|
147
147
|
if (checks.settingsHooks && !checks.settingsHooksMatch) issues.push(buildDoctorIssue('standby-hooks-drift', 'standby settings hooks 与当前 hooks 配置不一致', 'Standby settings hooks differ from the current hook configuration'))
|
|
@@ -160,7 +160,7 @@ function inspectClaudeDoctor(settings) {
|
|
|
160
160
|
issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
|
|
161
161
|
}
|
|
162
162
|
if (trackedMode !== 'none' && detectedMode === 'none' && trackedMode !== 'global') {
|
|
163
|
-
issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI
|
|
163
|
+
issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应的受管文件或配置', 'Config says this CLI is installed, but no managed artifacts were detected'))
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
const status = summarizeDoctorStatus(issues, { host, trackedMode, detectedMode })
|
|
@@ -188,8 +188,8 @@ function inspectGeminiDoctor(settings) {
|
|
|
188
188
|
issues.push(buildDoctorIssue('tracked-mode-mismatch', '记录模式与检测模式不一致', 'Tracked mode does not match detected mode'))
|
|
189
189
|
}
|
|
190
190
|
if (detectedMode === 'standby') {
|
|
191
|
-
if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby
|
|
192
|
-
if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby
|
|
191
|
+
if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 规则文件缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
|
|
192
|
+
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'))
|
|
193
193
|
if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby home 链接缺失或未指向当前包根目录', 'Standby home link is missing or points to a different package root'))
|
|
194
194
|
if (!checks.settingsHooks) issues.push(buildDoctorIssue('standby-hooks-missing', 'standby settings hooks 缺失', 'Standby settings hooks are missing'))
|
|
195
195
|
if (checks.settingsHooks && !checks.settingsHooksMatch) issues.push(buildDoctorIssue('standby-hooks-drift', 'standby settings hooks 与当前 hooks 配置不一致', 'Standby settings hooks differ from the current hook configuration'))
|
|
@@ -207,7 +207,7 @@ function inspectGeminiDoctor(settings) {
|
|
|
207
207
|
issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
|
|
208
208
|
}
|
|
209
209
|
if (trackedMode !== 'none' && detectedMode === 'none' && trackedMode !== 'global') {
|
|
210
|
-
issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI
|
|
210
|
+
issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应的受管文件或配置', 'Config says this CLI is installed, but no managed artifacts were detected'))
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
const status = summarizeDoctorStatus(issues, { host, trackedMode, detectedMode })
|
|
@@ -215,31 +215,32 @@ function inspectGeminiDoctor(settings) {
|
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
function appendCodexStandbyIssues(issues, checks) {
|
|
218
|
-
if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby
|
|
219
|
-
if (checks.carrierMarker && !checks.carrierContentMatch) issues.push(buildDoctorIssue('standby-carrier-drift', 'standby
|
|
218
|
+
if (!checks.carrierMarker) issues.push(buildDoctorIssue('standby-carrier-missing', 'standby 规则文件缺少 HELLOAGENTS 标记', 'Standby carrier is missing the HELLOAGENTS marker'))
|
|
219
|
+
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'))
|
|
220
220
|
if (!checks.homeLink) issues.push(buildDoctorIssue('standby-link-missing', 'standby home 链接缺失或未指向当前包根目录', 'Standby home link is missing or points to a different package root'))
|
|
221
|
-
if (checks.modelInstructionsFile) issues.push(buildDoctorIssue('standby-model-instructions-
|
|
221
|
+
if (!checks.modelInstructionsFile) issues.push(buildDoctorIssue('standby-model-instructions-missing', 'standby config 缺少受管 model_instructions_file', 'Standby config is missing the managed model_instructions_file'))
|
|
222
|
+
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`'))
|
|
222
223
|
if (!checks.codexNotify) issues.push(buildDoctorIssue('standby-notify-missing', 'standby notify 配置缺失', 'Standby notify configuration is missing'))
|
|
223
224
|
if (checks.codexNotify && !checks.notifyPathMatch) issues.push(buildDoctorIssue('standby-notify-drift', 'standby notify 路径未指向当前包根目录', 'Standby notify path does not point to the current package root'))
|
|
224
|
-
if (!checks.developerInstructions) issues.push(buildDoctorIssue('standby-developer-instructions-missing', 'standby developer_instructions 缺失', 'Standby developer_instructions block is missing'))
|
|
225
225
|
if (checks.pluginRoot || checks.pluginCache || checks.marketplaceEntry || checks.pluginEnabled || checks.globalNotifyPath) {
|
|
226
|
-
issues.push(buildDoctorIssue('standby-global-residue', 'standby 模式下仍残留 global
|
|
226
|
+
issues.push(buildDoctorIssue('standby-global-residue', 'standby 模式下仍残留 global 插件文件或配置', 'Global plugin artifacts still remain while Codex is in standby mode'))
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
function appendCodexGlobalIssues(issues, checks, pluginVersion, cacheVersion) {
|
|
231
|
-
if (!checks.carrierMarker) issues.push(buildDoctorIssue('global-home-carrier-missing', 'global `~/.codex/AGENTS.md` 缺少 HelloAGENTS
|
|
231
|
+
if (!checks.carrierMarker) issues.push(buildDoctorIssue('global-home-carrier-missing', 'global `~/.codex/AGENTS.md` 缺少 HelloAGENTS 规则内容', 'Global `~/.codex/AGENTS.md` is missing the HelloAGENTS carrier'))
|
|
232
232
|
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'))
|
|
233
|
+
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'))
|
|
233
234
|
if (!checks.pluginRoot) issues.push(buildDoctorIssue('global-plugin-root-missing', 'global 插件根目录缺失', 'Global plugin root is missing'))
|
|
234
235
|
if (!checks.pluginCache) issues.push(buildDoctorIssue('global-plugin-cache-missing', 'global 插件缓存目录缺失', 'Global plugin cache directory is missing'))
|
|
235
236
|
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'))
|
|
236
237
|
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'))
|
|
237
238
|
if (!checks.marketplaceEntry) issues.push(buildDoctorIssue('global-marketplace-missing', 'global marketplace 条目缺失', 'Global marketplace entry is missing'))
|
|
238
239
|
if (!checks.pluginEnabled) issues.push(buildDoctorIssue('global-plugin-disabled', 'global config 中缺少插件启用段', 'Global plugin enablement block is missing from config'))
|
|
239
|
-
if (checks.modelInstructionsFile) issues.push(buildDoctorIssue('global-model-instructions-
|
|
240
|
+
if (!checks.modelInstructionsFile) issues.push(buildDoctorIssue('global-model-instructions-missing', 'global config 缺少受管 model_instructions_file', 'Global config is missing the managed model_instructions_file'))
|
|
241
|
+
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`'))
|
|
240
242
|
if (!checks.globalNotifyPath) issues.push(buildDoctorIssue('global-notify-missing', 'global notify 路径缺失', 'Global notify path is missing'))
|
|
241
243
|
if (checks.globalNotifyPath && !checks.globalNotifyPathMatch) issues.push(buildDoctorIssue('global-notify-drift', 'global notify 路径未指向当前插件根目录', 'Global notify path does not point to the current plugin root'))
|
|
242
|
-
if (!checks.developerInstructions) issues.push(buildDoctorIssue('global-developer-instructions-missing', 'global developer_instructions 缺失', 'Global developer_instructions block is missing'))
|
|
243
244
|
if (pluginVersion && !checks.pluginVersionMatch) issues.push(buildDoctorIssue('global-plugin-version-drift', 'global 插件根目录版本与当前包版本不一致', 'Global plugin root version does not match the current package version'))
|
|
244
245
|
if (cacheVersion && !checks.pluginCacheVersionMatch) issues.push(buildDoctorIssue('global-plugin-cache-version-drift', 'global 插件缓存版本与当前包版本不一致', 'Global plugin cache version does not match the current package version'))
|
|
245
246
|
if (checks.homeLink) {
|
|
@@ -258,19 +259,26 @@ function inspectCodexDoctor(settings) {
|
|
|
258
259
|
const marketplace = safeJson(join(runtime.home, '.agents', 'plugins', 'marketplace.json')) || {}
|
|
259
260
|
const pluginVersion = safeJson(join(pluginRoot, 'package.json'))?.version || ''
|
|
260
261
|
const cacheVersion = safeJson(join(pluginCacheRoot, 'package.json'))?.version || ''
|
|
262
|
+
const homeLinkTarget = safeRealTarget(join(codexDir, 'helloagents'))
|
|
263
|
+
const pkgRootTarget = safeRealTarget(runtime.pkgRoot) || normalizePath(runtime.pkgRoot)
|
|
264
|
+
const pluginRootTarget = safeRealTarget(pluginRoot) || normalizePath(pluginRoot)
|
|
261
265
|
const standbyNotifyPath = normalizePath(join(runtime.pkgRoot, 'scripts', 'notify.mjs'))
|
|
262
266
|
const globalNotifyPath = normalizePath(join(pluginRoot, 'scripts', 'notify.mjs'))
|
|
267
|
+
const managedHomeCarrierPath = normalizePath(join(codexDir, 'AGENTS.md'))
|
|
268
|
+
const modelInstructionsLine = readTopLevelTomlLine(codexConfig, 'model_instructions_file')
|
|
263
269
|
const expectedHomeCarrier = (detectedMode === 'global' || (detectedMode === 'none' && trackedMode === 'global'))
|
|
264
270
|
? 'bootstrap.md'
|
|
265
271
|
: 'bootstrap-lite.md'
|
|
266
272
|
const checks = {
|
|
267
273
|
carrierMarker: (safeRead(join(codexDir, 'AGENTS.md')) || '').includes('HELLOAGENTS_START'),
|
|
268
274
|
carrierContentMatch: extractManagedCarrierContent(join(codexDir, 'AGENTS.md')) === readBootstrapContent(expectedHomeCarrier),
|
|
269
|
-
homeLink:
|
|
270
|
-
|
|
275
|
+
homeLink: homeLinkTarget === pkgRootTarget,
|
|
276
|
+
globalHomeLink: homeLinkTarget === pluginRootTarget,
|
|
277
|
+
modelInstructionsFile: !!modelInstructionsLine,
|
|
278
|
+
modelInstructionsPathMatch: !!modelInstructionsLine
|
|
279
|
+
&& normalizePath(modelInstructionsLine).includes(`"${managedHomeCarrierPath}"`),
|
|
271
280
|
codexNotify: codexConfig.includes('codex-notify'),
|
|
272
281
|
notifyPathMatch: codexConfig.includes(standbyNotifyPath),
|
|
273
|
-
developerInstructions: codexConfig.includes('HelloAGENTS'),
|
|
274
282
|
pluginRoot: existsSync(pluginRoot),
|
|
275
283
|
pluginCache: existsSync(pluginCacheRoot),
|
|
276
284
|
pluginCarrierMatch: normalizeText(safeRead(join(pluginRoot, 'AGENTS.md')) || '') === readBootstrapContent('bootstrap.md'),
|
|
@@ -298,7 +306,7 @@ function inspectCodexDoctor(settings) {
|
|
|
298
306
|
issues.push(buildDoctorIssue('untracked-managed-state', '检测到受管状态,但配置中未记录该 CLI 模式', 'Managed state detected but this CLI mode is not tracked in config'))
|
|
299
307
|
}
|
|
300
308
|
if (trackedMode !== 'none' && detectedMode === 'none') {
|
|
301
|
-
issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI
|
|
309
|
+
issues.push(buildDoctorIssue('tracked-state-missing', '配置记录该 CLI 已安装,但未检测到对应的受管文件或配置', 'Config says this CLI is installed, but no managed artifacts were detected'))
|
|
302
310
|
}
|
|
303
311
|
if (!checks.pluginVersionMatch && !pluginVersion && detectedMode === 'global') {
|
|
304
312
|
notes.push(runtime.msg('未读到 global 插件根目录版本信息', 'Global plugin root version was not readable'))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
1
|
+
import { existsSync, realpathSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import {
|
|
@@ -27,6 +27,18 @@ function hasHelloagentsSettings(filePath) {
|
|
|
27
27
|
return JSON.stringify(safeJson(filePath) || {}).includes('helloagents')
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function normalizePath(value = '') {
|
|
31
|
+
return String(value || '').replace(/\\/g, '/').toLowerCase()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function safeRealTarget(linkPath) {
|
|
35
|
+
try {
|
|
36
|
+
return normalizePath(realpathSync(linkPath))
|
|
37
|
+
} catch {
|
|
38
|
+
return ''
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
function detectClaudeMode(home) {
|
|
31
43
|
const claudeDir = join(home, '.claude')
|
|
32
44
|
if (
|
|
@@ -53,19 +65,23 @@ function detectGeminiMode(home) {
|
|
|
53
65
|
|
|
54
66
|
function detectCodexMode(home) {
|
|
55
67
|
const codexDir = join(home, '.codex')
|
|
68
|
+
const codexHomeLink = join(codexDir, 'helloagents')
|
|
56
69
|
const codexConfig = safeRead(join(codexDir, 'config.toml')) || ''
|
|
57
70
|
const marketplace = safeRead(join(home, '.agents', 'plugins', 'marketplace.json')) || ''
|
|
71
|
+
const globalPluginRoot = normalizePath(join(home, 'plugins', CODEX_PLUGIN_NAME))
|
|
72
|
+
const codexHomeLinkTarget = safeRealTarget(codexHomeLink)
|
|
58
73
|
if (
|
|
59
74
|
existsSync(join(home, 'plugins', CODEX_PLUGIN_NAME))
|
|
60
75
|
|| existsSync(join(codexDir, 'plugins', 'cache', CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_NAME))
|
|
61
76
|
|| marketplace.includes(`"name": "${CODEX_PLUGIN_NAME}"`)
|
|
62
77
|
|| codexConfig.includes(CODEX_PLUGIN_KEY)
|
|
63
78
|
|| codexConfig.includes(`/plugins/${CODEX_PLUGIN_NAME}/scripts/notify.mjs`)
|
|
79
|
+
|| codexHomeLinkTarget === globalPluginRoot
|
|
64
80
|
) {
|
|
65
81
|
return 'global'
|
|
66
82
|
}
|
|
67
83
|
if (
|
|
68
|
-
existsSync(
|
|
84
|
+
(existsSync(codexHomeLink) && codexHomeLinkTarget !== globalPluginRoot)
|
|
69
85
|
|| hasHelloagentsMarker(join(codexDir, 'AGENTS.md'))
|
|
70
86
|
|| codexConfig.includes('codex-notify')
|
|
71
87
|
|| codexConfig.includes('HelloAGENTS')
|
package/scripts/cli-messages.mjs
CHANGED
|
@@ -92,7 +92,7 @@ ${msg('单 CLI 管理', 'Scoped CLI management')}:
|
|
|
92
92
|
${msg('诊断', 'Diagnostics')}:
|
|
93
93
|
helloagents doctor
|
|
94
94
|
helloagents doctor codex --json
|
|
95
|
-
${msg('检查 carrier、链接、hooks、配置注入、Codex
|
|
95
|
+
${msg('检查 carrier、链接、hooks、配置注入、Codex 插件链路、受管 model_instructions_file 指向与版本漂移', 'Checks carriers, links, hooks, config injections, the Codex plugin chain, managed model_instructions_file targeting, and version drift')}
|
|
96
96
|
|
|
97
97
|
${msg('卸载', 'Uninstall')}:
|
|
98
98
|
helloagents cleanup ${msg('(推荐先执行,显式清理所有 CLI 注入/链接)', '(recommended first, explicitly cleans CLI injections/links)')}
|
package/scripts/cli-toml.mjs
CHANGED
|
@@ -33,6 +33,15 @@ function findFirstTomlSectionIndex(text) {
|
|
|
33
33
|
return normalized.length;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function splitTopLevelToml(text) {
|
|
37
|
+
const normalized = String(text || '').replace(/\r\n/g, '\n');
|
|
38
|
+
const topLevelEnd = findFirstTomlSectionIndex(normalized);
|
|
39
|
+
return {
|
|
40
|
+
topLevel: normalized.slice(0, topLevelEnd),
|
|
41
|
+
sections: normalized.slice(topLevelEnd),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
function findTopLevelTomlBlock(text, key) {
|
|
37
46
|
const normalized = String(text || '').replace(/\r\n/g, '\n');
|
|
38
47
|
const topLevelEnd = findFirstTomlSectionIndex(normalized);
|
|
@@ -93,6 +102,27 @@ export function removeTopLevelTomlBlock(text, key) {
|
|
|
93
102
|
return normalizeToml(`${normalized.slice(0, existing.start)}${normalized.slice(existing.end)}`);
|
|
94
103
|
}
|
|
95
104
|
|
|
105
|
+
export function prependTopLevelTomlBlocks(text, blocks) {
|
|
106
|
+
const normalizedBlocks = blocks
|
|
107
|
+
.map((block) => String(block || '').trim())
|
|
108
|
+
.filter(Boolean);
|
|
109
|
+
|
|
110
|
+
const { topLevel, sections } = splitTopLevelToml(text);
|
|
111
|
+
const normalizedTopLevel = topLevel.replace(/^\n+/, '').trimEnd();
|
|
112
|
+
const normalizedSections = sections.replace(/^\n+/, '').trimEnd();
|
|
113
|
+
const remainder = normalizedTopLevel && normalizedSections
|
|
114
|
+
? `${normalizedTopLevel}\n\n${normalizedSections}`
|
|
115
|
+
: normalizedTopLevel || normalizedSections;
|
|
116
|
+
if (!normalizedBlocks.length) return normalizeToml(remainder);
|
|
117
|
+
const managedPrelude = normalizedBlocks.join('\n');
|
|
118
|
+
|
|
119
|
+
return normalizeToml(
|
|
120
|
+
remainder
|
|
121
|
+
? `${managedPrelude}\n\n${remainder}`
|
|
122
|
+
: managedPrelude,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
96
126
|
export function upsertTopLevelTomlKey(text, key, value) {
|
|
97
127
|
const re = new RegExp(`^${key}\\s*=.*$`, 'm');
|
|
98
128
|
const next = re.test(text)
|
|
@@ -205,10 +205,11 @@ function main() {
|
|
|
205
205
|
data = JSON.parse(readFileSync(0, 'utf-8'))
|
|
206
206
|
} catch {}
|
|
207
207
|
const cwd = data.cwd || process.cwd()
|
|
208
|
-
const
|
|
209
|
-
const
|
|
208
|
+
const workflowOptions = { payload: data }
|
|
209
|
+
const snapshot = getWorkflowSnapshot(cwd, workflowOptions)
|
|
210
|
+
const recommendation = getWorkflowRecommendation(cwd, workflowOptions)
|
|
210
211
|
const verificationStatus = getVerifyEvidenceStatus(cwd)
|
|
211
|
-
const deliveryAction = getDeliveryAction(cwd)
|
|
212
|
+
const deliveryAction = getDeliveryAction(cwd, workflowOptions)
|
|
212
213
|
const gatePlans = selectGatePlans(snapshot)
|
|
213
214
|
const reviewStatus = getReviewEvidenceStatus(cwd, {
|
|
214
215
|
required: deliveryAction?.phase === 'verify' && deliveryAction?.mode === 'review-first',
|
|
@@ -248,7 +249,7 @@ function main() {
|
|
|
248
249
|
|
|
249
250
|
process.stdout.write(JSON.stringify({
|
|
250
251
|
decision: 'block',
|
|
251
|
-
reason: buildBlockReason(issues, recommendation, buildDeliveryGateHint(cwd)),
|
|
252
|
+
reason: buildBlockReason(issues, recommendation, buildDeliveryGateHint(cwd, workflowOptions)),
|
|
252
253
|
suppressOutput: true,
|
|
253
254
|
}))
|
|
254
255
|
}
|
package/scripts/guard-rules.mjs
CHANGED
|
@@ -6,10 +6,12 @@ export const DANGEROUS_PATTERNS = [
|
|
|
6
6
|
{ pattern: /(sudo\s+)?rm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+)?(-[a-zA-Z]*f[a-zA-Z]*\s+)?(\/|~|\*)/, reason: 'Recursive delete of critical path' },
|
|
7
7
|
{ pattern: /(sudo\s+)?rm\s+--recursive/, reason: 'Recursive delete (long option)' },
|
|
8
8
|
{ pattern: /(sudo\s+)?rm\s+-[a-zA-Z]*r[a-zA-Z]*\s+\.\.?(\s|$)/, reason: 'Recursive delete of current/parent directory' },
|
|
9
|
+
{ pattern: /\bcmd(?:\.exe)?\s*\/c\b/i, reason: 'Nested cmd invocation bypasses PowerShell safety rules' },
|
|
10
|
+
{ pattern: /\bStart-Process\s+cmd(?:\.exe)?\b/i, reason: 'Nested cmd invocation bypasses PowerShell safety rules' },
|
|
9
11
|
{ pattern: /git\s+push\s+(-f|--force)/, reason: 'Force push (specify branch explicitly)' },
|
|
10
12
|
{ pattern: /git\s+reset\s+--hard/, reason: 'Hard reset (destructive operation)' },
|
|
11
13
|
{ pattern: /DROP\s+(DATABASE|TABLE|SCHEMA)/i, reason: 'Database destruction command' },
|
|
12
|
-
{ pattern:
|
|
14
|
+
{ pattern: /\bTRUNCATE(?:\s+TABLE)?\b/i, reason: 'Table truncation' },
|
|
13
15
|
{ pattern: /chmod\s+777/, reason: 'World-writable permissions' },
|
|
14
16
|
{ pattern: /mkfs\b/, reason: 'Filesystem format command' },
|
|
15
17
|
{ pattern: /dd\s+.*of=\/dev\//, reason: 'Direct device write' },
|
|
@@ -68,6 +70,29 @@ export function scanHighRiskCommands(command) {
|
|
|
68
70
|
return warnings
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
export function scanShellSafetyWarnings(command = '') {
|
|
74
|
+
const warnings = []
|
|
75
|
+
const normalized = String(command || '')
|
|
76
|
+
|
|
77
|
+
if (/\bpowershell(?:\.exe)?\b/i.test(normalized) && /\s-Command\b/i.test(normalized)) {
|
|
78
|
+
const inlineScript = normalized.split(/\s-Command\b/i).slice(1).join(' ').trim()
|
|
79
|
+
const logicalLines = inlineScript
|
|
80
|
+
.split(/[;\r\n]+/)
|
|
81
|
+
.map((entry) => entry.trim())
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
if (logicalLines.length > 3) {
|
|
84
|
+
warnings.push('PowerShell inline script exceeds 3 logical lines; prefer a temporary .ps1 file')
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const fileOps = normalized.match(/\b(remove-item|move-item|copy-item|new-item|set-content|add-content|out-file|mkdir|md|touch|cp|copy|mv|move|ren|rename|del|erase|rm|rmdir)\b/ig) || []
|
|
89
|
+
if (fileOps.length > 1 && /[;\r\n]/.test(normalized)) {
|
|
90
|
+
warnings.push('Multiple file operations are chained in one shell command; split them into separate commands')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return warnings
|
|
94
|
+
}
|
|
95
|
+
|
|
71
96
|
export function scanUnrequestedFiles(filePath, toolName) {
|
|
72
97
|
if (!filePath || toolName?.toLowerCase() !== 'write') return []
|
|
73
98
|
const basename = filePath.split(/[/\\]/).pop() || ''
|
package/scripts/guard.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
scanEnvCoverage,
|
|
19
19
|
scanForSecrets,
|
|
20
20
|
scanHighRiskCommands,
|
|
21
|
+
scanShellSafetyWarnings,
|
|
21
22
|
scanUnrequestedFiles,
|
|
22
23
|
} from './guard-rules.mjs'
|
|
23
24
|
|
|
@@ -61,15 +62,16 @@ function emitGuardEvent(cwd, event, source, reason, details = {}) {
|
|
|
61
62
|
})
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
function buildHighRiskGate(matches, cwd) {
|
|
65
|
-
const
|
|
65
|
+
function buildHighRiskGate(matches, cwd, payload = {}) {
|
|
66
|
+
const workflowOptions = { payload }
|
|
67
|
+
const stateSyncHint = buildStateSyncHint(cwd, workflowOptions)
|
|
66
68
|
if (stateSyncHint) {
|
|
67
69
|
return {
|
|
68
70
|
reason: `[HelloAGENTS Guard] Blocked T3 command until project recovery state is synced.\n${stateSyncHint}`,
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
const recommendation = getWorkflowRecommendation(cwd)
|
|
74
|
+
const recommendation = getWorkflowRecommendation(cwd, workflowOptions)
|
|
73
75
|
if (!recommendation) return null
|
|
74
76
|
if (matches.some((match) => match.gate === 'post-verify')) {
|
|
75
77
|
return {
|
|
@@ -122,7 +124,7 @@ function buildPostWriteWarnings(data) {
|
|
|
122
124
|
const filePath = data.tool_input?.file_path || ''
|
|
123
125
|
return [
|
|
124
126
|
...(detectIdeaBoundaryContext(data)?.zeroSideEffect
|
|
125
|
-
? ['~idea
|
|
127
|
+
? ['~idea 本轮要求只读探索;检测到写入工具落地,请回退到探索输出或升级到 ~plan / ~build / ~prd / ~auto 后再修改文件']
|
|
126
128
|
: []),
|
|
127
129
|
...scanUnrequestedFiles(filePath, data.tool_name),
|
|
128
130
|
...(content ? [...scanForSecrets(content), ...scanDangerousPackages(content, filePath)] : []),
|
|
@@ -168,10 +170,10 @@ function handleDangerousCommand(data, command) {
|
|
|
168
170
|
|
|
169
171
|
function handleHighRiskCommand(data, command) {
|
|
170
172
|
const warnings = scanHighRiskCommands(command)
|
|
171
|
-
if (warnings.length === 0) return
|
|
173
|
+
if (warnings.length === 0) return []
|
|
172
174
|
|
|
173
175
|
const cwd = data.cwd || process.cwd()
|
|
174
|
-
const gate = buildHighRiskGate(warnings, cwd)
|
|
176
|
+
const gate = buildHighRiskGate(warnings, cwd, data)
|
|
175
177
|
if (gate) {
|
|
176
178
|
emitHookPayload({
|
|
177
179
|
hookSpecificOutput: {
|
|
@@ -185,20 +187,43 @@ function handleHighRiskCommand(data, command) {
|
|
|
185
187
|
guardType: 'high-risk-gate',
|
|
186
188
|
matches: warnings.map((warning) => warning.reason),
|
|
187
189
|
})
|
|
188
|
-
return
|
|
190
|
+
return null
|
|
191
|
+
}
|
|
192
|
+
return warnings.map((warning) => warning.reason)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function emitShellWarnings(data, command, highRiskWarnings, shellSafetyWarnings) {
|
|
196
|
+
const sections = []
|
|
197
|
+
if (highRiskWarnings.length > 0) {
|
|
198
|
+
sections.push(`⚠️ [HelloAGENTS 高风险链路提醒] 检测到高风险命令:\n${highRiskWarnings.map((warning) => ` - ${warning}`).join('\n')}\n请确认已完成相应规划/审查并获得必要授权。`)
|
|
199
|
+
}
|
|
200
|
+
if (shellSafetyWarnings.length > 0) {
|
|
201
|
+
sections.push(`⚠️ [HelloAGENTS Shell 安全提醒] 检测到建议调整的命令写法:\n${shellSafetyWarnings.map((warning) => ` - ${warning}`).join('\n')}\n当前仅提示,不中断执行。`)
|
|
189
202
|
}
|
|
203
|
+
if (sections.length === 0) return
|
|
190
204
|
|
|
191
205
|
emitHookPayload({
|
|
192
206
|
hookSpecificOutput: {
|
|
193
207
|
hookEventName: HOOK_EVENT,
|
|
194
|
-
additionalContext:
|
|
208
|
+
additionalContext: sections.join('\n\n'),
|
|
195
209
|
},
|
|
196
210
|
})
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
211
|
+
|
|
212
|
+
const cwd = data.cwd || process.cwd()
|
|
213
|
+
if (highRiskWarnings.length > 0) {
|
|
214
|
+
emitGuardEvent(cwd, 'guard_warning', 'command', '', {
|
|
215
|
+
guardType: 'high-risk-warning',
|
|
216
|
+
command: command.slice(0, 200),
|
|
217
|
+
warnings: highRiskWarnings,
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
if (shellSafetyWarnings.length > 0) {
|
|
221
|
+
emitGuardEvent(cwd, 'guard_warning', 'command', '', {
|
|
222
|
+
guardType: 'shell-safety-warning',
|
|
223
|
+
command: command.slice(0, 200),
|
|
224
|
+
warnings: shellSafetyWarnings,
|
|
225
|
+
})
|
|
226
|
+
}
|
|
202
227
|
}
|
|
203
228
|
|
|
204
229
|
function handleShellCommand(data) {
|
|
@@ -217,7 +242,11 @@ function handleShellCommand(data) {
|
|
|
217
242
|
}
|
|
218
243
|
|
|
219
244
|
if (handleDangerousCommand(data, command)) return
|
|
220
|
-
handleHighRiskCommand(data, command)
|
|
245
|
+
const highRiskWarnings = handleHighRiskCommand(data, command)
|
|
246
|
+
if (highRiskWarnings === null) return
|
|
247
|
+
|
|
248
|
+
const shellSafetyWarnings = scanShellSafetyWarnings(command)
|
|
249
|
+
emitShellWarnings(data, command, highRiskWarnings, shellSafetyWarnings)
|
|
221
250
|
}
|
|
222
251
|
|
|
223
252
|
async function main() {
|