helloagents 3.0.35 → 3.0.37

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.
@@ -9,10 +9,7 @@
9
9
  {
10
10
  "name": "helloagents",
11
11
  "description": "Quality-driven orchestration kernel for AI CLIs: intelligent routing, quality verification, safety guards, and notifications",
12
- "source": {
13
- "source": "github",
14
- "repo": "hellowind777/helloagents"
15
- },
12
+ "source": "./",
16
13
  "author": {
17
14
  "name": "HelloWind",
18
15
  "email": "hellowind777@gmail.com"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloagents",
3
- "version": "3.0.35",
3
+ "version": "3.0.37",
4
4
  "description": "HelloAGENTS — The orchestration kernel that makes any AI CLI smarter. Adds intelligent routing, unified QA gates, safety guards, and notifications.",
5
5
  "author": {
6
6
  "name": "HelloWind",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloagents",
3
- "version": "3.0.35",
3
+ "version": "3.0.37",
4
4
  "description": "HelloAGENTS — Quality-driven orchestration kernel for AI CLIs with intelligent routing, unified QA gates, safety guards, and notifications.",
5
5
  "author": {
6
6
  "name": "HelloWind",
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  **A workflow layer for AI coding CLIs: skills, project knowledge, delivery checks, safer config writes, and resumable execution.**
10
10
 
11
- [![Version](https://img.shields.io/badge/version-3.0.35-orange.svg)](./package.json)
11
+ [![Version](https://img.shields.io/badge/version-3.0.37-orange.svg)](./package.json)
12
12
  [![npm](https://img.shields.io/npm/v/helloagents.svg)](https://www.npmjs.com/package/helloagents)
13
13
  [![Node](https://img.shields.io/badge/node-%3E%3D18-339933.svg)](./package.json)
14
14
  [![Skills](https://img.shields.io/badge/skills-14-6366f1.svg)](./skills)
@@ -189,6 +189,8 @@ HelloAGENTS now resolves the current state file from `state_path`:
189
189
 
190
190
  `<workspace>` is the current Git branch, `detached-<sha>` for a detached HEAD, or `workspace` for non-Git projects. `.helloagents/sessions/active.json` only records the active session index.
191
191
 
192
+ For project-local sessions, HelloAGENTS now only uses stable host identifiers such as `sessionId`, `conversationId`, `threadId`, or `HELLOAGENTS_NOTIFY_SESSION_ID`. It no longer uses terminal/window ids such as `WT_SESSION`, `TERM_SESSION_ID`, or `WINDOWID` to create extra project session directories.
193
+
192
194
  `STATE.md` records where the current workflow stopped. It is not a universal memory file for every conversation. Codex `/goal` does not replace `state_path`, `turn-state`, or local evidence files; it only handles long-running continuation on the Codex side.
193
195
 
194
196
  ### 6) Verification and delivery evidence
@@ -206,6 +208,9 @@ Runtime state now stays intentionally small:
206
208
  - `~/.codex/.helloagents/notify-state.json` for Codex-native closeout de-duplication only
207
209
 
208
210
  `turn-state`, route context, and the artifact index are stored inside `STATE.md` metadata instead of a separate `capsule.json`. `events.jsonl` is opt-in trace output, not a default runtime file.
211
+ Project-local `STATE.md` is now materialized more lazily, and legacy root-level `.helloagents/artifacts/*.log` files are cleaned up automatically instead of growing as a second history system.
212
+
213
+ Standard runtime evidence and transient runtime state now expire after 72 hours. Long-running Codex goal flows still keep their 720-hour upper bound where the workflow explicitly needs it.
209
214
 
210
215
  Delivery gate, guard, and QA gate messages use action-oriented wording such as processing path, closeout action, and visual validation action, so blocked flows show what to do next without turning executable steps into optional suggestions. Final closeout also enforces a single HelloAGENTS wrapper, so one reply does not emit duplicate closeout headers.
211
216
  That wrapper is now reserved for direct final-user delivery only. Intermediate reports, delegated task results, and sub-agent replies stay natural, and sub-agent stop hooks reject wrapped closeout replies.
@@ -304,7 +309,7 @@ If you omit `--standby` or `--global`, HelloAGENTS first reuses the tracked/dete
304
309
 
305
310
  ### npm and one-shot script entries
306
311
 
307
- Use these when you do not want to depend on the `helloagents` binary being available during package updates. In `HELLOAGENTS=target[:mode]`, target can be `all`, `claude`, `gemini`, or `codex`; mode can be `standby` or `global`. For install, an omitted mode is treated as `standby`. For update, cleanup, uninstall, and branch switching, an omitted mode is forwarded unchanged so HelloAGENTS can reuse the tracked or detected mode for that CLI first. For a custom tarball or package spec, set `HELLOAGENTS_PACKAGE` instead of `HELLOAGENTS_BRANCH`. For a guaranteed refresh of an already installed package, prefer `npm explore -g helloagents -- npm run sync-hosts -- ...` after the package command.
312
+ Use these when you do not want to depend on the `helloagents` binary being available during package updates. In `HELLOAGENTS=target[:mode]`, target can be `all`, `claude`, `gemini`, or `codex`; mode can be `standby` or `global`. For install, an omitted mode is treated as `standby`. For update, cleanup, uninstall, and branch switching, an omitted mode is forwarded unchanged so HelloAGENTS can reuse the tracked or detected mode for that CLI first. If you do not provide `HELLOAGENTS`, the one-shot install scripts now behave like plain package install: they install or update the package only and do not auto-deploy any host CLI. For a custom tarball or package spec, set `HELLOAGENTS_PACKAGE` instead of `HELLOAGENTS_BRANCH`. For a guaranteed refresh of an already installed package, prefer `npm explore -g helloagents -- npm run sync-hosts -- ...` after the package command.
308
313
 
309
314
  Host configs use the stable `helloagents-js` entrypoint and runtime root `~/.helloagents/helloagents`, so Node global package paths can change without breaking managed hooks or Codex `notify`. Codex hooks use standalone `~/.codex/hooks.json` instead of adding large hook blocks to `config.toml`, and Codex global plugin roots plus plugin cache now link back to that same stable runtime root.
310
315
 
@@ -405,7 +410,7 @@ $env:HELLOAGENTS="codex:standby"; $env:HELLOAGENTS_ACTION="cleanup"; irm https:/
405
410
  $env:HELLOAGENTS="gemini"; $env:HELLOAGENTS_ACTION="uninstall"; irm https://raw.githubusercontent.com/hellowind777/helloagents/main/install.ps1 | iex
406
411
  ```
407
412
 
408
- The shell and PowerShell wrappers now parse `HELLOAGENTS` once, clear lifecycle env before update, branch switching, and uninstall, and then run one explicit sync or cleanup path.
413
+ The shell and PowerShell wrappers now parse `HELLOAGENTS` once, keep plain package install/update behavior when no target is specified, clear lifecycle env before update, branch switching, and uninstall, and then run one explicit sync or cleanup path.
409
414
 
410
415
  ### Branch switching
411
416
 
@@ -442,12 +447,12 @@ npm uninstall -g helloagents
442
447
  | Gemini CLI | native extension install | managed by Gemini extension system |
443
448
  | Codex CLI | native local-plugin chain | `~/.agents/plugins/marketplace.json`, `~/plugins/helloagents/ -> ~/.helloagents/helloagents`, `~/.codex/plugins/cache/local-plugins/helloagents/local/ -> ~/.helloagents/helloagents`, `~/.codex/config.toml`, `~/.codex/hooks.json`, `~/.codex/helloagents -> ~/.helloagents/helloagents` |
444
449
 
445
- In global mode, HelloAGENTS now attempts the host-native install commands automatically. If a host command is unavailable, run the same commands manually:
450
+ In global mode, HelloAGENTS now attempts the host-native install commands automatically. For Claude Code, the marketplace should be added from the Git URL so the plugin source stays on HTTPS and avoids an SSH-only clone during installation. If a host command is unavailable, run the same commands manually:
446
451
 
447
452
  ```text
448
- /plugin marketplace add hellowind777/helloagents
453
+ /plugin marketplace add https://github.com/hellowind777/helloagents.git
449
454
  /plugin install helloagents@helloagents
450
- gemini extensions install https://github.com/hellowind777/helloagents
455
+ helloagents install gemini --global
451
456
  ```
452
457
 
453
458
  For Claude Code, the CLI also tries the equivalent `claude plugin marketplace add ...` and `claude plugin install ...` commands. The marketplace is named `helloagents`, and the plugin is also named `helloagents`, so the install target is `helloagents@helloagents`. Restart the host CLI after a global install.
package/README_CN.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  **面向 AI 编码 CLI 的工作流层:技能、知识库、交付检查、更安全的配置写入,以及可恢复的执行流程。**
10
10
 
11
- [![Version](https://img.shields.io/badge/version-3.0.35-orange.svg)](./package.json)
11
+ [![Version](https://img.shields.io/badge/version-3.0.37-orange.svg)](./package.json)
12
12
  [![npm](https://img.shields.io/npm/v/helloagents.svg)](https://www.npmjs.com/package/helloagents)
13
13
  [![Node](https://img.shields.io/badge/node-%3E%3D18-339933.svg)](./package.json)
14
14
  [![Skills](https://img.shields.io/badge/skills-14-6366f1.svg)](./skills)
@@ -189,6 +189,8 @@ HelloAGENTS 现在只从 `state_path` 解析当前状态文件:
189
189
 
190
190
  `<workspace>` 是当前 Git 分支、detached HEAD 的 `detached-<sha>`,或非 Git 项目的 `workspace`。`.helloagents/sessions/active.json` 只记录当前活跃会话索引。
191
191
 
192
+ 对于项目本地会话目录,HelloAGENTS 现在只使用稳定宿主标识,如 `sessionId`、`conversationId`、`threadId` 或 `HELLOAGENTS_NOTIFY_SESSION_ID`。它不再使用 `WT_SESSION`、`TERM_SESSION_ID`、`WINDOWID` 这类终端或窗口标识去额外生成项目会话目录。
193
+
192
194
  `STATE.md` 只记录当前工作流做到哪里,不承担所有对话的统一记忆。Codex `/goal` 也不替代 `state_path`、`turn-state` 或本地证据文件;它只负责 Codex 侧的长程续跑。
193
195
 
194
196
  ### 6)验证与交付证据
@@ -206,6 +208,9 @@ HelloAGENTS 不把“命令通过”和“任务完成”简单画等号。交
206
208
  - 仅用于 Codex 原生收尾去重的 `~/.codex/.helloagents/notify-state.json`
207
209
 
208
210
  `turn-state`、路由上下文和 artifact 索引都写进 `STATE.md` 的元数据,不再单独生成 `capsule.json`。`events.jsonl` 改为可选 trace 输出,默认不写。
211
+ 项目本地 `STATE.md` 现在会更晚创建;旧版残留的项目根 `.helloagents/artifacts/*.log` 也会自动清理,不再继续充当第二套历史系统。
212
+
213
+ 标准运行态证据和临时运行态现在默认 72 小时过期。只有工作流明确需要的长程 Codex goal 链路,才继续保留 720 小时上限。
209
214
 
210
215
  交付门控、守卫和 QA gate 提示使用执行性表述,例如处理路径、收尾动作和视觉验收动作。阻塞流程会说明下一步要做什么,而不是把可执行步骤写成泛化建议。最终回复还会强制只保留一个 HelloAGENTS 外层块,避免同一条回复重复输出完成标题。
211
216
  这个外层格式现在只保留给直接面向最终用户的终局交付。中间汇报、委派任务结果和子代理回复都保持自然输出;子代理结束钩子也会拦截错误的外层收尾格式。
@@ -304,7 +309,7 @@ helloagents codex goals enable
304
309
 
305
310
  ### npm 和一键脚本入口
306
311
 
307
- 当你不想依赖更新过程中的 `helloagents` 可执行文件时,用 npm 或一键脚本。`HELLOAGENTS=目标[:模式]` 中,目标支持 `all`、`claude`、`gemini`、`codex`;模式支持 `standby`、`global`。用于安装时,省略模式按 `standby` 处理;用于更新、清理、卸载和切换分支时,省略模式会原样下传,让 HelloAGENTS 先复用该 CLI 已记录或检测到的模式。若要安装自定义 tarball 或包规格,用 `HELLOAGENTS_PACKAGE`,不要写 `HELLOAGENTS_BRANCH`。对于已经装好的包,如需确保宿主一定刷新,优先在包命令后显式执行一次 `npm explore -g helloagents -- npm run sync-hosts -- ...`。
312
+ 当你不想依赖更新过程中的 `helloagents` 可执行文件时,用 npm 或一键脚本。`HELLOAGENTS=目标[:模式]` 中,目标支持 `all`、`claude`、`gemini`、`codex`;模式支持 `standby`、`global`。用于安装时,省略模式按 `standby` 处理;用于更新、清理、卸载和切换分支时,省略模式会原样下传,让 HelloAGENTS 先复用该 CLI 已记录或检测到的模式。如果未提供 `HELLOAGENTS`,一键安装脚本现在会保持“只装包/只升级包”的默认语义,不会自动部署任何宿主 CLI。若要安装自定义 tarball 或包规格,用 `HELLOAGENTS_PACKAGE`,不要写 `HELLOAGENTS_BRANCH`。对于已经装好的包,如需确保宿主一定刷新,优先在包命令后显式执行一次 `npm explore -g helloagents -- npm run sync-hosts -- ...`。
308
313
 
309
314
  宿主配置使用稳定的 `helloagents-js` 入口和运行根目录 `~/.helloagents/helloagents`,Node 全局包路径变化不会破坏受管 hooks 或 Codex `notify`。Codex hooks 使用独立 `~/.codex/hooks.json`,不把大段配置写入 `config.toml`;Codex 全局插件根目录和插件缓存也会回链到这个稳定运行根目录。
310
315
 
@@ -405,7 +410,7 @@ $env:HELLOAGENTS="codex:standby"; $env:HELLOAGENTS_ACTION="cleanup"; irm https:/
405
410
  $env:HELLOAGENTS="gemini"; $env:HELLOAGENTS_ACTION="uninstall"; irm https://raw.githubusercontent.com/hellowind777/helloagents/main/install.ps1 | iex
406
411
  ```
407
412
 
408
- Shell 和 PowerShell 一键脚本现在都会先解析一次 `HELLOAGENTS`,再在更新、切分支和卸载前清掉生命周期环境变量,然后只走一条显式同步或清理链路。
413
+ Shell 和 PowerShell 一键脚本现在都会先解析一次 `HELLOAGENTS`;未指定目标时保持普通包安装/升级语义;在更新、切分支和卸载前清掉生命周期环境变量,然后只走一条显式同步或清理链路。
409
414
 
410
415
  ### 分支切换
411
416
 
@@ -442,12 +447,12 @@ npm uninstall -g helloagents
442
447
  | Gemini CLI | 原生扩展安装 | 由 Gemini 扩展系统管理 |
443
448
  | Codex CLI | 原生本地插件流程 | `~/.agents/plugins/marketplace.json`、`~/plugins/helloagents/ -> ~/.helloagents/helloagents`、`~/.codex/plugins/cache/local-plugins/helloagents/local/ -> ~/.helloagents/helloagents`、`~/.codex/config.toml`、`~/.codex/hooks.json`、`~/.codex/helloagents -> ~/.helloagents/helloagents` |
444
449
 
445
- 全局模式下,HelloAGENTS 会自动尝试宿主原生命令。若宿主命令不可用,再手动执行:
450
+ 全局模式下,HelloAGENTS 会自动尝试宿主原生命令。对 Claude Code,marketplace 应使用 Git URL 添加,这样插件安装阶段会继续走 HTTPS,不会落回 SSH-only clone。若宿主命令不可用,再手动执行:
446
451
 
447
452
  ```text
448
- /plugin marketplace add hellowind777/helloagents
453
+ /plugin marketplace add https://github.com/hellowind777/helloagents.git
449
454
  /plugin install helloagents@helloagents
450
- gemini extensions install https://github.com/hellowind777/helloagents
455
+ helloagents install gemini --global
451
456
  ```
452
457
 
453
458
  Claude Code 会自动尝试等价的 `claude plugin marketplace add ...` 和 `claude plugin install ...` 命令。marketplace 名称和插件名称都是 `helloagents`,所以安装目标是 `helloagents@helloagents`。全局安装后需要重启宿主 CLI。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloagents",
3
- "version": "3.0.35",
3
+ "version": "3.0.37",
4
4
  "description": "Quality-driven orchestration kernel for AI CLIs",
5
5
  "contextFileName": "bootstrap.md",
6
6
  "author": "HelloWind",
package/install.ps1 CHANGED
@@ -16,6 +16,7 @@ $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
18
  $HasExplicitPackage = [bool]$Package
19
+ $HasExplicitTarget = $false
19
20
 
20
21
  if ($env:HELLOAGENTS) {
21
22
  $Parts = $env:HELLOAGENTS.Split(":", 2)
@@ -26,6 +27,8 @@ if ($env:HELLOAGENTS) {
26
27
  if (-not $Mode -and $Parts.Count -gt 1) { $Mode = $Parts[1] }
27
28
  }
28
29
 
30
+ $HasExplicitTarget = [bool]$Target
31
+
29
32
  if (-not $Target) { $Target = "all" }
30
33
  $Target = $Target.ToLowerInvariant()
31
34
  if ($Mode) { $Mode = $Mode.ToLowerInvariant() }
@@ -107,7 +110,9 @@ function Uninstall-Hosts {
107
110
 
108
111
  switch ($Action) {
109
112
  "install" {
110
- Enable-PostinstallDeploy
113
+ if ($HasExplicitTarget) {
114
+ Enable-PostinstallDeploy
115
+ }
111
116
  Invoke-Npm -NpmArgs @("install", "-g", $Package)
112
117
  }
113
118
  "update" {
package/install.sh CHANGED
@@ -17,6 +17,7 @@ MODE="${HELLOAGENTS_MODE:-}"
17
17
  BRANCH="${HELLOAGENTS_BRANCH:-}"
18
18
  PACKAGE="${HELLOAGENTS_PACKAGE:-}"
19
19
  HAS_EXPLICIT_PACKAGE=0
20
+ HAS_EXPLICIT_TARGET=0
20
21
  if [ -n "$PACKAGE" ]; then
21
22
  HAS_EXPLICIT_PACKAGE=1
22
23
  fi
@@ -35,6 +36,10 @@ if [ -n "${HELLOAGENTS:-}" ]; then
35
36
  MODE="${MODE:-$SPEC_MODE}"
36
37
  fi
37
38
 
39
+ if [ -n "$TARGET" ]; then
40
+ HAS_EXPLICIT_TARGET=1
41
+ fi
42
+
38
43
  TARGET="${TARGET:-all}"
39
44
  TARGET="$(printf '%s' "$TARGET" | tr '[:upper:]' '[:lower:]')"
40
45
  MODE="$(printf '%s' "$MODE" | tr '[:upper:]' '[:lower:]')"
@@ -128,7 +133,9 @@ enable_postinstall_deploy() {
128
133
 
129
134
  case "$ACTION" in
130
135
  install)
131
- enable_postinstall_deploy
136
+ if [ "$HAS_EXPLICIT_TARGET" -eq 1 ]; then
137
+ enable_postinstall_deploy
138
+ fi
132
139
  npm install -g "$PACKAGE"
133
140
  ;;
134
141
  update)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helloagents",
3
- "version": "3.0.35",
3
+ "version": "3.0.37",
4
4
  "type": "module",
5
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",
@@ -1,5 +1,6 @@
1
1
  import { spawnSync } from 'node:child_process'
2
2
  import { existsSync, realpathSync } from 'node:fs'
3
+ import { platform } from 'node:os'
3
4
  import { join } from 'node:path'
4
5
 
5
6
  import { CODEX_MARKETPLACE_NAME, CODEX_PLUGIN_CONFIG_HEADER, CODEX_PLUGIN_NAME } from './cli-codex.mjs'
@@ -147,8 +148,9 @@ function summarizeNativeCodexDoctorOutput(payload = {}) {
147
148
  }
148
149
 
149
150
  function inspectNativeCodexDoctor(runtime) {
151
+ const command = platform() === 'win32' ? 'codex.cmd' : 'codex'
150
152
  try {
151
- const result = spawnSync('codex', ['doctor', '--json'], {
153
+ const result = spawnSync(command, ['doctor', '--json'], {
152
154
  cwd: process.cwd(),
153
155
  env: {
154
156
  ...process.env,
@@ -158,6 +160,7 @@ function inspectNativeCodexDoctor(runtime) {
158
160
  },
159
161
  encoding: 'utf-8',
160
162
  timeout: 20_000,
163
+ shell: platform() === 'win32',
161
164
  windowsHide: true,
162
165
  })
163
166
 
@@ -106,8 +106,8 @@ function suggestDoctorFix(host, status, trackedMode) {
106
106
  return `helloagents update ${host}${trackedMode && trackedMode !== 'none' ? ` --${trackedMode}` : ''}`
107
107
  }
108
108
  if (status === 'manual-plugin') {
109
- if (host === 'claude') return '/plugin marketplace add hellowind777/helloagents; /plugin install helloagents@helloagents'
110
- if (host === 'gemini') return 'gemini extensions install https://github.com/hellowind777/helloagents'
109
+ if (host === 'claude') return '/plugin marketplace add https://github.com/hellowind777/helloagents.git; /plugin install helloagents@helloagents'
110
+ if (host === 'gemini') return 'helloagents install gemini --global'
111
111
  }
112
112
  if (status === 'not-installed') {
113
113
  return `helloagents install ${host} --standby`
@@ -176,7 +176,7 @@ function inspectGeminiDoctor(settings) {
176
176
  const detectedMode = normalizeDoctorMode(runtime.detectHostMode(host))
177
177
  const geminiDir = join(runtime.home, '.gemini')
178
178
  const geminiSettings = safeJson(join(geminiDir, 'settings.json')) || {}
179
- const expectedHooks = readExpectedHooks('hooks.json', '${extensionPath}')
179
+ const expectedHooks = readExpectedHooks('hooks-gemini.json', '${extensionPath}')
180
180
  const checks = {
181
181
  carrierMarker: (safeRead(join(geminiDir, 'GEMINI.md')) || '').includes('HELLOAGENTS_START'),
182
182
  carrierContentMatch: extractManagedCarrierContent(join(geminiDir, 'GEMINI.md'))
@@ -65,7 +65,7 @@ export function installGeminiStandby(home, pkgRoot) {
65
65
  createLink(pkgRoot, join(geminiDir, 'helloagents'));
66
66
 
67
67
  const settingsPath = join(geminiDir, 'settings.json');
68
- const hooksData = loadHooksWithCliEntry(pkgRoot, 'hooks.json', '${extensionPath}');
68
+ const hooksData = loadHooksWithCliEntry(pkgRoot, 'hooks-gemini.json', '${extensionPath}');
69
69
  if (hooksData) mergeSettingsHooks(settingsPath, hooksData);
70
70
 
71
71
  return true;
@@ -1,4 +1,5 @@
1
1
  import { spawnSync } from 'node:child_process'
2
+ import { platform } from 'node:os'
2
3
 
3
4
  import {
4
5
  installClaudeStandby,
@@ -17,23 +18,50 @@ import { getHostLabel } from './cli-host-detect.mjs'
17
18
 
18
19
  const CLAUDE_COMMAND = process.env.HELLOAGENTS_CLAUDE_CMD || 'claude'
19
20
  const GEMINI_COMMAND = process.env.HELLOAGENTS_GEMINI_CMD || 'gemini'
20
- const CLAUDE_MARKETPLACE = 'hellowind777/helloagents'
21
+ const CLAUDE_MARKETPLACE = 'https://github.com/hellowind777/helloagents.git'
21
22
  const CLAUDE_PLUGIN = 'helloagents@helloagents'
22
- const GEMINI_EXTENSION = 'https://github.com/hellowind777/helloagents'
23
+
24
+ function normalizeCommand(command = '') {
25
+ return String(command || '').trim()
26
+ }
27
+
28
+ function commandCandidates(command = '') {
29
+ const normalized = normalizeCommand(command)
30
+ if (!normalized) return []
31
+
32
+ if (platform() !== 'win32') return [normalized]
33
+
34
+ const candidates = new Set([normalized])
35
+ if (!/\.(cmd|bat|exe|ps1)$/i.test(normalized)) {
36
+ candidates.add(`${normalized}.cmd`)
37
+ candidates.add(`${normalized}.exe`)
38
+ }
39
+ return [...candidates]
40
+ }
23
41
 
24
42
  function runHostCommand(command, args) {
25
- const needsShell = process.platform === 'win32' && /\.cmd$/i.test(command)
26
- const result = spawnSync(command, args, {
27
- encoding: 'utf-8',
28
- errors: 'replace',
29
- shell: needsShell,
30
- windowsHide: true,
31
- })
32
- const errorMessage = result.error?.message || ''
43
+ const attempts = commandCandidates(command)
44
+ let lastResult = null
45
+
46
+ for (const candidate of attempts) {
47
+ const needsShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(candidate)
48
+ const result = spawnSync(candidate, args, {
49
+ encoding: 'utf-8',
50
+ errors: 'replace',
51
+ shell: needsShell,
52
+ windowsHide: true,
53
+ })
54
+ lastResult = result
55
+ if (!result.error || !['ENOENT', 'EINVAL'].includes(result.error.code)) {
56
+ break
57
+ }
58
+ }
59
+
60
+ const errorMessage = lastResult?.error?.message || ''
33
61
  return {
34
- ok: result.status === 0,
35
- missing: result.error?.code === 'ENOENT',
36
- output: `${result.stdout || ''}${result.stderr || ''}${errorMessage}`.trim(),
62
+ ok: lastResult?.status === 0,
63
+ missing: ['ENOENT', 'EINVAL'].includes(lastResult?.error?.code || ''),
64
+ output: `${lastResult?.stdout || ''}${lastResult?.stderr || ''}${errorMessage}`.trim(),
37
65
  }
38
66
  }
39
67
 
@@ -60,8 +88,8 @@ function installClaudeGlobalPlugin() {
60
88
  return { ok: install.ok, output: install.output || add.output }
61
89
  }
62
90
 
63
- function installGeminiGlobalExtension() {
64
- return runHostCommand(GEMINI_COMMAND, ['extensions', 'install', GEMINI_EXTENSION])
91
+ function installGeminiGlobalExtension(runtimeRoot) {
92
+ return runHostCommand(GEMINI_COMMAND, ['extensions', 'link', runtimeRoot])
65
93
  }
66
94
 
67
95
  function removeClaudeGlobalPlugin() {
@@ -150,18 +178,18 @@ function installHostGlobal(runtime, host) {
150
178
  installClaudeGlobalPlugin(),
151
179
  '已自动安装 Claude Code 插件;重启 Claude Code 后生效',
152
180
  'Claude Code plugin installed automatically; restart Claude Code to apply',
153
- 'Claude Code 插件自动安装失败,请在 Claude Code 中执行: /plugin marketplace add hellowind777/helloagents;/plugin install helloagents@helloagents',
154
- 'Claude Code plugin auto-install failed. Run inside Claude Code: /plugin marketplace add hellowind777/helloagents; /plugin install helloagents@helloagents',
181
+ 'Claude Code 插件自动安装失败,请在 Claude Code 中执行: /plugin marketplace add https://github.com/hellowind777/helloagents.git;/plugin install helloagents@helloagents',
182
+ 'Claude Code plugin auto-install failed. Run inside Claude Code: /plugin marketplace add https://github.com/hellowind777/helloagents.git; /plugin install helloagents@helloagents',
155
183
  )
156
184
  }
157
185
  if (host === 'gemini') {
158
186
  uninstallGeminiStandby(runtime.home)
159
187
  return buildNativeResult(
160
- installGeminiGlobalExtension(),
188
+ installGeminiGlobalExtension(runtime.pkgRoot),
161
189
  '已自动安装 Gemini CLI 扩展;重启 Gemini CLI 后生效',
162
190
  'Gemini CLI extension installed automatically; restart Gemini CLI to apply',
163
- 'Gemini CLI 扩展自动安装失败,请手动执行: gemini extensions install https://github.com/hellowind777/helloagents',
164
- 'Gemini CLI extension auto-install failed. Run manually: gemini extensions install https://github.com/hellowind777/helloagents',
191
+ `Gemini CLI 扩展自动安装失败,请手动执行: gemini extensions link ${runtime.pkgRoot}`,
192
+ `Gemini CLI extension auto-install failed. Run manually: gemini extensions link ${runtime.pkgRoot}`,
165
193
  )
166
194
  }
167
195
  uninstallCodexStandby(runtime.home)
@@ -21,9 +21,9 @@ function codexGlobalStatus({ home, msg }) {
21
21
 
22
22
  function pluginCommands() {
23
23
  return [
24
- ' Claude Code: /plugin marketplace add hellowind777/helloagents',
24
+ ' Claude Code: /plugin marketplace add https://github.com/hellowind777/helloagents.git',
25
25
  ' /plugin install helloagents@helloagents',
26
- ' Gemini CLI: gemini extensions install https://github.com/hellowind777/helloagents',
26
+ ' Gemini CLI: helloagents install gemini --global',
27
27
  ].join('\n')
28
28
  }
29
29
 
@@ -1,4 +1,4 @@
1
- import { mkdtempSync, realpathSync, renameSync } from 'node:fs'
1
+ import { copyFileSync, existsSync, mkdtempSync, realpathSync, renameSync } from 'node:fs'
2
2
  import { dirname, join, resolve } from 'node:path'
3
3
 
4
4
  import { copyEntries, ensureDir, removeIfExists } from './cli-utils.mjs'
@@ -63,6 +63,13 @@ function retryTransientFs(operation) {
63
63
  throw lastError
64
64
  }
65
65
 
66
+ function materializeGeminiHooks(root) {
67
+ const source = join(root, 'hooks', 'hooks-gemini.json')
68
+ const target = join(root, 'hooks', 'hooks.json')
69
+ if (!existsSync(source)) return
70
+ copyFileSync(source, target)
71
+ }
72
+
66
73
  /** Sync package runtime files into the stable root without copying repo-only files. */
67
74
  export function syncRuntimeRoot(sourceRoot, runtimeRoot) {
68
75
  const source = resolve(sourceRoot)
@@ -77,6 +84,7 @@ export function syncRuntimeRoot(sourceRoot, runtimeRoot) {
77
84
 
78
85
  try {
79
86
  copyEntries(source, staging, RUNTIME_ROOT_ENTRIES)
87
+ materializeGeminiHooks(staging)
80
88
  retryTransientFs(() => {
81
89
  removeIfExists(target)
82
90
  renameSync(staging, target)
@@ -24,7 +24,8 @@ export function buildWindowsSoundCommand(filePath = '') {
24
24
  }
25
25
 
26
26
  function playWindows(filePath) {
27
- const result = spawnSync('powershell', [
27
+ const command = process.platform === 'win32' ? 'powershell.exe' : 'powershell'
28
+ const result = spawnSync(command, [
28
29
  '-NoProfile',
29
30
  '-c',
30
31
  buildWindowsSoundCommand(filePath),
@@ -10,9 +10,11 @@ import {
10
10
  writeJsonFileAtomic,
11
11
  } from './runtime-scope.mjs'
12
12
  import { LONG_RUNNING_TTL_MS } from './runtime-ttl.mjs'
13
+ import { readStateDocument } from './state-document.mjs'
13
14
 
14
15
  export const PROJECT_SESSION_CLEANUP_COOLDOWN_MS = 10 * 60 * 1000
15
16
  export const PROJECT_SESSION_MAX_AGE_MS = LONG_RUNNING_TTL_MS
17
+ const AUTO_CREATED_STATE_MARKER = '由运行时自动创建;后续按实际任务重写'
16
18
 
17
19
  function removePath(filePath, result, bucket) {
18
20
  try {
@@ -23,6 +25,10 @@ function removePath(filePath, result, bucket) {
23
25
  }
24
26
  }
25
27
 
28
+ function isDebugLog(entryName = '') {
29
+ return /\.log$/i.test(entryName)
30
+ }
31
+
26
32
  function isDirectoryEmptyRecursive(dirPath) {
27
33
  const entries = readdirSync(dirPath, { withFileTypes: true })
28
34
  if (entries.length === 0) return true
@@ -55,6 +61,15 @@ function hasStateSnapshot(sessionDir) {
55
61
  return existsSync(join(sessionDir, 'STATE.md'))
56
62
  }
57
63
 
64
+ function isAutoCreatedSeedSession(sessionDir) {
65
+ const statePath = join(sessionDir, 'STATE.md')
66
+ if (!existsSync(statePath)) return false
67
+
68
+ const { metadata, body } = readStateDocument(statePath)
69
+ if (metadata && typeof metadata === 'object' && Object.keys(metadata).length > 0) return false
70
+ return String(body || '').includes(AUTO_CREATED_STATE_MARKER)
71
+ }
72
+
58
73
  function readSessionStateMtimeMs(sessionDir) {
59
74
  try {
60
75
  return statSync(join(sessionDir, 'STATE.md')).mtimeMs
@@ -79,6 +94,32 @@ function cleanupTransientSessionTemps(sessionsDir, result) {
79
94
  }
80
95
  }
81
96
 
97
+ function cleanupLegacyProjectArtifacts(activationDir, result) {
98
+ const artifactsDir = join(activationDir, 'artifacts')
99
+ if (!existsSync(artifactsDir)) return
100
+
101
+ let removableEntries = []
102
+ try {
103
+ removableEntries = readdirSync(artifactsDir, { withFileTypes: true })
104
+ } catch (error) {
105
+ result.errors.push(`${artifactsDir}: ${error.message}`)
106
+ return
107
+ }
108
+
109
+ for (const entry of removableEntries) {
110
+ if (!entry.isFile() || !isDebugLog(entry.name)) continue
111
+ removePath(join(artifactsDir, entry.name), result, 'removedLegacyArtifacts')
112
+ }
113
+
114
+ try {
115
+ if (isDirectoryEmptyRecursive(artifactsDir)) {
116
+ removePath(artifactsDir, result, 'removedLegacyArtifacts')
117
+ }
118
+ } catch (error) {
119
+ result.errors.push(`${artifactsDir}: ${error.message}`)
120
+ }
121
+ }
122
+
82
123
  export function cleanupProjectSessions(cwd, { now = Date.now(), minIntervalMs = 0, maxAgeMs = PROJECT_SESSION_MAX_AGE_MS } = {}) {
83
124
  const projectRoot = getProjectRoot(cwd)
84
125
  const activationDir = getProjectActivationDir(projectRoot)
@@ -90,7 +131,9 @@ export function cleanupProjectSessions(cwd, { now = Date.now(), minIntervalMs =
90
131
  removedEmptyDirs: [],
91
132
  removedInactiveDirs: [],
92
133
  removedNoStateDirs: [],
134
+ removedSeedDirs: [],
93
135
  removedTempFiles: [],
136
+ removedLegacyArtifacts: [],
94
137
  errors: [],
95
138
  skipped: false,
96
139
  }
@@ -109,6 +152,7 @@ export function cleanupProjectSessions(cwd, { now = Date.now(), minIntervalMs =
109
152
  } catch (error) {
110
153
  result.errors.push(`${sessionsDir}: ${error.message}`)
111
154
  }
155
+ cleanupLegacyProjectArtifacts(activationDir, result)
112
156
 
113
157
  for (const workspaceEntry of readdirSync(sessionsDir, { withFileTypes: true })) {
114
158
  if (!workspaceEntry.isDirectory()) continue
@@ -124,6 +168,8 @@ export function cleanupProjectSessions(cwd, { now = Date.now(), minIntervalMs =
124
168
  removePath(sessionDir, result, 'removedEmptyDirs')
125
169
  } else if (!hasStateSnapshot(sessionDir)) {
126
170
  removePath(sessionDir, result, 'removedNoStateDirs')
171
+ } else if (isAutoCreatedSeedSession(sessionDir)) {
172
+ removePath(sessionDir, result, 'removedSeedDirs')
127
173
  } else if (isStaleStateSession(sessionDir, now, maxAgeMs)) {
128
174
  removePath(sessionDir, result, 'removedInactiveDirs')
129
175
  }
@@ -7,7 +7,7 @@ import {
7
7
  readSessionArtifact,
8
8
  writeSessionArtifact,
9
9
  } from './session-capsule.mjs'
10
- import { EVIDENCE_MAX_AGE_MS, LONG_RUNNING_TTL_HOURS } from './runtime-ttl.mjs'
10
+ import { EVIDENCE_MAX_AGE_MS, LONG_RUNNING_TTL_HOURS, STANDARD_RUNTIME_TTL_HOURS } from './runtime-ttl.mjs'
11
11
 
12
12
  export { EVIDENCE_MAX_AGE_MS }
13
13
 
@@ -87,7 +87,7 @@ export function validateEvidenceTimestamp(evidence, now, label) {
87
87
  required: true,
88
88
  status: 'stale-time',
89
89
  evidence,
90
- details: [`${label}超过 ${LONG_RUNNING_TTL_HOURS} 小时`],
90
+ details: [`${label}超过 ${STANDARD_RUNTIME_TTL_HOURS} 小时(长任务上限:${LONG_RUNNING_TTL_HOURS} 小时)`],
91
91
  }
92
92
  }
93
93
  return null
@@ -4,7 +4,7 @@ import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync,
4
4
  import { dirname, join, normalize, resolve } from 'node:path'
5
5
  import { homedir } from 'node:os'
6
6
 
7
- import { resolveSessionToken } from './session-token.mjs'
7
+ import { resolveProjectSessionToken, 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
10
  import { FULL_CARRIER_PROFILE_MARKER } from './cli-utils.mjs'
@@ -323,21 +323,14 @@ function findProjectActivationDir(cwd) {
323
323
 
324
324
  function resolvePayloadSessionToken(payload = {}) {
325
325
  if (payload?._helloagentsSessionAlias) return ''
326
- return resolveSessionToken({
326
+ return resolveProjectSessionToken({
327
327
  payload,
328
328
  env: {},
329
- ppid: 0,
330
- allowPpidFallback: false,
331
329
  })
332
330
  }
333
331
 
334
332
  function resolveEnvSessionToken(env = process.env) {
335
- return resolveSessionToken({
336
- payload: {},
337
- env,
338
- ppid: 0,
339
- allowPpidFallback: false,
340
- })
333
+ return resolveProjectSessionToken({ payload: {}, env })
341
334
  }
342
335
 
343
336
  function resolveTransientSessionToken({ payload = {}, env = process.env, ppid = process.ppid } = {}) {
@@ -437,11 +430,21 @@ function chooseProjectSession({ payload, env, activationDir, projectRoot, worksp
437
430
  return { session: DEFAULT_STATE_SESSION_TOKEN, sessionMode: 'default' }
438
431
  }
439
432
 
433
+ function removeLegacyProjectArtifacts(activationDir) {
434
+ if (!activationDir) return
435
+ const artifactsDir = join(activationDir, PROJECT_ARTIFACTS_DIR_NAME)
436
+ if (!existsSync(artifactsDir)) return
437
+ try {
438
+ rmSync(artifactsDir, { recursive: true, force: true })
439
+ } catch {}
440
+ }
441
+
440
442
  export function getProjectSessionScope(cwd, options = {}) {
441
443
  const normalizedCwd = normalizePath(cwd || process.cwd())
442
444
  const projectRoot = getProjectRoot(normalizedCwd)
443
445
  const { payload = {}, env = process.env } = normalizeRuntimeOptions(options)
444
446
  const activationDir = getProjectActivationDir(projectRoot)
447
+ removeLegacyProjectArtifacts(activationDir)
445
448
  const workspace = resolveWorkspaceName(projectRoot)
446
449
  const { session, sessionMode } = chooseProjectSession({
447
450
  payload,
@@ -1,7 +1,10 @@
1
1
  export const LONG_RUNNING_TTL_HOURS = 720
2
2
  export const LONG_RUNNING_TTL_MS = LONG_RUNNING_TTL_HOURS * 60 * 60 * 1000
3
3
 
4
- export const ROUTE_CONTEXT_TTL_MS = LONG_RUNNING_TTL_MS
5
- export const TURN_STATE_TTL_MS = LONG_RUNNING_TTL_MS
6
- export const EVIDENCE_MAX_AGE_MS = LONG_RUNNING_TTL_MS
7
- export const USER_RUNTIME_MAX_AGE_MS = LONG_RUNNING_TTL_MS
4
+ export const STANDARD_RUNTIME_TTL_HOURS = 72
5
+ export const STANDARD_RUNTIME_TTL_MS = STANDARD_RUNTIME_TTL_HOURS * 60 * 60 * 1000
6
+
7
+ export const ROUTE_CONTEXT_TTL_MS = STANDARD_RUNTIME_TTL_MS
8
+ export const TURN_STATE_TTL_MS = STANDARD_RUNTIME_TTL_MS
9
+ export const EVIDENCE_MAX_AGE_MS = STANDARD_RUNTIME_TTL_MS
10
+ export const USER_RUNTIME_MAX_AGE_MS = STANDARD_RUNTIME_TTL_MS
@@ -68,6 +68,21 @@ function getScope(cwd, options = {}) {
68
68
  return getRuntimeScope(cwd, normalizedOptions)
69
69
  }
70
70
 
71
+ function shouldMaterializeSessionState(options = {}) {
72
+ const normalizedOptions = normalizeOptions(options)
73
+ if (normalizedOptions.ensureProjectLocal === true) return true
74
+ if (normalizedOptions.project === true) return true
75
+ if (normalizedOptions.traceEvents === true) return true
76
+
77
+ const payload = normalizedOptions.payload || {}
78
+ if (payload.traceEvents === true || payload._helloagentsTraceEvents === true) return true
79
+
80
+ const raw = String(normalizedOptions.env?.HELLOAGENTS_TRACE_EVENTS || process.env.HELLOAGENTS_TRACE_EVENTS || '')
81
+ .trim()
82
+ .toLowerCase()
83
+ return raw === '1' || raw === 'true' || raw === 'yes'
84
+ }
85
+
71
86
  export function getSessionCapsulePath(cwd = process.cwd(), options = {}) {
72
87
  return getScope(cwd, options).statePath
73
88
  }
@@ -113,9 +128,10 @@ export function readSessionCapsule(cwd = process.cwd(), options = {}) {
113
128
  export function writeSessionCapsule(cwd, capsule, options = {}) {
114
129
  const normalizedOptions = normalizeOptions(options)
115
130
  const scope = getScope(cwd, normalizedOptions)
131
+ const shouldMaterialize = shouldMaterializeSessionState(normalizedOptions)
116
132
  const currentDocument = readStateDocument(scope.statePath)
117
133
  const hasBody = Boolean(currentDocument.body && currentDocument.body.trim())
118
- if (!hasBody && normalizedOptions.ensureProjectLocal !== true && !existsSync(scope.statePath)) {
134
+ if (!hasBody && !shouldMaterialize && !existsSync(scope.statePath)) {
119
135
  return {
120
136
  ...buildEmptyCapsule(scope),
121
137
  ...capsule,
@@ -129,6 +145,14 @@ export function writeSessionCapsule(cwd, capsule, options = {}) {
129
145
  updatedAt: new Date().toISOString(),
130
146
  }
131
147
  }
148
+ if (!hasBody && shouldMaterialize && !existsSync(scope.statePath)) {
149
+ ensureProjectLocalRuntime(cwd, {
150
+ ...normalizedOptions,
151
+ stateSeed: normalizedOptions.stateSeed && typeof normalizedOptions.stateSeed === 'object'
152
+ ? normalizedOptions.stateSeed
153
+ : {},
154
+ })
155
+ }
132
156
  const nextCapsule = {
133
157
  ...buildEmptyCapsule(scope),
134
158
  ...capsule,
@@ -17,6 +17,19 @@ const PAYLOAD_SESSION_KEYS = [
17
17
  'tab',
18
18
  ]
19
19
 
20
+ const PROJECT_PAYLOAD_SESSION_KEYS = [
21
+ 'sessionId',
22
+ 'session_id',
23
+ 'session',
24
+ 'conversationId',
25
+ 'conversation_id',
26
+ 'conversation',
27
+ 'threadId',
28
+ 'thread_id',
29
+ 'thread-id',
30
+ 'thread',
31
+ ]
32
+
20
33
  const ENV_SESSION_KEYS = [
21
34
  'HELLOAGENTS_NOTIFY_SESSION_ID',
22
35
  'WT_SESSION',
@@ -28,6 +41,10 @@ const ENV_SESSION_KEYS = [
28
41
  'TAB_ID',
29
42
  ]
30
43
 
44
+ const PROJECT_ENV_SESSION_KEYS = [
45
+ 'HELLOAGENTS_NOTIFY_SESSION_ID',
46
+ ]
47
+
31
48
  function readStringCandidate(input, key) {
32
49
  if (!input || typeof input !== 'object') return ''
33
50
  const value = input[key]
@@ -50,23 +67,41 @@ export function sanitizeSessionToken(value = '') {
50
67
  return cleaned.slice(0, 8)
51
68
  }
52
69
 
70
+ function resolveTokenFromKeys(input, keys = []) {
71
+ for (const key of keys) {
72
+ const value = sanitizeSessionToken(readStringCandidate(input, key))
73
+ if (value) return value
74
+ }
75
+ return ''
76
+ }
77
+
53
78
  export function resolveSessionToken({
54
79
  payload = {},
55
80
  env = process.env,
56
81
  ppid = process.ppid,
57
82
  allowPpidFallback = true,
58
83
  } = {}) {
59
- for (const key of PAYLOAD_SESSION_KEYS) {
60
- const value = sanitizeSessionToken(readStringCandidate(payload, key))
61
- if (value) return value
62
- }
84
+ const payloadToken = resolveTokenFromKeys(payload, PAYLOAD_SESSION_KEYS)
85
+ if (payloadToken) return payloadToken
63
86
 
64
- for (const key of ENV_SESSION_KEYS) {
65
- const value = sanitizeSessionToken(env?.[key] || '')
66
- if (value) return value
67
- }
87
+ const envToken = resolveTokenFromKeys(env, ENV_SESSION_KEYS)
88
+ if (envToken) return envToken
68
89
 
69
90
  return allowPpidFallback && ppid ? String(ppid) : ''
70
91
  }
71
92
 
72
- export { ENV_SESSION_KEYS, PAYLOAD_SESSION_KEYS }
93
+ export function resolveProjectSessionToken({
94
+ payload = {},
95
+ env = process.env,
96
+ } = {}) {
97
+ const payloadToken = resolveTokenFromKeys(payload, PROJECT_PAYLOAD_SESSION_KEYS)
98
+ if (payloadToken) return payloadToken
99
+ return resolveTokenFromKeys(env, PROJECT_ENV_SESSION_KEYS)
100
+ }
101
+
102
+ export {
103
+ ENV_SESSION_KEYS,
104
+ PAYLOAD_SESSION_KEYS,
105
+ PROJECT_ENV_SESSION_KEYS,
106
+ PROJECT_PAYLOAD_SESSION_KEYS,
107
+ }
File without changes