helloagents 3.0.33 → 3.0.35
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 +2 -2
- package/.codex-plugin/plugin.json +3 -4
- package/README.md +70 -71
- package/README_CN.md +70 -71
- package/bootstrap-lite.md +9 -11
- package/bootstrap.md +21 -23
- package/gemini-extension.json +1 -1
- package/install.ps1 +21 -3
- package/install.sh +19 -2
- package/package.json +2 -2
- package/scripts/capability-registry.mjs +5 -3
- package/scripts/cli-doctor-codex.mjs +150 -1
- package/scripts/cli-doctor-render.mjs +2 -1
- package/scripts/cli-lifecycle-hosts.mjs +76 -34
- package/scripts/cli-lifecycle.mjs +50 -15
- package/scripts/cli-messages.mjs +5 -5
- package/scripts/delivery-gate-messages.mjs +5 -4
- package/scripts/delivery-gate.mjs +11 -22
- package/scripts/guard.mjs +1 -1
- package/scripts/notify-closeout.mjs +61 -22
- package/scripts/notify-context.mjs +5 -5
- package/scripts/notify-route.mjs +1 -1
- package/scripts/notify.mjs +2 -2
- package/scripts/plan-contract.mjs +10 -14
- package/scripts/project-session-cleanup.mjs +45 -31
- package/scripts/qa-review-state.mjs +313 -0
- package/scripts/ralph-loop.mjs +32 -13
- package/scripts/runtime-scope.mjs +1 -3
- package/scripts/session-capsule.mjs +51 -13
- package/scripts/state-document.mjs +77 -0
- package/scripts/workflow-core.mjs +13 -19
- package/scripts/workflow-plan-files.mjs +1 -1
- package/scripts/workflow-recommendation.mjs +55 -67
- package/scripts/workflow-state.mjs +8 -8
- package/skills/commands/auto/SKILL.md +12 -12
- package/skills/commands/build/SKILL.md +9 -10
- package/skills/commands/commit/SKILL.md +1 -1
- package/skills/commands/help/SKILL.md +11 -13
- package/skills/commands/init/SKILL.md +18 -9
- package/skills/commands/loop/SKILL.md +70 -96
- package/skills/commands/plan/SKILL.md +7 -8
- package/skills/commands/prd/SKILL.md +3 -3
- package/skills/commands/qa/SKILL.md +49 -0
- package/skills/hello-ui/SKILL.md +3 -3
- package/skills/helloagents/SKILL.md +11 -14
- package/skills/qa-review/SKILL.md +92 -0
- package/templates/plans/contract.json +4 -7
- package/templates/plans/plan.md +1 -1
- package/templates/plans/tasks.md +1 -1
- package/templates/verify.yaml +1 -1
- package/scripts/review-state.mjs +0 -193
- package/scripts/verify-state.mjs +0 -175
- package/skills/commands/global/SKILL.md +0 -71
- package/skills/commands/verify/SKILL.md +0 -46
- package/skills/commands/wiki/SKILL.md +0 -57
- package/skills/hello-review/SKILL.md +0 -42
- package/skills/hello-verify/SKILL.md +0 -144
package/install.ps1
CHANGED
|
@@ -15,6 +15,7 @@ $Target = if ($env:HELLOAGENTS_TARGET) { $env:HELLOAGENTS_TARGET } else { "" }
|
|
|
15
15
|
$Mode = if ($env:HELLOAGENTS_MODE) { $env:HELLOAGENTS_MODE } else { "" }
|
|
16
16
|
$Branch = if ($env:HELLOAGENTS_BRANCH) { $env:HELLOAGENTS_BRANCH } else { "" }
|
|
17
17
|
$Package = if ($env:HELLOAGENTS_PACKAGE) { $env:HELLOAGENTS_PACKAGE } else { "" }
|
|
18
|
+
$HasExplicitPackage = [bool]$Package
|
|
18
19
|
|
|
19
20
|
if ($env:HELLOAGENTS) {
|
|
20
21
|
$Parts = $env:HELLOAGENTS.Split(":", 2)
|
|
@@ -53,6 +54,23 @@ function Invoke-Npm {
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
function Clear-HelloagentsEnv {
|
|
58
|
+
foreach ($name in @(
|
|
59
|
+
"HELLOAGENTS",
|
|
60
|
+
"HELLOAGENTS_ACTION",
|
|
61
|
+
"HELLOAGENTS_TARGET",
|
|
62
|
+
"HELLOAGENTS_HOST",
|
|
63
|
+
"HELLOAGENTS_MODE",
|
|
64
|
+
"HELLOAGENTS_BRANCH",
|
|
65
|
+
"HELLOAGENTS_PACKAGE",
|
|
66
|
+
"HELLOAGENTS_DEPLOY"
|
|
67
|
+
)) {
|
|
68
|
+
Remove-Item "Env:$name" -ErrorAction SilentlyContinue
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
Clear-HelloagentsEnv
|
|
73
|
+
|
|
56
74
|
function Enable-PostinstallDeploy {
|
|
57
75
|
$env:HELLOAGENTS_DEPLOY = "1"
|
|
58
76
|
$env:HELLOAGENTS_TARGET = $Target
|
|
@@ -93,7 +111,7 @@ switch ($Action) {
|
|
|
93
111
|
Invoke-Npm -NpmArgs @("install", "-g", $Package)
|
|
94
112
|
}
|
|
95
113
|
"update" {
|
|
96
|
-
if ($Branch -or $
|
|
114
|
+
if ($Branch -or $HasExplicitPackage) {
|
|
97
115
|
Invoke-Npm -NpmArgs @("install", "-g", $Package)
|
|
98
116
|
} else {
|
|
99
117
|
& npm update -g helloagents
|
|
@@ -107,14 +125,14 @@ switch ($Action) {
|
|
|
107
125
|
Cleanup-Hosts
|
|
108
126
|
}
|
|
109
127
|
"switch-branch" {
|
|
110
|
-
if (-not $Branch -and -not $
|
|
128
|
+
if (-not $Branch -and -not $HasExplicitPackage) {
|
|
111
129
|
throw "HELLOAGENTS_BRANCH or HELLOAGENTS_PACKAGE is required for switch-branch"
|
|
112
130
|
}
|
|
113
131
|
Invoke-Npm -NpmArgs @("install", "-g", $Package)
|
|
114
132
|
Sync-Hosts
|
|
115
133
|
}
|
|
116
134
|
"branch" {
|
|
117
|
-
if (-not $Branch -and -not $
|
|
135
|
+
if (-not $Branch -and -not $HasExplicitPackage) {
|
|
118
136
|
throw "HELLOAGENTS_BRANCH or HELLOAGENTS_PACKAGE is required for branch"
|
|
119
137
|
}
|
|
120
138
|
Invoke-Npm -NpmArgs @("install", "-g", $Package)
|
package/install.sh
CHANGED
|
@@ -16,6 +16,10 @@ TARGET="${HELLOAGENTS_TARGET:-}"
|
|
|
16
16
|
MODE="${HELLOAGENTS_MODE:-}"
|
|
17
17
|
BRANCH="${HELLOAGENTS_BRANCH:-}"
|
|
18
18
|
PACKAGE="${HELLOAGENTS_PACKAGE:-}"
|
|
19
|
+
HAS_EXPLICIT_PACKAGE=0
|
|
20
|
+
if [ -n "$PACKAGE" ]; then
|
|
21
|
+
HAS_EXPLICIT_PACKAGE=1
|
|
22
|
+
fi
|
|
19
23
|
|
|
20
24
|
if [ -n "${HELLOAGENTS:-}" ]; then
|
|
21
25
|
SPEC_TARGET="${HELLOAGENTS%%:*}"
|
|
@@ -55,6 +59,19 @@ if [ -z "$PACKAGE" ]; then
|
|
|
55
59
|
fi
|
|
56
60
|
fi
|
|
57
61
|
|
|
62
|
+
clear_lifecycle_env() {
|
|
63
|
+
unset HELLOAGENTS
|
|
64
|
+
unset HELLOAGENTS_ACTION
|
|
65
|
+
unset HELLOAGENTS_TARGET
|
|
66
|
+
unset HELLOAGENTS_HOST
|
|
67
|
+
unset HELLOAGENTS_MODE
|
|
68
|
+
unset HELLOAGENTS_BRANCH
|
|
69
|
+
unset HELLOAGENTS_PACKAGE
|
|
70
|
+
unset HELLOAGENTS_DEPLOY
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
clear_lifecycle_env
|
|
74
|
+
|
|
58
75
|
sync_hosts() {
|
|
59
76
|
if [ "$TARGET" = "all" ]; then
|
|
60
77
|
if [ -n "$MODE" ]; then
|
|
@@ -115,7 +132,7 @@ case "$ACTION" in
|
|
|
115
132
|
npm install -g "$PACKAGE"
|
|
116
133
|
;;
|
|
117
134
|
update)
|
|
118
|
-
if [ -n "$BRANCH" ] || [
|
|
135
|
+
if [ -n "$BRANCH" ] || [ "$HAS_EXPLICIT_PACKAGE" -eq 1 ]; then
|
|
119
136
|
npm install -g "$PACKAGE"
|
|
120
137
|
else
|
|
121
138
|
npm update -g helloagents || npm install -g helloagents
|
|
@@ -126,7 +143,7 @@ case "$ACTION" in
|
|
|
126
143
|
cleanup_hosts
|
|
127
144
|
;;
|
|
128
145
|
switch-branch|branch)
|
|
129
|
-
if [ -z "$BRANCH" ] && [
|
|
146
|
+
if [ -z "$BRANCH" ] && [ "$HAS_EXPLICIT_PACKAGE" -ne 1 ]; then
|
|
130
147
|
echo "HELLOAGENTS_BRANCH or HELLOAGENTS_PACKAGE is required for switch-branch" >&2
|
|
131
148
|
exit 1
|
|
132
149
|
fi
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helloagents",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.35",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "HelloAGENTS — The orchestration kernel that makes any AI CLI smarter. Adds intelligent routing,
|
|
5
|
+
"description": "HelloAGENTS — The orchestration kernel that makes any AI CLI smarter. Adds intelligent routing, unified QA gates, safety guards, and notifications.",
|
|
6
6
|
"author": "HelloWind",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
8
|
"homepage": "https://github.com/hellowind777/helloagents",
|
|
@@ -30,10 +30,12 @@ export function selectCapabilities({ cwd, skillName = '', options = {} }) {
|
|
|
30
30
|
: '独立 advisor:当前契约要求进入收尾前写当前会话 `artifacts/advisor.json`,记录 advisor reason、focus、consultedSources 与结论。',
|
|
31
31
|
})
|
|
32
32
|
}
|
|
33
|
-
if (plan?.contract?.
|
|
33
|
+
if ((plan?.contract?.qaFocus || []).length > 0) {
|
|
34
34
|
capabilities.push({
|
|
35
|
-
id: '
|
|
36
|
-
description:
|
|
35
|
+
id: 'qa-evaluator',
|
|
36
|
+
description: plan?.contract?.qaMode === 'deep'
|
|
37
|
+
? `深度 qa-review:当前收尾主路径是 ~qa,按阻断性质量闭环执行;重点:${plan.contract.qaFocus.join(';')}。`
|
|
38
|
+
: `统一 qa-review:当前收尾主路径是 ~qa,重点:${plan.contract.qaFocus.join(';')}。`,
|
|
37
39
|
})
|
|
38
40
|
}
|
|
39
41
|
if (plan?.contract?.ui?.required || existsSync(getProjectDesignContractPath(cwd))) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
1
2
|
import { existsSync, realpathSync } from 'node:fs'
|
|
2
3
|
import { join } from 'node:path'
|
|
3
4
|
|
|
@@ -76,6 +77,141 @@ function buildDoctorIssue(runtime, code, cn, en) {
|
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
function normalizeDoctorText(value = '') {
|
|
81
|
+
return String(value || '').replace(/\s+/g, ' ').trim()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function readFirstInteger(value = '') {
|
|
85
|
+
const match = String(value || '').match(/-?\d+/)
|
|
86
|
+
return match ? Number.parseInt(match[0], 10) : null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readNativeDoctorDetail(checks, checkId, detailKey) {
|
|
90
|
+
return String(checks?.[checkId]?.details?.[detailKey] || '').trim()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function readNativeDoctorList(value = '') {
|
|
94
|
+
const normalized = normalizeDoctorText(value)
|
|
95
|
+
if (!normalized || normalized === '(none)') return []
|
|
96
|
+
return normalized.split(/\s*,\s*/).map((entry) => entry.trim()).filter(Boolean)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function summarizeNativeCodexDoctor(payload = {}) {
|
|
100
|
+
const checks = payload?.checks || {}
|
|
101
|
+
const configCheck = checks['config.load'] || {}
|
|
102
|
+
const sandboxCheck = checks['sandbox.helpers'] || {}
|
|
103
|
+
const mcpCount = readFirstInteger(readNativeDoctorDetail(checks, 'config.load', 'mcp servers'))
|
|
104
|
+
const fsSandbox = readNativeDoctorDetail(checks, 'sandbox.helpers', 'filesystem sandbox').toLowerCase()
|
|
105
|
+
const linuxHelper = readNativeDoctorDetail(checks, 'sandbox.helpers', 'codex-linux-sandbox helper').toLowerCase()
|
|
106
|
+
|| readNativeDoctorDetail(checks, 'sandbox.helpers', 'linux helper').toLowerCase()
|
|
107
|
+
const execveHelper = readNativeDoctorDetail(checks, 'sandbox.helpers', 'execve wrapper helper').toLowerCase()
|
|
108
|
+
|
|
109
|
+
let sandboxAvailable = null
|
|
110
|
+
if (sandboxCheck && Object.keys(sandboxCheck).length > 0) {
|
|
111
|
+
sandboxAvailable = Boolean(
|
|
112
|
+
(fsSandbox && !fsSandbox.includes('unrestricted'))
|
|
113
|
+
|| (linuxHelper && linuxHelper !== 'none')
|
|
114
|
+
|| (execveHelper && execveHelper !== 'none')
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
version: String(payload?.codexVersion || '').trim(),
|
|
120
|
+
configPath: readNativeDoctorDetail(checks, 'config.load', 'config.toml'),
|
|
121
|
+
resolvedProvider: readNativeDoctorDetail(checks, 'config.load', 'model provider'),
|
|
122
|
+
resolvedModel: readNativeDoctorDetail(checks, 'config.load', 'model'),
|
|
123
|
+
sandboxAvailable,
|
|
124
|
+
mcpPresent: typeof mcpCount === 'number' ? mcpCount > 0 : false,
|
|
125
|
+
skillsSelected: readNativeDoctorList(
|
|
126
|
+
readNativeDoctorDetail(checks, 'config.load', 'selected skills')
|
|
127
|
+
|| readNativeDoctorDetail(checks, 'config.load', 'skills selected')
|
|
128
|
+
),
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function summarizeNativeCodexDoctorOutput(payload = {}) {
|
|
133
|
+
const checks = Object.values(payload?.checks || {})
|
|
134
|
+
const failedCheck = checks.find((check) => check?.status === 'fail')
|
|
135
|
+
if (failedCheck?.issues?.length) {
|
|
136
|
+
return normalizeDoctorText(failedCheck.issues.map((issue) => issue?.cause || issue?.measured || '').filter(Boolean).join(' | '))
|
|
137
|
+
}
|
|
138
|
+
if (failedCheck?.summary) return normalizeDoctorText(failedCheck.summary)
|
|
139
|
+
|
|
140
|
+
const warningCheck = checks.find((check) => check?.status === 'warn')
|
|
141
|
+
if (warningCheck?.issues?.length) {
|
|
142
|
+
return normalizeDoctorText(warningCheck.issues.map((issue) => issue?.cause || issue?.measured || '').filter(Boolean).join(' | '))
|
|
143
|
+
}
|
|
144
|
+
if (warningCheck?.summary) return normalizeDoctorText(warningCheck.summary)
|
|
145
|
+
|
|
146
|
+
return ''
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function inspectNativeCodexDoctor(runtime) {
|
|
150
|
+
try {
|
|
151
|
+
const result = spawnSync('codex', ['doctor', '--json'], {
|
|
152
|
+
cwd: process.cwd(),
|
|
153
|
+
env: {
|
|
154
|
+
...process.env,
|
|
155
|
+
HOME: runtime.home || process.env.HOME,
|
|
156
|
+
USERPROFILE: runtime.home || process.env.USERPROFILE,
|
|
157
|
+
NO_COLOR: process.env.NO_COLOR || '1',
|
|
158
|
+
},
|
|
159
|
+
encoding: 'utf-8',
|
|
160
|
+
timeout: 20_000,
|
|
161
|
+
windowsHide: true,
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
if (result.error) {
|
|
165
|
+
return {
|
|
166
|
+
available: false,
|
|
167
|
+
ok: false,
|
|
168
|
+
status: '',
|
|
169
|
+
summary: null,
|
|
170
|
+
output: normalizeDoctorText(result.error.message || ''),
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const stdout = String(result.stdout || '').trim()
|
|
175
|
+
if (!stdout) {
|
|
176
|
+
return {
|
|
177
|
+
available: true,
|
|
178
|
+
ok: result.status === 0,
|
|
179
|
+
status: '',
|
|
180
|
+
summary: null,
|
|
181
|
+
output: normalizeDoctorText(result.stderr || ''),
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const payload = JSON.parse(stdout)
|
|
187
|
+
const status = String(payload?.overallStatus || '').trim().toLowerCase()
|
|
188
|
+
return {
|
|
189
|
+
available: true,
|
|
190
|
+
ok: status ? status !== 'fail' : result.status === 0,
|
|
191
|
+
status,
|
|
192
|
+
summary: summarizeNativeCodexDoctor(payload),
|
|
193
|
+
output: summarizeNativeCodexDoctorOutput(payload),
|
|
194
|
+
}
|
|
195
|
+
} catch {
|
|
196
|
+
return {
|
|
197
|
+
available: true,
|
|
198
|
+
ok: result.status === 0,
|
|
199
|
+
status: '',
|
|
200
|
+
summary: null,
|
|
201
|
+
output: normalizeDoctorText(stdout || result.stderr || ''),
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch (error) {
|
|
205
|
+
return {
|
|
206
|
+
available: false,
|
|
207
|
+
ok: false,
|
|
208
|
+
status: '',
|
|
209
|
+
summary: null,
|
|
210
|
+
output: normalizeDoctorText(error?.message || ''),
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
79
215
|
function normalizeDoctorMode(mode = '') {
|
|
80
216
|
return mode || 'none'
|
|
81
217
|
}
|
|
@@ -209,6 +345,7 @@ export function inspectCodexDoctor(runtime, settings) {
|
|
|
209
345
|
const host = 'codex'
|
|
210
346
|
const trackedMode = normalizeDoctorMode(runtime.getTrackedHostMode(settings, host))
|
|
211
347
|
const detectedMode = normalizeDoctorMode(runtime.detectHostMode(host))
|
|
348
|
+
const nativeDoctor = inspectNativeCodexDoctor(runtime)
|
|
212
349
|
const { checks, pluginVersion, cacheVersion } = buildCodexChecks(runtime, settings, trackedMode, detectedMode)
|
|
213
350
|
checks.pluginVersionMatch = pluginVersion ? pluginVersion === runtime.pkgVersion : false
|
|
214
351
|
checks.pluginCacheVersionMatch = cacheVersion ? cacheVersion === runtime.pkgVersion : false
|
|
@@ -230,7 +367,19 @@ export function inspectCodexDoctor(runtime, settings) {
|
|
|
230
367
|
if (!checks.pluginCacheVersionMatch && !cacheVersion && detectedMode === 'global') notes.push(runtime.msg('未读到 global 插件缓存版本信息', 'Global plugin cache version was not readable'))
|
|
231
368
|
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.'))
|
|
232
369
|
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.'))
|
|
370
|
+
if (!nativeDoctor.available) notes.push(runtime.msg('未检测到原生 `codex doctor`;当前仅检查 HelloAGENTS 受管覆盖层。', 'Native `codex doctor` was not available; only the HelloAGENTS managed overlay was checked.'))
|
|
233
371
|
|
|
234
372
|
const status = summarizeDoctorStatus(issues, { trackedMode, detectedMode })
|
|
235
|
-
return {
|
|
373
|
+
return {
|
|
374
|
+
host,
|
|
375
|
+
label: runtime.getHostLabel(host),
|
|
376
|
+
trackedMode,
|
|
377
|
+
detectedMode,
|
|
378
|
+
status,
|
|
379
|
+
checks,
|
|
380
|
+
nativeDoctor,
|
|
381
|
+
issues,
|
|
382
|
+
notes,
|
|
383
|
+
suggestedFix: suggestCodexDoctorFix(status, trackedMode),
|
|
384
|
+
}
|
|
236
385
|
}
|
|
@@ -20,7 +20,8 @@ export function printDoctorText(runtime, report) {
|
|
|
20
20
|
if (entry.nativeDoctor) {
|
|
21
21
|
console.log(` native_doctor.available: ${entry.nativeDoctor.available ? 'ok' : 'missing'}`)
|
|
22
22
|
if (entry.nativeDoctor.available) {
|
|
23
|
-
console.log(` native_doctor.ok: ${entry.nativeDoctor.ok ? 'ok' : '
|
|
23
|
+
console.log(` native_doctor.ok: ${entry.nativeDoctor.ok ? 'ok' : 'fail'}`)
|
|
24
|
+
if (entry.nativeDoctor.status) console.log(` native_doctor.status: ${entry.nativeDoctor.status}`)
|
|
24
25
|
}
|
|
25
26
|
if (entry.nativeDoctor.summary) {
|
|
26
27
|
if (entry.nativeDoctor.summary.version) console.log(` native_doctor.version: ${entry.nativeDoctor.summary.version}`)
|
|
@@ -46,6 +46,13 @@ function buildNativeResult(result, successCN, successEN, manualCN, manualEN) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function preserveTrackedModeOnFailure(result = {}, trackedMode = '') {
|
|
50
|
+
if (result.ok === false && trackedMode) {
|
|
51
|
+
return { ...result, trackedModeOnFailure: trackedMode }
|
|
52
|
+
}
|
|
53
|
+
return result
|
|
54
|
+
}
|
|
55
|
+
|
|
49
56
|
function installClaudeGlobalPlugin() {
|
|
50
57
|
const add = runHostCommand(CLAUDE_COMMAND, ['plugin', 'marketplace', 'add', CLAUDE_MARKETPLACE])
|
|
51
58
|
if (!add.ok && add.missing) return { ok: false, output: '未找到 claude 命令' }
|
|
@@ -70,8 +77,11 @@ function reportHostAction(runtime, action, host, mode, result = {}) {
|
|
|
70
77
|
const isCleanup = action === 'cleanup' || action === 'uninstall'
|
|
71
78
|
if (result.skipped) {
|
|
72
79
|
console.log(runtime.msg(` - ${label} 未检测到,跳过`, ` - ${label} not detected, skipped`))
|
|
73
|
-
} else if (result.ok === false
|
|
74
|
-
console.log(runtime.msg(
|
|
80
|
+
} else if (result.ok === false) {
|
|
81
|
+
console.log(runtime.msg(
|
|
82
|
+
isCleanup ? ` - ${label} 自动清理未完成` : ` - ${label} 自动配置未完成`,
|
|
83
|
+
isCleanup ? ` - ${label} automatic cleanup did not complete` : ` - ${label} automatic setup did not complete`,
|
|
84
|
+
))
|
|
75
85
|
} else if (isCleanup) {
|
|
76
86
|
runtime.ok(runtime.msg(`${label} 已清理(${mode} 模式)`, `${label} cleaned (${mode} mode)`))
|
|
77
87
|
} else if (mode === 'standby') {
|
|
@@ -87,14 +97,46 @@ function reportHostAction(runtime, action, host, mode, result = {}) {
|
|
|
87
97
|
}
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
function
|
|
100
|
+
function prepareClaudeStandby(previousMode) {
|
|
101
|
+
if (previousMode !== 'global') return {}
|
|
102
|
+
return preserveTrackedModeOnFailure(
|
|
103
|
+
buildNativeResult(
|
|
104
|
+
removeClaudeGlobalPlugin(),
|
|
105
|
+
'已自动移除 Claude Code 插件',
|
|
106
|
+
'Claude Code plugin removed automatically',
|
|
107
|
+
'切到 standby 前无法自动移除 Claude Code 插件,请先在 Claude Code 中执行: /plugin remove helloagents',
|
|
108
|
+
'Could not remove the Claude Code plugin before switching to standby. Run inside Claude Code: /plugin remove helloagents',
|
|
109
|
+
),
|
|
110
|
+
'global',
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function prepareGeminiStandby(previousMode) {
|
|
115
|
+
if (previousMode !== 'global') return {}
|
|
116
|
+
return preserveTrackedModeOnFailure(
|
|
117
|
+
buildNativeResult(
|
|
118
|
+
removeGeminiGlobalExtension(),
|
|
119
|
+
'已自动移除 Gemini CLI 扩展',
|
|
120
|
+
'Gemini CLI extension removed automatically',
|
|
121
|
+
'切到 standby 前无法自动移除 Gemini CLI 扩展,请先手动执行: gemini extensions uninstall helloagents',
|
|
122
|
+
'Could not remove the Gemini CLI extension before switching to standby. Run manually: gemini extensions uninstall helloagents',
|
|
123
|
+
),
|
|
124
|
+
'global',
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function installHostStandby(runtime, host, { previousMode = '' } = {}) {
|
|
91
129
|
if (host === 'claude') {
|
|
130
|
+
const cleanupResult = prepareClaudeStandby(previousMode)
|
|
131
|
+
if (cleanupResult.ok === false) return cleanupResult
|
|
92
132
|
installClaudeStandby(runtime.home, runtime.pkgRoot)
|
|
93
|
-
return
|
|
133
|
+
return cleanupResult
|
|
94
134
|
}
|
|
95
135
|
if (host === 'gemini') {
|
|
136
|
+
const cleanupResult = prepareGeminiStandby(previousMode)
|
|
137
|
+
if (cleanupResult.ok === false) return cleanupResult
|
|
96
138
|
installGeminiStandby(runtime.home, runtime.pkgRoot)
|
|
97
|
-
return
|
|
139
|
+
return cleanupResult
|
|
98
140
|
}
|
|
99
141
|
if (!installCodexStandby(runtime.home, runtime.pkgRoot)) return { skipped: true }
|
|
100
142
|
cleanupCodexGlobalResidueForStandby(runtime.home)
|
|
@@ -137,41 +179,41 @@ function cleanupHostStandby(runtime, host) {
|
|
|
137
179
|
function cleanupHostGlobal(runtime, host) {
|
|
138
180
|
if (host === 'claude') {
|
|
139
181
|
uninstallClaudeStandby(runtime.home)
|
|
140
|
-
return
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
182
|
+
return preserveTrackedModeOnFailure(
|
|
183
|
+
buildNativeResult(
|
|
184
|
+
removeClaudeGlobalPlugin(),
|
|
185
|
+
'已自动移除 Claude Code 插件',
|
|
186
|
+
'Claude Code plugin removed automatically',
|
|
187
|
+
'Claude Code 插件自动移除失败,请手动执行: /plugin remove helloagents',
|
|
188
|
+
'Claude Code plugin auto-remove failed. Run manually: /plugin remove helloagents',
|
|
189
|
+
),
|
|
190
|
+
'global',
|
|
146
191
|
)
|
|
147
192
|
}
|
|
148
193
|
if (host === 'gemini') {
|
|
149
194
|
uninstallGeminiStandby(runtime.home)
|
|
150
|
-
return
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
195
|
+
return preserveTrackedModeOnFailure(
|
|
196
|
+
buildNativeResult(
|
|
197
|
+
removeGeminiGlobalExtension(),
|
|
198
|
+
'已自动移除 Gemini CLI 扩展',
|
|
199
|
+
'Gemini CLI extension removed automatically',
|
|
200
|
+
'Gemini CLI 扩展自动移除失败,请手动执行: gemini extensions uninstall helloagents',
|
|
201
|
+
'Gemini CLI extension auto-remove failed. Run manually: gemini extensions uninstall helloagents',
|
|
202
|
+
),
|
|
203
|
+
'global',
|
|
156
204
|
)
|
|
157
205
|
}
|
|
158
206
|
return { skipped: !uninstallCodexGlobal(runtime.home) }
|
|
159
207
|
}
|
|
160
208
|
|
|
161
|
-
function installStandby(runtime) {
|
|
209
|
+
function installStandby(runtime, previousModes = {}) {
|
|
162
210
|
const results = {}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
if (installGeminiStandby(runtime.home, runtime.pkgRoot)) {
|
|
170
|
-
runtime.ok(runtime.msg('Gemini CLI 已配置(standby 模式)', 'Gemini CLI configured (standby mode)'))
|
|
171
|
-
results.gemini = {}
|
|
172
|
-
} else {
|
|
173
|
-
results.gemini = { skipped: true }
|
|
174
|
-
}
|
|
211
|
+
const claudeResult = installHostStandby(runtime, 'claude', { previousMode: previousModes.claude || '' })
|
|
212
|
+
reportHostAction(runtime, 'install', 'claude', 'standby', claudeResult)
|
|
213
|
+
results.claude = claudeResult.skipped ? { skipped: true } : claudeResult
|
|
214
|
+
const geminiResult = installHostStandby(runtime, 'gemini', { previousMode: previousModes.gemini || '' })
|
|
215
|
+
reportHostAction(runtime, 'install', 'gemini', 'standby', geminiResult)
|
|
216
|
+
results.gemini = geminiResult.skipped ? { skipped: true } : geminiResult
|
|
175
217
|
if (installCodexStandby(runtime.home, runtime.pkgRoot)) {
|
|
176
218
|
cleanupCodexGlobalResidueForStandby(runtime.home)
|
|
177
219
|
runtime.ok(runtime.msg('Codex CLI 已配置(standby 模式)', 'Codex CLI configured (standby mode)'))
|
|
@@ -193,9 +235,9 @@ function installGlobal(runtime) {
|
|
|
193
235
|
return results
|
|
194
236
|
}
|
|
195
237
|
|
|
196
|
-
export function installAllHosts(runtime, mode) {
|
|
238
|
+
export function installAllHosts(runtime, mode, { previousModes = {} } = {}) {
|
|
197
239
|
if (mode === 'global') return installGlobal(runtime)
|
|
198
|
-
return installStandby(runtime)
|
|
240
|
+
return installStandby(runtime, previousModes)
|
|
199
241
|
}
|
|
200
242
|
|
|
201
243
|
export function uninstallAllHosts(runtime) {
|
|
@@ -205,10 +247,10 @@ export function uninstallAllHosts(runtime) {
|
|
|
205
247
|
uninstallCodexGlobal(runtime.home)
|
|
206
248
|
}
|
|
207
249
|
|
|
208
|
-
export function runHostLifecycle(runtime, action, host, mode) {
|
|
250
|
+
export function runHostLifecycle(runtime, action, host, mode, options = {}) {
|
|
209
251
|
const result = (action === 'cleanup' || action === 'uninstall')
|
|
210
252
|
? (mode === 'global' ? cleanupHostGlobal(runtime, host) : cleanupHostStandby(runtime, host))
|
|
211
|
-
: (mode === 'global' ? installHostGlobal(runtime, host) : installHostStandby(runtime, host))
|
|
253
|
+
: (mode === 'global' ? installHostGlobal(runtime, host) : installHostStandby(runtime, host, options))
|
|
212
254
|
|
|
213
255
|
reportHostAction(runtime, action, host, mode, result)
|
|
214
256
|
return result
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
getHostLabel as resolveHostLabel,
|
|
8
8
|
normalizeHost as normalizeLifecycleHost,
|
|
9
9
|
} from './cli-host-detect.mjs'
|
|
10
|
-
import { installAllHosts, runHostLifecycle
|
|
10
|
+
import { installAllHosts, runHostLifecycle } from './cli-lifecycle-hosts.mjs'
|
|
11
11
|
import { ensureDir, safeJson, safeWrite } from './cli-utils.mjs'
|
|
12
12
|
|
|
13
13
|
export const HOSTS = ['claude', 'gemini', 'codex']
|
|
@@ -82,6 +82,18 @@ function syncTrackedHostMode(settings, host, result, mode) {
|
|
|
82
82
|
setTrackedHostMode(settings, host, mode)
|
|
83
83
|
return
|
|
84
84
|
}
|
|
85
|
+
if (result?.trackedModeOnFailure) {
|
|
86
|
+
setTrackedHostMode(settings, host, result.trackedModeOnFailure)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
clearTrackedHostMode(settings, host)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function syncCleanupTrackedHostMode(settings, host, result) {
|
|
93
|
+
if (result?.trackedModeOnFailure) {
|
|
94
|
+
setTrackedHostMode(settings, host, result.trackedModeOnFailure)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
85
97
|
clearTrackedHostMode(settings, host)
|
|
86
98
|
}
|
|
87
99
|
|
|
@@ -129,6 +141,14 @@ export function getHostLabel(host) {
|
|
|
129
141
|
return resolveHostLabel(host)
|
|
130
142
|
}
|
|
131
143
|
|
|
144
|
+
function resolvePreviousHostMode(settings, host) {
|
|
145
|
+
return detectHostMode(host) || getTrackedHostMode(settings, host) || ''
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function buildPreviousHostModes(settings) {
|
|
149
|
+
return Object.fromEntries(HOSTS.map((host) => [host, resolvePreviousHostMode(settings, host)]))
|
|
150
|
+
}
|
|
151
|
+
|
|
132
152
|
function resolveHostMode(host, explicitMode, settings) {
|
|
133
153
|
if (explicitMode) return explicitMode
|
|
134
154
|
return detectHostMode(host)
|
|
@@ -167,6 +187,7 @@ export function switchMode(newMode) {
|
|
|
167
187
|
const config = readSettings(true)
|
|
168
188
|
const oldMode = config.install_mode || DEFAULTS.install_mode
|
|
169
189
|
const isRefresh = oldMode === newMode
|
|
190
|
+
const previousModes = buildPreviousHostModes(config)
|
|
170
191
|
|
|
171
192
|
if (!isRefresh) {
|
|
172
193
|
config.install_mode = newMode
|
|
@@ -175,7 +196,7 @@ export function switchMode(newMode) {
|
|
|
175
196
|
runtime.ok(runtime.msg(`当前已是 ${newMode} 模式,正在刷新安装`, `Already in ${newMode} mode, refreshing installation`))
|
|
176
197
|
}
|
|
177
198
|
|
|
178
|
-
const results = installAllHosts(runtime, newMode)
|
|
199
|
+
const results = installAllHosts(runtime, newMode, { previousModes })
|
|
179
200
|
clearAllTrackedHostModes(config)
|
|
180
201
|
for (const host of HOSTS) {
|
|
181
202
|
syncTrackedHostMode(config, host, results?.[host], newMode)
|
|
@@ -187,13 +208,25 @@ export function switchMode(newMode) {
|
|
|
187
208
|
function runAllHostsLifecycle(action, explicitMode) {
|
|
188
209
|
if (action === 'cleanup' || action === 'uninstall') {
|
|
189
210
|
console.log(`\n HelloAGENTS — ${runtime.msg('正在清理', 'Cleaning up')}\n`)
|
|
190
|
-
|
|
211
|
+
const settings = existsSync(runtime.configFile) ? readSettings() : {}
|
|
212
|
+
const results = {}
|
|
213
|
+
for (const host of HOSTS) {
|
|
214
|
+
const mode = explicitMode || resolveHostMode(host, '', settings)
|
|
215
|
+
results[host] = runHostLifecycle(runtime, action, host, mode, {
|
|
216
|
+
previousMode: resolvePreviousHostMode(settings, host),
|
|
217
|
+
})
|
|
218
|
+
}
|
|
191
219
|
if (existsSync(runtime.configFile)) {
|
|
192
|
-
const
|
|
193
|
-
|
|
220
|
+
for (const host of HOSTS) {
|
|
221
|
+
syncCleanupTrackedHostMode(settings, host, results[host])
|
|
222
|
+
}
|
|
194
223
|
writeSettings(settings)
|
|
195
224
|
}
|
|
196
|
-
|
|
225
|
+
const hasFailures = Object.values(results).some((result) => result?.ok === false)
|
|
226
|
+
runtime.ok(runtime.msg(
|
|
227
|
+
hasFailures ? '部分 CLI 仍需手动清理' : '所有 CLI 配置已清理',
|
|
228
|
+
hasFailures ? 'Some CLI cleanup still needs manual action' : 'All CLI configurations cleaned',
|
|
229
|
+
))
|
|
197
230
|
console.log(runtime.msg(
|
|
198
231
|
' ℹ ~/.helloagents/ 已保留(如需彻底清理请手动删除)\n ℹ 已自动尝试移除 Claude/Gemini 插件或扩展;如宿主命令不可用,请手动执行对应移除命令',
|
|
199
232
|
' ℹ ~/.helloagents/ preserved (delete manually if desired)\n ℹ Claude/Gemini plugin or extension removal was attempted automatically; if host commands are unavailable, remove them manually',
|
|
@@ -203,12 +236,14 @@ function runAllHostsLifecycle(action, explicitMode) {
|
|
|
203
236
|
}
|
|
204
237
|
|
|
205
238
|
const settings = readSettings(true)
|
|
239
|
+
const previousModes = buildPreviousHostModes(settings)
|
|
206
240
|
if (!explicitMode) {
|
|
207
241
|
for (const host of HOSTS) {
|
|
208
242
|
const mode = resolveHostMode(host, '', settings)
|
|
209
|
-
const result = runHostLifecycle(runtime, action, host, mode
|
|
210
|
-
|
|
211
|
-
|
|
243
|
+
const result = runHostLifecycle(runtime, action, host, mode, {
|
|
244
|
+
previousMode: previousModes[host],
|
|
245
|
+
})
|
|
246
|
+
syncTrackedHostMode(settings, host, result, mode)
|
|
212
247
|
}
|
|
213
248
|
writeSettings(settings)
|
|
214
249
|
const modes = Object.values(settings.host_install_modes || {})
|
|
@@ -221,12 +256,10 @@ function runAllHostsLifecycle(action, explicitMode) {
|
|
|
221
256
|
|
|
222
257
|
const mode = resolveInstallMode(explicitMode, settings)
|
|
223
258
|
if (explicitMode) settings.install_mode = explicitMode
|
|
224
|
-
const results = installAllHosts(runtime, mode)
|
|
259
|
+
const results = installAllHosts(runtime, mode, { previousModes })
|
|
225
260
|
settings.host_install_modes = {}
|
|
226
261
|
for (const host of HOSTS) {
|
|
227
|
-
|
|
228
|
-
settings.host_install_modes[host] = mode
|
|
229
|
-
}
|
|
262
|
+
syncTrackedHostMode(settings, host, results?.[host], mode)
|
|
230
263
|
}
|
|
231
264
|
writeSettings(settings)
|
|
232
265
|
runtime.printInstallMsg(mode, action === 'update' ? 'refresh' : 'install')
|
|
@@ -242,11 +275,13 @@ export function runScopedLifecycle(action, rawArgs) {
|
|
|
242
275
|
const shouldEnsure = action === 'install' || action === 'update'
|
|
243
276
|
const settings = readSettings(shouldEnsure)
|
|
244
277
|
const mode = resolveHostMode(host, explicitMode, settings)
|
|
245
|
-
const result = runHostLifecycle(runtime, action, host, mode
|
|
278
|
+
const result = runHostLifecycle(runtime, action, host, mode, {
|
|
279
|
+
previousMode: resolvePreviousHostMode(settings, host),
|
|
280
|
+
})
|
|
246
281
|
|
|
247
282
|
if (action === 'cleanup' || action === 'uninstall') {
|
|
248
283
|
if (existsSync(runtime.configFile)) {
|
|
249
|
-
|
|
284
|
+
syncCleanupTrackedHostMode(settings, host, result)
|
|
250
285
|
writeSettings(settings)
|
|
251
286
|
}
|
|
252
287
|
} else if (!result.skipped) {
|
package/scripts/cli-messages.mjs
CHANGED
|
@@ -65,18 +65,18 @@ function renderInstallMessage(context, mode, state) {
|
|
|
65
65
|
|
|
66
66
|
if (install) {
|
|
67
67
|
return msg(
|
|
68
|
-
`\n ✅ HelloAGENTS 已安装(standby 模式)!\n\n Claude Code: 已自动配置(~/.claude/CLAUDE.md + hooks)\n Gemini CLI: 已自动配置(~/.gemini/GEMINI.md)\n Codex: ${codexStandbyStatus(context)}\n\n ${restartHint(msg)}\n\n standby 模式下,hello-* 技能不会自动触发。\n 在项目中使用 ~
|
|
69
|
-
`\n ✅ HelloAGENTS installed (standby mode)!\n\n Claude Code: Auto-configured (~/.claude/CLAUDE.md + hooks)\n Gemini CLI: Auto-configured (~/.gemini/GEMINI.md)\n Codex: ${codexStandbyStatus(context)}\n\n ${restartHint(msg)}\n\n In standby mode, hello-* skills won't auto-trigger.\n Use ~
|
|
68
|
+
`\n ✅ HelloAGENTS 已安装(standby 模式)!\n\n Claude Code: 已自动配置(~/.claude/CLAUDE.md + hooks)\n Gemini CLI: 已自动配置(~/.gemini/GEMINI.md)\n Codex: ${codexStandbyStatus(context)}\n\n ${restartHint(msg)}\n\n standby 模式下,hello-* 技能不会自动触发。\n 在项目中使用 ~init 初始化完整项目工作流;未初始化时也可继续用 ~command 按需调用。\n\n 切换模式:\n helloagents --global 宿主级全局部署(自动尝试 Claude/Gemini 插件或扩展;Codex 自动装原生本地插件)`,
|
|
69
|
+
`\n ✅ HelloAGENTS installed (standby mode)!\n\n Claude Code: Auto-configured (~/.claude/CLAUDE.md + hooks)\n Gemini CLI: Auto-configured (~/.gemini/GEMINI.md)\n Codex: ${codexStandbyStatus(context)}\n\n ${restartHint(msg)}\n\n In standby mode, hello-* skills won't auto-trigger.\n Use ~init to initialize the full project workflow; uninitialized repos can still use ~command on demand.\n\n Switch modes:\n helloagents --global Host-wide global deployment (auto-attempts Claude/Gemini plugins or extensions; native local plugin auto-install for Codex)`,
|
|
70
70
|
)
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
return msg(
|
|
74
74
|
refresh
|
|
75
75
|
? ` standby 模式已刷新,CLI 注入与链接已同步最新文件。\n ${restartHint(msg)}\n ${removeHint(msg)}`
|
|
76
|
-
: ` 项目可通过 ~
|
|
76
|
+
: ` 项目可通过 ~init 初始化完整工作流;未初始化时仅注入轻量规则。\n ${restartHint(msg)}\n ${removeHint(msg)}`,
|
|
77
77
|
refresh
|
|
78
78
|
? ` Standby mode refreshed; injected files and links were synchronized.\n ${restartHint(msg)}\n ${removeHint(msg)}`
|
|
79
|
-
: ` Projects can use ~
|
|
79
|
+
: ` Projects can use ~init to initialize the full workflow; projects that are not initialized get lite rules only.\n ${restartHint(msg)}\n ${removeHint(msg)}`,
|
|
80
80
|
)
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -90,7 +90,7 @@ HelloAGENTS v${pkgVersion} — The orchestration kernel for AI CLIs
|
|
|
90
90
|
helloagents-js ${msg('(受管宿主配置的跨平台稳定入口)', '(cross-platform stable entrypoint for managed host configs)')}
|
|
91
91
|
|
|
92
92
|
${msg('模式切换', 'Mode switching')}:
|
|
93
|
-
helloagents --global ${msg('
|
|
93
|
+
helloagents --global ${msg('宿主级全局部署(自动尝试 Claude/Gemini 插件或扩展;Codex 自动装原生本地插件)', 'Host-wide global deployment (auto-attempts Claude/Gemini plugins or extensions; native local plugin auto-install for Codex)')}
|
|
94
94
|
helloagents --standby ${msg('标准模式(非插件安装,hello-* 不自动触发,默认)', "Standby mode (non-plugin install, hello-* won't auto-trigger, default)")}
|
|
95
95
|
|
|
96
96
|
${msg('单 CLI 管理', 'Scoped CLI management')}:
|