helloagents 3.0.29 → 3.0.30

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.
Files changed (44) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +50 -37
  4. package/README_CN.md +50 -35
  5. package/bootstrap-lite.md +6 -6
  6. package/bootstrap.md +10 -8
  7. package/gemini-extension.json +1 -1
  8. package/install.ps1 +5 -5
  9. package/install.sh +5 -5
  10. package/package.json +1 -1
  11. package/scripts/advisor-state.mjs +1 -1
  12. package/scripts/cli-branch.mjs +7 -3
  13. package/scripts/cli-codex.mjs +6 -5
  14. package/scripts/cli-deepseek.mjs +131 -0
  15. package/scripts/cli-doctor-codex.mjs +10 -5
  16. package/scripts/cli-doctor-render.mjs +16 -0
  17. package/scripts/cli-doctor.mjs +78 -5
  18. package/scripts/cli-host-detect.mjs +29 -0
  19. package/scripts/cli-hosts.mjs +8 -0
  20. package/scripts/cli-lifecycle-hosts.mjs +28 -2
  21. package/scripts/cli-lifecycle.mjs +1 -1
  22. package/scripts/cli-messages.mjs +28 -15
  23. package/scripts/cli-runtime-carrier.mjs +3 -3
  24. package/scripts/cli-utils.mjs +9 -0
  25. package/scripts/notify-events.mjs +1 -0
  26. package/scripts/notify-route.mjs +3 -4
  27. package/scripts/notify-source.mjs +1 -0
  28. package/scripts/notify.mjs +6 -1
  29. package/scripts/plan-contract.mjs +1 -1
  30. package/scripts/project-storage.mjs +4 -4
  31. package/scripts/replay-state.mjs +22 -4
  32. package/scripts/runtime-context.mjs +14 -2
  33. package/scripts/runtime-scope.mjs +144 -2
  34. package/scripts/session-capsule.mjs +14 -0
  35. package/scripts/turn-state.mjs +7 -0
  36. package/skills/commands/build/SKILL.md +1 -1
  37. package/skills/commands/commit/SKILL.md +2 -2
  38. package/skills/commands/global/SKILL.md +71 -0
  39. package/skills/commands/help/SKILL.md +16 -15
  40. package/skills/commands/init/SKILL.md +14 -31
  41. package/skills/commands/wiki/SKILL.md +1 -1
  42. package/skills/hello-review/SKILL.md +1 -1
  43. package/skills/hello-ui/SKILL.md +5 -5
  44. package/skills/helloagents/SKILL.md +5 -4
@@ -1,6 +1,15 @@
1
1
  import { spawnSync } from 'node:child_process'
2
2
 
3
- import { installClaudeStandby, installGeminiStandby, uninstallClaudeStandby, uninstallGeminiStandby } from './cli-hosts.mjs'
3
+ import {
4
+ installClaudeStandby,
5
+ installDeepseekGlobal,
6
+ installDeepseekStandby,
7
+ installGeminiStandby,
8
+ uninstallClaudeStandby,
9
+ uninstallDeepseekGlobal,
10
+ uninstallDeepseekStandby,
11
+ uninstallGeminiStandby,
12
+ } from './cli-hosts.mjs'
4
13
  import {
5
14
  cleanupCodexGlobalResidueForStandby,
6
15
  installCodexGlobal,
@@ -91,6 +100,10 @@ function installHostStandby(runtime, host) {
91
100
  installGeminiStandby(runtime.home, runtime.pkgRoot)
92
101
  return {}
93
102
  }
103
+ if (host === 'deepseek') {
104
+ installDeepseekStandby(runtime.home, runtime.pkgRoot)
105
+ return {}
106
+ }
94
107
  if (!installCodexStandby(runtime.home, runtime.pkgRoot)) return { skipped: true }
95
108
  cleanupCodexGlobalResidueForStandby(runtime.home)
96
109
  return {}
@@ -117,6 +130,10 @@ function installHostGlobal(runtime, host) {
117
130
  'Gemini CLI extension auto-install failed. Run manually: gemini extensions install https://github.com/hellowind777/helloagents',
118
131
  )
119
132
  }
133
+ if (host === 'deepseek') {
134
+ installDeepseekGlobal(runtime.home, runtime.pkgRoot)
135
+ return {}
136
+ }
120
137
  uninstallCodexStandby(runtime.home)
121
138
  return installCodexGlobal(runtime.home, runtime.pkgRoot) ? {} : { skipped: true }
122
139
  }
@@ -124,6 +141,7 @@ function installHostGlobal(runtime, host) {
124
141
  function cleanupHostStandby(runtime, host) {
125
142
  if (host === 'claude') return { skipped: !uninstallClaudeStandby(runtime.home) }
126
143
  if (host === 'gemini') return { skipped: !uninstallGeminiStandby(runtime.home) }
144
+ if (host === 'deepseek') return { skipped: !uninstallDeepseekStandby(runtime.home) }
127
145
  const standbyCleaned = uninstallCodexStandby(runtime.home)
128
146
  const globalResidueCleaned = uninstallCodexGlobal(runtime.home)
129
147
  return { skipped: !(standbyCleaned || globalResidueCleaned) }
@@ -150,6 +168,7 @@ function cleanupHostGlobal(runtime, host) {
150
168
  'Gemini CLI extension auto-remove failed. Run manually: gemini extensions uninstall helloagents',
151
169
  )
152
170
  }
171
+ if (host === 'deepseek') return { skipped: !uninstallDeepseekGlobal(runtime.home) }
153
172
  return { skipped: !uninstallCodexGlobal(runtime.home) }
154
173
  }
155
174
 
@@ -175,12 +194,18 @@ function installStandby(runtime) {
175
194
  console.log(runtime.msg(' - Codex CLI 未检测到,跳过', ' - Codex CLI not detected, skipped'))
176
195
  results.codex = { skipped: true }
177
196
  }
197
+ if (installDeepseekStandby(runtime.home, runtime.pkgRoot)) {
198
+ runtime.ok(runtime.msg('DeepSeek TUI 已配置(standby 模式)', 'DeepSeek TUI configured (standby mode)'))
199
+ results.deepseek = {}
200
+ } else {
201
+ results.deepseek = { skipped: true }
202
+ }
178
203
  return results
179
204
  }
180
205
 
181
206
  function installGlobal(runtime) {
182
207
  const results = {}
183
- for (const host of ['claude', 'gemini', 'codex']) {
208
+ for (const host of ['claude', 'gemini', 'codex', 'deepseek']) {
184
209
  const result = installHostGlobal(runtime, host)
185
210
  reportHostAction(runtime, 'install', host, 'global', result)
186
211
  results[host] = result
@@ -198,6 +223,7 @@ export function uninstallAllHosts(runtime) {
198
223
  cleanupHostGlobal(runtime, 'gemini')
199
224
  uninstallCodexStandby(runtime.home)
200
225
  uninstallCodexGlobal(runtime.home)
226
+ uninstallDeepseekStandby(runtime.home)
201
227
  }
202
228
 
203
229
  export function runHostLifecycle(runtime, action, host, mode) {
@@ -10,7 +10,7 @@ import {
10
10
  import { installAllHosts, runHostLifecycle, uninstallAllHosts } from './cli-lifecycle-hosts.mjs'
11
11
  import { ensureDir, safeJson, safeWrite } from './cli-utils.mjs'
12
12
 
13
- export const HOSTS = ['claude', 'gemini', 'codex']
13
+ export const HOSTS = ['claude', 'gemini', 'codex', 'deepseek']
14
14
 
15
15
  const runtime = {
16
16
  home: '',
@@ -19,6 +19,18 @@ function codexGlobalStatus({ home, msg }) {
19
19
  : msg('安装 Codex CLI 后重新运行 npm install -g helloagents', 'Install Codex CLI then re-run npm install -g helloagents')
20
20
  }
21
21
 
22
+ function deepseekStandbyStatus({ home, msg }) {
23
+ return existsSync(join(home, '.deepseek'))
24
+ ? msg('已自动配置(~/.deepseek/AGENTS.md)', 'Auto-configured (~/.deepseek/AGENTS.md)')
25
+ : msg('安装 DeepSeek TUI 后重新运行 npm install -g helloagents', 'Install DeepSeek TUI then re-run npm install -g helloagents')
26
+ }
27
+
28
+ function deepseekGlobalStatus({ home, msg }) {
29
+ return existsSync(join(home, '.deepseek'))
30
+ ? msg('已自动切到受管全局载体(~/.deepseek/AGENTS.md)', 'Managed global carrier applied (~/.deepseek/AGENTS.md)')
31
+ : msg('安装 DeepSeek TUI 后重新运行 npm install -g helloagents', 'Install DeepSeek TUI then re-run npm install -g helloagents')
32
+ }
33
+
22
34
  function pluginCommands() {
23
35
  return [
24
36
  ' Claude Code: /plugin marketplace add hellowind777/helloagents',
@@ -49,34 +61,34 @@ function renderInstallMessage(context, mode, state) {
49
61
  if (mode === 'global') {
50
62
  if (install) {
51
63
  return msg(
52
- `\n ✅ HelloAGENTS 已安装(global 模式)!\n\n Claude Code / Gemini CLI: 已自动尝试宿主原生插件/扩展安装\n Codex: ${codexGlobalStatus(context)}(~/.agents/plugins/marketplace.json + ~/plugins/helloagents)\n\n ${restartHint(msg)}\n\n 若宿主命令不可用,请手动执行:\n${pluginCommands()}\n\n 切换模式:\n helloagents --standby 标准模式(默认,非插件安装)`,
53
- `\n ✅ HelloAGENTS installed (global mode)!\n\n Claude Code / Gemini CLI: native plugin/extension install attempted automatically\n Codex: ${codexGlobalStatus(context)} (~/.agents/plugins/marketplace.json + ~/plugins/helloagents)\n\n ${restartHint(msg)}\n\n If a host command is unavailable, run manually:\n${pluginCommands()}\n\n Switch modes:\n helloagents --standby Standby mode (default, non-plugin install)`,
64
+ `\n ✅ HelloAGENTS 已安装(global 模式)!\n\n Claude Code / Gemini CLI: 已自动尝试宿主原生插件/扩展安装\n Codex: ${codexGlobalStatus(context)}(~/.agents/plugins/marketplace.json + ~/plugins/helloagents)\n DeepSeek TUI: ${deepseekGlobalStatus(context)}\n\n ${restartHint(msg)}\n\n 若宿主命令不可用,请手动执行:\n${pluginCommands()}\n\n 切换模式:\n helloagents --standby 标准模式(默认,非插件安装)`,
65
+ `\n ✅ HelloAGENTS installed (global mode)!\n\n Claude Code / Gemini CLI: native plugin/extension install attempted automatically\n Codex: ${codexGlobalStatus(context)} (~/.agents/plugins/marketplace.json + ~/plugins/helloagents)\n DeepSeek TUI: ${deepseekGlobalStatus(context)}\n\n ${restartHint(msg)}\n\n If a host command is unavailable, run manually:\n${pluginCommands()}\n\n Switch modes:\n helloagents --standby Standby mode (default, non-plugin install)`,
54
66
  )
55
67
  }
56
68
  return msg(
57
69
  refresh
58
- ? ` global 模式已刷新。\n Claude Code / Gemini 已自动尝试刷新宿主插件/扩展;Codex 原生本地插件已重装并同步最新文件。\n ${restartHint(msg)}`
59
- : ` 所有项目将自动启用完整 HelloAGENTS 规则。\n Claude Code / Gemini 已自动尝试安装宿主插件/扩展;Codex 已自动安装原生本地插件。\n ${restartHint(msg)}\n\n若宿主命令不可用,请手动执行:\n${pluginCommands()}`,
70
+ ? ` global 模式已刷新。\n Claude Code / Gemini 已自动尝试刷新宿主插件/扩展;Codex 原生本地插件已重装并同步最新文件;DeepSeek TUI 受管全局载体已同步。\n ${restartHint(msg)}`
71
+ : ` 所有项目将自动启用完整 HelloAGENTS 规则。\n Claude Code / Gemini 已自动尝试安装宿主插件/扩展;Codex 已自动安装原生本地插件;DeepSeek TUI 已切到受管全局载体。\n ${restartHint(msg)}\n\n若宿主命令不可用,请手动执行:\n${pluginCommands()}`,
60
72
  refresh
61
- ? ` Global mode refreshed.\n Claude Code / Gemini native plugin/extension refresh was attempted automatically; Codex native local-plugin files were reinstalled and synced.\n ${restartHint(msg)}`
62
- : ` All projects will use full HelloAGENTS rules.\n Claude Code / Gemini native plugin/extension install was attempted automatically; Codex now uses the native local-plugin path automatically.\n ${restartHint(msg)}\n\nIf a host command is unavailable, run manually:\n${pluginCommands()}`,
73
+ ? ` Global mode refreshed.\n Claude Code / Gemini native plugin/extension refresh was attempted automatically; Codex native local-plugin files were reinstalled and synced; the managed DeepSeek TUI global carrier was refreshed.\n ${restartHint(msg)}`
74
+ : ` All projects will use full HelloAGENTS rules.\n Claude Code / Gemini native plugin/extension install was attempted automatically; Codex now uses the native local-plugin path automatically; DeepSeek TUI now uses the managed global carrier.\n ${restartHint(msg)}\n\nIf a host command is unavailable, run manually:\n${pluginCommands()}`,
63
75
  )
64
76
  }
65
77
 
66
78
  if (install) {
67
79
  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 在项目中使用 ~wiki 仅创建/同步知识库,或用 ~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 ~wiki to create or sync the KB only, or ~init for the full project setup; ~command stays available on demand.\n\n Switch modes:\n helloagents --global Global mode (auto-attempts Claude/Gemini plugins or extensions; native local plugin auto-install for Codex)`,
80
+ `\n ✅ HelloAGENTS 已安装(standby 模式)!\n\n Claude Code: 已自动配置(~/.claude/CLAUDE.md + hooks)\n Gemini CLI: 已自动配置(~/.gemini/GEMINI.md)\n Codex: ${codexStandbyStatus(context)}\n DeepSeek TUI: ${deepseekStandbyStatus(context)}\n\n ${restartHint(msg)}\n\n standby 模式下,hello-* 技能不会自动触发。\n 在项目中使用 ~wiki ~init 仅创建/同步知识库;用 ~global 初始化项目级全局模式;也可用 ~command 按需调用。\n\n 切换模式:\n helloagents --global 项目级全局模式(自动尝试 Claude/Gemini 插件或扩展;Codex 自动装原生本地插件;DeepSeek 使用受管 AGENTS 载体)`,
81
+ `\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 DeepSeek TUI: ${deepseekStandbyStatus(context)}\n\n ${restartHint(msg)}\n\n In standby mode, hello-* skills won't auto-trigger.\n Use ~wiki or ~init to create or sync the KB only; use ~global to initialize project-level global mode; ~command stays available on demand.\n\n Switch modes:\n helloagents --global Project-level global mode (auto-attempts Claude/Gemini plugins or extensions; native local plugin auto-install for Codex; DeepSeek uses a managed AGENTS carrier)`,
70
82
  )
71
83
  }
72
84
 
73
85
  return msg(
74
86
  refresh
75
87
  ? ` standby 模式已刷新,CLI 注入与链接已同步最新文件。\n ${restartHint(msg)}\n ${removeHint(msg)}`
76
- : ` 项目可通过 ~wiki 创建/同步知识库,或通过 ~init 完整初始化;未激活项目仅注入通用规则。\n ${restartHint(msg)}\n ${removeHint(msg)}`,
88
+ : ` 项目可通过 ~wiki ~init 创建/同步知识库;用 ~global 初始化项目级全局模式;未初始化时仅注入轻量规则。\n ${restartHint(msg)}\n ${removeHint(msg)}`,
77
89
  refresh
78
90
  ? ` Standby mode refreshed; injected files and links were synchronized.\n ${restartHint(msg)}\n ${removeHint(msg)}`
79
- : ` Projects can use ~wiki for KB-only activation or ~init for the full project setup. Unactivated projects get lite rules only.\n ${restartHint(msg)}\n ${removeHint(msg)}`,
91
+ : ` Projects can use ~wiki or ~init to create/sync the KB; use ~global to initialize project-level global mode. Projects that are not initialized get lite rules only.\n ${restartHint(msg)}\n ${removeHint(msg)}`,
80
92
  )
81
93
  }
82
94
 
@@ -84,33 +96,34 @@ function renderHelp({ pkgVersion, msg }) {
84
96
  return `
85
97
  HelloAGENTS v${pkgVersion} — The orchestration kernel for AI CLIs
86
98
 
87
- ${msg('安装', 'Install')}:
99
+ ${msg('安装', 'Install')}:
88
100
  npm install -g helloagents ${msg('(安装命令并同步稳定运行根目录;CLI 部署需显式执行 helloagents install ...)', '(installs the command and syncs the stable runtime root; deploy to CLIs explicitly with helloagents install ...)')}
89
101
  HELLOAGENTS=codex:global npm install -g helloagents
90
102
  helloagents-js ${msg('(受管宿主配置的跨平台稳定入口)', '(cross-platform stable entrypoint for managed host configs)')}
91
103
 
92
104
  ${msg('模式切换', 'Mode switching')}:
93
- helloagents --global ${msg('全局模式(自动尝试 Claude/Gemini 插件或扩展;Codex 自动装原生本地插件)', 'Global mode (auto-attempts Claude/Gemini plugins or extensions; native local plugin auto-install for Codex)')}
105
+ helloagents --global ${msg('项目级全局模式(自动尝试 Claude/Gemini 插件或扩展;Codex 自动装原生本地插件;DeepSeek 使用受管 AGENTS 载体)', 'Project-level global mode (auto-attempts Claude/Gemini plugins or extensions; native local plugin auto-install for Codex; DeepSeek uses a managed AGENTS carrier)')}
94
106
  helloagents --standby ${msg('标准模式(非插件安装,hello-* 不自动触发,默认)', "Standby mode (non-plugin install, hello-* won't auto-trigger, default)")}
95
107
 
96
108
  ${msg('单 CLI 管理', 'Scoped CLI management')}:
97
109
  helloagents install codex --standby
110
+ helloagents install deepseek --standby
98
111
  helloagents install --all --global
99
112
  helloagents update codex
100
113
  helloagents cleanup claude --global
101
114
  helloagents uninstall gemini
102
- ${msg('支持: claude | gemini | codex | --all;省略模式时优先沿用该 CLI 已记录/已检测的模式,否则回退 standby', 'Hosts: claude | gemini | codex | --all; omit mode to reuse the tracked/detected mode for that CLI, then fall back to standby')}
115
+ ${msg('支持: claude | gemini | codex | deepseek | --all;省略模式时优先沿用该 CLI 已记录/已检测的模式,否则回退 standby', 'Hosts: claude | gemini | codex | deepseek | --all; omit mode to reuse the tracked/detected mode for that CLI, then fall back to standby')}
103
116
 
104
117
  ${msg('分支切换', 'Branch switching')}:
105
118
  helloagents switch-branch beta
106
119
  helloagents switch-branch beta claude --global
107
- helloagents branch github:hellowind777/helloagents#beta --all --standby
120
+ helloagents branch beta --all --standby
108
121
  ${msg('先通过 npm 安装指定 ref,再通过 npm 脚本同步宿主 CLI', 'Installs the requested ref with npm first, then syncs host CLIs through npm scripts')}
109
122
 
110
123
  ${msg('诊断', 'Diagnostics')}:
111
124
  helloagents doctor
112
125
  helloagents doctor codex --json
113
- ${msg('检查 carrier、链接、hooks、配置注入、Codex 插件安装、受管 model_instructions_file 指向、Codex hook trust 本机状态与版本漂移', 'Checks carriers, links, hooks, config injections, Codex plugin installation, managed model_instructions_file targeting, machine-local Codex hook trust state, and version drift')}
126
+ ${msg('检查 carrier、链接、hooks、配置注入、Codex 插件安装、DeepSeek 原生 doctor 摘要、受管 model_instructions_file 指向、Codex hook trust 本机状态与版本漂移', 'Checks carriers, links, hooks, config injections, Codex plugin installation, DeepSeek native doctor summaries, managed model_instructions_file targeting, machine-local Codex hook trust state, and version drift')}
114
127
 
115
128
  ${msg('Codex /goal', 'Codex /goal')}:
116
129
  helloagents codex goals status
@@ -1,15 +1,15 @@
1
1
  import { join } from 'node:path'
2
2
 
3
- import { safeJson } from './cli-utils.mjs'
3
+ import { safeJson, withCarrierProfile } from './cli-utils.mjs'
4
4
 
5
5
  export function readCarrierSettings(home) {
6
6
  return safeJson(join(home, '.helloagents', 'helloagents.json')) || {}
7
7
  }
8
8
 
9
- export function buildRuntimeCarrier(bootstrapContent, settings = {}) {
9
+ export function buildRuntimeCarrier(bootstrapContent, settings = {}, options = {}) {
10
10
  void settings
11
11
  const normalized = String(bootstrapContent || '').trim()
12
12
  if (!normalized) return ''
13
13
 
14
- return `${normalized}\n`
14
+ return withCarrierProfile(normalized, options.profile || '')
15
15
  }
@@ -54,8 +54,17 @@ export function removeLink(p) {
54
54
 
55
55
  const MARKER = '<!-- HELLOAGENTS_START -->';
56
56
  const MARKER_END = '<!-- HELLOAGENTS_END -->';
57
+ export const FULL_CARRIER_PROFILE_MARKER = '<!-- HELLOAGENTS_PROFILE: full -->';
57
58
  const MARKER_RE = new RegExp(`\\n*${MARKER}[\\s\\S]*?${MARKER_END}\\n*`, 'g');
58
59
 
60
+ export function withCarrierProfile(content, profile = '') {
61
+ const normalized = String(content || '').trim();
62
+ if (!normalized) return '';
63
+ if (profile !== 'full') return `${normalized}\n`;
64
+ if (normalized.includes(FULL_CARRIER_PROFILE_MARKER)) return `${normalized}\n`;
65
+ return `${FULL_CARRIER_PROFILE_MARKER}\n${normalized}\n`;
66
+ }
67
+
59
68
  /** Inject content wrapped in markers, preserving existing content outside markers. */
60
69
  export function injectMarkedContent(filePath, content) {
61
70
  const existing = safeRead(filePath) || '';
@@ -11,6 +11,7 @@ export function shouldIgnoreFormattedSubagent(lastMsg, outputFormatEnabled) {
11
11
  export function resolveNotifyHost(argv = []) {
12
12
  const args = Array.from(argv, (value) => String(value || ''));
13
13
  const command = args[2] || args[0] || '';
14
+ if (args.includes('--deepseek')) return 'deepseek';
14
15
  if (args.includes('--gemini')) return 'gemini';
15
16
  if (args.includes('--codex') || command === 'codex-notify') return 'codex';
16
17
  return 'claude';
@@ -1,4 +1,4 @@
1
- import { isProjectRuntimeActive } from './runtime-scope.mjs'
1
+ import { hasProjectFullCarrier } from './runtime-scope.mjs'
2
2
 
3
3
  export function resolveRuntimeInstallMode(settings = {}, host = '') {
4
4
  if (!settings || typeof settings !== 'object') return 'standby'
@@ -13,8 +13,7 @@ export function resolveBootstrapFile(cwd, settings = {}, host = '') {
13
13
  const installMode = typeof settings === 'string'
14
14
  ? settings
15
15
  : resolveRuntimeInstallMode(settings, host)
16
- const isActivated = isProjectRuntimeActive(cwd)
17
- return (installMode === 'global' || isActivated) ? 'bootstrap.md' : 'bootstrap-lite.md'
16
+ return (installMode === 'global' || hasProjectFullCarrier(cwd, host)) ? 'bootstrap.md' : 'bootstrap-lite.md'
18
17
  }
19
18
 
20
19
  function shouldBypassRoute(prompt) {
@@ -23,7 +22,7 @@ function shouldBypassRoute(prompt) {
23
22
 
24
23
  function buildHelpExtraRules(skillName) {
25
24
  if (skillName !== 'help') return ''
26
- return ' 这是 HelloAGENTS 的帮助命令,不是宿主 CLI 的内置帮助。仅显示 HelloAGENTS 的帮助和当前设置;优先使用当前会话上下文中已注入的“当前用户设置”、配置文件原始 JSON 或此前读取结果摘要,上下文不存在或缺少要展示的配置项时才读取一次 ~/.helloagents/helloagents.json;自动激活技能说明仅在全局模式或已激活项目中生效。不要调用宿主 CLI 的帮助工具(如 cli_help 或 /help),不要使用子代理,不要读取项目文件;若受工作区限制无法读取配置,必须明确说明并按已知默认值或已注入设置展示。'
25
+ return ' 这是 HelloAGENTS 的帮助命令,不是宿主 CLI 的内置帮助。仅显示 HelloAGENTS 的帮助和当前设置;优先使用当前会话上下文中已注入的“当前用户设置”、配置文件原始 JSON 或此前读取结果摘要,上下文不存在或缺少要展示的配置项时才读取一次 ~/.helloagents/helloagents.json;自动激活技能说明仅在全局模式或已初始化项目时生效。不要调用宿主 CLI 的帮助工具(如 cli_help 或 /help),不要使用子代理,不要读取项目文件;若受工作区限制无法读取配置,必须明确说明并按已知默认值或已注入设置展示。'
27
26
  }
28
27
 
29
28
  function routeExplicitCommand({
@@ -6,6 +6,7 @@ const HOST_LABELS = {
6
6
  codex: 'Codex',
7
7
  claude: 'Claude Code',
8
8
  gemini: 'Gemini',
9
+ deepseek: 'DeepSeek TUI',
9
10
  }
10
11
 
11
12
  function normalizePath(filePath = '') {
@@ -391,6 +391,7 @@ function cmdInject() {
391
391
  const cwd = payload.cwd || process.cwd();
392
392
  const settings = getSettings();
393
393
  const bootstrapFile = resolveBootstrapFile(cwd, settings, HOST);
394
+ const shouldEnsureProjectLocal = bootstrapFile === 'bootstrap.md' || source === 'resume' || source === 'compact';
394
395
 
395
396
  startReplaySession(cwd, {
396
397
  host: HOST,
@@ -398,6 +399,7 @@ function cmdInject() {
398
399
  bootstrapFile,
399
400
  installMode: settings.install_mode || '',
400
401
  payload,
402
+ ensureProjectLocal: shouldEnsureProjectLocal,
401
403
  });
402
404
  if (!IS_SILENT) {
403
405
  appendReplayEvent(cwd, {
@@ -413,7 +415,10 @@ function cmdInject() {
413
415
  });
414
416
  }
415
417
  clearRouteContext({ cwd, payload });
416
- clearTurnState(cwd, { payload });
418
+ clearTurnState(cwd, {
419
+ payload,
420
+ ensureProjectLocal: shouldEnsureProjectLocal,
421
+ });
417
422
  cleanupProjectSessions(cwd, {
418
423
  minIntervalMs: IS_SILENT ? PROJECT_SESSION_CLEANUP_COOLDOWN_MS : 0,
419
424
  });
@@ -5,7 +5,7 @@ import { resolveProjectPlanDir } from './project-storage.mjs'
5
5
 
6
6
  export const PLAN_CONTRACT_FILE_NAME = 'contract.json'
7
7
  const VALID_VERIFY_MODES = new Set(['test-first', 'review-first'])
8
- const VALID_ADVISOR_SOURCES = new Set(['claude', 'codex', 'gemini'])
8
+ const VALID_ADVISOR_SOURCES = new Set(['claude', 'codex', 'gemini', 'deepseek'])
9
9
 
10
10
  function normalizeStringArray(values) {
11
11
  if (!Array.isArray(values)) return []
@@ -250,7 +250,7 @@ export function buildProjectStorageHint(cwd, options = {}) {
250
250
  hints.push(`当前宿主未提供稳定会话标识,因此使用工作区默认位置 \`${summary.stateSessionToken}\``)
251
251
  }
252
252
  if (summary.usesSharedStore) {
253
- hints.push(`项目存储:\`project_store_mode=repo-shared\`;本地激活/会话运行态目录仍是 \`${summary.promptActivationDir}\`,知识库/方案目录改为 \`${summary.promptStoreDir}\``)
253
+ hints.push(`项目存储:\`project_store_mode=repo-shared\`;项目本地存储/会话运行态目录仍是 \`${summary.promptActivationDir}\`,知识库/方案目录改为 \`${summary.promptStoreDir}\``)
254
254
  }
255
255
  return hints.join('。') + (hints.length > 0 ? '。' : '')
256
256
  }
@@ -263,7 +263,7 @@ export function buildProjectStorageBlock(cwd, options = {}) {
263
263
 
264
264
  const details = {
265
265
  project_store_mode: summary.projectStoreMode,
266
- activation_dir: summary.promptActivationDir,
266
+ project_local_dir: summary.promptActivationDir,
267
267
  state_scope: summary.stateScope,
268
268
  state_path: summary.promptStatePath,
269
269
  state_workspace: summary.stateWorkspace,
@@ -281,9 +281,9 @@ export function buildProjectStorageBlock(cwd, options = {}) {
281
281
  explanations.push('说明:当前宿主未提供稳定会话标识,因此使用工作区默认位置。')
282
282
  }
283
283
  if (summary.usesSharedStore) {
284
- explanations.push('说明:状态文件与会话产物写本地激活目录;`context.md`、`guidelines.md`、`DESIGN.md`、`verify.yaml`、`modules/`、`plans/`、`archive/` 写知识库/方案目录。')
284
+ explanations.push('说明:状态文件与会话产物写项目本地存储目录;`context.md`、`guidelines.md`、`DESIGN.md`、`verify.yaml`、`modules/`、`plans/`、`archive/` 写知识库/方案目录。')
285
285
  } else {
286
- explanations.push('说明:当前使用项目本地 `.helloagents/` 作为激活目录、知识库目录和方案目录。')
286
+ explanations.push('说明:当前使用项目本地 `.helloagents/` 作为知识、方案、状态和运行态目录。')
287
287
  }
288
288
 
289
289
  return [
@@ -2,10 +2,11 @@ import { dirname } from 'node:path'
2
2
 
3
3
  import {
4
4
  appendSessionEvent,
5
+ getRuntimeScope,
5
6
  getSessionEventsPath,
6
7
  resetSessionEvents,
7
8
  } from './session-capsule.mjs'
8
- import { getProjectSessionScope } from './runtime-scope.mjs'
9
+ import { ensureProjectLocalRuntime, getProjectSessionScope } from './runtime-scope.mjs'
9
10
 
10
11
  function sanitizeReplayValue(value) {
11
12
  if (typeof value === 'string') {
@@ -60,10 +61,27 @@ export function startReplaySession(cwd, {
60
61
  bootstrapFile = '',
61
62
  installMode = '',
62
63
  payload = {},
64
+ ensureProjectLocal = false,
63
65
  env,
64
66
  ppid,
65
67
  } = {}) {
66
- const scope = getProjectSessionScope(cwd, { payload, env, ppid })
68
+ const scope = ensureProjectLocal
69
+ ? {
70
+ ...ensureProjectLocalRuntime(cwd, {
71
+ payload,
72
+ env,
73
+ ppid,
74
+ stateSeed: {
75
+ goal: '进入当前项目级执行流程',
76
+ doing: '正在初始化当前会话运行态',
77
+ context: '由运行时自动创建;后续按实际任务重写',
78
+ next: '根据当前用户请求继续执行当前流程',
79
+ },
80
+ }),
81
+ active: true,
82
+ scope: 'project-session',
83
+ }
84
+ : getProjectSessionScope(cwd, { payload, env, ppid })
67
85
  if (!scope.active) return ''
68
86
 
69
87
  const filePath = resetSessionEvents(cwd, { payload, env, ppid })
@@ -95,8 +113,8 @@ export function appendReplayEvent(cwd, {
95
113
  env,
96
114
  ppid,
97
115
  } = {}) {
98
- const scope = getProjectSessionScope(cwd, { payload, env, ppid })
99
- if (!scope.active || !event) return ''
116
+ const scope = getRuntimeScope(cwd, { payload, env, ppid })
117
+ if (scope.scope !== 'project-session' || !scope.active || !event) return ''
100
118
 
101
119
  return appendSessionEvent(cwd, sanitizeReplayValue({
102
120
  event,
@@ -78,7 +78,8 @@ export function clearRouteContext(options = {}) {
78
78
  }
79
79
 
80
80
  export function writeRouteContext({ cwd, skillName, sourceSkillName = skillName, payload = {}, env, ppid }) {
81
- const scope = getRuntimeScope(cwd, { payload, env, ppid })
81
+ const shouldEnsureProjectLocal = skillName !== 'idea' && skillName !== 'help'
82
+ const scope = getRuntimeScope(cwd, { payload, env, ppid, ensureProjectLocal: shouldEnsureProjectLocal })
82
83
  const context = {
83
84
  cwd: normalizePath(cwd),
84
85
  skillName,
@@ -91,7 +92,18 @@ export function writeRouteContext({ cwd, skillName, sourceSkillName = skillName,
91
92
  key: scope.key,
92
93
  updatedAt: Date.now(),
93
94
  }
94
- writeCapsuleSection(cwd, 'route', context, { payload, env, ppid })
95
+ writeCapsuleSection(cwd, 'route', context, {
96
+ payload,
97
+ env,
98
+ ppid,
99
+ ensureProjectLocal: shouldEnsureProjectLocal,
100
+ stateSeed: {
101
+ goal: `执行 ~${sourceSkillName}`,
102
+ doing: `已进入 ~${sourceSkillName} 路由`,
103
+ context: '由运行时首次进入非只读命令时创建;后续按实际任务重写',
104
+ next: `读取并执行 ~${skillName} 对应流程`,
105
+ },
106
+ })
95
107
  }
96
108
 
97
109
  export function readRouteContext(options = {}) {
@@ -7,6 +7,7 @@ import { homedir } from 'node:os'
7
7
  import { resolveSessionToken } from './session-token.mjs'
8
8
  import { USER_RUNTIME_MAX_AGE_MS } from './runtime-ttl.mjs'
9
9
  import { cleanupUserRuntimeRoot, getUserRuntimeRoot } from './runtime-user-cleanup.mjs'
10
+ import { FULL_CARRIER_PROFILE_MARKER } from './cli-utils.mjs'
10
11
 
11
12
  export const PROJECT_DIR_NAME = '.helloagents'
12
13
  export const PROJECT_SESSIONS_DIR_NAME = 'sessions'
@@ -143,6 +144,138 @@ export function getProjectRoot(cwd) {
143
144
  return activeDir ? dirname(activeDir) : normalizePath(cwd || process.cwd())
144
145
  }
145
146
 
147
+ function getCarrierPathForRoot(root, host = '') {
148
+ if (!root) return ''
149
+ if (host === 'codex' || host === 'deepseek') return join(root, 'AGENTS.md')
150
+ if (host === 'gemini') return join(root, '.gemini', 'GEMINI.md')
151
+ return join(root, 'CLAUDE.md')
152
+ }
153
+
154
+ function getCarrierCandidatePaths(root, host = '', { anyHost = false } = {}) {
155
+ if (!root) return []
156
+ if (!anyHost) return [getCarrierPathForRoot(root, host)]
157
+ return [
158
+ join(root, 'AGENTS.md'),
159
+ join(root, 'CLAUDE.md'),
160
+ join(root, '.gemini', 'GEMINI.md'),
161
+ ]
162
+ }
163
+
164
+ function hasFullCarrierMarker(filePath = '') {
165
+ if (!filePath || !existsSync(filePath)) return false
166
+ try {
167
+ return readFileSync(filePath, 'utf-8').includes(FULL_CARRIER_PROFILE_MARKER)
168
+ } catch {
169
+ return false
170
+ }
171
+ }
172
+
173
+ function findProjectCarrierRoot(cwd, host = '', options = {}) {
174
+ const normalizedCwd = normalizePath(cwd || process.cwd())
175
+ const gitRoot = resolveGitTopLevel(normalizedCwd)
176
+ const requireFullProfile = options.requireFullProfile === true
177
+ const anyHost = options.anyHost === true
178
+ let current = normalizedCwd
179
+
180
+ while (current) {
181
+ const candidates = getCarrierCandidatePaths(current, host, { anyHost })
182
+ const matched = candidates.some((filePath) =>
183
+ requireFullProfile ? hasFullCarrierMarker(filePath) : existsSync(filePath))
184
+ if (matched) return current
185
+ if (isUserHomeDir(current)) break
186
+ if (gitRoot && samePath(current, gitRoot)) break
187
+
188
+ const parent = dirname(current)
189
+ if (!parent || parent === current) break
190
+ current = parent
191
+ }
192
+
193
+ return ''
194
+ }
195
+
196
+ export function getProjectLocalRoot(cwd) {
197
+ const normalizedCwd = normalizePath(cwd || process.cwd())
198
+ const activeDir = findProjectActivationDir(normalizedCwd)
199
+ if (activeDir) return dirname(activeDir)
200
+
201
+ const fullCarrierRoot = findProjectCarrierRoot(normalizedCwd, '', {
202
+ anyHost: true,
203
+ requireFullProfile: true,
204
+ })
205
+ return fullCarrierRoot || resolveGitTopLevel(normalizedCwd) || normalizedCwd
206
+ }
207
+
208
+ export function getProjectLocalDir(cwd) {
209
+ return join(getProjectLocalRoot(cwd), PROJECT_DIR_NAME)
210
+ }
211
+
212
+ export function getProjectCarrierRoot(cwd) {
213
+ const normalizedCwd = normalizePath(cwd || process.cwd())
214
+ return findProjectCarrierRoot(normalizedCwd, '', { anyHost: true })
215
+ || resolveGitTopLevel(normalizedCwd)
216
+ || getProjectRoot(normalizedCwd)
217
+ }
218
+
219
+ export function getProjectCarrierPath(cwd, host = '') {
220
+ const normalizedCwd = normalizePath(cwd || process.cwd())
221
+ const carrierRoot = findProjectCarrierRoot(normalizedCwd, host, {
222
+ requireFullProfile: true,
223
+ }) || findProjectCarrierRoot(normalizedCwd, host)
224
+ || getProjectCarrierRoot(normalizedCwd)
225
+ return getCarrierPathForRoot(carrierRoot, host)
226
+ }
227
+
228
+ export function hasProjectFullCarrier(cwd, host = '') {
229
+ const carrierPath = getProjectCarrierPath(cwd, host)
230
+ return hasFullCarrierMarker(carrierPath)
231
+ }
232
+
233
+ function buildInitialStateSnapshot({
234
+ goal = '继续当前非只读任务',
235
+ doing = '已进入当前任务执行流程',
236
+ context = '由运行时自动创建;后续按实际任务重写',
237
+ next = '根据当前用户请求继续执行,并按实际任务重写本状态文件',
238
+ } = {}) {
239
+ return [
240
+ '# 恢复快照',
241
+ '',
242
+ '## 主线目标',
243
+ goal,
244
+ '',
245
+ '## 正在做什么',
246
+ doing,
247
+ '',
248
+ '## 关键上下文',
249
+ context,
250
+ '',
251
+ '## 下一步',
252
+ next,
253
+ '',
254
+ '## 阻塞项',
255
+ '(无)',
256
+ '',
257
+ '## 方案',
258
+ '',
259
+ '## 已标记技能',
260
+ '',
261
+ ].join('\n')
262
+ }
263
+
264
+ export function ensureProjectLocalRuntime(cwd, options = {}) {
265
+ const normalizedCwd = normalizePath(cwd || process.cwd())
266
+ const localDir = getProjectLocalDir(normalizedCwd)
267
+ mkdirSync(localDir, { recursive: true })
268
+
269
+ const scope = getProjectSessionScope(normalizedCwd, options)
270
+ mkdirSync(dirname(scope.statePath), { recursive: true })
271
+
272
+ if (!existsSync(scope.statePath)) {
273
+ writeFileSync(scope.statePath, `${buildInitialStateSnapshot(options.stateSeed || {})}\n`, 'utf-8')
274
+ }
275
+
276
+ return scope
277
+ }
278
+
146
279
  function isUserHomeHelloagentsDir(dirPath) {
147
280
  const homeCandidates = [
148
281
  getHomeDir(),
@@ -368,7 +501,16 @@ function buildTransientRuntimeDir(cwd, options = {}) {
368
501
  }
369
502
 
370
503
  export function getRuntimeScope(cwd = process.cwd(), options = {}) {
371
- const projectScope = getProjectSessionScope(cwd, options)
504
+ const normalizedOptions = normalizeRuntimeOptions(options)
505
+ if (normalizedOptions.ensureProjectLocal === true) {
506
+ return {
507
+ ...ensureProjectLocalRuntime(cwd, normalizedOptions),
508
+ active: true,
509
+ scope: 'project-session',
510
+ }
511
+ }
512
+
513
+ const projectScope = getProjectSessionScope(cwd, normalizedOptions)
372
514
  if (projectScope.active) {
373
515
  return {
374
516
  ...projectScope,
@@ -377,7 +519,7 @@ export function getRuntimeScope(cwd = process.cwd(), options = {}) {
377
519
  }
378
520
 
379
521
  return {
380
- ...buildTransientRuntimeDir(cwd, options),
522
+ ...buildTransientRuntimeDir(cwd, normalizedOptions),
381
523
  active: false,
382
524
  scope: 'user-runtime',
383
525
  }
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
2
2
  import { basename, dirname, join } from 'node:path'
3
3
 
4
4
  import {
5
+ ensureProjectLocalRuntime,
5
6
  getProjectSessionScope,
6
7
  getRuntimeScope,
7
8
  readJsonFile,
@@ -44,6 +45,19 @@ function getEventSessionAlias(eventPayload = {}) {
44
45
 
45
46
  function getScope(cwd, options = {}) {
46
47
  const normalizedOptions = normalizeOptions(options)
48
+ const stateSeed = normalizedOptions.stateSeed && typeof normalizedOptions.stateSeed === 'object'
49
+ ? normalizedOptions.stateSeed
50
+ : {}
51
+ if (normalizedOptions.ensureProjectLocal === true) {
52
+ return {
53
+ ...ensureProjectLocalRuntime(cwd, {
54
+ ...normalizedOptions,
55
+ stateSeed,
56
+ }),
57
+ active: true,
58
+ scope: 'project-session',
59
+ }
60
+ }
47
61
  if (normalizedOptions.project === true) {
48
62
  return {
49
63
  ...getProjectSessionScope(cwd, normalizedOptions),
@@ -119,6 +119,13 @@ export function writeTurnState(cwd = process.cwd(), input = {}) {
119
119
  payload: input.payload && typeof input.payload === 'object' ? input.payload : input,
120
120
  env: input.env || process.env,
121
121
  ppid: input.ppid ?? process.ppid,
122
+ ensureProjectLocal: true,
123
+ stateSeed: {
124
+ goal: '记录当前非只读任务状态',
125
+ doing: '正在写入 turn-state',
126
+ context: '由运行时在需要识别完成、等待或阻塞时自动创建',
127
+ next: '根据当前 turn-state 继续或等待后续动作',
128
+ },
122
129
  }
123
130
  const scope = getRuntimeScope(cwd, runtimeOptions)
124
131
  const normalized = normalizeTurnState(input)