helloagents 3.0.38 → 3.0.39
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 +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +5 -5
- package/README_CN.md +5 -5
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/scripts/cli-codex-config.mjs +1 -26
- package/scripts/cli-codex.mjs +0 -10
- package/scripts/cli-doctor-codex.mjs +1 -7
- package/scripts/project-session-cleanup.mjs +0 -32
- package/scripts/runtime-scope.mjs +185 -100
- package/scripts/session-capsule.mjs +6 -65
- package/scripts/session-token.mjs +16 -2
- package/scripts/state-document.mjs +7 -51
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helloagents",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.39",
|
|
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.
|
|
3
|
+
"version": "3.0.39",
|
|
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
|
-
[](./package.json)
|
|
12
12
|
[](https://www.npmjs.com/package/helloagents)
|
|
13
13
|
[](./package.json)
|
|
14
14
|
[](./skills)
|
|
@@ -189,7 +189,7 @@ 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. `<session>` is the current project-local session token. `.helloagents/sessions/active.json` only keeps the latest active workspace/session mapping plus alias bridges, so the same CLI session stays in one directory and `/resume` can reuse it.
|
|
191
191
|
|
|
192
|
-
For project-local sessions, HelloAGENTS first uses stable host identifiers such as `sessionId`, `conversationId`, `threadId`, or `HELLOAGENTS_NOTIFY_SESSION_ID`. If the host only exposes a window or terminal id such as `WT_SESSION`, `TERM_SESSION_ID`, or `WINDOWID`, HelloAGENTS uses it only as a lightweight alias bridge and reuses the mapped session first instead of fanning out duplicate directories.
|
|
192
|
+
For project-local sessions, HelloAGENTS first uses stable host identifiers such as `sessionId`, `conversationId`, `threadId`, or `HELLOAGENTS_NOTIFY_SESSION_ID`. If the host only exposes a window or terminal id such as `WT_SESSION`, `TERM_SESSION_ID`, or `WINDOWID`, HelloAGENTS uses it only as a lightweight alias bridge and reuses the mapped session first instead of fanning out duplicate directories. If a session starts before a stable host identifier is available, HelloAGENTS can begin in `default` and keep reusing that same active directory after the same CLI session later exposes a stable identifier, instead of splitting into a second session directory.
|
|
193
193
|
|
|
194
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.
|
|
195
195
|
|
|
@@ -210,7 +210,7 @@ Runtime state now stays intentionally small:
|
|
|
210
210
|
- `~/.codex/.helloagents/notify-state.json` for Codex-native closeout de-duplication only
|
|
211
211
|
|
|
212
212
|
`STATE.md` only keeps the human-readable recovery snapshot. `runtime.json` is machine-only and keeps the minimal runtime state. `artifacts/*.json` stays limited to structured receipts. `events.jsonl` remains opt-in trace output and stays off by default.
|
|
213
|
-
Project-local `STATE.md` is now materialized more lazily
|
|
213
|
+
Project-local `STATE.md` is now materialized more lazily.
|
|
214
214
|
|
|
215
215
|
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.
|
|
216
216
|
|
|
@@ -660,7 +660,7 @@ Codex is rules-file driven by default.
|
|
|
660
660
|
- standby creates `~/.codex/helloagents -> ~/.helloagents/helloagents`
|
|
661
661
|
- global mode installs the native local-plugin chain, but keeps `~/.helloagents/helloagents` as the single managed runtime source by linking plugin roots, plugin cache, and `~/.codex/helloagents` back to it
|
|
662
662
|
- for Codex app/plugin discovery, `global` is the native path; `standby` remains the lighter default for explicit project work
|
|
663
|
-
- cleanup removes only the HelloAGENTS-managed hook trust entries
|
|
663
|
+
- cleanup removes only the HelloAGENTS-managed hook trust entries, while keeping user-owned hook state untouched
|
|
664
664
|
- Codex hooks only synchronize runtime state and enforce Stop gates; they do not inject HelloAGENTS rules or route text through hook output
|
|
665
665
|
- Codex closeout de-duplicates Stop hooks and native `codex-notify`, so one turn does not notify twice, and clientless delegated child-completion events stay silent when the managed Stop hook is active
|
|
666
666
|
- `/goal` remains Codex-native. Enable it explicitly with `helloagents codex goals enable` when long-running plan execution is needed
|
|
@@ -681,7 +681,7 @@ The current suite covers:
|
|
|
681
681
|
- one-shot shell and PowerShell lifecycle dispatch, plus wrapper env cleanup and mode-routing rules for install, update, cleanup, uninstall, and branch switching
|
|
682
682
|
- Claude, Gemini, and Codex host integration behavior, including global-to-standby cleanup and failed native cleanup tracking
|
|
683
683
|
- Codex managed `model_instructions_file`, `notify`, `hooks.json`, hook trust state, local plugin, marketplace, and cache behavior
|
|
684
|
-
- Codex cleanup
|
|
684
|
+
- Codex cleanup and canonical managed notify restoration rules
|
|
685
685
|
- Codex `/goal` feature toggles, long-running route context, and goal-aware command contracts
|
|
686
686
|
- `helloagents doctor`
|
|
687
687
|
- project storage and `repo-shared` behavior
|
package/README_CN.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
**面向 AI 编码 CLI 的工作流层:技能、知识库、交付检查、更安全的配置写入,以及可恢复的执行流程。**
|
|
10
10
|
|
|
11
|
-
[](./package.json)
|
|
12
12
|
[](https://www.npmjs.com/package/helloagents)
|
|
13
13
|
[](./package.json)
|
|
14
14
|
[](./skills)
|
|
@@ -189,7 +189,7 @@ HelloAGENTS 现在只从 `state_path` 解析当前状态文件:
|
|
|
189
189
|
|
|
190
190
|
`<workspace>` 是当前 Git 分支、detached HEAD 的 `detached-<sha>`,或非 Git 项目的 `workspace`。`<session>` 是当前项目本地会话标识。`.helloagents/sessions/active.json` 只保留最近一次活跃的工作区/会话映射和 alias 桥接,这样同一个 CLI 会话会稳定落在同一个目录里,`/resume` 也能复用它。
|
|
191
191
|
|
|
192
|
-
对于项目本地会话目录,HelloAGENTS 会优先使用稳定宿主标识,如 `sessionId`、`conversationId`、`threadId` 或 `HELLOAGENTS_NOTIFY_SESSION_ID`。如果宿主只能提供 `WT_SESSION`、`TERM_SESSION_ID`、`WINDOWID` 这类窗口或终端标识,HelloAGENTS 只把它们当作轻量 alias
|
|
192
|
+
对于项目本地会话目录,HelloAGENTS 会优先使用稳定宿主标识,如 `sessionId`、`conversationId`、`threadId` 或 `HELLOAGENTS_NOTIFY_SESSION_ID`。如果宿主只能提供 `WT_SESSION`、`TERM_SESSION_ID`、`WINDOWID` 这类窗口或终端标识,HelloAGENTS 只把它们当作轻量 alias 桥接,并优先复用已映射的会话目录,而不是继续分裂出重复目录。如果一个会话启动时还拿不到稳定宿主标识,HelloAGENTS 可以先落到 `default`,等同一个 CLI 会话后续拿到稳定标识时,仍继续复用这个活动目录,而不是再拆出第二个会话目录。
|
|
193
193
|
|
|
194
194
|
`STATE.md` 只记录当前工作流做到哪里,不承担所有对话的统一记忆。Codex `/goal` 也不替代 `state_path`、`turn-state` 或本地证据文件;它只负责 Codex 侧的长程续跑。
|
|
195
195
|
|
|
@@ -210,7 +210,7 @@ HelloAGENTS 不把“命令通过”和“任务完成”简单画等号。交
|
|
|
210
210
|
- 仅用于 Codex 原生收尾去重的 `~/.codex/.helloagents/notify-state.json`
|
|
211
211
|
|
|
212
212
|
`STATE.md` 只保留给人看的恢复快照。`runtime.json` 只给机器用,只保存极少量运行态。`artifacts/*.json` 只保留结构化收据。`events.jsonl` 仍是可选 trace 输出,默认不写。
|
|
213
|
-
项目本地 `STATE.md`
|
|
213
|
+
项目本地 `STATE.md` 现在会更晚创建。
|
|
214
214
|
|
|
215
215
|
标准运行态证据和临时运行态现在默认 72 小时过期。只有工作流明确需要的长程 Codex goal 链路,才继续保留 720 小时上限。
|
|
216
216
|
|
|
@@ -664,7 +664,7 @@ Codex 默认走规则文件驱动。
|
|
|
664
664
|
- 标准模式创建 `~/.codex/helloagents -> ~/.helloagents/helloagents`
|
|
665
665
|
- 全局模式安装原生本地插件流程,但仍把 `~/.helloagents/helloagents` 作为唯一受管运行时源;插件根目录、插件缓存和 `~/.codex/helloagents` 都会回链到它
|
|
666
666
|
- 如果你主要看重 Codex app / 插件发现链路,优先使用 `global`;如果你主要看重更轻量、更显式的项目工作流,保留 `standby`
|
|
667
|
-
- 清理时只删除 HelloAGENTS 自己写入的 hook trust
|
|
667
|
+
- 清理时只删除 HelloAGENTS 自己写入的 hook trust 条目,不影响用户已有的 hook 状态
|
|
668
668
|
- Codex hooks 只做静默运行态同步和 Stop 门禁,不通过 hook 注入 HelloAGENTS 规则或路由说明
|
|
669
669
|
- Codex 收尾会对 Stop hook 和原生 `codex-notify` 去重,避免同一轮重复通知;受管 Stop hook 生效时,client 为空的委派子任务完成事件也会保持静默
|
|
670
670
|
- `/goal` 保持 Codex 原生能力;需要长程执行时,用 `helloagents codex goals enable` 显式启用
|
|
@@ -685,7 +685,7 @@ npm test
|
|
|
685
685
|
- shell 与 PowerShell 一键脚本分发链路,以及包装脚本在安装、更新、清理、卸载和分支切换中的环境清理与模式传递规则
|
|
686
686
|
- Claude、Gemini、Codex 的宿主集成行为,包括全局切回标准模式的清理和原生清理失败时的模式保留
|
|
687
687
|
- Codex 受管 `model_instructions_file`、`notify`、`hooks.json`、hook trust 状态、本地插件、marketplace 和缓存行为
|
|
688
|
-
-
|
|
688
|
+
- Codex 清理链路,以及受管 notify 恢复规则
|
|
689
689
|
- Codex `/goal` 功能开关、长程路由上下文和 goal 感知命令契约
|
|
690
690
|
- `helloagents doctor`
|
|
691
691
|
- 项目存储和 `repo-shared`
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helloagents",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.39",
|
|
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",
|
|
@@ -13,13 +13,8 @@ export const CODEX_MANAGED_TOML_COMMENT = '# helloagents-managed'
|
|
|
13
13
|
export const CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH = '~/.codex/AGENTS.md'
|
|
14
14
|
export const CODEX_MANAGED_NOTIFY_COMMAND = 'helloagents-js'
|
|
15
15
|
export const CODEX_MANAGED_NOTIFY_VALUE = `["${CODEX_MANAGED_NOTIFY_COMMAND}", "codex-notify"]`
|
|
16
|
-
const CODEX_MANAGED_NOTIFY_LEGACY_VALUES = [
|
|
17
|
-
`["${CODEX_MANAGED_NOTIFY_COMMAND}.cmd", "codex-notify"]`,
|
|
18
|
-
`["${CODEX_MANAGED_NOTIFY_COMMAND}.exe", "codex-notify"]`,
|
|
19
|
-
]
|
|
20
16
|
export const CODEX_MANAGED_TUI_NOTIFICATIONS_VALUE = '["plan-mode-prompt"]'
|
|
21
17
|
export const CODEX_HOOKS_FEATURE_KEY = 'hooks'
|
|
22
|
-
export const CODEX_LEGACY_HOOKS_FEATURE_KEY = 'codex_hooks'
|
|
23
18
|
export const CODEX_GOALS_FEATURE_KEY = 'goals'
|
|
24
19
|
export const CODEX_MANAGED_GOALS_FEATURE_LINE = `${CODEX_GOALS_FEATURE_KEY} = true ${CODEX_MANAGED_TOML_COMMENT}`
|
|
25
20
|
export const CODEX_MANAGED_GOALS_DISABLED_LINE = `${CODEX_GOALS_FEATURE_KEY} = false ${CODEX_MANAGED_TOML_COMMENT}`
|
|
@@ -105,10 +100,6 @@ export function readCodexHooksFeatureLine(text) {
|
|
|
105
100
|
return readCodexFeatureLine(text, CODEX_HOOKS_FEATURE_KEY)
|
|
106
101
|
}
|
|
107
102
|
|
|
108
|
-
export function readLegacyCodexHooksFeatureLine(text) {
|
|
109
|
-
return readCodexFeatureLine(text, CODEX_LEGACY_HOOKS_FEATURE_KEY)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
103
|
export function readCodexGoalsFeatureLine(text) {
|
|
113
104
|
return readCodexFeatureLine(text, CODEX_GOALS_FEATURE_KEY)
|
|
114
105
|
}
|
|
@@ -146,15 +137,6 @@ export function removeCodexGoalsFeatureConfig(text) {
|
|
|
146
137
|
)
|
|
147
138
|
}
|
|
148
139
|
|
|
149
|
-
export function removeLegacyManagedCodexHooksFeatureConfig(text) {
|
|
150
|
-
return removeTomlSectionLine(
|
|
151
|
-
text,
|
|
152
|
-
CODEX_FEATURES_HEADER,
|
|
153
|
-
CODEX_LEGACY_HOOKS_FEATURE_KEY,
|
|
154
|
-
isManagedLegacyCodexHooksFeature,
|
|
155
|
-
)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
140
|
export function restoreCodexGoalsFeatureConfig(text, { codexGoalsLine = '' } = {}) {
|
|
159
141
|
if (!codexGoalsLine) return normalizeToml(text)
|
|
160
142
|
return upsertTomlSectionLine(
|
|
@@ -173,10 +155,7 @@ export function isManagedCodexModelInstruction(line = '') {
|
|
|
173
155
|
export function isManagedCodexNotify(line = '') {
|
|
174
156
|
const value = String(line || '').replace(/\\/g, '/')
|
|
175
157
|
return value.includes(CODEX_MANAGED_TOML_COMMENT)
|
|
176
|
-
&& (
|
|
177
|
-
value.includes(CODEX_MANAGED_NOTIFY_VALUE)
|
|
178
|
-
|| CODEX_MANAGED_NOTIFY_LEGACY_VALUES.some((entry) => value.includes(entry))
|
|
179
|
-
)
|
|
158
|
+
&& value.includes(CODEX_MANAGED_NOTIFY_VALUE)
|
|
180
159
|
}
|
|
181
160
|
|
|
182
161
|
export function isManagedCodexTuiNotifications(line = '') {
|
|
@@ -193,10 +172,6 @@ export function isManagedCodexHooksFeature(line = '') {
|
|
|
193
172
|
return isManagedFeatureLine(line, CODEX_HOOKS_FEATURE_KEY)
|
|
194
173
|
}
|
|
195
174
|
|
|
196
|
-
export function isManagedLegacyCodexHooksFeature(line = '') {
|
|
197
|
-
return isManagedFeatureLine(line, CODEX_LEGACY_HOOKS_FEATURE_KEY)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
175
|
export function isManagedCodexGoalsFeature(line = '') {
|
|
201
176
|
return isManagedFeatureLine(line, CODEX_GOALS_FEATURE_KEY)
|
|
202
177
|
}
|
package/scripts/cli-codex.mjs
CHANGED
|
@@ -17,12 +17,9 @@ import {
|
|
|
17
17
|
isManagedCodexGoalsFeature,
|
|
18
18
|
isManagedCodexModelInstruction,
|
|
19
19
|
isManagedCodexNotify,
|
|
20
|
-
isManagedLegacyCodexHooksFeature,
|
|
21
20
|
readCodexGoalsFeatureLine,
|
|
22
|
-
readLegacyCodexHooksFeatureLine,
|
|
23
21
|
removeCodexGoalsFeatureConfig,
|
|
24
22
|
removeCodexManagedTuiConfig,
|
|
25
|
-
removeLegacyManagedCodexHooksFeatureConfig,
|
|
26
23
|
removeCodexPluginConfig,
|
|
27
24
|
restoreCodexGoalsFeatureConfig,
|
|
28
25
|
restoreCodexTopLevelConfig,
|
|
@@ -156,12 +153,10 @@ function cleanupCodexManagedConfig(configPath, { removePluginConfig = false } =
|
|
|
156
153
|
const currentModelInstructions = readTopLevelTomlLine(toml, 'model_instructions_file');
|
|
157
154
|
const currentNotify = readTopLevelTomlBlock(toml, 'notify');
|
|
158
155
|
const currentCodexGoalsFeature = readCodexGoalsFeatureLine(toml);
|
|
159
|
-
const currentLegacyCodexHooksFeature = readLegacyCodexHooksFeatureLine(toml);
|
|
160
156
|
|
|
161
157
|
const shouldRestoreModelInstructions = isManagedCodexModelInstruction(currentModelInstructions);
|
|
162
158
|
const shouldRestoreNotify = isManagedCodexNotify(currentNotify);
|
|
163
159
|
const shouldRestoreCodexGoalsFeature = isManagedCodexGoalsFeature(currentCodexGoalsFeature);
|
|
164
|
-
const shouldRemoveLegacyCodexHooksFeature = isManagedLegacyCodexHooksFeature(currentLegacyCodexHooksFeature);
|
|
165
160
|
|
|
166
161
|
if (removePluginConfig) {
|
|
167
162
|
toml = removeCodexPluginConfig(toml);
|
|
@@ -170,9 +165,6 @@ function cleanupCodexManagedConfig(configPath, { removePluginConfig = false } =
|
|
|
170
165
|
toml = removeCodexGoalsFeatureConfig(toml);
|
|
171
166
|
}
|
|
172
167
|
toml = removeCodexManagedTuiConfig(toml);
|
|
173
|
-
if (shouldRemoveLegacyCodexHooksFeature) {
|
|
174
|
-
toml = removeLegacyManagedCodexHooksFeatureConfig(toml);
|
|
175
|
-
}
|
|
176
168
|
if (shouldRestoreModelInstructions) {
|
|
177
169
|
toml = removeTopLevelTomlLines(toml, (line) =>
|
|
178
170
|
line.startsWith('model_instructions_file =') && isManagedCodexModelInstruction(line)).text;
|
|
@@ -220,7 +212,6 @@ export function installCodexStandby(home, pkgRoot) {
|
|
|
220
212
|
modelInstructionsPath: CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH,
|
|
221
213
|
});
|
|
222
214
|
toml = installCodexManagedTuiConfig(toml);
|
|
223
|
-
toml = removeLegacyManagedCodexHooksFeatureConfig(toml);
|
|
224
215
|
safeWrite(configPath, toml);
|
|
225
216
|
installCodexStandaloneHooks(home, pkgRoot);
|
|
226
217
|
|
|
@@ -312,7 +303,6 @@ export function installCodexGlobal(home, pkgRoot) {
|
|
|
312
303
|
modelInstructionsPath: CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH,
|
|
313
304
|
});
|
|
314
305
|
toml = installCodexManagedTuiConfig(toml);
|
|
315
|
-
toml = removeLegacyManagedCodexHooksFeatureConfig(toml);
|
|
316
306
|
toml = upsertCodexPluginConfig(toml);
|
|
317
307
|
safeWrite(configPath, toml);
|
|
318
308
|
installCodexStandaloneHooks(home, pkgRoot);
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
CODEX_MANAGED_NOTIFY_VALUE,
|
|
10
10
|
readCodexGoalsFeatureLine,
|
|
11
11
|
readCodexHooksFeatureLine,
|
|
12
|
-
readLegacyCodexHooksFeatureLine,
|
|
13
12
|
} from './cli-codex-config.mjs'
|
|
14
13
|
import {
|
|
15
14
|
buildManagedCodexHookTrustEntries,
|
|
@@ -301,8 +300,6 @@ function buildCodexChecks(runtime, settings, trackedMode, detectedMode) {
|
|
|
301
300
|
)
|
|
302
301
|
const hooksFeatureLine = readCodexHooksFeatureLine(codexConfig)
|
|
303
302
|
const goalsFeatureLine = readCodexGoalsFeatureLine(codexConfig)
|
|
304
|
-
const legacyHooksFeatureLine = readLegacyCodexHooksFeatureLine(codexConfig)
|
|
305
|
-
|
|
306
303
|
return {
|
|
307
304
|
checks: {
|
|
308
305
|
carrierMarker: (safeRead(join(codexDir, 'AGENTS.md')) || '').includes('HELLOAGENTS_START'),
|
|
@@ -319,10 +316,8 @@ function buildCodexChecks(runtime, settings, trackedMode, detectedMode) {
|
|
|
319
316
|
modelInstructionsPathMatch: !!modelInstructionsLine && normalizePath(modelInstructionsLine).includes(`"${CODEX_MANAGED_MODEL_INSTRUCTIONS_PATH}"`),
|
|
320
317
|
codexNotify: codexConfig.includes('codex-notify'),
|
|
321
318
|
notifyPathMatch: codexConfig.includes(CODEX_MANAGED_NOTIFY_VALUE),
|
|
322
|
-
codexHooksFeature: !/^\s*hooks\s*=\s*false\b/.test(hooksFeatureLine)
|
|
323
|
-
&& !/^\s*codex_hooks\s*=\s*false\b/.test(legacyHooksFeatureLine),
|
|
319
|
+
codexHooksFeature: !/^\s*hooks\s*=\s*false\b/.test(hooksFeatureLine),
|
|
324
320
|
codexGoalsFeature: /^\s*goals\s*=\s*true\b/.test(goalsFeatureLine),
|
|
325
|
-
legacyCodexHooksFeature: Boolean(legacyHooksFeatureLine),
|
|
326
321
|
standaloneHooks: JSON.stringify(codexHooks.hooks || {}).includes('helloagents'),
|
|
327
322
|
standaloneHooksMatch: managedHooksMatch(codexHooks.hooks || {}, expectedHooks),
|
|
328
323
|
managedHookTrust: expectedHookTrust.every((entry) => managedHookTrust.has(entry.key)),
|
|
@@ -369,7 +364,6 @@ export function inspectCodexDoctor(runtime, settings) {
|
|
|
369
364
|
if (!checks.pluginVersionMatch && !pluginVersion && detectedMode === 'global') notes.push(runtime.msg('未读到 global 插件根目录版本信息', 'Global plugin root version was not readable'))
|
|
370
365
|
if (!checks.pluginCacheVersionMatch && !cacheVersion && detectedMode === 'global') notes.push(runtime.msg('未读到 global 插件缓存版本信息', 'Global plugin cache version was not readable'))
|
|
371
366
|
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.'))
|
|
372
|
-
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.'))
|
|
373
367
|
if (!nativeDoctor.available) notes.push(runtime.msg('未检测到原生 `codex doctor`;当前仅检查 HelloAGENTS 受管覆盖层。', 'Native `codex doctor` was not available; only the HelloAGENTS managed overlay was checked.'))
|
|
374
368
|
|
|
375
369
|
const status = summarizeDoctorStatus(issues, { trackedMode, detectedMode })
|
|
@@ -23,10 +23,6 @@ function removePath(filePath, result, bucket) {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function isDebugLog(entryName = '') {
|
|
27
|
-
return /\.log$/i.test(entryName)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
26
|
function isDirectoryEmptyRecursive(dirPath) {
|
|
31
27
|
const entries = readdirSync(dirPath, { withFileTypes: true })
|
|
32
28
|
if (entries.length === 0) return true
|
|
@@ -92,32 +88,6 @@ function cleanupTransientSessionTemps(sessionsDir, result) {
|
|
|
92
88
|
}
|
|
93
89
|
}
|
|
94
90
|
|
|
95
|
-
function cleanupLegacyProjectArtifacts(activationDir, result) {
|
|
96
|
-
const artifactsDir = join(activationDir, 'artifacts')
|
|
97
|
-
if (!existsSync(artifactsDir)) return
|
|
98
|
-
|
|
99
|
-
let removableEntries = []
|
|
100
|
-
try {
|
|
101
|
-
removableEntries = readdirSync(artifactsDir, { withFileTypes: true })
|
|
102
|
-
} catch (error) {
|
|
103
|
-
result.errors.push(`${artifactsDir}: ${error.message}`)
|
|
104
|
-
return
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
for (const entry of removableEntries) {
|
|
108
|
-
if (!entry.isFile() || !isDebugLog(entry.name)) continue
|
|
109
|
-
removePath(join(artifactsDir, entry.name), result, 'removedLegacyArtifacts')
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
if (isDirectoryEmptyRecursive(artifactsDir)) {
|
|
114
|
-
removePath(artifactsDir, result, 'removedLegacyArtifacts')
|
|
115
|
-
}
|
|
116
|
-
} catch (error) {
|
|
117
|
-
result.errors.push(`${artifactsDir}: ${error.message}`)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
91
|
export function cleanupProjectSessions(cwd, { now = Date.now(), minIntervalMs = 0, maxAgeMs = PROJECT_SESSION_MAX_AGE_MS } = {}) {
|
|
122
92
|
const projectRoot = getProjectRoot(cwd)
|
|
123
93
|
const activationDir = getProjectActivationDir(projectRoot)
|
|
@@ -131,7 +101,6 @@ export function cleanupProjectSessions(cwd, { now = Date.now(), minIntervalMs =
|
|
|
131
101
|
removedNoStateDirs: [],
|
|
132
102
|
removedSeedDirs: [],
|
|
133
103
|
removedTempFiles: [],
|
|
134
|
-
removedLegacyArtifacts: [],
|
|
135
104
|
errors: [],
|
|
136
105
|
skipped: false,
|
|
137
106
|
}
|
|
@@ -150,7 +119,6 @@ export function cleanupProjectSessions(cwd, { now = Date.now(), minIntervalMs =
|
|
|
150
119
|
} catch (error) {
|
|
151
120
|
result.errors.push(`${sessionsDir}: ${error.message}`)
|
|
152
121
|
}
|
|
153
|
-
cleanupLegacyProjectArtifacts(activationDir, result)
|
|
154
122
|
|
|
155
123
|
for (const workspaceEntry of readdirSync(sessionsDir, { withFileTypes: true })) {
|
|
156
124
|
if (!workspaceEntry.isDirectory()) continue
|
|
@@ -5,14 +5,17 @@ import { dirname, join, normalize, resolve } from 'node:path'
|
|
|
5
5
|
import { homedir } from 'node:os'
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
+
PROJECT_CONVERSATION_PAYLOAD_KEYS,
|
|
9
|
+
PROJECT_SESSION_PAYLOAD_KEYS,
|
|
10
|
+
PROJECT_THREAD_PAYLOAD_KEYS,
|
|
8
11
|
resolveProjectSessionAliasToken,
|
|
9
12
|
resolveProjectSessionToken,
|
|
10
13
|
resolveSessionToken,
|
|
14
|
+
sanitizeSessionToken,
|
|
11
15
|
} from './session-token.mjs'
|
|
12
16
|
import { USER_RUNTIME_MAX_AGE_MS } from './runtime-ttl.mjs'
|
|
13
17
|
import { cleanupUserRuntimeRoot, getUserRuntimeRoot } from './runtime-user-cleanup.mjs'
|
|
14
18
|
import { FULL_CARRIER_PROFILE_MARKER } from './cli-utils.mjs'
|
|
15
|
-
import { readStateDocument, writeStateDocument } from './state-document.mjs'
|
|
16
19
|
|
|
17
20
|
export const PROJECT_DIR_NAME = '.helloagents'
|
|
18
21
|
export const PROJECT_SESSIONS_DIR_NAME = 'sessions'
|
|
@@ -21,7 +24,6 @@ export const EVENTS_FILE_NAME = 'events.jsonl'
|
|
|
21
24
|
export const ACTIVE_SESSION_FILE_NAME = 'active.json'
|
|
22
25
|
export const PROJECT_RUNTIME_FILE_NAME = 'runtime.json'
|
|
23
26
|
export const DEFAULT_STATE_SESSION_TOKEN = 'default'
|
|
24
|
-
export const LEGACY_SESSION_POINTERS_FILE_NAME = 'session-pointers.json'
|
|
25
27
|
export const USER_RUNTIME_DIR_NAME = 'runtime'
|
|
26
28
|
export { cleanupUserRuntimeRoot, getUserRuntimeRoot, USER_RUNTIME_MAX_AGE_MS }
|
|
27
29
|
|
|
@@ -267,38 +269,6 @@ function buildInitialStateSnapshot({
|
|
|
267
269
|
].join('\n')
|
|
268
270
|
}
|
|
269
271
|
|
|
270
|
-
function normalizeProjectSessionState(scope) {
|
|
271
|
-
if (!scope?.statePath) return
|
|
272
|
-
|
|
273
|
-
const currentDocument = readStateDocument(scope.statePath)
|
|
274
|
-
if (currentDocument.hasMetadata) {
|
|
275
|
-
if (currentDocument.metadata && typeof currentDocument.metadata === 'object' && !existsSync(scope.runtimePath)) {
|
|
276
|
-
writeJsonFileAtomic(scope.runtimePath, currentDocument.metadata)
|
|
277
|
-
}
|
|
278
|
-
writeStateDocument(scope.statePath, {
|
|
279
|
-
body: currentDocument.body,
|
|
280
|
-
})
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const workspaceStatePath = scope.workspaceDir ? join(scope.workspaceDir, 'STATE.md') : ''
|
|
284
|
-
if (!workspaceStatePath || samePath(workspaceStatePath, scope.statePath) || !existsSync(workspaceStatePath)) return
|
|
285
|
-
|
|
286
|
-
const legacyDocument = readStateDocument(workspaceStatePath)
|
|
287
|
-
if (!existsSync(scope.statePath) && legacyDocument.body.trim()) {
|
|
288
|
-
writeStateDocument(scope.statePath, {
|
|
289
|
-
body: legacyDocument.body,
|
|
290
|
-
})
|
|
291
|
-
}
|
|
292
|
-
if (legacyDocument.metadata && typeof legacyDocument.metadata === 'object' && !existsSync(scope.runtimePath)) {
|
|
293
|
-
writeJsonFileAtomic(scope.runtimePath, legacyDocument.metadata)
|
|
294
|
-
}
|
|
295
|
-
if (legacyDocument.hasMetadata) {
|
|
296
|
-
writeStateDocument(workspaceStatePath, {
|
|
297
|
-
body: legacyDocument.body,
|
|
298
|
-
})
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
272
|
export function ensureProjectLocalRuntime(cwd, options = {}) {
|
|
303
273
|
const normalizedCwd = normalizePath(cwd || process.cwd())
|
|
304
274
|
const localDir = getProjectLocalDir(normalizedCwd)
|
|
@@ -310,7 +280,6 @@ export function ensureProjectLocalRuntime(cwd, options = {}) {
|
|
|
310
280
|
if (!existsSync(scope.statePath)) {
|
|
311
281
|
writeFileSync(scope.statePath, `${buildInitialStateSnapshot(options.stateSeed || {})}\n`, 'utf-8')
|
|
312
282
|
}
|
|
313
|
-
normalizeProjectSessionState(scope)
|
|
314
283
|
|
|
315
284
|
return scope
|
|
316
285
|
}
|
|
@@ -369,6 +338,31 @@ function resolvePayloadSessionToken(payload = {}) {
|
|
|
369
338
|
})
|
|
370
339
|
}
|
|
371
340
|
|
|
341
|
+
function readRawPayloadValue(payload = {}, key = '') {
|
|
342
|
+
if (!payload || typeof payload !== 'object') return ''
|
|
343
|
+
const value = payload[key]
|
|
344
|
+
if (typeof value === 'string') return value.trim()
|
|
345
|
+
if (typeof value === 'number') return String(value)
|
|
346
|
+
return ''
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function readPayloadSessionIdentity(payload = {}) {
|
|
350
|
+
const groups = [
|
|
351
|
+
['session', PROJECT_SESSION_PAYLOAD_KEYS],
|
|
352
|
+
['conversation', PROJECT_CONVERSATION_PAYLOAD_KEYS],
|
|
353
|
+
['thread', PROJECT_THREAD_PAYLOAD_KEYS],
|
|
354
|
+
]
|
|
355
|
+
|
|
356
|
+
for (const [kind, keys] of groups) {
|
|
357
|
+
for (const key of keys) {
|
|
358
|
+
const value = sanitizeRuntimeSegment(sanitizeSessionToken(readRawPayloadValue(payload, key)), '')
|
|
359
|
+
if (value) return { kind, token: value }
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return { kind: '', token: '' }
|
|
364
|
+
}
|
|
365
|
+
|
|
372
366
|
function resolveEnvSessionToken(env = process.env) {
|
|
373
367
|
return resolveProjectSessionToken({ payload: {}, env })
|
|
374
368
|
}
|
|
@@ -377,6 +371,17 @@ function resolveEnvSessionAliasToken(env = process.env) {
|
|
|
377
371
|
return resolveProjectSessionAliasToken({ env })
|
|
378
372
|
}
|
|
379
373
|
|
|
374
|
+
function resolveProjectSessionHostHint({ env = process.env, ppid = process.ppid } = {}) {
|
|
375
|
+
const envToken = sanitizeRuntimeSegment(resolveEnvSessionToken(env), '')
|
|
376
|
+
if (envToken) return `host:${envToken}`
|
|
377
|
+
|
|
378
|
+
const envAliasToken = sanitizeRuntimeSegment(resolveEnvSessionAliasToken(env), '')
|
|
379
|
+
if (envAliasToken) return `alias:${envAliasToken}`
|
|
380
|
+
|
|
381
|
+
const parentToken = sanitizeRuntimeSegment(String(ppid || '').trim(), '')
|
|
382
|
+
return parentToken ? `ppid:${parentToken}` : ''
|
|
383
|
+
}
|
|
384
|
+
|
|
380
385
|
function resolveTransientSessionToken({ payload = {}, env = process.env, ppid = process.ppid } = {}) {
|
|
381
386
|
return resolveSessionToken({
|
|
382
387
|
payload,
|
|
@@ -393,18 +398,33 @@ function buildScopedSessionToken(kind = '', raw = '') {
|
|
|
393
398
|
return `${normalizedKind}-${value}`
|
|
394
399
|
}
|
|
395
400
|
|
|
396
|
-
function
|
|
397
|
-
|
|
401
|
+
function buildSessionAliasKeys({ payload = {}, env = process.env } = {}) {
|
|
402
|
+
const keys = []
|
|
403
|
+
const payloadIdentity = readPayloadSessionIdentity(payload)
|
|
404
|
+
if (payloadIdentity.token) {
|
|
405
|
+
keys.push(`${payloadIdentity.kind}:${payloadIdentity.token}`)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const envSession = sanitizeRuntimeSegment(resolveEnvSessionToken(env), '')
|
|
409
|
+
if (envSession && !payloadIdentity.token) keys.push(`host:${envSession}`)
|
|
410
|
+
|
|
411
|
+
const payloadAlias = sanitizeRuntimeSegment(sanitizeSessionToken(payload?._helloagentsSessionAlias), '')
|
|
412
|
+
if (payloadAlias) keys.push(`alias:${payloadAlias}`)
|
|
413
|
+
|
|
414
|
+
const envAlias = sanitizeRuntimeSegment(resolveEnvSessionAliasToken(env), '')
|
|
415
|
+
if (envAlias && envSession && envAlias === envSession) {
|
|
416
|
+
return [...new Set(keys.filter(Boolean))]
|
|
417
|
+
}
|
|
418
|
+
if (envAlias) keys.push(`alias:${envAlias}`)
|
|
419
|
+
|
|
420
|
+
return [...new Set(keys.filter(Boolean))]
|
|
398
421
|
}
|
|
399
422
|
|
|
400
|
-
function
|
|
401
|
-
|
|
402
|
-
try {
|
|
403
|
-
rmSync(join(activationDir, PROJECT_SESSIONS_DIR_NAME, LEGACY_SESSION_POINTERS_FILE_NAME), { force: true })
|
|
404
|
-
} catch {}
|
|
423
|
+
function getActiveSessionPath(activationDir) {
|
|
424
|
+
return join(activationDir, PROJECT_SESSIONS_DIR_NAME, ACTIVE_SESSION_FILE_NAME)
|
|
405
425
|
}
|
|
406
426
|
|
|
407
|
-
function
|
|
427
|
+
function readActiveProjectSession({ activationDir, projectRoot, workspace, now = Date.now() } = {}) {
|
|
408
428
|
const active = readJsonFile(getActiveSessionPath(activationDir), null)
|
|
409
429
|
if (!active || typeof active !== 'object') return ''
|
|
410
430
|
if (active.cwd && !samePath(active.cwd, projectRoot)) return ''
|
|
@@ -415,43 +435,77 @@ function resolveActiveSessionToken({ activationDir, projectRoot, workspace, now
|
|
|
415
435
|
const updatedAt = Date.parse(active.updatedAt || '')
|
|
416
436
|
if (!Number.isFinite(updatedAt) || now - updatedAt > USER_RUNTIME_MAX_AGE_MS) return ''
|
|
417
437
|
|
|
418
|
-
return
|
|
438
|
+
return active
|
|
419
439
|
}
|
|
420
440
|
|
|
421
441
|
function resolveActiveAliasSession({ activationDir, projectRoot, workspace, alias, now = Date.now() } = {}) {
|
|
422
442
|
if (!alias) return ''
|
|
423
|
-
const active =
|
|
443
|
+
const active = readActiveProjectSession({
|
|
444
|
+
activationDir,
|
|
445
|
+
projectRoot,
|
|
446
|
+
workspace,
|
|
447
|
+
now,
|
|
448
|
+
})
|
|
424
449
|
if (!active || typeof active !== 'object') return ''
|
|
425
|
-
if (active.cwd && !samePath(active.cwd, projectRoot)) return ''
|
|
426
450
|
|
|
427
|
-
const
|
|
428
|
-
|
|
451
|
+
const aliases = active.aliases && typeof active.aliases === 'object' ? active.aliases : {}
|
|
452
|
+
const mapped = sanitizeRuntimeSegment(aliases[alias], '')
|
|
453
|
+
if (mapped) return mapped
|
|
454
|
+
if (
|
|
455
|
+
Object.prototype.hasOwnProperty.call(aliases, alias)
|
|
456
|
+
&& resolveActiveSessionToken(active, join(activationDir, PROJECT_SESSIONS_DIR_NAME, workspace)) === DEFAULT_STATE_SESSION_TOKEN
|
|
457
|
+
) {
|
|
458
|
+
return DEFAULT_STATE_SESSION_TOKEN
|
|
459
|
+
}
|
|
460
|
+
return ''
|
|
461
|
+
}
|
|
429
462
|
|
|
430
|
-
|
|
431
|
-
|
|
463
|
+
function choosePreferredProjectSession(activeSession = '', candidates = []) {
|
|
464
|
+
for (const candidate of candidates) {
|
|
465
|
+
if (!candidate) continue
|
|
466
|
+
if (activeSession && candidate === activeSession) return candidate
|
|
467
|
+
}
|
|
468
|
+
return candidates.find(Boolean) || ''
|
|
469
|
+
}
|
|
432
470
|
|
|
433
|
-
|
|
434
|
-
|
|
471
|
+
function resolveActiveSessionToken(active = {}, workspaceDir = '') {
|
|
472
|
+
const session = sanitizeRuntimeSegment(active?.session || '', '')
|
|
473
|
+
if (session) return session
|
|
474
|
+
|
|
475
|
+
const defaultStatePath = workspaceDir
|
|
476
|
+
? join(workspaceDir, DEFAULT_STATE_SESSION_TOKEN, 'STATE.md')
|
|
477
|
+
: ''
|
|
478
|
+
return defaultStatePath && existsSync(defaultStatePath)
|
|
479
|
+
? DEFAULT_STATE_SESSION_TOKEN
|
|
480
|
+
: ''
|
|
435
481
|
}
|
|
436
482
|
|
|
437
|
-
export function writeActiveProjectSession(scope, { host = '', source = '', env = process.env } = {}) {
|
|
483
|
+
export function writeActiveProjectSession(scope, { host = '', source = '', payload = {}, env = process.env, ppid = process.ppid } = {}) {
|
|
438
484
|
if (!scope?.active || !scope.activationDir || !scope.workspace) return ''
|
|
439
485
|
|
|
440
486
|
const activePath = getActiveSessionPath(scope.activationDir)
|
|
441
487
|
const current = readJsonFile(activePath, null) || {}
|
|
442
|
-
const aliases = current.aliases && typeof current.aliases === 'object' ? current.aliases : {}
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
488
|
+
const aliases = current.aliases && typeof current.aliases === 'object' ? { ...current.aliases } : {}
|
|
489
|
+
const session = scope.session || DEFAULT_STATE_SESSION_TOKEN
|
|
490
|
+
const sessionMode = scope.session
|
|
491
|
+
? scope.sessionMode
|
|
492
|
+
: 'default'
|
|
493
|
+
const hostHint = resolveProjectSessionHostHint({ env, ppid }) || current.hostHint || ''
|
|
494
|
+
const aliasKeys = buildSessionAliasKeys({ payload, env })
|
|
495
|
+
for (const aliasKey of aliasKeys) {
|
|
496
|
+
aliases[aliasKey] = session
|
|
497
|
+
const [, aliasValue = ''] = String(aliasKey).split(':')
|
|
498
|
+
if (aliasValue) aliases[aliasValue] = session
|
|
499
|
+
}
|
|
447
500
|
writeJsonFileAtomic(activePath, {
|
|
448
501
|
version: 1,
|
|
449
502
|
cwd: scope.cwd,
|
|
450
503
|
workspace: scope.workspace || scope.branch,
|
|
451
|
-
session
|
|
452
|
-
sessionMode
|
|
504
|
+
session,
|
|
505
|
+
sessionMode,
|
|
453
506
|
host,
|
|
454
507
|
source,
|
|
508
|
+
...(hostHint ? { hostHint } : {}),
|
|
455
509
|
aliases,
|
|
456
510
|
...(current.cleanupCheckedAt ? { cleanupCheckedAt: current.cleanupCheckedAt } : {}),
|
|
457
511
|
updatedAt: new Date().toISOString(),
|
|
@@ -459,23 +513,73 @@ export function writeActiveProjectSession(scope, { host = '', source = '', env =
|
|
|
459
513
|
return activePath
|
|
460
514
|
}
|
|
461
515
|
|
|
462
|
-
function chooseProjectSession({ payload, env, activationDir, projectRoot, workspace }) {
|
|
463
|
-
const
|
|
464
|
-
const
|
|
516
|
+
function chooseProjectSession({ payload, env, ppid, activationDir, projectRoot, workspace }) {
|
|
517
|
+
const workspaceDir = join(activationDir, PROJECT_SESSIONS_DIR_NAME, workspace)
|
|
518
|
+
const active = readActiveProjectSession({
|
|
519
|
+
activationDir,
|
|
520
|
+
projectRoot,
|
|
521
|
+
workspace,
|
|
522
|
+
})
|
|
523
|
+
const activeSession = resolveActiveSessionToken(active, workspaceDir)
|
|
524
|
+
const payloadIdentity = readPayloadSessionIdentity(payload)
|
|
525
|
+
const payloadToken = payloadIdentity.token
|
|
526
|
+
const payloadAlias = sanitizeRuntimeSegment(sanitizeSessionToken(payload?._helloagentsSessionAlias), '')
|
|
527
|
+
const payloadAliases = [
|
|
528
|
+
payloadIdentity.token ? `${payloadIdentity.kind}:${payloadIdentity.token}` : '',
|
|
529
|
+
payloadAlias ? `alias:${payloadAlias}` : '',
|
|
530
|
+
].filter(Boolean)
|
|
531
|
+
const payloadMappedSession = choosePreferredProjectSession(
|
|
532
|
+
activeSession,
|
|
533
|
+
payloadAliases.map((alias) => resolveActiveAliasSession({
|
|
534
|
+
activationDir,
|
|
535
|
+
projectRoot,
|
|
536
|
+
workspace,
|
|
537
|
+
alias,
|
|
538
|
+
})),
|
|
539
|
+
)
|
|
540
|
+
if (payloadMappedSession) {
|
|
541
|
+
return { session: payloadMappedSession, sessionMode: 'active-session' }
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const envToken = sanitizeRuntimeSegment(resolveEnvSessionToken(env), '')
|
|
545
|
+
const envAliasToken = sanitizeRuntimeSegment(resolveEnvSessionAliasToken(env), '')
|
|
546
|
+
const envMappedSession = choosePreferredProjectSession(
|
|
547
|
+
activeSession,
|
|
548
|
+
[
|
|
549
|
+
envToken ? resolveActiveAliasSession({
|
|
550
|
+
activationDir,
|
|
551
|
+
projectRoot,
|
|
552
|
+
workspace,
|
|
553
|
+
alias: `host:${envToken}`,
|
|
554
|
+
}) : '',
|
|
555
|
+
envAliasToken ? resolveActiveAliasSession({
|
|
556
|
+
activationDir,
|
|
557
|
+
projectRoot,
|
|
558
|
+
workspace,
|
|
559
|
+
alias: `alias:${envAliasToken}`,
|
|
560
|
+
}) : '',
|
|
561
|
+
],
|
|
562
|
+
)
|
|
563
|
+
if (envMappedSession) return { session: envMappedSession, sessionMode: 'active-session' }
|
|
564
|
+
|
|
565
|
+
if (
|
|
566
|
+
activeSession === DEFAULT_STATE_SESSION_TOKEN
|
|
567
|
+
&& active?.hostHint
|
|
568
|
+
&& active.hostHint === resolveProjectSessionHostHint({ env, ppid })
|
|
569
|
+
&& (payloadToken || payloadAlias || envToken || envAliasToken)
|
|
570
|
+
) {
|
|
571
|
+
return {
|
|
572
|
+
session: activeSession,
|
|
573
|
+
sessionMode: 'active-session',
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
465
577
|
if (payloadToken) {
|
|
466
578
|
return {
|
|
467
579
|
session: buildScopedSessionToken('host', payloadToken),
|
|
468
580
|
sessionMode: 'host-session',
|
|
469
581
|
}
|
|
470
582
|
}
|
|
471
|
-
|
|
472
|
-
const payloadAliasToken = resolveActiveAliasSession({
|
|
473
|
-
activationDir,
|
|
474
|
-
projectRoot,
|
|
475
|
-
workspace,
|
|
476
|
-
alias: payloadAlias,
|
|
477
|
-
})
|
|
478
|
-
if (payloadAliasToken) return { session: payloadAliasToken, sessionMode: 'active-session' }
|
|
479
583
|
if (payloadAlias) {
|
|
480
584
|
return {
|
|
481
585
|
session: buildScopedSessionToken('alias', payloadAlias),
|
|
@@ -483,16 +587,6 @@ function chooseProjectSession({ payload, env, activationDir, projectRoot, worksp
|
|
|
483
587
|
}
|
|
484
588
|
}
|
|
485
589
|
|
|
486
|
-
const envToken = sanitizeRuntimeSegment(resolveEnvSessionToken(env), '')
|
|
487
|
-
const envAliasToken = sanitizeRuntimeSegment(resolveEnvSessionAliasToken(env), '')
|
|
488
|
-
const aliasToken = resolveActiveAliasSession({
|
|
489
|
-
activationDir,
|
|
490
|
-
projectRoot,
|
|
491
|
-
workspace,
|
|
492
|
-
alias: envToken,
|
|
493
|
-
})
|
|
494
|
-
if (aliasToken) return { session: aliasToken, sessionMode: 'active-session' }
|
|
495
|
-
|
|
496
590
|
if (envToken) {
|
|
497
591
|
return {
|
|
498
592
|
session: buildScopedSessionToken('host', envToken),
|
|
@@ -501,42 +595,33 @@ function chooseProjectSession({ payload, env, activationDir, projectRoot, worksp
|
|
|
501
595
|
}
|
|
502
596
|
|
|
503
597
|
if (envAliasToken) {
|
|
504
|
-
const activeAliasToken = resolveActiveAliasSession({
|
|
505
|
-
activationDir,
|
|
506
|
-
projectRoot,
|
|
507
|
-
workspace,
|
|
508
|
-
alias: envAliasToken,
|
|
509
|
-
})
|
|
510
|
-
if (activeAliasToken) return { session: activeAliasToken, sessionMode: 'active-session' }
|
|
511
598
|
return {
|
|
512
599
|
session: buildScopedSessionToken('alias', envAliasToken),
|
|
513
600
|
sessionMode: 'alias-session',
|
|
514
601
|
}
|
|
515
602
|
}
|
|
516
603
|
|
|
517
|
-
|
|
518
|
-
|
|
604
|
+
const source = String(payload?.source || '').trim().toLowerCase()
|
|
605
|
+
if ((source === 'resume' || source === 'compact') && activeSession) {
|
|
606
|
+
return {
|
|
607
|
+
session: activeSession,
|
|
608
|
+
sessionMode: 'active-session',
|
|
609
|
+
}
|
|
610
|
+
}
|
|
519
611
|
|
|
520
|
-
|
|
521
|
-
if (!activationDir) return
|
|
522
|
-
const artifactsDir = join(activationDir, PROJECT_ARTIFACTS_DIR_NAME)
|
|
523
|
-
if (!existsSync(artifactsDir)) return
|
|
524
|
-
try {
|
|
525
|
-
rmSync(artifactsDir, { recursive: true, force: true })
|
|
526
|
-
} catch {}
|
|
612
|
+
return { session: '', sessionMode: 'unidentified' }
|
|
527
613
|
}
|
|
528
614
|
|
|
529
615
|
export function getProjectSessionScope(cwd, options = {}) {
|
|
530
616
|
const normalizedCwd = normalizePath(cwd || process.cwd())
|
|
531
617
|
const projectRoot = getProjectRoot(normalizedCwd)
|
|
532
|
-
const { payload = {}, env = process.env } = normalizeRuntimeOptions(options)
|
|
618
|
+
const { payload = {}, env = process.env, ppid = process.ppid } = normalizeRuntimeOptions(options)
|
|
533
619
|
const activationDir = getProjectActivationDir(projectRoot)
|
|
534
|
-
removeLegacyProjectArtifacts(activationDir)
|
|
535
|
-
removeLegacySessionPointersFile(activationDir)
|
|
536
620
|
const workspace = resolveWorkspaceName(projectRoot)
|
|
537
621
|
const { session, sessionMode } = chooseProjectSession({
|
|
538
622
|
payload,
|
|
539
623
|
env,
|
|
624
|
+
ppid,
|
|
540
625
|
activationDir,
|
|
541
626
|
projectRoot,
|
|
542
627
|
workspace,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync,
|
|
1
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { basename, dirname, join } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import {
|
|
@@ -43,67 +43,6 @@ function writeRuntimeDocument(filePath, payload) {
|
|
|
43
43
|
writeJsonFileAtomic(filePath, payload)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
function isSamePath(left = '', right = '') {
|
|
47
|
-
if (process.platform === 'win32') {
|
|
48
|
-
return left.toLowerCase() === right.toLowerCase()
|
|
49
|
-
}
|
|
50
|
-
return left === right
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function isSeedOnlyState(body = '') {
|
|
54
|
-
return String(body || '').includes('由运行时自动创建;后续按实际任务重写')
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function looksLikeLegacyFlattenedSessionDir(entryName = '') {
|
|
58
|
-
return /^[a-z0-9]{8}$/i.test(String(entryName || '').trim())
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function migrateLegacyProjectScope(scope) {
|
|
62
|
-
if (scope.scope !== 'project-session') return
|
|
63
|
-
const workspaceDir = scope.workspaceDir || join(scope.activationDir, 'sessions', scope.workspace || scope.branch)
|
|
64
|
-
const legacyStatePath = join(workspaceDir, 'STATE.md')
|
|
65
|
-
const legacyRuntimePath = join(workspaceDir, 'runtime.json')
|
|
66
|
-
if (isSamePath(workspaceDir, scope.sessionDir)) return
|
|
67
|
-
|
|
68
|
-
const currentDocument = readStateDocument(scope.statePath)
|
|
69
|
-
const currentCapsule = currentDocument.metadata && typeof currentDocument.metadata === 'object'
|
|
70
|
-
? currentDocument.metadata
|
|
71
|
-
: null
|
|
72
|
-
const legacyDocument = readStateDocument(legacyStatePath)
|
|
73
|
-
const legacyCapsule = readRuntimeDocument(legacyRuntimePath)
|
|
74
|
-
const shouldNormalizeCurrentBody = currentDocument.hasMetadata
|
|
75
|
-
const shouldWriteBody = (!currentDocument.body.trim() && legacyDocument.body.trim()) || shouldNormalizeCurrentBody
|
|
76
|
-
const shouldWriteRuntime = (legacyCapsule || currentCapsule) && !readRuntimeDocument(scope.runtimePath)
|
|
77
|
-
|
|
78
|
-
if (shouldWriteBody) {
|
|
79
|
-
writeStateDocument(scope.statePath, {
|
|
80
|
-
body: currentDocument.body.trim() ? currentDocument.body : legacyDocument.body,
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
if (shouldWriteRuntime) {
|
|
84
|
-
writeRuntimeDocument(scope.runtimePath, legacyCapsule || currentCapsule)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (existsSync(legacyStatePath) && shouldWriteBody) {
|
|
88
|
-
const legacyCurrent = readStateDocument(legacyStatePath)
|
|
89
|
-
if (legacyCurrent.hasMetadata) {
|
|
90
|
-
writeStateDocument(legacyStatePath, {
|
|
91
|
-
body: legacyCurrent.body,
|
|
92
|
-
})
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (existsSync(legacyRuntimePath) && shouldWriteRuntime) {
|
|
96
|
-
rmSync(legacyRuntimePath, { force: true })
|
|
97
|
-
}
|
|
98
|
-
if (existsSync(workspaceDir)) {
|
|
99
|
-
for (const entry of readdirSync(workspaceDir, { withFileTypes: true })) {
|
|
100
|
-
if (!entry.isDirectory()) continue
|
|
101
|
-
if (!looksLikeLegacyFlattenedSessionDir(entry.name)) continue
|
|
102
|
-
rmSync(join(workspaceDir, entry.name), { recursive: true, force: true })
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
46
|
function normalizeOptions(options = {}) {
|
|
108
47
|
if (!options || typeof options !== 'object') return {}
|
|
109
48
|
if (options.payload && typeof options.payload === 'object') return options
|
|
@@ -114,7 +53,7 @@ function normalizeOptions(options = {}) {
|
|
|
114
53
|
}
|
|
115
54
|
|
|
116
55
|
function getEventSessionAlias(eventPayload = {}) {
|
|
117
|
-
return eventPayload.
|
|
56
|
+
return eventPayload.sessionAlias || eventPayload.session_alias || eventPayload['session-alias'] || eventPayload._helloagentsSessionAlias || ''
|
|
118
57
|
}
|
|
119
58
|
|
|
120
59
|
function getScope(cwd, options = {}) {
|
|
@@ -182,7 +121,6 @@ export function getSessionArtifactRelativePath(cwd, fileName, options = {}) {
|
|
|
182
121
|
|
|
183
122
|
export function readSessionCapsule(cwd = process.cwd(), options = {}) {
|
|
184
123
|
const scope = getScope(cwd, options)
|
|
185
|
-
migrateLegacyProjectScope(scope)
|
|
186
124
|
const capsule = readRuntimeDocument(scope.runtimePath)
|
|
187
125
|
if (!capsule || Array.isArray(capsule)) return buildEmptyCapsule(scope)
|
|
188
126
|
return {
|
|
@@ -201,7 +139,6 @@ export function readSessionCapsule(cwd = process.cwd(), options = {}) {
|
|
|
201
139
|
export function writeSessionCapsule(cwd, capsule, options = {}) {
|
|
202
140
|
const normalizedOptions = normalizeOptions(options)
|
|
203
141
|
const scope = getScope(cwd, normalizedOptions)
|
|
204
|
-
migrateLegacyProjectScope(scope)
|
|
205
142
|
const shouldMaterialize = shouldMaterializeSessionState(normalizedOptions)
|
|
206
143
|
const currentDocument = readStateDocument(scope.statePath)
|
|
207
144
|
const hasBody = Boolean(currentDocument.body && currentDocument.body.trim())
|
|
@@ -246,7 +183,9 @@ export function writeSessionCapsule(cwd, capsule, options = {}) {
|
|
|
246
183
|
})
|
|
247
184
|
}
|
|
248
185
|
writeActiveProjectSession(scope, {
|
|
186
|
+
payload: normalizedOptions.payload,
|
|
249
187
|
env: normalizedOptions.env,
|
|
188
|
+
ppid: normalizedOptions.ppid,
|
|
250
189
|
})
|
|
251
190
|
return nextCapsule
|
|
252
191
|
}
|
|
@@ -305,7 +244,9 @@ export function appendSessionEvent(cwd, eventPayload, options = {}) {
|
|
|
305
244
|
writeActiveProjectSession(scope, {
|
|
306
245
|
host: eventPayload.host || '',
|
|
307
246
|
source: eventPayload.source || eventName,
|
|
247
|
+
payload: scopedOptions.payload,
|
|
308
248
|
env: scopedOptions.env,
|
|
249
|
+
ppid: scopedOptions.ppid,
|
|
309
250
|
})
|
|
310
251
|
if (!shouldRecordSessionEvents(scopedOptions)) return ''
|
|
311
252
|
|
|
@@ -17,19 +17,31 @@ const PAYLOAD_SESSION_KEYS = [
|
|
|
17
17
|
'tab',
|
|
18
18
|
]
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const PROJECT_SESSION_PAYLOAD_KEYS = [
|
|
21
21
|
'sessionId',
|
|
22
22
|
'session_id',
|
|
23
23
|
'session',
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
const PROJECT_CONVERSATION_PAYLOAD_KEYS = [
|
|
24
27
|
'conversationId',
|
|
25
28
|
'conversation_id',
|
|
26
29
|
'conversation',
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
const PROJECT_THREAD_PAYLOAD_KEYS = [
|
|
27
33
|
'threadId',
|
|
28
34
|
'thread_id',
|
|
29
35
|
'thread-id',
|
|
30
36
|
'thread',
|
|
31
37
|
]
|
|
32
38
|
|
|
39
|
+
const PROJECT_PAYLOAD_SESSION_KEYS = [
|
|
40
|
+
...PROJECT_SESSION_PAYLOAD_KEYS,
|
|
41
|
+
...PROJECT_CONVERSATION_PAYLOAD_KEYS,
|
|
42
|
+
...PROJECT_THREAD_PAYLOAD_KEYS,
|
|
43
|
+
]
|
|
44
|
+
|
|
33
45
|
const ENV_SESSION_KEYS = [
|
|
34
46
|
'HELLOAGENTS_NOTIFY_SESSION_ID',
|
|
35
47
|
'WT_SESSION',
|
|
@@ -46,7 +58,6 @@ const PROJECT_ENV_SESSION_KEYS = [
|
|
|
46
58
|
]
|
|
47
59
|
|
|
48
60
|
const PROJECT_ALIAS_ENV_SESSION_KEYS = [
|
|
49
|
-
'HELLOAGENTS_NOTIFY_SESSION_ID',
|
|
50
61
|
'WT_SESSION',
|
|
51
62
|
'TERM_SESSION_ID',
|
|
52
63
|
'KITTY_WINDOW_ID',
|
|
@@ -120,6 +131,9 @@ export {
|
|
|
120
131
|
ENV_SESSION_KEYS,
|
|
121
132
|
PAYLOAD_SESSION_KEYS,
|
|
122
133
|
PROJECT_ALIAS_ENV_SESSION_KEYS,
|
|
134
|
+
PROJECT_CONVERSATION_PAYLOAD_KEYS,
|
|
123
135
|
PROJECT_ENV_SESSION_KEYS,
|
|
124
136
|
PROJECT_PAYLOAD_SESSION_KEYS,
|
|
137
|
+
PROJECT_SESSION_PAYLOAD_KEYS,
|
|
138
|
+
PROJECT_THREAD_PAYLOAD_KEYS,
|
|
125
139
|
}
|
|
@@ -1,67 +1,23 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { dirname } from 'node:path'
|
|
3
3
|
|
|
4
|
-
const STATE_META_BEGIN = '<!-- HELLOAGENTS_STATE_META'
|
|
5
|
-
const STATE_META_END = 'HELLOAGENTS_STATE_META -->'
|
|
6
4
|
export const AUTO_CREATED_STATE_MARKER = '由运行时自动创建;后续按实际任务重写'
|
|
7
5
|
|
|
8
6
|
function normalizeText(content = '') {
|
|
9
7
|
return String(content || '').replace(/^\uFEFF/, '')
|
|
10
8
|
}
|
|
11
9
|
|
|
12
|
-
function splitLines(content = '') {
|
|
13
|
-
return normalizeText(content).replace(/\r\n/g, '\n').split('\n')
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function parseStateDocument(content = '') {
|
|
17
|
-
const lines = splitLines(content)
|
|
18
|
-
if (lines[0]?.trim() !== STATE_META_BEGIN) {
|
|
19
|
-
return {
|
|
20
|
-
hasMetadata: false,
|
|
21
|
-
metadata: null,
|
|
22
|
-
body: normalizeText(content),
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const endIndex = lines.findIndex((line, index) => index > 0 && line.trim() === STATE_META_END)
|
|
27
|
-
if (endIndex < 0) {
|
|
28
|
-
return {
|
|
29
|
-
hasMetadata: false,
|
|
30
|
-
metadata: null,
|
|
31
|
-
body: normalizeText(content),
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const metadataText = lines.slice(1, endIndex).join('\n').trim()
|
|
36
|
-
const body = lines.slice(endIndex + 1).join('\n').replace(/^\n+/, '')
|
|
37
|
-
try {
|
|
38
|
-
return {
|
|
39
|
-
hasMetadata: true,
|
|
40
|
-
metadata: JSON.parse(metadataText),
|
|
41
|
-
body,
|
|
42
|
-
}
|
|
43
|
-
} catch {
|
|
44
|
-
return {
|
|
45
|
-
hasMetadata: false,
|
|
46
|
-
metadata: null,
|
|
47
|
-
body,
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
10
|
export function readStateDocument(filePath) {
|
|
53
11
|
if (!filePath || !existsSync(filePath)) {
|
|
54
|
-
return {
|
|
55
|
-
hasMetadata: false,
|
|
56
|
-
metadata: null,
|
|
57
|
-
body: '',
|
|
58
|
-
}
|
|
12
|
+
return { body: '' }
|
|
59
13
|
}
|
|
60
14
|
|
|
61
|
-
return
|
|
15
|
+
return {
|
|
16
|
+
body: normalizeText(readFileSync(filePath, 'utf-8')),
|
|
17
|
+
}
|
|
62
18
|
}
|
|
63
19
|
|
|
64
|
-
export function composeStateDocument({
|
|
20
|
+
export function composeStateDocument({ body = '' } = {}) {
|
|
65
21
|
const normalizedBody = normalizeText(body).replace(/^\n+/, '')
|
|
66
22
|
return normalizedBody ? `${normalizedBody.replace(/\n+$/, '')}\n` : ''
|
|
67
23
|
}
|
|
@@ -70,7 +26,7 @@ export function looksLikeAutoCreatedState(body = '') {
|
|
|
70
26
|
return normalizeText(body).includes(AUTO_CREATED_STATE_MARKER)
|
|
71
27
|
}
|
|
72
28
|
|
|
73
|
-
export function writeStateDocument(filePath, {
|
|
29
|
+
export function writeStateDocument(filePath, { body = '' } = {}) {
|
|
74
30
|
mkdirSync(dirname(filePath), { recursive: true })
|
|
75
|
-
writeFileSync(filePath, composeStateDocument({
|
|
31
|
+
writeFileSync(filePath, composeStateDocument({ body }), 'utf-8')
|
|
76
32
|
}
|