jeo-code 0.12.22 → 0.12.24
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 +25 -1
- package/README.ja.md +17 -2
- package/README.ko.md +17 -2
- package/README.md +17 -2
- package/README.zh.md +17 -2
- package/package.json +1 -1
- package/src/agent/AGENTS.md +1 -1
- package/src/agent/config-schema.ts +1 -1
- package/src/agent/engine.ts +20 -3
- package/src/agent/hooks.ts +342 -32
- package/src/agent/loop-guards.ts +14 -2
- package/src/agent/sandbox/hands-source.text +528 -528
- package/src/agent/state.ts +27 -6
- package/src/commands/doctor.ts +34 -0
- package/src/commands/launch.ts +28 -3
- package/src/skills/catalog.ts +36 -11
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,28 @@ The README mirrors the latest 5 entries — regenerate with `bun run changelog:s
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.12.24] - 2026-11-06
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **`jeo doctor` hooks section** - a read-only `[hooks]` line (and `hooks` in `--json`) shows the global `hooks.enabled` state, the user registry and the project `.jeo/hooks.json` as written (valid and invalid entry counts, unreadable or `enabled: false` files) and what runs right now per event. Installers write these files and an unrunnable entry is dropped silently, so this is where to see why a hook is idle. It never affects `ready` or `--strict`, and prints nothing when no hook is configured and nothing is wrong.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- **A one-shot task now ranks the skills the system prompt advertises** - `jeo launch "<task>"` never passed the task to `skillsPromptSection`, so its relevance sort did not run and the ~40 visible skills (of 780+ installed) were whatever the directory listing returned first. The task now reaches it; matching is Unicode-aware (Korean, Japanese and Chinese words were dropped by the ASCII-only tokenizer); and a skill's description counts, at most +8, always below an exact name (+20) or name part (+10) match. Interactive sessions have no task at boot and are unchanged - per-prompt routing there stays with `pre-prompt` hooks.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- **The done gate names an unmeasured shell call** - when the workspace snapshot around a bash/computer call is inconclusive (entry cap, time cap, or an error such as an unreadable subdirectory), the call is still counted as a possible file change (unchanged, deliberate), but the engine now emits one `workspace snapshot inconclusive (<reason>)` notice per distinct reason and the unverified / stale-verification message appends it. Before, a read-only `pwd` in such a directory ended `UNVERIFIED` after three correction rounds with only "You modified files this turn".
|
|
22
|
+
|
|
23
|
+
## [0.12.23] - 2026-11-06
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- **`pre-prompt` and `turn-end` hook events** - `pre-prompt` runs once per typed prompt before the model sees it (Claude `UserPromptSubmit`): stdout text, or JSON `hookSpecificOutput.additionalContext` / `additionalContext` / `context`, is appended to the prompt message as a `[pre-prompt hook context]` block; the prompt card, history title and turn-end payload keep the user's own words. `turn-end` runs once per finished turn (Claude `Stop`) with `prompt`, `response` (also `last_assistant_message` / `assistant_output`), `done`, `steps` and `model`. Both are advisory (non-zero exit, timeout or abort prints a notice, never blocks; default timeouts 10 s / 30 s), skipped for `$skill` runs and messenger-literal sessions, and an `agy/` model skips `pre-prompt` (the official CLI owns its prompt) but runs `turn-end`.
|
|
27
|
+
- **User hook registry `~/.jeo/hooks.json`** - a hooks-only file (same shape as `.jeo/hooks.json`, follows `JEO_CONFIG_DIR`) for tools that install hooks, such as jeo-skills' OpenSpace prompt routing (`route-hooks.py install --host jeo`) and knowledge-pipeline ingesters, kept apart from `config.json` so credentials are never rewritten. It runs without `hooks.enabled: true`, ahead of configured hooks, and survives a project `.jeo/hooks.json` replacing the `config.json` list. Sandboxed sessions skip it. Entries with an unknown event, a missing `run` or a bad `timeoutMs` are dropped instead of failing the whole batch.
|
|
28
|
+
- **Structured `pre-tool` decisions** - a `pre-tool` hook that exits 0 may print `{"hookSpecificOutput":{"permissionDecision":"deny"|"ask","permissionDecisionReason":"…"}}` (also `decision: "block"` or `continue: false`) to veto the call; `ask` counts as deny because jeo has no hook approval prompt. `additionalContext` is appended to that tool's result. Plain stdout stays ignored.
|
|
29
|
+
- **Claude-protocol payload fields** - every host-session hook payload now carries `hook_event_name`, `session_id` and `cwd`, and tool events add `tool_name` / `tool_input`, beside the existing `event` / `tool` / `args`, so Claude Code and Codex hook scripts run unmodified. Sandboxed hooks keep the previous payload and never see the host `cwd` or session id.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
- **`hooks.enabled` is now an explicit kill switch** - `"hooks": { "enabled": false }` in `config.json` disables every hook, registry included. Left unset it still disables `config.json` and project hooks (a repo cannot enable its own hooks) but no longer silences the user registry. A project `.jeo/hooks.json` with `"enabled": false` opts that project out of everything.
|
|
33
|
+
|
|
12
34
|
## [0.12.22] - 2026-11-04
|
|
13
35
|
|
|
14
36
|
### Fixed
|
|
@@ -2735,7 +2757,9 @@ _Initial release._
|
|
|
2735
2757
|
### Added
|
|
2736
2758
|
- Initial jeo-code agent and CLI.
|
|
2737
2759
|
|
|
2738
|
-
[Unreleased]: https://github.com/akillness/jeo-code/compare/v0.12.
|
|
2760
|
+
[Unreleased]: https://github.com/akillness/jeo-code/compare/v0.12.24...HEAD
|
|
2761
|
+
[0.12.24]: https://github.com/akillness/jeo-code/releases/tag/v0.12.24
|
|
2762
|
+
[0.12.23]: https://github.com/akillness/jeo-code/releases/tag/v0.12.23
|
|
2739
2763
|
[0.12.22]: https://github.com/akillness/jeo-code/compare/v0.12.21...v0.12.22
|
|
2740
2764
|
[0.12.21]: https://github.com/akillness/jeo-code/releases/tag/v0.12.21
|
|
2741
2765
|
[0.12.20]: https://github.com/akillness/jeo-code/releases/tag/v0.12.20
|
package/README.ja.md
CHANGED
|
@@ -218,6 +218,21 @@ jeo ultragoal
|
|
|
218
218
|
|
|
219
219
|
非ゼロ終了したフックの出力はモデルが読むツール結果に付加され(バッチ内で重複排除)、フックが赤のまま `done` を呼ぶとフック名付きでプッシュバックされます。
|
|
220
220
|
|
|
221
|
+
### プロンプト・ターンフックとユーザーレジストリ
|
|
222
|
+
|
|
223
|
+
ツールループのイベントに加えて、ターンごとに 1 回発火するイベントが 2 つあります:
|
|
224
|
+
|
|
225
|
+
| イベント | タイミング | ペイロード(stdin JSON) | フック出力 |
|
|
226
|
+
| -------- | ---------- | ---------------------- | ---------- |
|
|
227
|
+
| `pre-prompt` | 入力したプロンプトをモデルが見る前 | `{event, hook_event_name: "UserPromptSubmit", session_id, cwd, prompt}` | stdout のテキスト、または JSON の `hookSpecificOutput.additionalContext` / `additionalContext` / `context` が `[pre-prompt hook context]` ブロックとしてプロンプトメッセージの末尾に付加されます(プロンプトカードと履歴タイトルにはユーザー自身の言葉だけが残ります) |
|
|
228
|
+
| `turn-end` | 最終返信の直後 | `{event, hook_event_name: "Stop", session_id, cwd, prompt, response, last_assistant_message, done, steps, model}` | 無視 |
|
|
229
|
+
|
|
230
|
+
どちらも助言(advisory)扱いです: 非ゼロ終了・タイムアウト・中断は通知を出すだけでプロンプトをブロックしません(既定タイムアウト 10 秒 / 30 秒)。`$skill` 実行とメッセンジャー literal セッションはスキップされ、`agy/` モデルでは公式 CLI がプロンプトを管理するため `pre-prompt` はスキップされ、`turn-end` は実行されます。`pre-tool` フックは exit 0 で `{"hookSpecificOutput":{"permissionDecision":"deny","permissionDecisionReason":"…"}}` を出力して拒否(`ask` は deny 扱い)したり、`additionalContext` を出力してそのツール結果に付加したりできます。通常の stdout は引き続き無視されます。すべてのペイロードに `hook_event_name`、`session_id`、`cwd`(ツールイベントは `tool_name`/`tool_input` も)が含まれるため、Claude Code / Codex のフックスクリプトをそのまま使えます — ホストセッションのみで、サンドボックスのフックは従来のペイロードを受け取ります。
|
|
231
|
+
|
|
232
|
+
**ユーザーレジストリ.** `~/.jeo/hooks.json`(`.jeo/hooks.json` と同形式、`JEO_CONFIG_DIR` に従う)は、jeo-skills のプロンプトルーティングやナレッジパイプラインのようにフックを代わりに設置するツール向けのフック専用ファイルで、認証情報が書き換えられないよう `config.json` から分離されています。優先順位: `config.json` の `"hooks": { "enabled": false }` はすべてを無効にし、プロジェクト `.jeo/hooks.json` の `"enabled": false` はそのプロジェクトだけを除外し、それ以外は `hooks.enabled: true` なしでレジストリが設定済みフックより先に実行されます。プロジェクト自身の `hooks` リストは `config.json` のリストだけを置き換えレジストリは置き換えず、プロジェクト / `config.json` のフックは従来どおり `hooks.enabled: true` のときだけ実行されます。サンドボックスセッションはレジストリをスキップします。
|
|
233
|
+
|
|
234
|
+
`jeo doctor` は現在実行されるフックを `[hooks]` 行(`--json` では `hooks`)で表示し、レジストリ/プロジェクトファイルが読めない、エントリが不正で無視された、グローバルスイッチやプロジェクトのオプトアウトがすべてを止めている場合に警告します。
|
|
235
|
+
|
|
221
236
|
## メモリフロー
|
|
222
237
|
|
|
223
238
|
`jeo` は `.jeo/memory/` 配下に **ローカルファースト・蒸留されたプロジェクトメモリ** を保持します(リモートバックエンドなし、ネイティブ依存ゼロ)。過去のセッションは [OKF](docs/okf_mem/) コンセプトバンドルへ蒸留され、次のセッションは関連性の高い予算内のスライスだけをシステムプロンプトへ再注入します — 指示ではなく DATA として堅牢化されます。`JEO_NO_MEMORY=1` ですべて無効化。
|
|
@@ -447,10 +462,10 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
|
|
|
447
462
|
|
|
448
463
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
449
464
|
- **[Unreleased]**
|
|
465
|
+
- **[0.12.24]** (2026-11-06)
|
|
466
|
+
- **[0.12.23]** (2026-11-06)
|
|
450
467
|
- **[0.12.22]** (2026-11-04)
|
|
451
468
|
- **[0.12.21]** (2026-11-04)
|
|
452
|
-
- **[0.12.20]** (2026-11-04)
|
|
453
|
-
- **[0.12.19]** (2026-11-04)
|
|
454
469
|
|
|
455
470
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
456
471
|
<!-- CHANGELOG:END -->
|
package/README.ko.md
CHANGED
|
@@ -272,6 +272,21 @@ jeo ultragoal # 5. 최종 증거 기반 종
|
|
|
272
272
|
|
|
273
273
|
비정상 종료한 훅의 출력은 모델이 읽는 도구 결과에 첨부되고(배치 내 중복 제거), 훅이 빨간 채로 `done`을 부르면 훅 이름과 함께 푸시백됩니다.
|
|
274
274
|
|
|
275
|
+
### 프롬프트·턴 훅과 사용자 레지스트리
|
|
276
|
+
|
|
277
|
+
도구 루프 이벤트 외에 턴마다 한 번씩 발생하는 이벤트가 둘 더 있습니다:
|
|
278
|
+
|
|
279
|
+
| 이벤트 | 시점 | 페이로드(stdin JSON) | 훅 출력 |
|
|
280
|
+
| ------ | ---- | -------------------- | ------- |
|
|
281
|
+
| `pre-prompt` | 입력한 프롬프트를 모델이 보기 전 | `{event, hook_event_name: "UserPromptSubmit", session_id, cwd, prompt}` | stdout 텍스트 또는 JSON `hookSpecificOutput.additionalContext` / `additionalContext` / `context`가 `[pre-prompt hook context]` 블록으로 프롬프트 메시지 뒤에 붙습니다(프롬프트 카드와 히스토리 제목에는 사용자가 쓴 말만 남음) |
|
|
282
|
+
| `turn-end` | 최종 답변 직후 | `{event, hook_event_name: "Stop", session_id, cwd, prompt, response, last_assistant_message, done, steps, model}` | 무시 |
|
|
283
|
+
|
|
284
|
+
둘 다 권고(advisory)용입니다: 비정상 종료·타임아웃·중단은 알림만 출력하고 프롬프트를 막지 않습니다(기본 타임아웃 10초 / 30초). `$skill` 실행과 메신저 literal 세션은 건너뛰며, `agy/` 모델은 공식 CLI가 프롬프트를 소유하므로 `pre-prompt`는 건너뛰고 `turn-end`는 실행됩니다. `pre-tool` 훅은 exit 0으로 `{"hookSpecificOutput":{"permissionDecision":"deny","permissionDecisionReason":"…"}}`를 출력해 거부(`ask`는 deny로 처리)하거나 `additionalContext`를 출력해 해당 도구 결과에 덧붙일 수 있으며, 일반 stdout은 계속 무시됩니다. 모든 페이로드에 `hook_event_name`, `session_id`, `cwd`(도구 이벤트는 `tool_name`/`tool_input` 추가)가 들어 있어 Claude Code / Codex 훅 스크립트를 수정 없이 쓸 수 있습니다 — 호스트 세션 한정이며 샌드박스 훅은 기존 페이로드를 받습니다.
|
|
285
|
+
|
|
286
|
+
**사용자 레지스트리.** `~/.jeo/hooks.json`(`.jeo/hooks.json`과 같은 형식, `JEO_CONFIG_DIR`을 따름)은 jeo-skills의 프롬프트 라우팅·지식 파이프라인처럼 훅을 대신 설치하는 도구를 위한 훅 전용 파일이며, 자격 증명이 다시 쓰이지 않도록 `config.json`과 분리되어 있습니다. 우선순위: `config.json`의 `"hooks": { "enabled": false }`는 모든 훅을 끄고, 프로젝트 `.jeo/hooks.json`의 `"enabled": false`는 그 프로젝트만 제외하며, 그 외에는 `hooks.enabled: true` 없이도 레지스트리가 설정된 훅보다 먼저 실행됩니다. 프로젝트 자체 `hooks` 목록은 `config.json` 목록만 대체하고 레지스트리는 대체하지 않으며, 프로젝트 / `config.json` 훅은 여전히 `hooks.enabled: true`일 때만 실행됩니다. 샌드박스 세션은 레지스트리를 건너뜁니다.
|
|
287
|
+
|
|
288
|
+
`jeo doctor` 는 지금 실행되는 훅을 `[hooks]` 줄(`--json` 에서는 `hooks`)로 보여 주고, 레지스트리/프로젝트 파일을 읽을 수 없거나 항목이 잘못되어 무시되었거나 전역 스위치·프로젝트 opt-out 이 모든 훅을 끈 경우 경고합니다.
|
|
289
|
+
|
|
275
290
|
## 메모리 흐름
|
|
276
291
|
|
|
277
292
|
`jeo`는 `.jeo/memory/` 아래에 **로컬 우선·증류된 프로젝트 메모리**를 둡니다(원격 백엔드 없음, 네이티브 의존성 0). 지난 세션은 [OKF](docs/okf_mem/) 개념 번들로 증류되고, 다음 세션은 관련성 높은 예산 한도 내 일부만 시스템 프롬프트로 다시 주입합니다 — 지시가 아닌 DATA로 강화 처리됩니다. `JEO_NO_MEMORY=1`로 전체 비활성화.
|
|
@@ -502,10 +517,10 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
|
|
|
502
517
|
|
|
503
518
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
504
519
|
- **[Unreleased]**
|
|
520
|
+
- **[0.12.24]** (2026-11-06)
|
|
521
|
+
- **[0.12.23]** (2026-11-06)
|
|
505
522
|
- **[0.12.22]** (2026-11-04)
|
|
506
523
|
- **[0.12.21]** (2026-11-04)
|
|
507
|
-
- **[0.12.20]** (2026-11-04)
|
|
508
|
-
- **[0.12.19]** (2026-11-04)
|
|
509
524
|
|
|
510
525
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
511
526
|
<!-- CHANGELOG:END -->
|
package/README.md
CHANGED
|
@@ -275,6 +275,21 @@ Enable hooks once globally (`"hooks": { "enabled": true }` in `~/.jeo/config.jso
|
|
|
275
275
|
|
|
276
276
|
Non-zero hook output is appended to the tool result the model reads (deduped per batch); a still-red hook triggers a `done` pushback naming the hook.
|
|
277
277
|
|
|
278
|
+
### Prompt and turn hooks, and the user registry
|
|
279
|
+
|
|
280
|
+
Two more events fire once per turn, beside the tool-loop events:
|
|
281
|
+
|
|
282
|
+
| Event | When | Payload (stdin JSON) | Hook output |
|
|
283
|
+
| ----- | ---- | -------------------- | ----------- |
|
|
284
|
+
| `pre-prompt` | before the model sees a typed prompt | `{event, hook_event_name: "UserPromptSubmit", session_id, cwd, prompt}` | stdout text, or JSON `hookSpecificOutput.additionalContext` / `additionalContext` / `context`, is appended to the prompt message as a `[pre-prompt hook context]` block (the prompt card and history title keep your own words) |
|
|
285
|
+
| `turn-end` | after the final reply | `{event, hook_event_name: "Stop", session_id, cwd, prompt, response, last_assistant_message, done, steps, model}` | ignored |
|
|
286
|
+
|
|
287
|
+
Both are advisory: a non-zero exit, timeout or abort prints a notice and never blocks the prompt (default timeouts 10 s / 30 s). `$skill` runs and messenger-literal sessions skip them; `pre-prompt` is skipped for an `agy/` model (the official CLI owns its prompt) while `turn-end` still runs. A `pre-tool` hook may also exit 0 and print `{"hookSpecificOutput":{"permissionDecision":"deny","permissionDecisionReason":"…"}}` to veto (`ask` counts as deny) or `additionalContext` to append to that tool's result; plain stdout stays ignored. Every payload also carries `hook_event_name`, `session_id` and `cwd` (tool events add `tool_name`/`tool_input`), so Claude Code / Codex hook scripts run unmodified — host sessions only; sandboxed hooks get the legacy payload.
|
|
288
|
+
|
|
289
|
+
**User registry.** `~/.jeo/hooks.json` (same shape as `.jeo/hooks.json`; follows `JEO_CONFIG_DIR`) is a hooks-only file for tools that install hooks for you, such as jeo-skills' prompt routing and knowledge pipeline, kept apart from `config.json` so credentials are never rewritten. Precedence: `"hooks": { "enabled": false }` in `config.json` disables everything; a project `.jeo/hooks.json` with `"enabled": false` opts that project out; otherwise the registry runs, with no `hooks.enabled: true` needed, ahead of configured hooks. A project's own `hooks` list still replaces the `config.json` list, never the registry, and project / `config.json` hooks still run only with `hooks.enabled: true`. Sandboxed sessions skip the registry.
|
|
290
|
+
|
|
291
|
+
`jeo doctor` prints a `[hooks]` line (and `hooks` in `--json`) with what runs right now, and warns when the registry or project file is unreadable, an entry was dropped as invalid, or a kill switch or project opt-out is silencing everything.
|
|
292
|
+
|
|
278
293
|
## Memory flow
|
|
279
294
|
|
|
280
295
|
`jeo` keeps a **local-first, distilled project memory** under `.jeo/memory/` (no remote backend, zero native deps). Past sessions are distilled into an [OKF](docs/okf_mem/) concept bundle, and the next session injects only the relevant, budget-bounded slice back into the system prompt — hardened as DATA, never as instructions. Disable everything with `JEO_NO_MEMORY=1`.
|
|
@@ -491,10 +506,10 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
|
|
|
491
506
|
|
|
492
507
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
493
508
|
- **[Unreleased]**
|
|
509
|
+
- **[0.12.24]** (2026-11-06)
|
|
510
|
+
- **[0.12.23]** (2026-11-06)
|
|
494
511
|
- **[0.12.22]** (2026-11-04)
|
|
495
512
|
- **[0.12.21]** (2026-11-04)
|
|
496
|
-
- **[0.12.20]** (2026-11-04)
|
|
497
|
-
- **[0.12.19]** (2026-11-04)
|
|
498
513
|
|
|
499
514
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
500
515
|
<!-- CHANGELOG:END -->
|
package/README.zh.md
CHANGED
|
@@ -218,6 +218,21 @@ jeo ultragoal
|
|
|
218
218
|
|
|
219
219
|
非零退出钩子的输出会附加到模型读取的工具结果中(批内去重);钩子未通过就调用 `done` 会收到带钩子名称的回推。
|
|
220
220
|
|
|
221
|
+
### 提示与回合钩子,以及用户注册表
|
|
222
|
+
|
|
223
|
+
除工具循环事件外,每个回合还会触发两个事件:
|
|
224
|
+
|
|
225
|
+
| 事件 | 时机 | 载荷(stdin JSON) | 钩子输出 |
|
|
226
|
+
| ---- | ---- | ---------------- | -------- |
|
|
227
|
+
| `pre-prompt` | 模型看到所输入提示之前 | `{event, hook_event_name: "UserPromptSubmit", session_id, cwd, prompt}` | stdout 文本,或 JSON 的 `hookSpecificOutput.additionalContext` / `additionalContext` / `context`,会以 `[pre-prompt hook context]` 块的形式追加到提示消息末尾(提示卡片和历史标题只保留你自己的话) |
|
|
228
|
+
| `turn-end` | 最终回复之后 | `{event, hook_event_name: "Stop", session_id, cwd, prompt, response, last_assistant_message, done, steps, model}` | 忽略 |
|
|
229
|
+
|
|
230
|
+
两者都是建议性的:非零退出、超时或中止只会打印通知,绝不阻止提示(默认超时 10 秒 / 30 秒)。`$skill` 运行和消息机器人 literal 会话会跳过它们;`agy/` 模型因官方 CLI 自行管理提示而跳过 `pre-prompt`,但仍运行 `turn-end`。`pre-tool` 钩子也可以 exit 0 并输出 `{"hookSpecificOutput":{"permissionDecision":"deny","permissionDecisionReason":"…"}}` 来否决(`ask` 视为 deny),或输出 `additionalContext` 追加到该工具的结果;普通 stdout 仍被忽略。所有载荷还带有 `hook_event_name`、`session_id` 和 `cwd`(工具事件另有 `tool_name`/`tool_input`),因此 Claude Code / Codex 的钩子脚本无需修改即可运行 —— 仅限主机会话;沙箱内的钩子收到旧版载荷。
|
|
231
|
+
|
|
232
|
+
**用户注册表。** `~/.jeo/hooks.json`(与 `.jeo/hooks.json` 格式相同,遵循 `JEO_CONFIG_DIR`)是供代你安装钩子的工具(如 jeo-skills 的提示路由和知识管道)使用的仅含钩子的文件,与 `config.json` 分离,因此凭据不会被重写。优先级:`config.json` 中的 `"hooks": { "enabled": false }` 会禁用一切;项目 `.jeo/hooks.json` 中的 `"enabled": false` 仅让该项目退出;否则注册表无需 `hooks.enabled: true` 即可运行,并排在已配置钩子之前。项目自己的 `hooks` 列表仍然只替换 `config.json` 的列表,绝不替换注册表,且项目 / `config.json` 钩子仍然只在 `hooks.enabled: true` 时运行。沙箱会话会跳过注册表。
|
|
233
|
+
|
|
234
|
+
`jeo doctor` 会用 `[hooks]` 行(`--json` 中为 `hooks`)显示当前实际运行的钩子,并在注册表/项目文件无法读取、条目因无效被忽略,或全局开关/项目退出让所有钩子失效时发出警告。
|
|
235
|
+
|
|
221
236
|
## 内存流程
|
|
222
237
|
|
|
223
238
|
`jeo` 在 `.jeo/memory/` 下保存 **本地优先、蒸馏后的项目内存**(无远程后端,零原生依赖)。过往会话被蒸馏为 [OKF](docs/okf_mem/) 概念包,下一次会话仅把相关的、受预算约束的切片重新注入系统提示 —— 作为 DATA 而非指令加固。用 `JEO_NO_MEMORY=1` 完全禁用。
|
|
@@ -445,10 +460,10 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
|
|
|
445
460
|
|
|
446
461
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
447
462
|
- **[Unreleased]**
|
|
463
|
+
- **[0.12.24]** (2026-11-06)
|
|
464
|
+
- **[0.12.23]** (2026-11-06)
|
|
448
465
|
- **[0.12.22]** (2026-11-04)
|
|
449
466
|
- **[0.12.21]** (2026-11-04)
|
|
450
|
-
- **[0.12.20]** (2026-11-04)
|
|
451
|
-
- **[0.12.19]** (2026-11-04)
|
|
452
467
|
|
|
453
468
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
454
469
|
<!-- CHANGELOG:END -->
|
package/package.json
CHANGED
package/src/agent/AGENTS.md
CHANGED
|
@@ -26,7 +26,7 @@ The core runtime loop, tool registry, session management, and state persistence
|
|
|
26
26
|
| `config-schema.ts` | Brief description of purpose |
|
|
27
27
|
| `context-files.ts` | Brief description of purpose |
|
|
28
28
|
| `engine.ts` | Brief description of purpose |
|
|
29
|
-
| `hooks.ts` |
|
|
29
|
+
| `hooks.ts` | Command-hook runtime: `loadHooks` (user registry `~/.jeo/hooks.json` ⊕ `config.json`/project `.jeo/hooks.json`, with the `hooks.enabled=false` kill switch and project `enabled:false` opt-out; the registry is skipped in sandboxed sessions), `runPreToolHooks` (veto by exit code or structured `permissionDecision`, `additionalContext` passthrough), `runPostTurnHooksForBatch`, `runPostImplementationHooks`, and the per-turn `runPrePromptHooks` (context injected by `commands/launch.ts` into the prompt message) / `runTurnEndHooks` (final reply to ingesters); payloads follow the Claude hook protocol on the host (`hook_event_name`, `session_id`, `cwd`, `tool_name`/`tool_input`) and stay legacy-shaped in a sandbox |
|
|
30
30
|
| `json.ts` | Brief description of purpose |
|
|
31
31
|
| `loop.ts` | The primary execution loop orchestrating model calls and tool execution |
|
|
32
32
|
| `loop-guards.ts` | Intermediate-judgment classification (gjc ultragoal-guard parity): named `GuardState` taxonomy, `GUARD_LIMITS` thresholds, and pure classifiers (`verifierCores`/`isVerificationSignal` — recognizes wrapped verifiers such as `cd x && bun test`, env/`timeout`/`uv run`/`rtk` prefixes and `2>&1`, never pipes or `;` — `repeatHint`, `classifyDoneGate` incl. `done_stale_verification`, and `formatRejectedCompletion`, which keeps the last candidate answer under an `UNVERIFIED` marker when corrections are exhausted) consumed by `engine.ts` |
|
|
@@ -45,7 +45,7 @@ const HookConfigSchema = z.object({
|
|
|
45
45
|
hooks: z
|
|
46
46
|
.array(
|
|
47
47
|
z.object({
|
|
48
|
-
event: z.enum(["pre-tool", "post-turn", "post-implementation"]),
|
|
48
|
+
event: z.enum(["pre-tool", "post-turn", "post-implementation", "pre-prompt", "turn-end"]),
|
|
49
49
|
match: z
|
|
50
50
|
.object({
|
|
51
51
|
tool: z.string().optional(),
|
package/src/agent/engine.ts
CHANGED
|
@@ -549,14 +549,28 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
|
|
|
549
549
|
// Turn-scoped memo: once the workspace is over the snapshot cap, gated calls count as
|
|
550
550
|
// mutations without re-walking a tree that is known to be too large.
|
|
551
551
|
let snapshotCapped = false;
|
|
552
|
+
// Why a bash/computer bracket could not be measured (cap or error), when one failed. The call still
|
|
553
|
+
// counts as a possible mutation, but the user and the done gate are told the real reason instead of
|
|
554
|
+
// only "files were modified" — a busy or oversized cwd (e.g. $HOME, /tmp) looks identical otherwise.
|
|
555
|
+
let unmeasuredSnapshot = null as string | null;
|
|
556
|
+
const reportedUnmeasured = new Set<string>();
|
|
557
|
+
const noteUnmeasured = (snap: WorkspaceSnapshot): WorkspaceSnapshot => {
|
|
558
|
+
if (snap.ok) return snap;
|
|
559
|
+
unmeasuredSnapshot = `${snap.reason}: ${snap.detail}`;
|
|
560
|
+
if (!reportedUnmeasured.has(unmeasuredSnapshot)) {
|
|
561
|
+
reportedUnmeasured.add(unmeasuredSnapshot);
|
|
562
|
+
ev.onNotice?.(`workspace snapshot inconclusive (${unmeasuredSnapshot}); shell calls are counted as possible file changes`);
|
|
563
|
+
}
|
|
564
|
+
return snap;
|
|
565
|
+
};
|
|
552
566
|
const takeSnapshot = async (racySinceMs?: number): Promise<WorkspaceSnapshot> => {
|
|
553
567
|
if (snapshotCapped) return { ok: false, reason: "cap", detail: "workspace exceeded the snapshot cap earlier this turn" };
|
|
554
568
|
try {
|
|
555
569
|
const snap = await snapshotter(cwd, racySinceMs);
|
|
556
570
|
if (!snap.ok && snap.reason === "cap") snapshotCapped = true;
|
|
557
|
-
return snap;
|
|
571
|
+
return noteUnmeasured(snap);
|
|
558
572
|
} catch (err) {
|
|
559
|
-
return { ok: false, reason: "error", detail: err instanceof Error ? err.message : String(err) };
|
|
573
|
+
return noteUnmeasured({ ok: false, reason: "error", detail: err instanceof Error ? err.message : String(err) });
|
|
560
574
|
}
|
|
561
575
|
};
|
|
562
576
|
// bash/computer calls whose mutation status is decided by a workspace diff. Recognized
|
|
@@ -1205,7 +1219,7 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
|
|
|
1205
1219
|
};
|
|
1206
1220
|
const candidate = stripLeakedReasoningTags(String(toolCalls[0].arguments?.reason ?? ""));
|
|
1207
1221
|
if (candidate.trim()) lastCompletionCandidate = candidate;
|
|
1208
|
-
const doneGate = classifyDoneGate({ ...evidence, pendingHookFailure });
|
|
1222
|
+
const doneGate = classifyDoneGate({ ...evidence, pendingHookFailure, unmeasured: unmeasuredSnapshot ?? undefined });
|
|
1209
1223
|
const nudge = await ev.onBeforeDone?.((toolCalls[0].arguments?.reason as string) ?? "", evidence);
|
|
1210
1224
|
const rejection = [doneGate.block ? doneGate.message : "", nudge].filter(Boolean).join("\n\n");
|
|
1211
1225
|
if (rejection) {
|
|
@@ -1411,6 +1425,9 @@ export async function runAgentLoop(history: Message[], opts: AgentLoopOptions):
|
|
|
1411
1425
|
// an unchanged recently-modified file is hashed on both sides or neither.
|
|
1412
1426
|
mutationMayHaveOccurred = before.ok ? workspaceSnapshotChanged(before, await takeSnapshot(before.startedAtMs)) : true;
|
|
1413
1427
|
}
|
|
1428
|
+
// A pre-tool hook's structured `additionalContext` rides along with the result
|
|
1429
|
+
// so the model sees it next to the output it concerns.
|
|
1430
|
+
if (preHookResult.context) output = `${output}\n\n[pre-tool hook context]\n${preHookResult.context}`;
|
|
1414
1431
|
}
|
|
1415
1432
|
}
|
|
1416
1433
|
}
|