easy-coding-harness 0.8.2 → 0.8.3
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/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
- `y`:常规功能升级
|
|
7
7
|
- `z`:日常 bug 修复
|
|
8
8
|
|
|
9
|
+
## 0.8.3
|
|
10
|
+
|
|
11
|
+
- 正式发布 Codex App thread 级 session 隔离:标准 hook `session_id` 缺失时使用 `CODEX_THREAD_ID`,避免同一 App 进程内多个逻辑会话共享 PPID session。
|
|
12
|
+
- 正式发布 Qoder CLI Agent 识别修复:共享 hook 优先按脚本平台目录判断,无法判断时优先使用 Qoder 专属环境变量,避免 Claude 兼容变量造成误识别。
|
|
13
|
+
- 汇总 `0.8.3-beta.0` 的 Codex thread fallback、Qoder/Claude 双环境变量、legacy PPID 回退、设计文档与安装后 hook 回归验证结果。
|
|
14
|
+
|
|
15
|
+
## 0.8.3-beta.0
|
|
16
|
+
|
|
17
|
+
- 修复 Codex App 未在 hook payload 中提供 `session_id` 时仍回退到 PPID、导致同一 App 进程内多个逻辑会话共享 Easy Coding session 的问题;Codex 现在会在标准 hook session ID 缺失时使用 `CODEX_THREAD_ID`,仅在两者都不可用时保留 PPID 兼容回退。
|
|
18
|
+
- 修复 Qoder CLI 同时暴露 Claude 兼容环境变量时被识别为 Claude Code 的问题;共享 hook 统一按脚本平台目录优先、Qoder 专属环境次之、Claude 环境最后的顺序解析 Agent,避免各入口判断逻辑漂移。
|
|
19
|
+
- 新增 Codex App thread fallback、标准 payload 优先级、legacy PPID 和 Qoder/Claude 双环境变量回归测试,并同步设计文档与安装后 hook 验证。
|
|
20
|
+
|
|
9
21
|
## 0.8.2
|
|
10
22
|
|
|
11
23
|
- 正式发布原生选择超时恢复:平台明确保证永久等待时禁用或省略 timeout / auto-resolution,无法保证时在调用原生选择前预先展示持久化文本编号。
|
package/package.json
CHANGED
|
@@ -142,17 +142,18 @@ def normalize_session_agent(agent: str | None) -> str:
|
|
|
142
142
|
|
|
143
143
|
|
|
144
144
|
def detect_runtime_agent() -> str:
|
|
145
|
-
if os.environ.get("CLAUDE_PROJECT_DIR"):
|
|
146
|
-
return "claude-code"
|
|
147
|
-
if os.environ.get("QODER_PROJECT_DIR"):
|
|
148
|
-
return "qoder"
|
|
149
145
|
script_path = Path(sys.argv[0]).as_posix()
|
|
150
|
-
if ".
|
|
151
|
-
return "
|
|
146
|
+
if ".qoder/" in script_path or ".qodercn/" in script_path:
|
|
147
|
+
return "qoder"
|
|
152
148
|
if ".codex/" in script_path:
|
|
153
149
|
return "codex"
|
|
154
|
-
if ".
|
|
150
|
+
if ".claude/" in script_path:
|
|
151
|
+
return "claude-code"
|
|
152
|
+
# Qoder CLI 会暴露 Claude 兼容环境变量,专属信号必须优先于兼容信号。
|
|
153
|
+
if os.environ.get("QODER_PROJECT_DIR"):
|
|
155
154
|
return "qoder"
|
|
155
|
+
if os.environ.get("CLAUDE_PROJECT_DIR"):
|
|
156
|
+
return "claude-code"
|
|
156
157
|
return "unknown"
|
|
157
158
|
|
|
158
159
|
|
|
@@ -167,7 +168,7 @@ def normalize_session_component(value: str) -> str:
|
|
|
167
168
|
return f"sha256-{digest}"
|
|
168
169
|
|
|
169
170
|
|
|
170
|
-
# 逻辑会话由 Agent
|
|
171
|
+
# 逻辑会话由 Agent 命名空间与平台会话 ID 共同标识;PPID 只用于缺少逻辑 ID 的兼容回退。
|
|
171
172
|
def hook_session_identity(
|
|
172
173
|
payload: dict,
|
|
173
174
|
agent: str | None,
|
|
@@ -176,9 +177,18 @@ def hook_session_identity(
|
|
|
176
177
|
namespace = normalize_session_agent(agent)
|
|
177
178
|
raw_session_id = payload.get("session_id") or payload.get("sessionId")
|
|
178
179
|
external_session_id = str(raw_session_id).strip() if raw_session_id is not None else ""
|
|
180
|
+
source = "hook-session-id"
|
|
181
|
+
if not external_session_id and namespace == "codex":
|
|
182
|
+
# Codex App 当前会把 thread ID 暴露在进程环境中;标准 hook session_id 仍保持最高优先级。
|
|
183
|
+
raw_thread_id = (
|
|
184
|
+
payload.get("thread_id")
|
|
185
|
+
or payload.get("threadId")
|
|
186
|
+
or os.environ.get("CODEX_THREAD_ID")
|
|
187
|
+
)
|
|
188
|
+
external_session_id = str(raw_thread_id).strip() if raw_thread_id is not None else ""
|
|
189
|
+
source = "codex-thread-id"
|
|
179
190
|
if external_session_id:
|
|
180
191
|
component = normalize_session_component(external_session_id)
|
|
181
|
-
source = "hook-session-id"
|
|
182
192
|
else:
|
|
183
193
|
component = f"ppid-{ppid if ppid is not None else os.getppid()}"
|
|
184
194
|
source = "legacy-ppid"
|
|
@@ -637,7 +647,8 @@ def resolve_session_path(root: Path, session_file: str | Path | None = None) ->
|
|
|
637
647
|
f"{session_file}. Must be a file under .easy-coding/sessions/."
|
|
638
648
|
)
|
|
639
649
|
return resolved
|
|
640
|
-
|
|
650
|
+
identity = hook_session_identity({}, detect_runtime_agent())
|
|
651
|
+
return sessions_dir / f"{identity['session_key']}.json"
|
|
641
652
|
|
|
642
653
|
|
|
643
654
|
def resolve_hook_session_path(
|
|
@@ -4,7 +4,7 @@ import os
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
import sys
|
|
6
6
|
|
|
7
|
-
from easy_coding_state import ensure_hook_session, snapshot_state
|
|
7
|
+
from easy_coding_state import detect_runtime_agent, ensure_hook_session, snapshot_state
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def configure_stdio() -> None:
|
|
@@ -30,21 +30,6 @@ def find_ec_root(start: Path) -> Path | None:
|
|
|
30
30
|
current = current.parent
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
def detect_agent() -> str:
|
|
34
|
-
if os.environ.get("CLAUDE_PROJECT_DIR"):
|
|
35
|
-
return "claude-code"
|
|
36
|
-
if os.environ.get("QODER_PROJECT_DIR"):
|
|
37
|
-
return "qoder"
|
|
38
|
-
hook_path = Path(sys.argv[0]).as_posix()
|
|
39
|
-
if ".claude/" in hook_path:
|
|
40
|
-
return "claude-code"
|
|
41
|
-
if ".codex/" in hook_path:
|
|
42
|
-
return "codex"
|
|
43
|
-
if ".qoder/" in hook_path or ".qodercn/" in hook_path:
|
|
44
|
-
return "qoder"
|
|
45
|
-
return "unknown"
|
|
46
|
-
|
|
47
|
-
|
|
48
33
|
def emit(event_name: str, context: str) -> None:
|
|
49
34
|
print(
|
|
50
35
|
json.dumps(
|
|
@@ -69,7 +54,7 @@ def main() -> int:
|
|
|
69
54
|
if root is None:
|
|
70
55
|
return 0
|
|
71
56
|
|
|
72
|
-
agent =
|
|
57
|
+
agent = detect_runtime_agent()
|
|
73
58
|
session, session_path = ensure_hook_session(root, payload, agent)
|
|
74
59
|
if session and session.get("harness_disabled") is True:
|
|
75
60
|
return 0
|
|
@@ -4,7 +4,7 @@ import os
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
import sys
|
|
6
6
|
|
|
7
|
-
from easy_coding_state import ensure_hook_session
|
|
7
|
+
from easy_coding_state import detect_runtime_agent, ensure_hook_session
|
|
8
8
|
from easy_coding_status import build_status_context
|
|
9
9
|
|
|
10
10
|
|
|
@@ -31,21 +31,6 @@ def find_ec_root(start: Path) -> Path | None:
|
|
|
31
31
|
current = current.parent
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def detect_agent() -> str:
|
|
35
|
-
if os.environ.get("CLAUDE_PROJECT_DIR"):
|
|
36
|
-
return "claude-code"
|
|
37
|
-
if os.environ.get("QODER_PROJECT_DIR"):
|
|
38
|
-
return "qoder"
|
|
39
|
-
hook_path = Path(sys.argv[0]).as_posix()
|
|
40
|
-
if ".claude/" in hook_path:
|
|
41
|
-
return "claude-code"
|
|
42
|
-
if ".codex/" in hook_path:
|
|
43
|
-
return "codex"
|
|
44
|
-
if ".qoder/" in hook_path or ".qodercn/" in hook_path:
|
|
45
|
-
return "qoder"
|
|
46
|
-
return "unknown"
|
|
47
|
-
|
|
48
|
-
|
|
49
34
|
def emit(event_name: str, context: str) -> None:
|
|
50
35
|
print(
|
|
51
36
|
json.dumps(
|
|
@@ -71,7 +56,7 @@ def main() -> int:
|
|
|
71
56
|
return 0
|
|
72
57
|
|
|
73
58
|
event_name = payload.get("hook_event_name") or payload.get("hookEventName") or "UserPromptSubmit"
|
|
74
|
-
agent =
|
|
59
|
+
agent = detect_runtime_agent()
|
|
75
60
|
session, session_path = ensure_hook_session(root, payload, agent)
|
|
76
61
|
emit(event_name, build_status_context(root, session, agent, session_path))
|
|
77
62
|
return 0
|
|
@@ -4,7 +4,7 @@ import os
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
import sys
|
|
6
6
|
|
|
7
|
-
from easy_coding_state import ensure_hook_session
|
|
7
|
+
from easy_coding_state import detect_runtime_agent, ensure_hook_session
|
|
8
8
|
from easy_coding_status import build_status_context
|
|
9
9
|
|
|
10
10
|
|
|
@@ -31,21 +31,6 @@ def find_ec_root(start: Path) -> Path | None:
|
|
|
31
31
|
current = current.parent
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def detect_agent() -> str:
|
|
35
|
-
if os.environ.get("CLAUDE_PROJECT_DIR"):
|
|
36
|
-
return "claude-code"
|
|
37
|
-
if os.environ.get("QODER_PROJECT_DIR"):
|
|
38
|
-
return "qoder"
|
|
39
|
-
hook_path = Path(sys.argv[0]).as_posix()
|
|
40
|
-
if ".claude/" in hook_path:
|
|
41
|
-
return "claude-code"
|
|
42
|
-
if ".codex/" in hook_path:
|
|
43
|
-
return "codex"
|
|
44
|
-
if ".qoder/" in hook_path or ".qodercn/" in hook_path:
|
|
45
|
-
return "qoder"
|
|
46
|
-
return "unknown"
|
|
47
|
-
|
|
48
|
-
|
|
49
34
|
def emit(event_name: str, context: str) -> None:
|
|
50
35
|
print(
|
|
51
36
|
json.dumps(
|
|
@@ -70,7 +55,7 @@ def main() -> int:
|
|
|
70
55
|
if root is None:
|
|
71
56
|
return 0
|
|
72
57
|
|
|
73
|
-
agent =
|
|
58
|
+
agent = detect_runtime_agent()
|
|
74
59
|
event_name = payload.get("hook_event_name") or payload.get("hookEventName") or "SessionStart"
|
|
75
60
|
session, session_path = ensure_hook_session(root, payload, agent)
|
|
76
61
|
emit(event_name, build_status_context(root, session, agent, session_path))
|