helloagents 3.0.8-beta.1 → 3.0.10-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 +18 -9
- package/README_CN.md +42 -33
- package/bootstrap-lite.md +18 -17
- package/bootstrap.md +22 -21
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/scripts/capability-registry.mjs +5 -5
- package/scripts/cli-codex-config.mjs +8 -2
- package/scripts/cli-codex.mjs +7 -3
- package/scripts/cli-doctor.mjs +6 -1
- package/scripts/cli-host-detect.mjs +18 -2
- package/scripts/cli-lifecycle.mjs +11 -0
- package/scripts/cli-messages.mjs +3 -3
- package/scripts/cli-toml-values.mjs +25 -0
- package/scripts/cli-toml.mjs +15 -0
- package/scripts/delivery-gate.mjs +5 -4
- package/scripts/guard.mjs +7 -6
- package/scripts/notify-context.mjs +32 -30
- package/scripts/notify-events.mjs +0 -8
- package/scripts/notify-gates.mjs +128 -0
- package/scripts/notify-route.mjs +5 -2
- package/scripts/notify-source.mjs +3 -60
- package/scripts/notify-ui.mjs +3 -0
- package/scripts/notify.mjs +72 -83
- package/scripts/project-storage.mjs +107 -15
- package/scripts/session-token.mjs +73 -0
- package/scripts/workflow-core.mjs +16 -8
- package/scripts/workflow-plan-files.mjs +17 -6
- package/scripts/workflow-recommendation.mjs +4 -4
- package/scripts/workflow-state.mjs +13 -13
- package/skills/commands/auto/SKILL.md +13 -13
- package/skills/commands/build/SKILL.md +5 -5
- package/skills/commands/clean/SKILL.md +6 -6
- package/skills/commands/commit/SKILL.md +2 -2
- package/skills/commands/help/SKILL.md +2 -2
- package/skills/commands/idea/SKILL.md +5 -5
- package/skills/commands/init/SKILL.md +4 -4
- package/skills/commands/loop/SKILL.md +4 -4
- package/skills/commands/plan/SKILL.md +13 -13
- package/skills/commands/prd/SKILL.md +13 -13
- package/skills/commands/verify/SKILL.md +3 -3
- package/skills/commands/wiki/SKILL.md +5 -5
- package/skills/hello-subagent/SKILL.md +1 -1
- package/skills/hello-ui/SKILL.md +3 -3
- package/skills/helloagents/SKILL.md +3 -2
- package/templates/plans/contract.json +2 -2
|
@@ -27,7 +27,13 @@ export function isManagedCodexModelInstruction(line = '') {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function isManagedCodexNotify(line = '') {
|
|
30
|
-
|
|
30
|
+
const value = String(line || '').replace(/\\/g, '/')
|
|
31
|
+
return value.includes(CODEX_MANAGED_TOML_COMMENT)
|
|
32
|
+
|| (
|
|
33
|
+
value.includes('codex-notify')
|
|
34
|
+
&& value.includes('/scripts/notify.mjs')
|
|
35
|
+
&& /(^|[/\\])helloagents([/\\]|-|$)|[/\\]plugins[/\\]helloagents[/\\]/i.test(value)
|
|
36
|
+
)
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
export function isManagedCodexBackupInstruction(line = '') {
|
|
@@ -51,7 +57,7 @@ function formatManagedCodexNotifyValue(notifyScriptPath) {
|
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
function formatManagedCodexNotifyLine(notifyScriptPath) {
|
|
54
|
-
return `notify = ${formatManagedCodexNotifyValue(notifyScriptPath)}`
|
|
60
|
+
return `notify = ${formatManagedCodexNotifyValue(notifyScriptPath)} ${CODEX_MANAGED_TOML_COMMENT}`
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
function removeTopLevelLinesBeingReplaced(toml, lines) {
|
package/scripts/cli-codex.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from './cli-codex-config.mjs';
|
|
21
21
|
import {
|
|
22
22
|
readTopLevelTomlLine,
|
|
23
|
+
readTopLevelTomlBlock,
|
|
23
24
|
readTomlKeyInSection,
|
|
24
25
|
removeTomlKeyInSection,
|
|
25
26
|
ensureTomlKeyInSection,
|
|
@@ -98,7 +99,8 @@ function removeCodexMarketplaceEntry(marketplaceFile) {
|
|
|
98
99
|
}
|
|
99
100
|
if (!removedHelloagents) return false;
|
|
100
101
|
if (!nextPlugins.length) {
|
|
101
|
-
|
|
102
|
+
marketplace.plugins = [];
|
|
103
|
+
safeWrite(marketplaceFile, JSON.stringify(marketplace, null, 2) + '\n');
|
|
102
104
|
return true;
|
|
103
105
|
}
|
|
104
106
|
marketplace.plugins = nextPlugins;
|
|
@@ -130,7 +132,7 @@ function cleanupCodexManagedConfig(configPath, { removePluginConfig = false } =
|
|
|
130
132
|
let toml = safeRead(configPath) || '';
|
|
131
133
|
|
|
132
134
|
const currentModelInstructions = readTopLevelTomlLine(toml, 'model_instructions_file');
|
|
133
|
-
const currentNotify =
|
|
135
|
+
const currentNotify = readTopLevelTomlBlock(toml, 'notify');
|
|
134
136
|
const currentCodexHooks = readTomlKeyInSection(toml, '[features]', 'codex_hooks');
|
|
135
137
|
|
|
136
138
|
const shouldRestoreModelInstructions = isManagedCodexModelInstruction(currentModelInstructions);
|
|
@@ -153,7 +155,7 @@ function cleanupCodexManagedConfig(configPath, { removePluginConfig = false } =
|
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
const backupModelInstructions = readTopLevelTomlLine(backupToml, 'model_instructions_file');
|
|
156
|
-
const backupNotify =
|
|
158
|
+
const backupNotify = readTopLevelTomlBlock(backupToml, 'notify');
|
|
157
159
|
const backupCodexHooks = readTomlKeyInSection(backupToml, '[features]', 'codex_hooks');
|
|
158
160
|
|
|
159
161
|
toml = restoreCodexTopLevelConfig(toml, {
|
|
@@ -248,6 +250,7 @@ export function installCodexGlobal(home, pkgRoot) {
|
|
|
248
250
|
|
|
249
251
|
copyEntries(pkgRoot, pluginRoot, CODEX_RUNTIME_ENTRIES);
|
|
250
252
|
copyEntries(pkgRoot, installedPluginRoot, CODEX_RUNTIME_ENTRIES);
|
|
253
|
+
createLink(pluginRoot, join(codexDir, 'helloagents'));
|
|
251
254
|
writeCodexRuntimeCarrier(
|
|
252
255
|
join(pluginRoot, CODEX_RUNTIME_CARRIER),
|
|
253
256
|
join(pluginRoot, 'bootstrap.md'),
|
|
@@ -286,6 +289,7 @@ export function uninstallCodexGlobal(home) {
|
|
|
286
289
|
removeIfExists(pluginCacheRoot);
|
|
287
290
|
removeCodexMarketplaceEntry(marketplaceFile);
|
|
288
291
|
removeMarkedContent(join(codexDir, 'AGENTS.md'));
|
|
292
|
+
removeLink(join(codexDir, 'helloagents'));
|
|
289
293
|
|
|
290
294
|
const toml = cleanupCodexManagedConfig(configPath, { removePluginConfig: true });
|
|
291
295
|
if (toml.trim()) safeWrite(configPath, toml);
|
package/scripts/cli-doctor.mjs
CHANGED
|
@@ -230,6 +230,7 @@ function appendCodexStandbyIssues(issues, checks) {
|
|
|
230
230
|
function appendCodexGlobalIssues(issues, checks, pluginVersion, cacheVersion) {
|
|
231
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'))
|
|
@@ -258,6 +259,9 @@ 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'))
|
|
263
267
|
const managedHomeCarrierPath = normalizePath(join(codexDir, 'AGENTS.md'))
|
|
@@ -268,7 +272,8 @@ function inspectCodexDoctor(settings) {
|
|
|
268
272
|
const checks = {
|
|
269
273
|
carrierMarker: (safeRead(join(codexDir, 'AGENTS.md')) || '').includes('HELLOAGENTS_START'),
|
|
270
274
|
carrierContentMatch: extractManagedCarrierContent(join(codexDir, 'AGENTS.md')) === readBootstrapContent(expectedHomeCarrier),
|
|
271
|
-
homeLink:
|
|
275
|
+
homeLink: homeLinkTarget === pkgRootTarget,
|
|
276
|
+
globalHomeLink: homeLinkTarget === pluginRootTarget,
|
|
272
277
|
modelInstructionsFile: !!modelInstructionsLine,
|
|
273
278
|
modelInstructionsPathMatch: !!modelInstructionsLine
|
|
274
279
|
&& normalizePath(modelInstructionsLine).includes(`"${managedHomeCarrierPath}"`),
|
|
@@ -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')
|
|
@@ -181,6 +181,17 @@ function runAllHostsLifecycle(action, explicitMode) {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
const settings = readSettings(true)
|
|
184
|
+
if (action === 'update' && !explicitMode) {
|
|
185
|
+
for (const host of HOSTS) {
|
|
186
|
+
const mode = resolveHostMode(host, '', settings)
|
|
187
|
+
const result = runHostLifecycle(runtime, action, host, mode)
|
|
188
|
+
if (!result.skipped) setTrackedHostMode(settings, host, mode)
|
|
189
|
+
}
|
|
190
|
+
writeSettings(settings)
|
|
191
|
+
runtime.printInstallMsg(settings.install_mode || DEFAULTS.install_mode, 'refresh')
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
184
195
|
const mode = resolveInstallMode(explicitMode, settings)
|
|
185
196
|
if (explicitMode) settings.install_mode = explicitMode
|
|
186
197
|
installAllHosts(runtime, mode)
|
package/scripts/cli-messages.mjs
CHANGED
|
@@ -44,8 +44,8 @@ function renderInstallMessage(context, mode, state) {
|
|
|
44
44
|
}
|
|
45
45
|
return msg(
|
|
46
46
|
refresh
|
|
47
|
-
? ' global 模式已刷新。\n Claude Code / Gemini 请保持插件已安装;Codex
|
|
48
|
-
: ' 所有项目将自动启用完整 HelloAGENTS 规则。\n Claude Code / Gemini 请手动安装插件;Codex
|
|
47
|
+
? ' global 模式已刷新。\n Claude Code / Gemini 请保持插件已安装;Codex 原生本地插件已重装并同步最新文件。'
|
|
48
|
+
: ' 所有项目将自动启用完整 HelloAGENTS 规则。\n Claude Code / Gemini 请手动安装插件;Codex 已自动安装原生本地插件。',
|
|
49
49
|
refresh
|
|
50
50
|
? ' Global mode refreshed.\n Keep Claude Code / Gemini plugins installed; Codex native local-plugin files were reinstalled and synced.'
|
|
51
51
|
: ' All projects will use full HelloAGENTS rules.\n Install Claude Code / Gemini plugins manually; Codex now uses the native local-plugin path automatically.',
|
|
@@ -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, Codex plugin installation, 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)')}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function getTomlArrayDepthDelta(text) {
|
|
2
|
+
let depth = 0
|
|
3
|
+
let quoted = false
|
|
4
|
+
let escaped = false
|
|
5
|
+
|
|
6
|
+
for (const char of String(text || '')) {
|
|
7
|
+
if (escaped) {
|
|
8
|
+
escaped = false
|
|
9
|
+
continue
|
|
10
|
+
}
|
|
11
|
+
if (char === '\\' && quoted) {
|
|
12
|
+
escaped = true
|
|
13
|
+
continue
|
|
14
|
+
}
|
|
15
|
+
if (char === '"') {
|
|
16
|
+
quoted = !quoted
|
|
17
|
+
continue
|
|
18
|
+
}
|
|
19
|
+
if (quoted) continue
|
|
20
|
+
if (char === '[') depth += 1
|
|
21
|
+
if (char === ']') depth -= 1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return depth
|
|
25
|
+
}
|
package/scripts/cli-toml.mjs
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Targets the small subset of TOML structures used by Codex CLI config.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { getTomlArrayDepthDelta } from './cli-toml-values.mjs'
|
|
7
|
+
|
|
6
8
|
export function isTomlTableHeader(line) {
|
|
7
9
|
const trimmed = String(line || '').trim();
|
|
8
10
|
return trimmed.startsWith('[') && trimmed.endsWith(']');
|
|
@@ -62,6 +64,19 @@ function findTopLevelTomlBlock(text, key) {
|
|
|
62
64
|
const closeIndex = normalized.indexOf('"""', openIndex + 3);
|
|
63
65
|
end = closeIndex >= 0 ? closeIndex + 3 : normalized.length;
|
|
64
66
|
}
|
|
67
|
+
if (value.startsWith('[')) {
|
|
68
|
+
let depth = getTomlArrayDepthDelta(firstLine.slice(firstLine.indexOf('=') + 1));
|
|
69
|
+
let lineStart = firstLineEnd + (normalized[firstLineEnd] === '\n' ? 1 : 0);
|
|
70
|
+
|
|
71
|
+
while (depth > 0 && lineStart < normalized.length) {
|
|
72
|
+
const lineEndIndex = normalized.indexOf('\n', lineStart);
|
|
73
|
+
const nextLineEnd = lineEndIndex >= 0 ? lineEndIndex : normalized.length;
|
|
74
|
+
const nextLine = normalized.slice(lineStart, nextLineEnd);
|
|
75
|
+
depth += getTomlArrayDepthDelta(nextLine);
|
|
76
|
+
end = nextLineEnd;
|
|
77
|
+
lineStart = nextLineEnd + 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
65
80
|
|
|
66
81
|
while (end < normalized.length && normalized[end] === '\n') {
|
|
67
82
|
end += 1;
|
|
@@ -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.mjs
CHANGED
|
@@ -62,15 +62,16 @@ function emitGuardEvent(cwd, event, source, reason, details = {}) {
|
|
|
62
62
|
})
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
function buildHighRiskGate(matches, cwd) {
|
|
66
|
-
const
|
|
65
|
+
function buildHighRiskGate(matches, cwd, payload = {}) {
|
|
66
|
+
const workflowOptions = { payload }
|
|
67
|
+
const stateSyncHint = buildStateSyncHint(cwd, workflowOptions)
|
|
67
68
|
if (stateSyncHint) {
|
|
68
69
|
return {
|
|
69
70
|
reason: `[HelloAGENTS Guard] Blocked T3 command until project recovery state is synced.\n${stateSyncHint}`,
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
const recommendation = getWorkflowRecommendation(cwd)
|
|
74
|
+
const recommendation = getWorkflowRecommendation(cwd, workflowOptions)
|
|
74
75
|
if (!recommendation) return null
|
|
75
76
|
if (matches.some((match) => match.gate === 'post-verify')) {
|
|
76
77
|
return {
|
|
@@ -123,7 +124,7 @@ function buildPostWriteWarnings(data) {
|
|
|
123
124
|
const filePath = data.tool_input?.file_path || ''
|
|
124
125
|
return [
|
|
125
126
|
...(detectIdeaBoundaryContext(data)?.zeroSideEffect
|
|
126
|
-
? ['~idea
|
|
127
|
+
? ['~idea 本轮要求只读探索;检测到写入文件的工具调用,请回到探索输出,或升级到 ~plan / ~build / ~prd / ~auto 后再修改文件']
|
|
127
128
|
: []),
|
|
128
129
|
...scanUnrequestedFiles(filePath, data.tool_name),
|
|
129
130
|
...(content ? [...scanForSecrets(content), ...scanDangerousPackages(content, filePath)] : []),
|
|
@@ -172,7 +173,7 @@ function handleHighRiskCommand(data, command) {
|
|
|
172
173
|
if (warnings.length === 0) return []
|
|
173
174
|
|
|
174
175
|
const cwd = data.cwd || process.cwd()
|
|
175
|
-
const gate = buildHighRiskGate(warnings, cwd)
|
|
176
|
+
const gate = buildHighRiskGate(warnings, cwd, data)
|
|
176
177
|
if (gate) {
|
|
177
178
|
emitHookPayload({
|
|
178
179
|
hookSpecificOutput: {
|
|
@@ -194,7 +195,7 @@ function handleHighRiskCommand(data, command) {
|
|
|
194
195
|
function emitShellWarnings(data, command, highRiskWarnings, shellSafetyWarnings) {
|
|
195
196
|
const sections = []
|
|
196
197
|
if (highRiskWarnings.length > 0) {
|
|
197
|
-
sections.push(`⚠️ [HelloAGENTS
|
|
198
|
+
sections.push(`⚠️ [HelloAGENTS 高风险操作提醒] 检测到高风险命令:\n${highRiskWarnings.map((warning) => ` - ${warning}`).join('\n')}\n请确认已完成相应规划/审查并获得必要授权。`)
|
|
198
199
|
}
|
|
199
200
|
if (shellSafetyWarnings.length > 0) {
|
|
200
201
|
sections.push(`⚠️ [HelloAGENTS Shell 安全提醒] 检测到建议调整的命令写法:\n${shellSafetyWarnings.map((warning) => ` - ${warning}`).join('\n')}\n当前仅提示,不中断执行。`)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
-
import { buildCommandRouteHint, buildStateSyncHint, buildWorkflowRouteHint } from './workflow-state.mjs';
|
|
4
|
+
import { buildCommandRouteHint, buildStateSyncHint, buildWorkflowRouteHint, readStateSnapshot } from './workflow-state.mjs';
|
|
5
5
|
import { buildCapabilityHint } from './capability-registry.mjs';
|
|
6
6
|
import {
|
|
7
7
|
buildProjectStorageBlock,
|
|
@@ -69,16 +69,14 @@ export function buildCompactionContext({ payload, pkgRoot, settings, bootstrapFi
|
|
|
69
69
|
summaryParts.push('以下信息在上下文压缩前保存,确保压缩后不丢失关键状态。');
|
|
70
70
|
|
|
71
71
|
const cwd = payload.cwd || process.cwd();
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
summaryParts.push(stateContent);
|
|
81
|
-
} catch {}
|
|
72
|
+
const workflowOptions = { payload };
|
|
73
|
+
const stateSnapshot = readStateSnapshot(cwd, workflowOptions);
|
|
74
|
+
const stateSyncHint = buildStateSyncHint(cwd, workflowOptions);
|
|
75
|
+
if (stateSnapshot.exists && stateSnapshot.content) {
|
|
76
|
+
summaryParts.push('');
|
|
77
|
+
summaryParts.push(`## 状态文件(从 ${stateSnapshot.statePath.replace(/\\/g, '/')} 读取,只用于找回上次停在哪)`);
|
|
78
|
+
summaryParts.push('恢复时先看当前用户消息;如果仍是同一任务,再参考状态文件。');
|
|
79
|
+
summaryParts.push(stateSnapshot.content);
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
let bootstrap = '';
|
|
@@ -103,7 +101,7 @@ export function buildCompactionContext({ payload, pkgRoot, settings, bootstrapFi
|
|
|
103
101
|
summaryParts.push(readRootBlock);
|
|
104
102
|
}
|
|
105
103
|
|
|
106
|
-
const projectStorageBlock = buildProjectStorageBlock(cwd);
|
|
104
|
+
const projectStorageBlock = buildProjectStorageBlock(cwd, workflowOptions);
|
|
107
105
|
if (projectStorageBlock) {
|
|
108
106
|
summaryParts.push('');
|
|
109
107
|
summaryParts.push(projectStorageBlock);
|
|
@@ -111,7 +109,7 @@ export function buildCompactionContext({ payload, pkgRoot, settings, bootstrapFi
|
|
|
111
109
|
|
|
112
110
|
if (stateSyncHint) {
|
|
113
111
|
summaryParts.push('');
|
|
114
|
-
summaryParts.push('##
|
|
112
|
+
summaryParts.push('## 状态文件提醒');
|
|
115
113
|
summaryParts.push(stateSyncHint);
|
|
116
114
|
}
|
|
117
115
|
|
|
@@ -123,13 +121,15 @@ export function buildCompactionContext({ payload, pkgRoot, settings, bootstrapFi
|
|
|
123
121
|
return summaryParts.join('\n');
|
|
124
122
|
}
|
|
125
123
|
|
|
126
|
-
export function buildInjectContext({ source, bootstrap, settings, pkgRoot, host, cwd }) {
|
|
124
|
+
export function buildInjectContext({ source, bootstrap, settings, pkgRoot, host, cwd, payload = {} }) {
|
|
125
|
+
const workflowOptions = { payload };
|
|
127
126
|
const packageRootBlock = buildPackageRootBlock(pkgRoot);
|
|
128
127
|
const readRootBlock = buildReadRootBlock(resolveReadRoot({ cwd, pkgRoot, host, settings }));
|
|
129
|
-
const workflowHint = buildWorkflowRouteHint(cwd);
|
|
130
|
-
const capabilityHint = buildCapabilityHint({ cwd });
|
|
131
|
-
const projectStorageBlock = buildProjectStorageBlock(cwd);
|
|
132
|
-
const
|
|
128
|
+
const workflowHint = buildWorkflowRouteHint(cwd, workflowOptions);
|
|
129
|
+
const capabilityHint = buildCapabilityHint({ cwd, options: workflowOptions });
|
|
130
|
+
const projectStorageBlock = buildProjectStorageBlock(cwd, workflowOptions);
|
|
131
|
+
const stateSnapshot = readStateSnapshot(cwd, workflowOptions);
|
|
132
|
+
const stateSyncHint = buildStateSyncHint(cwd, workflowOptions);
|
|
133
133
|
const settingsBlock = Object.keys(settings).length
|
|
134
134
|
? `\n\n## 当前用户设置\n\`\`\`json\n${JSON.stringify(settings, null, 2)}\n\`\`\``
|
|
135
135
|
: '';
|
|
@@ -140,34 +140,36 @@ export function buildInjectContext({ source, bootstrap, settings, pkgRoot, host,
|
|
|
140
140
|
if (projectStorageBlock) context += `\n\n${projectStorageBlock}`;
|
|
141
141
|
if (workflowHint) context += `\n\n## 当前工作流提示\n${workflowHint}`;
|
|
142
142
|
if (capabilityHint) context += `\n\n## 当前按需能力\n${capabilityHint}`;
|
|
143
|
-
if (stateSyncHint) context += `\n\n##
|
|
143
|
+
if (stateSyncHint) context += `\n\n## 状态文件提醒\n${stateSyncHint}`;
|
|
144
144
|
context += settingsBlock;
|
|
145
145
|
if (source === 'resume' || source === 'compact') {
|
|
146
|
-
context +=
|
|
146
|
+
context += `\n\n> ⚠️ 会话已恢复/压缩,请先读取 \`state_path\` 指向的 \`${stateSnapshot.statePath.replace(/\\/g, '/')}\`;先看当前用户消息,如果仍是同一任务,再参考状态文件。`;
|
|
147
147
|
}
|
|
148
148
|
return context;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
export function buildRouteInstruction({ skillName, extraRules = '', cwd, pkgRoot, host, settings }) {
|
|
151
|
+
export function buildRouteInstruction({ skillName, extraRules = '', cwd, pkgRoot, host, settings, payload = {} }) {
|
|
152
|
+
const workflowOptions = { payload };
|
|
152
153
|
const readRoot = resolveReadRoot({ cwd, pkgRoot, host, settings });
|
|
153
154
|
const canonicalSkillName = resolveCanonicalCommandSkill(skillName);
|
|
154
155
|
const skillPath = join(readRoot.root, 'skills', 'commands', canonicalSkillName, 'SKILL.md');
|
|
155
156
|
const aliasNote = buildAliasRouteNote(skillName);
|
|
156
|
-
const commandHint = buildCommandRouteHint(canonicalSkillName, cwd);
|
|
157
|
-
const capabilityHint = buildCapabilityHint({ cwd, skillName: canonicalSkillName });
|
|
158
|
-
const projectStorageHint = buildProjectStorageHint(cwd);
|
|
157
|
+
const commandHint = buildCommandRouteHint(canonicalSkillName, cwd, workflowOptions);
|
|
158
|
+
const capabilityHint = buildCapabilityHint({ cwd, skillName: canonicalSkillName, options: workflowOptions });
|
|
159
|
+
const projectStorageHint = buildProjectStorageHint(cwd, workflowOptions);
|
|
159
160
|
return `用户使用了 ~${skillName} 命令。当前命令技能文件已解析为:${skillPath}。请直接读取这个 SKILL.md;不要再探测其他 helloagents 路径。${aliasNote ? ` ${aliasNote}` : ''}${projectStorageHint ? ` ${projectStorageHint}` : ''}${commandHint ? ` ${commandHint}` : ''}${capabilityHint ? ` ${capabilityHint}` : ''}${extraRules}`;
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
export function buildSemanticRouteInstruction(cwd) {
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
163
|
+
export function buildSemanticRouteInstruction(cwd, payload = {}) {
|
|
164
|
+
const workflowOptions = { payload };
|
|
165
|
+
const workflowHint = buildWorkflowRouteHint(cwd, workflowOptions);
|
|
166
|
+
const capabilityHint = buildCapabilityHint({ cwd, options: workflowOptions });
|
|
167
|
+
const projectStorageHint = buildProjectStorageHint(cwd, workflowOptions);
|
|
166
168
|
return [
|
|
167
169
|
'当前消息未使用 ~command。',
|
|
168
170
|
'请根据用户请求的真实意图选路,不依赖关键词表。',
|
|
169
|
-
'Delivery Tier: T0=探索/比较;T1=低风险小改动或显式验证;T2=多文件功能/新项目/需要结构化产物;T3
|
|
170
|
-
'路由映射:~idea=只读探索,不创建文件;~build=明确实现;~verify=审查/验证;~plan=结构化规划;~prd=重型规格;~auto
|
|
171
|
+
'Delivery Tier: T0=探索/比较;T1=低风险小改动或显式验证;T2=多文件功能/新项目/需要结构化产物;T3=高风险或不可逆操作。',
|
|
172
|
+
'路由映射:~idea=只读探索,不创建文件;~build=明确实现;~verify=审查/验证;~plan=结构化规划;~prd=重型规格;~auto=自动选择并继续执行后续阶段。',
|
|
171
173
|
'若判定为 T3,默认先走 ~plan / ~prd;纯审查/验证请求才优先 ~verify。',
|
|
172
174
|
`涉及 UI 任务时,设计决策优先级:当前活跃 plan / PRD → ${describeProjectStoreFile(cwd, 'DESIGN.md')} → 通用 UI 规则。`,
|
|
173
175
|
projectStorageHint,
|
|
@@ -5,11 +5,3 @@ export function shouldIgnoreCodexNotifyClient(client) {
|
|
|
5
5
|
export function shouldIgnoreFormattedSubagent(lastMsg, outputFormatEnabled) {
|
|
6
6
|
return outputFormatEnabled && !lastMsg.includes('【HelloAGENTS】');
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
export function claimsTaskComplete(lastMsg) {
|
|
10
|
-
if (!lastMsg) return false;
|
|
11
|
-
if (/^✅【HelloAGENTS】- .*(当前任务已完成|任务已完成|已修复|完成交付|done|fixed|completed|finished)/im.test(lastMsg)) {
|
|
12
|
-
return true;
|
|
13
|
-
}
|
|
14
|
-
return /(当前任务已完成|任务已完成|已全部完成|已修复|修复完成|\b(done|fixed|completed|finished)\b)/i.test(lastMsg);
|
|
15
|
-
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { spawnSync } from 'node:child_process'
|
|
3
|
+
|
|
4
|
+
function truncateText(value = '') {
|
|
5
|
+
const text = String(value || '').trim()
|
|
6
|
+
return text.length > 1000 ? `${text.slice(0, 1000)}\n...(truncated)` : text
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function buildGateErrorReason(source, detail = '') {
|
|
10
|
+
return [
|
|
11
|
+
`[HelloAGENTS Runtime] ${source} 执行失败,已暂停完成通知。`,
|
|
12
|
+
detail ? `原因:${detail}` : '',
|
|
13
|
+
'请修复脚本或重新运行验证后再报告完成。',
|
|
14
|
+
].filter(Boolean).join('\n')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function emitGateError({
|
|
18
|
+
payload,
|
|
19
|
+
host,
|
|
20
|
+
source,
|
|
21
|
+
reason,
|
|
22
|
+
appendReplayEvent,
|
|
23
|
+
output,
|
|
24
|
+
}) {
|
|
25
|
+
appendReplayEvent(payload.cwd || process.cwd(), {
|
|
26
|
+
host,
|
|
27
|
+
event: 'runtime_gate_error',
|
|
28
|
+
source,
|
|
29
|
+
reason,
|
|
30
|
+
})
|
|
31
|
+
output({
|
|
32
|
+
decision: 'block',
|
|
33
|
+
reason,
|
|
34
|
+
suppressOutput: true,
|
|
35
|
+
})
|
|
36
|
+
return true
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function runGateScript({
|
|
40
|
+
payload,
|
|
41
|
+
host,
|
|
42
|
+
scriptPath,
|
|
43
|
+
args = [],
|
|
44
|
+
source,
|
|
45
|
+
blockEvent,
|
|
46
|
+
timeout,
|
|
47
|
+
appendReplayEvent,
|
|
48
|
+
output,
|
|
49
|
+
}) {
|
|
50
|
+
if (!existsSync(scriptPath)) {
|
|
51
|
+
return emitGateError({
|
|
52
|
+
payload,
|
|
53
|
+
host,
|
|
54
|
+
source,
|
|
55
|
+
reason: buildGateErrorReason(source, `脚本不存在:${scriptPath}`),
|
|
56
|
+
appendReplayEvent,
|
|
57
|
+
output,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const result = spawnSync(process.execPath, [scriptPath, ...args], {
|
|
62
|
+
input: JSON.stringify(payload),
|
|
63
|
+
encoding: 'utf-8',
|
|
64
|
+
timeout,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
if (result.error) {
|
|
68
|
+
return emitGateError({
|
|
69
|
+
payload,
|
|
70
|
+
host,
|
|
71
|
+
source,
|
|
72
|
+
reason: buildGateErrorReason(source, result.error.message),
|
|
73
|
+
appendReplayEvent,
|
|
74
|
+
output,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (result.status !== 0) {
|
|
79
|
+
const detail = truncateText(`${result.stderr || ''}\n${result.stdout || ''}`) || `退出码 ${result.status}`
|
|
80
|
+
return emitGateError({
|
|
81
|
+
payload,
|
|
82
|
+
host,
|
|
83
|
+
source,
|
|
84
|
+
reason: buildGateErrorReason(source, detail),
|
|
85
|
+
appendReplayEvent,
|
|
86
|
+
output,
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const stdout = String(result.stdout || '').trim()
|
|
91
|
+
if (!stdout) {
|
|
92
|
+
return emitGateError({
|
|
93
|
+
payload,
|
|
94
|
+
host,
|
|
95
|
+
source,
|
|
96
|
+
reason: buildGateErrorReason(source, '脚本未返回有效结果'),
|
|
97
|
+
appendReplayEvent,
|
|
98
|
+
output,
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let gateOutput
|
|
103
|
+
try {
|
|
104
|
+
gateOutput = JSON.parse(stdout)
|
|
105
|
+
} catch {
|
|
106
|
+
return emitGateError({
|
|
107
|
+
payload,
|
|
108
|
+
host,
|
|
109
|
+
source,
|
|
110
|
+
reason: buildGateErrorReason(source, `脚本返回了无法解析的 JSON:${truncateText(stdout)}`),
|
|
111
|
+
appendReplayEvent,
|
|
112
|
+
output,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (gateOutput.decision === 'block') {
|
|
117
|
+
appendReplayEvent(payload.cwd || process.cwd(), {
|
|
118
|
+
host,
|
|
119
|
+
event: blockEvent,
|
|
120
|
+
source,
|
|
121
|
+
reason: gateOutput.reason || '',
|
|
122
|
+
})
|
|
123
|
+
output(gateOutput)
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return false
|
|
128
|
+
}
|
package/scripts/notify-route.mjs
CHANGED
|
@@ -17,6 +17,7 @@ function buildHelpExtraRules(skillName) {
|
|
|
17
17
|
|
|
18
18
|
function routeExplicitCommand({
|
|
19
19
|
prompt,
|
|
20
|
+
payload,
|
|
20
21
|
cwd,
|
|
21
22
|
host,
|
|
22
23
|
pkgRoot,
|
|
@@ -51,6 +52,7 @@ function routeExplicitCommand({
|
|
|
51
52
|
pkgRoot,
|
|
52
53
|
host,
|
|
53
54
|
settings,
|
|
55
|
+
payload,
|
|
54
56
|
}))
|
|
55
57
|
return true
|
|
56
58
|
}
|
|
@@ -80,6 +82,7 @@ export function handleRouteCommand({
|
|
|
80
82
|
|
|
81
83
|
if (routeExplicitCommand({
|
|
82
84
|
prompt,
|
|
85
|
+
payload,
|
|
83
86
|
cwd,
|
|
84
87
|
host,
|
|
85
88
|
pkgRoot,
|
|
@@ -100,9 +103,9 @@ export function handleRouteCommand({
|
|
|
100
103
|
host,
|
|
101
104
|
event: 'semantic_route_prompted',
|
|
102
105
|
source: 'route',
|
|
103
|
-
recommendation: getWorkflowRecommendation(cwd),
|
|
106
|
+
recommendation: getWorkflowRecommendation(cwd, { payload }),
|
|
104
107
|
})
|
|
105
|
-
suppress(buildSemanticRouteInstruction(cwd))
|
|
108
|
+
suppress(buildSemanticRouteInstruction(cwd, payload))
|
|
106
109
|
return
|
|
107
110
|
}
|
|
108
111
|
|