jeo-code 0.12.13 → 0.12.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +55 -1
  2. package/README.ja.md +33 -9
  3. package/README.ko.md +33 -9
  4. package/README.md +33 -9
  5. package/README.zh.md +33 -9
  6. package/package.json +4 -2
  7. package/src/agent/browser-tool.ts +9 -1
  8. package/src/agent/config-schema.ts +8 -0
  9. package/src/agent/context-files.ts +19 -0
  10. package/src/agent/engine.ts +25 -6
  11. package/src/agent/eval-tool.ts +6 -0
  12. package/src/agent/goal-verifier.ts +30 -6
  13. package/src/agent/hooks.ts +52 -4
  14. package/src/agent/job-registry.ts +110 -18
  15. package/src/agent/job-tool.ts +43 -9
  16. package/src/agent/loop-guards.ts +17 -2
  17. package/src/agent/memory.ts +151 -112
  18. package/src/agent/monitor-registry.ts +142 -30
  19. package/src/agent/monitor-tool.ts +34 -6
  20. package/src/agent/notify/discord-daemon.ts +135 -42
  21. package/src/agent/notify/session-endpoint.ts +27 -5
  22. package/src/agent/sandbox/assets.d.ts +4 -0
  23. package/src/agent/sandbox/authority.ts +358 -0
  24. package/src/agent/sandbox/bundle.ts +40 -0
  25. package/src/agent/sandbox/control.ts +49 -0
  26. package/src/agent/sandbox/docker.ts +293 -0
  27. package/src/agent/sandbox/hands-source.text +14548 -0
  28. package/src/agent/sandbox/hands-worker.ts +168 -0
  29. package/src/agent/sandbox/policy.ts +172 -0
  30. package/src/agent/sandbox/rpc.ts +90 -0
  31. package/src/agent/sandbox/supervisor.ts +210 -0
  32. package/src/agent/sandbox/types.ts +77 -0
  33. package/src/agent/shell-lease.ts +190 -0
  34. package/src/agent/state.ts +167 -0
  35. package/src/agent/subagents.ts +4 -3
  36. package/src/agent/task-tool.ts +8 -0
  37. package/src/agent/todo-tool.ts +30 -1
  38. package/src/agent/tool-schemas.ts +13 -0
  39. package/src/ai/model-discovery.ts +12 -0
  40. package/src/ai/providers/anthropic.ts +27 -2
  41. package/src/ai/providers/antigravity.ts +10 -2
  42. package/src/ai/providers/gemini.ts +20 -16
  43. package/src/ai/providers/ollama.ts +14 -0
  44. package/src/ai/providers/openai-responses.ts +22 -5
  45. package/src/ai/providers/openai.ts +23 -40
  46. package/src/cli/runner.ts +3 -0
  47. package/src/commands/approve.ts +37 -13
  48. package/src/commands/launch/flags.ts +2 -1
  49. package/src/commands/launch/jobs-slash.ts +1 -1
  50. package/src/commands/launch.ts +275 -35
  51. package/src/commands/messenger.ts +107 -39
  52. package/src/commands/notify.ts +2 -2
  53. package/src/commands/team.ts +44 -12
  54. package/src/commands/ultragoal.ts +6 -16
  55. package/src/messenger/activity.ts +2 -1
  56. package/src/messenger/config.ts +78 -10
  57. package/src/messenger/discord-setup.ts +216 -34
  58. package/src/messenger/discord.ts +37 -43
  59. package/src/messenger/gateway.ts +85 -37
  60. package/src/messenger/model-selection.ts +101 -0
  61. package/src/messenger/session-pool.ts +189 -35
  62. package/src/messenger/types.ts +5 -2
  63. package/src/skills/catalog.ts +207 -0
  64. package/src/util/retry.ts +12 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  The README mirrors the latest 5 entries — regenerate with `bun run changelog:sync`.
9
9
 
10
+ ## [Unreleased]
11
+
12
+ ## [0.12.16] - 2026-11-04
13
+
14
+ ### Upgrade
15
+ - **Discord messenger:** Existing configurations whose `cwd` overlaps the messenger profile directory now fail closed. Set `cwd` to a directory disjoint from the profile before restarting; re-running setup preserves an explicitly configured `cwd` and does not relocate files. Changing `cwd` starts new sessions for existing threads.
16
+
17
+ ### Fixed
18
+ - **Discord concurrency** — independent threads can progress concurrently, and same-thread follow-ups wait without occupying a worker slot; rejected full-queue requests receive a failure status without creating a thread.
19
+ - **Discord sandbox setup** — create fresh workspaces outside private configuration paths, validate workspace admission before replacing settings, include `sandbox_request` in the default tool list, and keep messenger profile files outside workspace and permission grants.
20
+ - **Antigravity model responses** — retry malformed function-call responses within the existing retry limit without executing incomplete tool calls or replaying already-streamed output.
21
+ - **Windows sandbox** — preserve owner-only ACL protection and serialize authority directory lifecycle.
22
+
23
+ ## [0.12.15] - 2026-11-03
24
+
25
+ The GitHub-only 0.12.14 release was not published to npm; its changes (listed under [0.12.14]) are included in this release.
26
+
27
+ ### Changed
28
+ - **Discord session continuity** — one persistent session per thread regardless of sender/message, FIFO within a thread and concurrency across sessions, without creating per-session worktrees. Existing terminal Discord control also requires isolation and preserves the local input draft.
29
+ - **Explicit local Docker isolation** — require `sandbox: {image, limits: {memoryBytes, pids, cpus}}`, an already-local Linux Bun image resolved to its immutable ID, network `none`, and workspace/approved mounts. No pull, dependency installation, remote Docker endpoint, or host fallback; macOS/Windows still execute a Linux guest shell. Parent-human `/sandbox status|approve <requestId>|deny <requestId>|revoke` controls require the authenticated actor's own session/current request revision; `sandbox_request {host, mode}` only requests access and cannot self-approve.
30
+ - **Private state and migration** — reject `independentSessions` except for its explicit removal by `jeo messenger setup`; reject legacy workspace transcript and raw-history storage with move guidance, never automatic copy/deletion. Full histories remain parent-only; derived learning remains shared. Reuse existing ECC skills/workflow installation without a global reinstall; snapshot protected global skills and read workspace/granted documents through the guest.
31
+
32
+ ### Verified (local pre-release evidence)
33
+ - Full repository regression: `bun test` with `JEO_TEST_TERMINAL_FIFO=1` and inherited provider credentials/base-URL overrides cleared for the test process passed 4,437 tests, with 4 existing conditional skips and 0 failures (4,441 total across 386 files).
34
+ - `bun run build` and `bun run typecheck` passed. `bun run verify:sandbox` passed **20 macOS arm64 host / Linux x64 guest contracts**, confirmed container removal, and removed its fixtures. The verifier requires an already-local image and Docker; it does not install prerequisites.
35
+ - Copied `dist/jeo` standalone qualification: `bun test test/sandbox-terminal-fifo.test.ts test/sandbox-requester-reachability.test.ts test/sandbox-metadata-privacy.test.ts test/sandbox-worker-ipc-shutdown.test.ts` with `JEO_TEST_TERMINAL_FIFO=1` and `JEO_TEST_CLI_BINARY` set to the absolute `dist/jeo` path passed 16 tests, 0 failed, 151 assertions. It verified rejected private-IPC bad frames; active guest shutdown with no signals, a matching stderr ACK, exit 143 and container removal; natural EOF exit 0; and immediate startup shutdown before any model call.
36
+ - Protected-worker shutdown sends a private Bun IPC exact-nonce frame instead of SIGTERM. Parent cleanup ACK plus actual worker exit remains the gate; a missing or throwing `send` quarantines the worker with no signal fallback (no SIGTERM/SIGKILL), and signal-only termination is not relied on for Windows. Focused pool tests passed 75, 0 failed.
37
+ - Current source wrappers preserve the teardown cleanup owner, bounded shutdown waits, human per-session grants, one persistent thread/FIFO, and the shared workspace. Legacy workspace history in `.jeo/memory/raw`, `.jeo/sessions`, or `.jeo/messenger-sessions` fails closed until deliberately relocated; no automatic copy or deletion occurs.
38
+
39
+ ### Verified (Linux x64 GitHub Actions dry-run, completed)
40
+ - Ubuntu 24.04 Linux x64 [run 41696052194](https://github.com/akillness/jeo-code/actions/runs/41696052194) at source `d44ef5a1b1ec5b44e4607dfc20976f73b3b92265` is now COMPLETED SUCCESS. It is a dry-run, recorded separately from the local macOS arm64 checks above.
41
+ - Primary `bun test`: 4422 pass, 19 skip, 0 fail, 4441 total across 386 files. The separate mandatory browser step passed 15, 0 failed; those 15 browser cases were skipped in the primary process, leaving 4 existing conditional skips.
42
+ - The real native Ubuntu 24.04 / Linux x64 sandbox verifier passed 20 contracts. Collision evidence: `hostSentinelsUnchanged=true`, `unwritableNamespaceWriteDenied=true`, `tmpNamespaceCollision=true`, `collisionWriteWasGuestPrivate=true`.
43
+ - `pack:check` passed 329 files; the packed CLI runner and launch booted; the npm dry-run passed. No real npm publish occurred, and the binary job was intentionally skipped in the dry-run.
44
+ - The Linux `/tmp` namespace-collision contract proves together that host sentinels are preserved, the session-private `/tmp` shadows the colliding path inside the guest, and a write to the unwritable namespace is denied. This tightens the isolation test; it does not imply a production escape, and no assertion was weakened.
45
+
46
+ ### Limitations
47
+ - The local macOS checks above do not qualify native Linux or Windows hosts. The Linux x64 GitHub Actions dry-run above completed successfully but is not an all-OS qualification and is not a published npm release. Native Windows is not qualified, and live Discord is not qualified. A Linux guest on macOS does not qualify native hosts.
48
+ - Worker/container cleanup must be confirmed; errors or unconfirmed removal must not be reported as success.
49
+ - Tagged release CI results are recorded separately from this local pre-release evidence.
50
+
51
+ ### Fixed
52
+ - **Release verification** — make subprocess tests reliable on Linux and gate release binaries on successful npm publishing.
53
+ - **Background process lifecycle** — `job`/`monitor` shells now run under a group-pinning lease holder: descendants that keep stdout/stderr open after the shell exits are killed through the still-pinned process group, a one-shot monitor stopped after its first line reports `killed`, and a lost lease or a pipe holder that left the group is reported as unresolved instead of success (no signal is ever sent to a released or reaped process group).
54
+
55
+ ## [0.12.14] - 2026-11-03
56
+
57
+ ### Fixed
58
+ - **Discord on Windows** — improve setup cancellation, launcher handling, and progress privacy; explain notification daemon state.
59
+ - **Discord model selection** — choose from the authenticated provider's live models, preserve session continuity across model changes, and reject unverifiable choices.
60
+ - **Completion evidence** — expose background process failures and require fresh verification after unobserved jobs and monitors settle.
61
+ - **Provider responses** — reject truncated or explicitly incomplete model responses instead of reporting completion.
62
+
10
63
  ## [0.12.13] - 2026-11-03
11
64
 
12
65
  ### Fixed
@@ -2629,7 +2682,8 @@ _Initial release._
2629
2682
  ### Added
2630
2683
  - Initial jeo-code agent and CLI.
2631
2684
 
2632
- [Unreleased]: https://github.com/akillness/jeo-code/compare/v0.12.6...HEAD
2685
+ [Unreleased]: https://github.com/akillness/jeo-code/compare/v0.12.16...HEAD
2686
+ [0.12.16]: https://github.com/akillness/jeo-code/releases/tag/v0.12.16
2633
2687
  [0.12.6]: https://github.com/akillness/jeo-code/releases/tag/v0.12.6
2634
2688
  [0.11.4]: https://github.com/akillness/jeo-code/releases/tag/v0.11.4
2635
2689
  [0.11.3]: https://github.com/akillness/jeo-code/releases/tag/v0.11.3
package/README.ja.md CHANGED
@@ -13,6 +13,7 @@
13
13
  <a href="https://github.com/akillness/jeo-code"><img alt="license" src="https://img.shields.io/badge/license-MIT-green?style=flat-square"></a>
14
14
  <img alt="runtime" src="https://img.shields.io/badge/runtime-Bun%20%E2%89%A5%201.3.14-f9f1e1?style=flat-square&logo=bun&logoColor=black">
15
15
  <img alt="zero native deps" src="https://img.shields.io/badge/native%20deps-0-blue?style=flat-square">
16
+ <a href="https://buymeacoffee.com/akillness3q"><img alt="Buy Me a Coffeeで支援する" src="https://img.shields.io/badge/Buy%20Me%20a%20Coffee-support-AC6415?style=flat-square&logo=buy-me-a-coffee"></a>
16
17
  </p>
17
18
 
18
19
  <p align="center">
@@ -32,7 +33,7 @@
32
33
 
33
34
  - [双方向メッセンジャーゲートウェイ (Discord, Telegram, Slack)](docs/integrations/messenger.md)
34
35
 
35
- **メッセンジャーゲートウェイ** — `jeo messenger` は通知デーモンとは別に、許可された Discord・Telegram・Slack の会話を扱います。ローカルツールは既定で無効です。`computer` を明示的に許可すると共有ホストのデスクトップに、`bash` を許可するとシェルにアクセスでき、サンドボックスではありません。[ガイド](docs/integrations/messenger.md)を参照してください。
36
+ **メッセンジャーゲートウェイ** — `jeo messenger` は通知デーモンとは別に、許可された Discord・Telegram・Slack の会話を扱います。Discord には明示的なローカル Docker サンドボックスが必須で、設定されたツールはホストのデスクトップ/シェルではなく Linux ゲストで実行します。[ガイド](docs/integrations/messenger.md)と以下の現行制約を参照してください。
36
37
 
37
38
  📖 **[使い方ガイド](docs/usage-guide.md)** — インストール、TUI操作(↑履歴、Ctrl+O、`!`シェル)、スラッシュコマンド、`/resume`、スペックファーストワークフローをデモ動画付きで解説。
38
39
 
@@ -59,7 +60,7 @@
59
60
  - **プラン critic ゲート** — プランは architect/critic の工程を省略できません。`PlanSchema` は未検証の変更で終わるプランを拒否し(変更より前の verifier は対象にならない)、`ralplan` の作成時と `team`/`approve` の実行時に適用します。critic 評決には観測されたリポジトリ読み取り・検索の証拠が必要ですが、これはレビュー可能性を支えるものであり、ワークスペースの独立検証ではありません。
60
61
  - **セーフティバウンダリの自動モデルフォールバック** — 未分類のセーフティ拒否(本物のコンテンツポリシー違反ではなく、分類器の誤検知の可能性)は、同じモデルで永遠に譲歩するのではなく、実際に別プロバイダのモデルへ切り替わるようになりました — 既存のレート制限時の高速フォールバックと同じパターンです。`Refusal (<category>)` 形式の確定的な拒否は影響を受けず、引き続きフォールバックなしでハードフェイルします。
61
62
  - **メモリ: 勝ち取った信頼** — コンセプトの検証日付は、あらゆる書き込みではなく、蒸留パスが明示的に検証済みとマークした場合にのみ記録されるようになりました。`isConceptStale` は受動的なタイムスタンプを信頼する代わりに、未検証(または30日超の陳腐化)のコンセプトを再検証が必要なものとして扱います。
62
- - **ダイナミックワークフロー(`eval` ツール)** — サブエージェントディスパッチの周りに実際の JS 制御フローを書けます: `task(role, taskText, context?)`、`parallel(thunks)`、`pipeline(items, ...stages)`、`log(message)` を組み合わせ、`task` の単一ステージ `tasks[]` バッチでは表現できない逐次/分岐オーケストレーションを構成します。隔離された Worker スレッドで、真のプリエンプティブなタイムアウト(同一プロセス内のレースではなく `worker.terminate()`)を伴って実行され — `bash` と同じフルプロセスの信頼度で、サンドボックスのふりはせず、同じ interview 変更ロックでゲートされます。
63
+ - **ダイナミックワークフロー(`eval` ツール)** — `task(role, taskText, context?)`、`parallel(thunks)`、`pipeline(items, ...stages)`、`log(message)` で JS フローを構成します。セッションサンドボックスなしでは Worker スレッドのタイムアウトはセキュリティ境界ではありません。サンドボックスでは eval は Linux ゲストで実行され、task/parallel/pipeline の委任ヘルパーは非対応です。委任には親の `task` ツールを使ってください。
63
64
  - **壊れた出力パイプで静かに終了** — 早期に読み取りを止めるコマンドへパイプした場合(`jeo --help | head`、消えたリモートピア)、生の `EPIPE` スタックダンプはもう発生しません — シェルが SIGPIPE で終了したパイプラインプロデューサに対して報告するのと同じコード(141)で静かに終了します。本物のクラッシュには影響せず、引き続き明確に表面化します。
64
65
  - **macOS の低いファイルディスクリプタ上限警告** — 低い `ulimit -n`(BSD のデフォルト 256/1024)は、ファイル監視、ブラウザツール、広範なリポジトリスキャンで不明瞭な `EMFILE` 失敗を招くおそれがあります — jeo は起動時に一度(stderr のみ、パイプされる `-p` 出力を汚染しません)、具体的な `ulimit`/`launchctl` ガイダンスとともに警告するようになりました。`JEO_SKIP_NOFILE_CHECK=1` でオプトアウトできます。
65
66
 
@@ -238,6 +239,29 @@ jeo ultragoal
238
239
 
239
240
  オプトイン: 共有デーモン 1 つがマシン上の稼働中の `jeo` セッションをすべて監視し、**サブエージェントの状態遷移**(開始 → 完了/失敗/キャンセル)ごとにメッセージを送ります。加えてセッション識別ヘッダー、ターンの開始/終了サマリー、確定したターンのテキストも送りますが、「まだ実行中」という繰り返しの ping は決して送りません。allowlist に登録された人間はチャットから一覧・送信・ステア・キャンセルができ、すべての制御はローカルセッションへ中継されて **確認応答済み(受理であって完了ではない)** または **未確認応答** として報告されます — デーモンがコマンドの実行完了を主張することはありません。
240
241
 
242
+ ### Discord セッションとサンドボックス境界
243
+
244
+ Discord の1スレッドは送信者・メッセージに関係なく1つの永続セッションを保持します。同じスレッドは FIFO、別セッションは並行実行できます。選択したワークスペースを共有し、セッション別の Git worktree は作らないため、ファイル変更は競合し得ます。
245
+
246
+ メッセンジャー設定に `sandbox` を指定します。`~/.jeo/config.json` にも同じ形式で設定すると、ターミナルセッションを隔離します:
247
+
248
+ ```json
249
+ {
250
+ "sandbox": {
251
+ "image": "oven/bun:1.3.14",
252
+ "limits": { "memoryBytes": 268435456, "pids": 64, "cpus": 1 }
253
+ }
254
+ }
255
+ ```
256
+
257
+ これは完全なメッセンジャープロファイルではなくサンドボックス部分です。既存の `platforms` 許可リストと認証情報の環境変数名は保持してください。Linux Bun イメージはローカル Docker デーモンに既に存在する必要があります。不変のイメージ ID を確認して `--pull=never` で起動し、pull・依存関係インストール・リモート Docker・ホスト実行へのフォールバックは行いません。Mac/Windows でもホストシェルではなく **Linux ゲストシェル**です。ネットワークは `none`、`/workspace` は読み書き可能、追加パスにはセッション別 `ro`/`rw` 許可が必要です。ホーム全体・非公開制御パス・制御ソケットは許可できません。ホスト運用者と Docker デーモンは信頼対象です。
258
+
259
+ アクセスを決定できるのは親に直接提出された人間の `/sandbox status`、`/sandbox approve <requestId>`、`/sandbox deny <requestId>`、`/sandbox revoke` のみです。モデルの `sandbox_request {host, mode:'ro'|'rw'}` は要求専用で、自己承認できません。認証済み主体自身のセッションと現在の要求 revision が必要で、古い要求や別セッションの要求は拒否します。既存ターミナルの Discord 制御にもサンドボックスが必要です。ローカル `/discord control on --allow-full-control` で有効、`/discord control off` で無効にし、リモートターンはローカル入力の下書きを保持します。
260
+
261
+ `independentSessions` は実行時に拒否し、明示的な `jeo messenger setup` 移行で削除します。`sessionDir` はワークスペースと重ならない外部の非公開パスに置いてください。旧 `.jeo/sessions`・`.jeo/messenger-sessions`・`.jeo/memory/raw` の記録がワークスペースにある場合は移動案内とともに起動を拒否します。再起動前に意図的に移動し、自動コピー・削除は行いません。完全な会話履歴は親専用ストアに保持し、蒸留済みプロジェクト学習は共有します。既存 ECC スキル/ワークフロー導入を再利用し、グローバル再インストールは行っていません。保護されたグローバルスキルは実行前にスナップショット化し、ワークスペース/許可済み文書はゲスト経由で読みます。
262
+
263
+ **検証範囲(2026-11-03):** 実際の macOS 実 Docker 検証(`bun run verify:sandbox`)が **20動作契約**に成功し、ソースのスタンドアロンビルドも成功しました。コピーしたスタンドアロンバイナリは、実 PTY FIFO、親の `sandbox_request` と実 Linux ゲストツール、全履歴の非公開、親が生成した stderr シャットダウン nonce と一致する ACK、実ワーカー終了(143)、Docker コンテナ不在を対象とする限定的な検証に成功しました。現在のソースラッパーは teardown のクリーンアップ所有者、有界なシャットダウン待機、人間によるセッション別許可、永続スレッド/FIFO 1 つ、共有ワークスペースを維持します。これは全スイート・クロスプラットフォーム・本番の検証ではありません。全体の `bun test` はタイムアウトし**成功ではなく**、一般の集中スイートには古いフィクスチャの失敗が残り修正中です。ネイティブ Linux・Windows ホストの検証は利用できず、実 Discord 送信も行っていません。macOS 上の Linux ゲストはホスト認定ではありません。`bun run build:hands` はワーカーをビルドし、`bun run verify:sandbox` は前提条件をインストールせず実 Docker を検査します。ワーカー/コンテナの除去確認が必要で、エラーや未確認の除去を成功と報告しません。
264
+
241
265
  プラットフォーム別詳細ガイド:
242
266
  - 📖 [チャネル連携概要 & アーキテクチャ](docs/integrations/README.md) — 機能マトリクスと全体構成
243
267
  - 📱 [Telegram 連携詳細ガイド](docs/integrations/telegram.md) — 内蔵バックグラウンドデーモン、BotFather ペアリング、フォーラムトピック、`/steer` コマンド
@@ -267,7 +291,7 @@ jeo daemon start|stop|status|reload # 共有デーモンにプロバイダ選
267
291
  - **セットアップ**は公式 API(Telegram `getMe`、Discord のボット + チャネル検索、Slack `auth.test`/`bots.info`/`conversations.info` と Socket Mode URL)でボット ID と宛先アクセスを検証し、結果を `~/.jeo/config.json` の `notifications.<provider>` 配下に **平文** で保存します — このファイルは非公開に保ってください。メッセージは送信せず、ソケットも開きません。
268
292
  - **`initialized`**(`jeo daemon status`)はデーモンプロセスがロックを所有しトランスポートを起動したことを意味し、Telegram ポール、Discord Gateway ハンドシェイク、Slack Socket Mode 接続が成功した証明ではありません。それには `notify health` / `notify test` を使ってください。
269
293
  - **トークンや宛先を変更する**と、Discord と Slack では allowlist が(`--allowed-user-ids` を再度渡す必要があります)、Telegram ではセッション単位トピックのマップがリセットされます。まったく同じ認証情報と宛先でセットアップを再実行した場合は保持されます。
270
- - **リモートコマンドはリテラルなテキスト**(`/subagents` など)で、設定済みチャット/チャネルと allowlist に対して認可されます。Slack ではさらに同じ名前を登録済みスラッシュコマンドとしても受け付けます。allowlist に載ったオペレータは事実上ローカルエージェントの能力を握るため、この一覧は信頼境界として扱ってください。
294
+ - **リモートコマンドはリテラルテキスト**(`/subagents` など)で、設定済みチャット/チャネルと allowlist に対して認可されます。Slack は同名の登録済みスラッシュコマンドも受け付けます。Discord ターミナル制御は既定で無効です。サンドボックスセッションでローカル `/discord control on --allow-full-control` により有効化し、`/discord control off` で解除します。上記の隔離・人間の承認境界に従い、認証情報をチャットに書かないでください。
271
295
  - **再試行はプロバイダごとに上限あり**: Telegram は 429 の `retry_after` クールダウン(1 秒〜1 時間にクランプ)を尊重し、ポールのバックオフは別途管理します。Discord と Slack は 429 時にサーバーの `Retry-After` を使って最大 3 回まで再試行し、結果が不明な POST を再送することはありません(プラットフォーム側がすでに受理している可能性があるため)。
272
296
  - **オーファン復旧**: デーモンがクラッシュするとロックは `stale` になり、次の `jeo daemon start` が回収します。`jeo daemon stop` は、ホストがプロセス起動時刻を報告できる場合は再利用された PID へのシグナル送信を拒否し、報告できない場合はその旨を伝えます。
273
297
  - **これまでの検証状況**: Telegram はエンドツーエンドで検証済み(実送信、ポーリング、クリーンシャットダウン)。Discord と Slack は同じデーモンの背後に実装され、オフラインのトランスポート/配線テストとコード・セキュリティ・型レビューを経ていますが、実際のボット/アプリ認証情報を使ったライブ接続はまだ行っていません。
@@ -322,7 +346,7 @@ jeo routine init --trigger schedule --cron "0 7 * * *" --prompt "Re-run the eval
322
346
  jeo routine init --trigger issues --prompt "Triage this issue" --name "issue-triage"
323
347
  ```
324
348
 
325
- GitHub Actions ワークフロー(`.github/workflows/<name>.yml`)を生成し、jeo をインストールして `schedule` / `issues` / `pull_request` でヘッドレス実行します(`jeo "<prompt>" -p`) — 手動テスト実行のために常に `workflow_dispatch` と組み合わせます — GitHub 自身のホステッドランナー上で。これが jeo の「ラップトップなしで動く」ストーリーです: jeo 自体の内部にインプロセスのスケジューラも、Webhook リスナーも、コード実行サンドボックスもありません — GitHub のインフラがトリガーを担い、jeo は既存のヘッドレスモードを実行するだけです。デフォルトでは変更があれば PR を開きます(`peter-evans/create-pull-request`、diff が空なら安全な no-op); `--no-pr` はトリガーしたブランチへ直接コミットします。`--dry-run` は YAML を書き込まずに出力するだけです; 同じ `--out` パスで `jeo routine init` を再実行すると `--force` なしでは上書きを拒否します。ワークフローの最初の実際の実行前に、リポジトリシークレット `ANTHROPIC_API_KEY`(または `--api-key-env <VAR>`)を設定してください。
349
+ GitHub Actions ワークフロー(`.github/workflows/<name>.yml`)を生成し、GitHub ホステッドランナーで jeo をインストールして `schedule` / `issues` / `pull_request` によりヘッドレス実行します(`jeo "<prompt>" -p`)。手動実行用 `workflow_dispatch` も含みます。トリガーは GitHub が担い、ルーティンはプロセス内スケジューラや Webhook リスナーを追加しません。セッションサンドボックスは上記ローカル前提条件を伴う別の明示的設定で、ルーティン生成がインストールするものではありません。既定は変更の PR 作成(`peter-evans/create-pull-request`、空 diff は no-op)、`--no-pr` はトリガーブランチへコミットします。`--dry-run` は YAML 出力のみ、既存 `--out` の上書きには `--force` が必要です。最初の実行前にリポジトリシークレット `ANTHROPIC_API_KEY`(または `--api-key-env <VAR>`)を設定してください。
326
350
 
327
351
  ## ローカルモデル
328
352
 
@@ -335,7 +359,7 @@ jeo doctor && jeo
335
359
  ## 設定
336
360
 
337
361
  - グローバル設定: `~/.jeo/config.json`(モデル選択は MRU 永続)
338
- - プロジェクト状態/セッション: `<project>/.jeo/`
362
+ - プロジェクト状態: `<project>/.jeo/`。サンドボックスの会話記録はワークスペース外に保持します(上記 Discord セッション境界を参照)。
339
363
 
340
364
  ```bash
341
365
  ANTHROPIC_API_KEY=... OPENAI_API_KEY=... GEMINI_API_KEY=...
@@ -422,11 +446,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
422
446
  ## 変更履歴 (Changelog)
423
447
 
424
448
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
449
+ - **[Unreleased]**
450
+ - **[0.12.16]** (2026-11-04)
451
+ - **[0.12.15]** (2026-11-03)
452
+ - **[0.12.14]** (2026-11-03)
425
453
  - **[0.12.13]** (2026-11-03)
426
- - **[0.12.12]** (2026-11-03)
427
- - **[0.12.10]** (2026-11-02)
428
- - **[0.12.11]** (2026-11-02)
429
- - **[0.12.9]** (2026-11-02)
430
454
 
431
455
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
432
456
  <!-- CHANGELOG:END -->
package/README.ko.md CHANGED
@@ -21,6 +21,7 @@
21
21
  <a href="https://github.com/akillness/jeo-code"><img alt="license" src="https://img.shields.io/badge/license-MIT-green?style=flat-square"></a>
22
22
  <img alt="runtime" src="https://img.shields.io/badge/runtime-Bun%20%E2%89%A5%201.3.14-f9f1e1?style=flat-square&logo=bun&logoColor=black">
23
23
  <img alt="zero native deps" src="https://img.shields.io/badge/native%20deps-0-blue?style=flat-square">
24
+ <a href="https://buymeacoffee.com/akillness3q"><img alt="Buy Me a Coffee로 후원하기" src="https://img.shields.io/badge/Buy%20Me%20a%20Coffee-support-AC6415?style=flat-square&logo=buy-me-a-coffee"></a>
24
25
  </p>
25
26
 
26
27
  <p align="center">
@@ -40,7 +41,7 @@
40
41
 
41
42
  - [양방향 메신저 게이트웨이 (Discord, Telegram, Slack)](docs/integrations/messenger.md)
42
43
 
43
- **메신저 게이트웨이** — `jeo messenger`는 알림 데몬과 별도로 허용된 Discord·Telegram·Slack 대화를 지원합니다. 로컬 도구는 기본적으로 꺼져 있으며, `computer`를 명시적으로 허용하면 공유 호스트 데스크톱에, `bash`를 허용하면 셸에 접근합니다. 샌드박스가 아닙니다. [메신저 가이드](docs/integrations/messenger.md)를 참고하세요.
44
+ **메신저 게이트웨이** — `jeo messenger`는 알림 데몬과 별도로 허용된 Discord·Telegram·Slack 대화를 지원합니다. Discord는 명시적인 로컬 Docker 샌드박스가 필수이며, 설정된 도구는 호스트 데스크톱/셸이 아닌 Linux 게스트에서 실행됩니다. [메신저 가이드](docs/integrations/messenger.md)와 아래의 현재 제약을 참고하세요.
44
45
 
45
46
  📖 **[사용 가이드](docs/usage-guide.md)** — 설치, TUI 조작(↑ 이전 쿼리, Ctrl+O, `!` 셸), 슬래시 명령, `/resume`, 스펙 우선 워크플로를 데모 영상과 함께 설명합니다.
46
47
 
@@ -94,7 +95,7 @@ jeo-code는 단순한 코드 완성을 넘어 실제 엔지니어링 사이클(
94
95
  - **실제로 강제되는 독립 검증자** — 플랜은 이제 architect/critic 단계를 건너뛸 수 없습니다: `PlanSchema`는 미검증 변이로 끝나는 모든 플랜을 거부하며(검증 대상 변이보다 앞에 배치된 검증자도 인정하지 않음), `ralplan` 드래프트 시점과 `team`/`approve` 실행 시점 양쪽 모두에 적용됩니다. 모든 architect/critic 평결도 실제 증거를 제시해야 하며, 관찰된 `read`/`search`/`find`/`ast_grep`/`lsp` 호출이 0건이면 텍스트가 무엇을 주장하든 평결이 차단됩니다.
95
96
  - **안전 경계 자동 모델 폴백** — 미분류 안전 거부(실제 콘텐츠 정책 위반이 아니라 분류기의 오탐일 가능성)가 발생하면 이제 같은 모델에서 영원히 물러서는 대신 실제로 다른 프로바이더의 모델로 전환합니다 — 기존 rate-limit 빠른 폴백과 동일한 방식입니다. `Refusal (<category>)` 형태의 결정론적 거부는 영향받지 않고 여전히 폴백 없이 하드 실패합니다.
96
97
  - **메모리: 획득한 신뢰** — 개념의 검증 날짜는 이제 모든 쓰기 시점이 아니라 증류 패스가 명시적으로 검증됨으로 표시할 때만 기록됩니다. `isConceptStale`은 수동적인 타임스탬프를 신뢰하는 대신 미검증(또는 30일 초과) 개념을 재검증이 필요한 것으로 처리합니다.
97
- - **동적 워크플로(`eval` 도구)** — 서브에이전트 디스패치 주변에 실제 JS 제어 흐름을 작성합니다: `task(role, taskText, context?)`, `parallel(thunks)`, `pipeline(items, ...stages)`, `log(message)`로 `task`의 단일 스테이지 `tasks[]` 배치로는 표현할 수 없는 순차/분기 오케스트레이션을 구성합니다. 격리된 Worker 스레드에서 진정한 선점형 타임아웃(같은 프로세스 내 경쟁이 아닌 `worker.terminate()`)으로 실행되며 — `bash`와 동일한 풀프로세스 신뢰 수준으로, 샌드박스인 척하지 않고, 동일한 인터뷰 변이 락으로 게이트됩니다.
98
+ - **동적 워크플로(`eval` 도구)** — `task(role, taskText, context?)`, `parallel(thunks)`, `pipeline(items, ...stages)`, `log(message)`로 JS 흐름을 구성합니다. 세션 샌드박스가 없으면 Worker 스레드의 타임아웃은 보안 경계가 아닙니다. 샌드박스에서는 eval이 Linux 게스트에서 실행되며 task/parallel/pipeline 위임 헬퍼는 지원하지 않으므로 부모 `task` 도구로 위임하세요.
98
99
  - **끊긴 출력 파이프에서 조용히 종료** — 읽기를 일찍 멈추는 명령으로 파이프할 때(`jeo --help | head`, 사라진 원격 피어) 더 이상 원시 `EPIPE` 스택을 덤프하지 않습니다 — 셸이 SIGPIPE로 종료된 파이프라인 생산자에게 보고하는 것과 동일한 코드(141)로 조용히 종료됩니다. 실제 크래시는 영향받지 않고 여전히 명확하게 드러납니다.
99
100
  - **macOS 낮은 파일 디스크립터 한도 경고** — 낮은 `ulimit -n`(BSD 기본값 256/1024)은 파일 감시, 브라우저 도구, 넓은 리포지토리 스캔에서 불명확한 `EMFILE` 실패를 유발할 수 있습니다 — jeo는 이제 실행 시 한 번(stderr로만, 파이프되는 `-p` 출력은 오염시키지 않음) `ulimit`/`launchctl` 구체적인 안내와 함께 경고합니다. `JEO_SKIP_NOFILE_CHECK=1`로 옵트아웃할 수 있습니다.
100
101
 
@@ -292,6 +293,29 @@ jeo ultragoal # 5. 최종 증거 기반 종
292
293
 
293
294
  옵트인: 공유 데몬 하나가 머신의 모든 활성 `jeo` 세션을 지켜보다가 **서브에이전트 상태 전환**(시작 → 완료/실패/취소)마다 메시지를 보내고, 세션 식별 헤더, 턴 시작/종료 요약, 확정된 턴 텍스트도 함께 전송합니다 — "여전히 실행 중" 반복 핑은 절대 보내지 않습니다. allowlist에 등록된 사람은 채팅에서 목록 조회·전송·조종·취소를 할 수 있고, 모든 제어는 로컬 세션으로 중계되어 **확인 응답됨(수락됨, 완료 아님)** 또는 **확인 응답 안 됨**으로 보고됩니다 — 데몬은 명령이 실행됐다고 절대 주장하지 않습니다.
294
295
 
296
+ ### Discord 세션과 샌드박스 경계
297
+
298
+ Discord 스레드 하나는 발신자·메시지와 무관하게 하나의 영속 세션을 유지합니다. 같은 스레드는 FIFO로 처리하고 다른 세션은 동시에 실행할 수 있습니다. 선택한 워크스페이스를 공유하며 세션별 Git worktree를 만들지 않으므로 파일 수정은 충돌할 수 있습니다.
299
+
300
+ 메신저 설정에 `sandbox`를 지정하세요. `~/.jeo/config.json`에도 같은 형식으로 설정하면 터미널 세션을 격리합니다:
301
+
302
+ ```json
303
+ {
304
+ "sandbox": {
305
+ "image": "oven/bun:1.3.14",
306
+ "limits": { "memoryBytes": 268435456, "pids": 64, "cpus": 1 }
307
+ }
308
+ }
309
+ ```
310
+
311
+ 전체 메신저 프로필이 아닌 샌드박스 부분 예시입니다. 기존 `platforms` 허용 목록과 자격증명 환경변수 이름은 유지하세요. Linux Bun 이미지는 로컬 Docker 데몬에 이미 있어야 합니다. 백엔드는 불변 이미지 ID를 확인하고 `--pull=never`로 실행하며 이미지 pull·의존성 설치·원격 Docker·호스트 실행 폴백을 하지 않습니다. Mac/Windows에서도 호스트 셸이 아닌 **Linux 게스트 셸**입니다. 네트워크는 `none`, `/workspace`는 읽기/쓰기이며 추가 경로는 세션별 `ro`/`rw` 승인이 필요합니다. 전체 홈·비공개 제어 경로·제어 소켓은 허용할 수 없습니다. 호스트 운영자와 Docker 데몬은 신뢰 대상입니다.
312
+
313
+ 접근 결정은 부모에 직접 제출한 사람의 `/sandbox status`, `/sandbox approve <requestId>`, `/sandbox deny <requestId>`, `/sandbox revoke`만 처리합니다. 모델의 `sandbox_request {host, mode:'ro'|'rw'}`는 요청 전용이며 자체 승인할 수 없습니다. 인증된 행위자 자신의 세션과 현재 요청 revision이 필요하며, 오래되거나 다른 세션의 요청은 거부합니다. 기존 터미널 Discord 제어도 샌드박스가 필수입니다. 로컬 `/discord control on --allow-full-control`로 켜고 `/discord control off`로 끄며, 원격 턴은 로컬 입력 초안을 보존합니다.
314
+
315
+ `independentSessions`는 런타임에서 거부하며 명시적인 `jeo messenger setup` 마이그레이션이 제거합니다. `sessionDir`는 워크스페이스 밖의 서로 겹치지 않는 비공개 경로여야 합니다. 워크스페이스 안의 구형 `.jeo/sessions`·`.jeo/messenger-sessions`·`.jeo/memory/raw` 기록 저장소는 이동 안내와 함께 시작을 차단합니다. 재시작 전 직접 옮겨야 하며 자동 복사·삭제하지 않습니다. 전체 대화 이력은 부모 전용 저장소에만 두고 증류된 프로젝트 학습은 공유합니다. 기존 ECC 스킬/워크플로 설치를 재사용하며 전역 재설치는 하지 않았습니다. 보호된 전역 스킬 내용은 실행 전 스냅샷으로 고정하고 워크스페이스/승인 문서는 게스트를 통해 읽습니다.
316
+
317
+ **검증 범위(2026-11-03):** 실제 macOS 실제 Docker 검증기(`bun run verify:sandbox`)가 **행동 계약 20개**를 통과했고 소스 단독 빌드도 통과했습니다. 복사한 단독 바이너리는 실제 PTY FIFO, 부모 `sandbox_request`와 실제 Linux 게스트 도구, 전체 이력 비공개, 부모가 생성한 stderr 종료 nonce와 일치하는 ACK, 실제 워커 종료(143), Docker 컨테이너 부재를 다루는 제한된 범위의 검증을 통과했습니다. 현재 소스 래퍼는 teardown 정리 소유자, 제한된 종료 대기, 사람의 세션별 승인, 영속 스레드/FIFO 하나, 공유 워크스페이스를 유지합니다. 이는 전체 테스트 스위트·크로스 플랫폼·프로덕션 검증이 아닙니다. 전체 `bun test`는 시간 초과로 **통과가 아니며**, 일반 집중 스위트에는 낡은 픽스처 실패가 남아 수정 중입니다. 네이티브 Linux·Windows 호스트 검증은 불가능한 상태이며 실제 Discord 전송은 하지 않았습니다. macOS의 Linux 게스트는 호스트 검증이 아닙니다. `bun run build:hands`는 워커 빌드, `bun run verify:sandbox`는 사전 조건을 설치하지 않는 실제 Docker 검사입니다. 워커/컨테이너 정리는 확인되어야 하며 오류·제거 미확인을 성공으로 보고하지 않습니다.
318
+
295
319
  플랫폼별 상세 단독 가이드:
296
320
  - 📖 [채널 연동 개요 & 아키텍처](docs/integrations/README.md) — 기능 매트릭스 및 구조 개요
297
321
  - 📱 [Telegram 연동 상세 가이드](docs/integrations/telegram.md) — 내장 백그라운드 데몬, BotFather 대화형 페어링, 포럼 토픽, `/steer` 원격 명령
@@ -321,7 +345,7 @@ jeo daemon start|stop|status|reload # 공유 데몬에는 프로바이더 선
321
345
  - **Setup**은 공식 API로 봇 신원과 목적지 접근을 검증하고(Telegram `getMe`, Discord 봇 + 채널 조회, Slack `auth.test`/`bots.info`/`conversations.info`와 Socket Mode URL) 결과를 `~/.jeo/config.json`의 `notifications.<provider>` 아래에 저장합니다 — **평문**이므로 그 파일은 비공개로 유지하세요. 메시지를 보내지도, 소켓을 열지도 않습니다.
322
346
  - **`initialized`**(`jeo daemon status`)는 데몬 프로세스가 락을 소유하고 전송 계층을 시작했다는 뜻이지, Telegram poll·Discord Gateway 핸드셰이크·Slack Socket Mode 연결이 성공했다는 증거가 아닙니다. 그 확인에는 `notify health` / `notify test`를 사용하세요.
323
347
  - **토큰이나 목적지를 바꾸면** Discord와 Slack은 allowlist가 초기화되고(`--allowed-user-ids`를 다시 넘겨야 함), Telegram은 세션별 토픽 맵이 초기화됩니다. 정확히 같은 자격증명과 목적지로 setup을 다시 실행하면 그대로 유지됩니다.
324
- - **원격 명령은 리터럴 텍스트**(`/subagents`, …)이며 설정된 채팅/채널과 allowlist에만 인가됩니다; Slack은 추가로 같은 이름을 등록된 슬래시 명령으로도 받습니다. allowlist에 등록된 운영자는 사실상 로컬 에이전트의 능력을 그대로 쥐게 되므로, 그 목록을 신뢰 경계로 다루세요.
348
+ - **원격 명령은 리터럴 텍스트**(`/subagents`, …)이며 설정된 채팅/채널과 allowlist에만 인가됩니다. Slack은 같은 이름의 등록된 슬래시 명령도 받습니다. Discord 터미널 제어는 기본 꺼짐이며 샌드박스 세션에서 로컬 `/discord control on --allow-full-control`로 켜고 `/discord control off`로 해제합니다. 위의 샌드박스·사람 승인 경계를 따르며 채팅에 자격증명을 넣지 마세요.
325
349
  - **재시도는 프로바이더별로 상한이 있습니다**: Telegram은 429의 `retry_after` 쿨다운(1초–1시간으로 클램프)을 poll 백오프와 별도로 지키고, Discord와 Slack은 429에 대해 서버의 `Retry-After`를 따라 최대 3회만 재시도하며, 결과를 알 수 없는 POST는 절대 재전송하지 않습니다(플랫폼이 이미 받았을 수 있음).
326
350
  - **고아 복구**: 데몬이 크래시하면 락이 `stale`이 되고, 다음 `jeo daemon start`가 회수합니다. `jeo daemon stop`은 호스트가 프로세스 시작 시각을 보고할 수 있으면 재활용된 PID에 시그널을 보내기를 거부하고, 보고할 수 없으면 그 사실을 알립니다.
327
351
  - **지금까지 검증된 범위**: Telegram은 엔드투엔드(실제 전송, 폴링, 깔끔한 종료). Discord와 Slack은 같은 데몬 뒤에 오프라인 전송/배선 테스트와 코드·보안·타입 리뷰로 구현되어 있으며, 실제 봇/앱 자격증명으로 라이브 연결은 아직 실행하지 않았습니다.
@@ -377,7 +401,7 @@ jeo routine init --trigger schedule --cron "0 7 * * *" --prompt "Re-run the eval
377
401
  jeo routine init --trigger issues --prompt "Triage this issue" --name "issue-triage"
378
402
  ```
379
403
 
380
- GitHub Actions 워크플로(`.github/workflows/<name>.yml`)를 생성해 jeo를 설치하고 `schedule` / `issues` / `pull_request`에서 헤드리스로 실행합니다(`jeo "<prompt>" -p`) — 수동 테스트 실행을 위해 항상 `workflow_dispatch`와 함께 짝지어집니다 — GitHub 자체 호스팅 러너 위에서. 이것이 jeo의 "노트북 없이 동작"하는 이야기입니다: jeo 자체 내부에 인프로세스 스케줄러도, 웹훅 리스너도, 코드 실행 샌드박스도 없습니다 — GitHub의 인프라가 트리거를 담당하고, jeo는 기존 헤드리스 모드를 그대로 실행할 뿐입니다. 기본적으로 변경사항이 있으면 PR을 엽니다(`peter-evans/create-pull-request`, diff가 비어 있으면 안전하게 no-op); `--no-pr`은 대신 트리거한 브랜치에 직접 커밋합니다. `--dry-run`은 YAML을 작성하지 않고 출력만 합니다; 동일한 `--out` 경로에서 `jeo routine init`을 재실행하면 `--force` 없이는 덮어쓰기를 거부합니다. 워크플로의 첫 실제 실행 전에 저장소 시크릿 `ANTHROPIC_API_KEY`(또는 `--api-key-env <VAR>`)를 설정하세요.
404
+ GitHub Actions 워크플로(`.github/workflows/<name>.yml`)를 생성해 GitHub 호스팅 러너에서 jeo를 설치하고 `schedule` / `issues` / `pull_request`로 헤드리스 실행합니다(`jeo "<prompt>" -p`). 수동 실행용 `workflow_dispatch`도 포함합니다. 트리거는 GitHub가 담당하며 루틴은 인프로세스 스케줄러나 웹훅 리스너를 추가하지 않습니다. 세션 샌드박스는 위 로컬 사전 조건이 필요한 별도 명시적 설정이며 루틴 생성이 설치해 주지 않습니다. 기본값은 변경 PR 생성(`peter-evans/create-pull-request`, 빈 diff는 no-op), `--no-pr`은 트리거 브랜치에 커밋합니다. `--dry-run`은 YAML 출력만 하며 기존 `--out` 덮어쓰기는 `--force`가 필요합니다. 첫 실제 실행 전 저장소 시크릿 `ANTHROPIC_API_KEY`(또는 `--api-key-env <VAR>`)를 설정하세요.
381
405
 
382
406
  ## 로컬 모델
383
407
 
@@ -390,7 +414,7 @@ jeo doctor && jeo
390
414
  ## 설정
391
415
 
392
416
  - 전역 설정: `~/.jeo/config.json` (모델 선택은 MRU 영속)
393
- - 프로젝트 상태/세션: `<project>/.jeo/`
417
+ - 프로젝트 상태: `<project>/.jeo/`; 샌드박스 대화 기록은 워크스페이스 밖에 보관합니다(위 Discord 세션 경계 참고).
394
418
 
395
419
  ```bash
396
420
  ANTHROPIC_API_KEY=... OPENAI_API_KEY=... GEMINI_API_KEY=...
@@ -477,11 +501,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
477
501
  ## 변경 이력 (Changelog)
478
502
 
479
503
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
504
+ - **[Unreleased]**
505
+ - **[0.12.16]** (2026-11-04)
506
+ - **[0.12.15]** (2026-11-03)
507
+ - **[0.12.14]** (2026-11-03)
480
508
  - **[0.12.13]** (2026-11-03)
481
- - **[0.12.12]** (2026-11-03)
482
- - **[0.12.10]** (2026-11-02)
483
- - **[0.12.11]** (2026-11-02)
484
- - **[0.12.9]** (2026-11-02)
485
509
 
486
510
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
487
511
  <!-- CHANGELOG:END -->
package/README.md CHANGED
@@ -21,6 +21,7 @@
21
21
  <a href="https://github.com/akillness/jeo-code"><img alt="license" src="https://img.shields.io/badge/license-MIT-green?style=flat-square"></a>
22
22
  <img alt="runtime" src="https://img.shields.io/badge/runtime-Bun%20%E2%89%A5%201.3.14-f9f1e1?style=flat-square&logo=bun&logoColor=black">
23
23
  <img alt="zero native deps" src="https://img.shields.io/badge/native%20deps-0-blue?style=flat-square">
24
+ <a href="https://buymeacoffee.com/akillness3q"><img alt="Support on Buy Me a Coffee" src="https://img.shields.io/badge/Buy%20Me%20a%20Coffee-support-AC6415?style=flat-square&logo=buy-me-a-coffee"></a>
24
25
  </p>
25
26
 
26
27
  <p align="center">
@@ -42,7 +43,7 @@ Run `jeo` inside a repository and it reads files, edits them, runs commands, and
42
43
 
43
44
  - [Bidirectional messenger gateway (Discord, Telegram, Slack)](docs/integrations/messenger.md)
44
45
 
45
- **Messenger gateway** — `jeo messenger` supports allowlisted Discord, Telegram, and Slack conversations separately from the notification daemon. Local tools are off by default; explicitly enabling `computer` grants access to the shared host desktop, and enabling `bash` grants shell authority. Neither is sandboxed. See the [messenger guide](docs/integrations/messenger.md).
46
+ **Messenger gateway** — `jeo messenger` supports allowlisted Discord, Telegram, and Slack conversations separately from the notification daemon. Discord requires an explicit local Docker sandbox; its configured tools execute in a Linux guest, not on the host desktop or shell. See the [messenger guide](docs/integrations/messenger.md) and the current constraints below.
46
47
 
47
48
  📖 **[Usage guide](docs/usage-guide.md)** — install, TUI controls (↑ recall, Ctrl+O, `!` shell), slash commands, `/resume`, and the spec-first workflow, with a demo video.
48
49
 
@@ -99,7 +100,7 @@ jeo-code is a **local-first AI coding agent CLI** designed to safely drive entir
99
100
  - **Plan critic gate** — a plan cannot skip its architect/critic step: `PlanSchema` rejects plans ending with an unverified mutation (a verifier before the mutation does not count), at `ralplan` draft time and `team`/`approve` execution time. Critic verdicts require observed repository-read/search evidence; that supports reviewability, not independent validation of the workspace.
100
101
  - **Safety-boundary automatic model fallback** — an uncategorized safety refusal (a possible classifier false positive, not a genuine content-policy hit) now switches to a genuinely different-provider model instead of backing off forever on the same one — mirrors the existing rate-limit fast-fallback. A `Refusal (<category>)`-shaped deterministic hit is untouched and still hard-fails with zero fallback.
101
102
  - **Memory: earned confidence** — a concept's verification date is now written only when a distillation pass is explicitly marked verified, not on every write; `isConceptStale` treats an unverified (or >30-day-stale) concept as needing re-verification instead of trusting a passive timestamp.
102
- - **Dynamic Workflows (`eval` tool)** — write real JS control flow around subagent dispatch: `task(role, taskText, context?)`, `parallel(thunks)`, `pipeline(items, ...stages)`, and `log(message)`, composing sequential/branching orchestration that `task`'s single-stage `tasks[]` batch can't express. Runs in an isolated Worker thread with a genuinely preemptive timeout (`worker.terminate()`, not a same-process race) — same full-process trust as `bash`, no sandbox pretense, gated by the same interview mutation lock.
103
+ - **Dynamic Workflows (`eval` tool)** — compose JS control flow around subagents with `task(role, taskText, context?)`, `parallel(thunks)`, `pipeline(items, ...stages)`, and `log(message)`. Without a session sandbox this Worker-thread timeout is not a security boundary. With a sandbox, eval executes in the Linux guest; its task/parallel/pipeline delegation helpers are unsupported there, so use the parent `task` tool for delegation.
103
104
  - **Quiet exit on a broken output pipe** — piping jeo into a command that stops reading early (`jeo --help | head`, a vanished remote peer) no longer dumps a raw `EPIPE` stack; it exits quietly with the same code (141) a shell reports for any SIGPIPE-killed pipeline producer. A genuine crash is unaffected and still surfaces clearly.
104
105
  - **macOS low file-descriptor-limit warning** — a low `ulimit -n` (BSD's 256/1024 default) risks opaque `EMFILE` failures from file watching, the browser tool, or a broad repo scan; jeo now warns once at launch (stderr only, never piped `-p` output) with concrete `ulimit`/`launchctl` guidance. Opt out with `JEO_SKIP_NOFILE_CHECK=1`.
105
106
 
@@ -295,6 +296,29 @@ Non-zero hook output is appended to the tool result the model reads (deduped per
295
296
 
296
297
  Opt-in: one shared daemon watches every live `jeo` session on the machine and pushes a message on each **subagent state edge** (started → completed/failed/cancelled), plus the session identity header, turn start/finish summaries, and the finalized turn text — never repeated "still running" pings. Allowlisted humans can list, send, steer, and cancel from the chat; every control is relayed to the local session and reported as **acknowledged (accepted, not completed)** or **not acknowledged** — the daemon never claims a command ran.
297
298
 
299
+ ### Discord sessions and sandbox boundaries
300
+
301
+ One Discord thread keeps one persistent session, irrespective of sender or message. Same-thread requests run FIFO; other sessions can run concurrently. Sessions share the selected workspace: no per-session Git worktree is created, so filesystem edits can conflict.
302
+
303
+ Set `sandbox` in the messenger config; the same shape in `~/.jeo/config.json` enables terminal session isolation:
304
+
305
+ ```json
306
+ {
307
+ "sandbox": {
308
+ "image": "oven/bun:1.3.14",
309
+ "limits": { "memoryBytes": 268435456, "pids": 64, "cpus": 1 }
310
+ }
311
+ }
312
+ ```
313
+
314
+ This is the sandbox portion, not a complete messenger profile; keep the existing `platforms` allowlists and credential environment-variable names. The Linux Bun image must already exist in the local Docker daemon. The backend resolves its immutable image ID and uses `--pull=never`: no image pull, dependency installation, remote Docker endpoint, or host-execution fallback. macOS and Windows hosts still run a **Linux guest shell**, not native host shell commands. Network is `none`; `/workspace` is read-write and extra paths require session grants (`ro` or `rw`). Whole-home/private control paths and control sockets cannot be granted. The host operator and Docker daemon remain trusted.
315
+
316
+ Only directly submitted parent-human controls can decide access: `/sandbox status`, `/sandbox approve <requestId>`, `/sandbox deny <requestId>`, `/sandbox revoke`. The model's `sandbox_request {host, mode:'ro'|'rw'}` is request-only, never self-approval. Decisions require the authenticated actor's own session and current request revision; stale/cross-session requests fail closed. Existing terminal Discord control also requires a sandbox: enable locally with `/discord control on --allow-full-control`, disable with `/discord control off`; remote turns preserve the local input draft.
317
+
318
+ `independentSessions` is rejected at runtime; explicit `jeo messenger setup` migration removes it. Keep `sessionDir` private, outside and disjoint from the workspace. Old workspace `.jeo/sessions`, `.jeo/messenger-sessions`, or `.jeo/memory/raw` transcript and raw-history storage fails closed with relocation guidance: move it deliberately before restarting; no automatic copy or deletion occurs. Full conversation history stays in the parent-only store; derived project learning remains shared. Existing ECC skills/workflow installation is reused, not globally reinstalled; protected global skill bytes are snapshotted before execution, while workspace/granted documents are read through the guest.
319
+
320
+ **Verification scope (2026-11-03):** the actual macOS real-Docker verifier (`bun run verify:sandbox`) passed **20 behavior contracts**, and a source standalone build passed. A copied standalone binary passed a narrow qualification covering the actual PTY FIFO, a parent `sandbox_request` with a real Linux guest tool, full-history privacy, a matching parent-generated stderr shutdown nonce ACK, a real worker exit (143), and Docker container absence. Source wrappers preserve the teardown cleanup owner, bounded shutdown waits, human per-session grants, one persistent thread/FIFO, and the shared workspace. This is not a full-suite, cross-platform, or production verification: the full `bun test` run timed out and is **not a pass**, and the general focused suite still has stale-fixture failures being fixed. Native Linux and Windows host verification is unavailable, and no live Discord sends were made. A Linux guest on macOS is not host qualification. `bun run build:hands` builds the worker; `bun run verify:sandbox` runs the real-Docker check without installing prerequisites. Worker/container cleanup must be confirmed; cleanup errors or unconfirmed removal are not success.
321
+
298
322
  Detailed standalone guides:
299
323
  - 📖 [Channel Integrations Overview](docs/integrations/README.md) — Feature matrix and architecture overview
300
324
  - 📱 [Telegram Integration Guide](docs/integrations/telegram.md) — Built-in background daemon, interactive BotFather pairing, forum topics, and `/steer` commands
@@ -324,7 +348,7 @@ What the words mean, because they are not interchangeable:
324
348
  - **Setup** validates bot identity and destination access with the official API (Telegram `getMe`, Discord bot + channel lookup, Slack `auth.test`/`bots.info`/`conversations.info` and a Socket Mode URL) and stores the result in `~/.jeo/config.json` under `notifications.<provider>` — **plaintext**; keep that file private. It does not send a message and does not open a socket.
325
349
  - **`initialized`** (`jeo daemon status`) means the daemon process owns its lock and has started its transports; it is not proof of a successful Telegram poll, Discord Gateway handshake, or Slack Socket Mode connection. Use `notify health` / `notify test` for that.
326
350
  - **Changing a token or destination** resets the allowlist for Discord and Slack (you must pass `--allowed-user-ids` again) and the per-session topic map for Telegram; re-running setup with the exact same credentials and destination keeps them.
327
- - **Remote commands are literal text** (`/subagents`, …) authorized to the configured chat/channel and allowlist; Slack additionally accepts the same names as registered slash commands. Discord session control defaults off and requires the host operator to submit `/discord control on --allow-full-control` locally; `/discord control off` revokes it. Authorized users can exercise the host session's tool permissions; this is not a sandbox. See the [Discord guide](docs/integrations/discord.md) for file handling and revocation limits. Never put credentials in chat.
351
+ - **Remote commands are literal text** (`/subagents`, …) authorized to the configured chat/channel and allowlist; Slack additionally accepts the same names as registered slash commands. Discord terminal control defaults off and requires local `/discord control on --allow-full-control` in a sandbox session; `/discord control off` revokes it. The sandbox and human grant rules above apply. Never put credentials in chat.
328
352
  - **Retries are bounded per provider**: Telegram honours a 429 `retry_after` cooldown (clamped 1 s–1 h) with separate poll backoff; Discord and Slack retry at most 3 times on 429 using the server's `Retry-After`, and never re-send a POST whose outcome is unknown (the platform may already have it).
329
353
  - **Orphan recovery**: if a daemon crashes, its lock becomes `stale`; the next `jeo daemon start` reclaims it. `jeo daemon stop` refuses to signal a recycled PID when the host can report process start times, and says so when it cannot.
330
354
  - **Verified so far**: Telegram end to end (real send, polling, clean shutdown). Discord and Slack are implemented behind the same daemon with offline transport/wiring tests and code, security, and type reviews; live connectivity has not been exercised against real bot/app credentials.
@@ -365,7 +389,7 @@ jeo routine init --trigger schedule --cron "0 7 * * *" --prompt "Re-run the eval
365
389
  jeo routine init --trigger issues --prompt "Triage this issue" --name "issue-triage"
366
390
  ```
367
391
 
368
- Generates a GitHub Actions workflow (`.github/workflows/<name>.yml`) that installs jeo and runs it headlessly (`jeo "<prompt>" -p`) on `schedule` / `issues` / `pull_request` — always paired with `workflow_dispatch` for a manual test run — on GitHub's own hosted runners. This is jeo's "runs without your laptop" story: no in-process scheduler, no webhook listener, no code-exec sandbox inside jeo itself — GitHub's infrastructure does the triggering, jeo just runs its existing headless mode. Defaults to opening a PR with any changes (`peter-evans/create-pull-request`, a safe no-op when the diff is empty); `--no-pr` commits directly to the triggering branch instead. `--dry-run` prints the YAML without writing it; re-running `jeo routine init` at the same `--out` path refuses to overwrite without `--force`. Set the `ANTHROPIC_API_KEY` (or `--api-key-env <VAR>`) repo secret before the workflow's first real run.
392
+ Generates a GitHub Actions workflow (`.github/workflows/<name>.yml`) that installs jeo and runs it headlessly (`jeo "<prompt>" -p`) on `schedule` / `issues` / `pull_request`, always with `workflow_dispatch` for a manual run, on GitHub-hosted runners. GitHub handles triggering; routines add no in-process scheduler or webhook listener. Session sandboxing is an explicit, separate configuration with the local prerequisites above, not something routine generation installs. Defaults to opening a PR for changes (`peter-evans/create-pull-request`, no-op for an empty diff); `--no-pr` commits to the triggering branch. `--dry-run` prints YAML without writing; an existing `--out` requires `--force`. Set the `ANTHROPIC_API_KEY` (or `--api-key-env <VAR>`) repository secret before the first real run.
369
393
 
370
394
  ## Local models
371
395
 
@@ -378,7 +402,7 @@ jeo doctor && jeo
378
402
  ## Configuration
379
403
 
380
404
  - Global config: `~/.jeo/config.json` (model picks are MRU-persisted)
381
- - Project state/sessions: `<project>/.jeo/`
405
+ - Project state: `<project>/.jeo/`; sandbox session transcripts stay outside the workspace (see Discord session boundaries above).
382
406
 
383
407
  ```bash
384
408
  ANTHROPIC_API_KEY=... OPENAI_API_KEY=... GEMINI_API_KEY=...
@@ -466,11 +490,11 @@ Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the i
466
490
  ## Changelog
467
491
 
468
492
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
493
+ - **[Unreleased]**
494
+ - **[0.12.16]** (2026-11-04)
495
+ - **[0.12.15]** (2026-11-03)
496
+ - **[0.12.14]** (2026-11-03)
469
497
  - **[0.12.13]** (2026-11-03)
470
- - **[0.12.12]** (2026-11-03)
471
- - **[0.12.10]** (2026-11-02)
472
- - **[0.12.11]** (2026-11-02)
473
- - **[0.12.9]** (2026-11-02)
474
498
 
475
499
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
476
500
  <!-- CHANGELOG:END -->
package/README.zh.md CHANGED
@@ -13,6 +13,7 @@
13
13
  <a href="https://github.com/akillness/jeo-code"><img alt="license" src="https://img.shields.io/badge/license-MIT-green?style=flat-square"></a>
14
14
  <img alt="runtime" src="https://img.shields.io/badge/runtime-Bun%20%E2%89%A5%201.3.14-f9f1e1?style=flat-square&logo=bun&logoColor=black">
15
15
  <img alt="zero native deps" src="https://img.shields.io/badge/native%20deps-0-blue?style=flat-square">
16
+ <a href="https://buymeacoffee.com/akillness3q"><img alt="通过 Buy Me a Coffee 赞助" src="https://img.shields.io/badge/Buy%20Me%20a%20Coffee-support-AC6415?style=flat-square&logo=buy-me-a-coffee"></a>
16
17
  </p>
17
18
 
18
19
  <p align="center">
@@ -32,7 +33,7 @@
32
33
 
33
34
  - [双向消息网关(Discord、Telegram、Slack)](docs/integrations/messenger.md)
34
35
 
35
- **消息网关** — `jeo messenger` 独立于通知守护进程,支持白名单内的 Discord、Telegram 和 Slack 会话。本地工具默认关闭;明确启用 `computer` 可访问共享主机桌面,启用 `bash` 可访问 shell,两者均非沙箱。详见[消息网关指南](docs/integrations/messenger.md)。
36
+ **消息网关** — `jeo messenger` 独立于通知守护进程,支持白名单内的 Discord、Telegram 和 Slack 会话。Discord 必须显式配置本地 Docker 沙箱;配置的工具在 Linux 客体中执行,不直接操作主机桌面或 shell。参见[消息网关指南](docs/integrations/messenger.md)及下文当前约束。
36
37
 
37
38
  📖 **[使用指南](docs/usage-guide.md)** — 安装、TUI 操作(↑ 历史、Ctrl+O、`!` shell)、斜杠命令、`/resume`、规格优先工作流,附演示视频。
38
39
 
@@ -59,7 +60,7 @@
59
60
  - **计划 critic 门禁** — 计划不能跳过 architect/critic 步骤:`PlanSchema` 会拒绝以未验证变更结束的计划(变更之前的 verifier 不算),并在 `ralplan` 起草阶段以及 `team`/`approve` 执行阶段生效。critic 裁决需要观察到仓库读取/搜索证据;这提高可审查性,但不等于独立验证工作区。
60
61
  - **安全边界自动模型回退** — 未分类的安全拒绝(可能是分类器误报,而非真实的内容策略命中)现在会切换到真正不同提供商的模型,而不是在同一个模型上无限退让 — 与现有的 rate-limit 快速回退是同一套模式。`Refusal (<category>)` 这种形态的确定性拒绝不受影响,仍然零回退硬失败。
61
62
  - **内存: 应得的信任** — 概念的验证日期现在只在蒸馏阶段被明确标记为已验证时才会写入,而不是每次写入都记录;`isConceptStale` 把未验证(或超过 30 天过期)的概念视为需要重新验证,而不是信任一个被动的时间戳。
62
- - **动态工作流(`eval` 工具)** — 围绕子代理派遣编写真正的 JS 控制流: `task(role, taskText, context?)`、`parallel(thunks)`、`pipeline(items, ...stages)`、`log(message)`,组合出 `task` 单阶段 `tasks[]` 批次无法表达的顺序/分支编排。运行在隔离的 Worker 线程中,具备真正的抢占式超时(`worker.terminate()`,而非同进程内的竞争) — 与 `bash` 同等的全进程信任级别,不假装沙箱,受同一个 interview 变更锁门控。
63
+ - **动态工作流(`eval` 工具)** — 用 `task(role, taskText, context?)`、`parallel(thunks)`、`pipeline(items, ...stages)`、`log(message)` 组合 JS 流程。没有会话沙箱时,Worker 线程超时不是安全边界。启用沙箱后 eval 在 Linux 客体中执行,不支持其中的 task/parallel/pipeline 委派辅助函数;请使用父进程的 `task` 工具委派。
63
64
  - **在破损输出管道上安静退出** — 当管道输出到提前停止读取的命令时(`jeo --help | head`、消失的远程对端),不再转储原始的 `EPIPE` 堆栈 — 现在会以 shell 对被 SIGPIPE 终止的管道生产者报告的相同退出码(141)安静退出。真正的崩溃不受影响,仍会清晰地暴露出来。
64
65
  - **macOS 低文件描述符上限警告** — 较低的 `ulimit -n`(BSD 默认 256/1024)可能导致文件监视、浏览器工具或大范围仓库扫描出现不明的 `EMFILE` 失败 — jeo 现在会在启动时警告一次(仅 stderr,绝不污染被管道的 `-p` 输出),并给出具体的 `ulimit`/`launchctl` 指引。可通过 `JEO_SKIP_NOFILE_CHECK=1` 选择退出。
65
66
 
@@ -238,6 +239,29 @@ jeo ultragoal
238
239
 
239
240
  可选功能: 一个共享守护进程监视本机上所有活跃的 `jeo` 会话,并在每次**子代理状态变化**(启动 → 完成/失败/取消)时推送消息,另附会话身份头、回合开始/结束摘要以及最终的回合文本 — 绝不会重复"仍在运行"之类的通知。allowlist 中的人类可以在聊天里列出、发送、引导和取消;每一条控制都会转发给本地会话,并报告为**已确认(已接受,未必已完成)**或**未确认** — 守护进程绝不声称某条命令已执行。
240
241
 
242
+ ### Discord 会话与沙箱边界
243
+
244
+ 一个 Discord 线程保留一个持久会话,不因发送者或消息而改变。同线程请求按 FIFO 执行,其他会话可并发。会话共享所选工作区,不创建每会话 Git worktree,因此文件修改可能冲突。
245
+
246
+ 在消息网关配置中设置 `sandbox`;在 `~/.jeo/config.json` 中采用相同格式可隔离终端会话:
247
+
248
+ ```json
249
+ {
250
+ "sandbox": {
251
+ "image": "oven/bun:1.3.14",
252
+ "limits": { "memoryBytes": 268435456, "pids": 64, "cpus": 1 }
253
+ }
254
+ }
255
+ ```
256
+
257
+ 这只是沙箱部分,不是完整的消息网关配置;请保留现有 `platforms` 白名单及凭据环境变量名。Linux Bun 镜像必须已存在于本地 Docker 守护进程。后端解析其不可变镜像 ID,使用 `--pull=never`,不会拉取镜像、安装依赖、使用远程 Docker 或回退到主机执行。即使主机是 Mac/Windows,运行的仍是 **Linux 客体 shell**,不是主机原生 shell。网络为 `none`;`/workspace` 可读写,额外路径需要会话级 `ro`/`rw` 授权。整个主目录、私有控制路径及控制 socket 均不可授权。主机操作者和 Docker 守护进程仍属于可信边界。
258
+
259
+ 只有直接提交给父进程的人类控制才能决定授权:`/sandbox status`、`/sandbox approve <requestId>`、`/sandbox deny <requestId>`、`/sandbox revoke`。模型的 `sandbox_request {host, mode:'ro'|'rw'}` 仅能申请,不能自批。决定要求已认证操作者自己的会话及当前请求 revision,过期或跨会话请求会被拒绝。现有终端 Discord 远程控制也必须使用沙箱:本地 `/discord control on --allow-full-control` 开启,`/discord control off` 关闭;远程轮次保留本地输入草稿。
260
+
261
+ 运行时拒绝 `independentSessions`,显式执行 `jeo messenger setup` 迁移才会移除该项。`sessionDir` 必须是工作区外、与之不重叠的私有路径。工作区中的旧 `.jeo/sessions`、`.jeo/messenger-sessions`、`.jeo/memory/raw` 会话与原始记录存储会阻止启动并给出移动指引;重启前须主动迁移,不会自动复制或删除。完整对话历史只留在父进程专用存储,蒸馏后的项目学习共享。复用现有 ECC 技能/工作流安装,未进行全局重装;受保护的全局技能在执行前保存快照,工作区/获准文档通过客体读取。
262
+
263
+ **验证范围(2026-11-03):** 实际的 macOS 真实 Docker 验证器(`bun run verify:sandbox`)通过 **20 项行为契约**,源码独立构建也已通过。复制出的独立二进制通过了范围有限的验证,涵盖真实 PTY FIFO、父进程 `sandbox_request` 与真实 Linux 客体工具、完整历史隐私、与父进程生成的 stderr 关闭 nonce 匹配的 ACK、真实 worker 退出(143)以及 Docker 容器不存在。当前源码包装层保留 teardown 清理所有者、有界的关闭等待、人类按会话授权、一个持久线程/FIFO 以及共享工作区。这不是完整测试套件、跨平台或生产环境的验证:完整 `bun test` 超时,**未通过**;通用聚焦套件仍有陈旧 fixture 失败,正在修复。原生 Linux 和 Windows 主机验证暂不可用,也未实际向 Discord 发送消息。macOS 上的 Linux 客体不等于主机认证。`bun run build:hands` 构建 worker;`bun run verify:sandbox` 运行真实 Docker 检查,不安装前置依赖。必须确认 worker/容器已清理,错误或未确认移除不能报告成功。
264
+
241
265
  各平台详细独立指南:
242
266
  - 📖 [渠道集成概览与架构](docs/integrations/README.md) — 功能对比矩阵与架构图
243
267
  - 📱 [Telegram 集成指南](docs/integrations/telegram.md) — 内置后台守护进程、BotFather 交互配对、论坛主题与 `/steer` 命令
@@ -267,7 +291,7 @@ jeo daemon start|stop|status|reload # the shared daemon has no provider select
267
291
  - **Setup** 会通过官方 API 验证机器人身份和目标访问权限(Telegram `getMe`,Discord 机器人 + 频道查询,Slack `auth.test`/`bots.info`/`conversations.info` 以及一个 Socket Mode URL),并把结果存入 `~/.jeo/config.json` 的 `notifications.<provider>` 下 — **明文**;请保持该文件私有。它不会发送消息,也不会打开 socket。
268
292
  - **`initialized`**(`jeo daemon status`)表示守护进程已持有自己的锁并启动了各传输层;它并不证明 Telegram 轮询、Discord Gateway 握手或 Slack Socket Mode 连接已经成功。要验证这些请使用 `notify health` / `notify test`。
269
293
  - **更改 token 或目标**会重置 Discord 和 Slack 的 allowlist(你必须再次传入 `--allowed-user-ids`)以及 Telegram 的每会话主题映射;用完全相同的凭证和目标重新运行 setup 则会保留它们。
270
- - **远程命令是字面文本**(`/subagents` 等),仅对已配置的聊天/频道和 allowlist 授权;Slack 还额外接受同名的已注册斜杠命令。allowlist 中的操作者实际上拥有本地代理的全部能力 — 请把这份名单当作信任边界对待。
294
+ - **远程命令是字面文本**(`/subagents` 等),仅对配置的聊天/频道及白名单授权;Slack 也接受同名注册斜杠命令。Discord 终端控制默认关闭,须在沙箱会话中本地提交 `/discord control on --allow-full-control` 开启,`/discord control off` 撤销。遵守上文沙箱和人类授权边界,不要在聊天中放入凭据。
271
295
  - **重试按提供商有界**: Telegram 遵守 429 的 `retry_after` 冷却(限制在 1 秒–1 小时之间)并使用独立的轮询退避;Discord 和 Slack 在 429 时按服务器的 `Retry-After` 最多重试 3 次,且绝不重发结果未知的 POST(平台可能已经收到了它)。
272
296
  - **孤儿恢复**: 守护进程崩溃后,其锁会变为 `stale`;下一次 `jeo daemon start` 会回收它。当宿主机能够报告进程启动时间时,`jeo daemon stop` 会拒绝向已被复用的 PID 发送信号;无法报告时也会明确说明。
273
297
  - **目前已验证**: Telegram 端到端(真实发送、轮询、干净关闭)。Discord 和 Slack 基于同一个守护进程实现,配有离线的传输/接线测试以及代码、安全和类型审查;尚未用真实的机器人/应用凭证进行实际连通性验证。
@@ -322,7 +346,7 @@ jeo routine init --trigger schedule --cron "0 7 * * *" --prompt "Re-run the eval
322
346
  jeo routine init --trigger issues --prompt "Triage this issue" --name "issue-triage"
323
347
  ```
324
348
 
325
- 生成一个 GitHub Actions 工作流(`.github/workflows/<name>.yml`),安装 jeo 并在 `schedule` / `issues` / `pull_request` 上以无头模式运行它(`jeo "<prompt>" -p`)— 始终与 `workflow_dispatch` 搭配,便于手动测试运行 — 运行在 GitHub 自己的托管 runner 上。这就是 jeo "无需笔记本电脑运行"的故事:jeo 自身内部没有进程内调度器、没有 webhook 监听器、没有代码执行沙箱 — 由 GitHub 的基础设施负责触发,jeo 只是运行它现有的无头模式。默认在有任何变更时开一个 PR(`peter-evans/create-pull-request`,diff 为空时是安全的空操作);`--no-pr` 则改为直接提交到触发分支。`--dry-run` 只打印 YAML 而不写入;在相同的 `--out` 路径上重新运行 `jeo routine init`,若不加 `--force` 会拒绝覆盖。请在工作流首次真正运行前,设置好仓库密钥 `ANTHROPIC_API_KEY`(或 `--api-key-env <VAR>`)。
349
+ 生成 GitHub Actions 工作流(`.github/workflows/<name>.yml`),在 GitHub 托管 runner 上安装 jeo,并按 `schedule` / `issues` / `pull_request` 无头执行(`jeo "<prompt>" -p`),同时提供手动执行的 `workflow_dispatch`。GitHub 负责触发;例行任务不增加进程内调度器或 webhook 监听器。会话沙箱是需要上述本地前置条件的独立显式配置,不由工作流生成器安装。默认对变更创建 PR(`peter-evans/create-pull-request`,空 diff 为 no-op);`--no-pr` 则提交到触发分支。`--dry-run` 仅输出 YAML,覆盖已有 `--out` 须加 `--force`。首次实际运行前设置仓库 secret `ANTHROPIC_API_KEY`(或 `--api-key-env <VAR>`)。
326
350
 
327
351
  ## 本地模型
328
352
 
@@ -335,7 +359,7 @@ jeo doctor && jeo
335
359
  ## 配置
336
360
 
337
361
  - 全局配置: `~/.jeo/config.json`(模型选择 MRU 持久化)
338
- - 项目状态/会话: `<project>/.jeo/`
362
+ - 项目状态:`<project>/.jeo/`;沙箱会话记录保留在工作区外(参见上文 Discord 会话边界)。
339
363
 
340
364
  ```bash
341
365
  ANTHROPIC_API_KEY=... OPENAI_API_KEY=... GEMINI_API_KEY=...
@@ -420,11 +444,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
420
444
  ## 更新日志 (Changelog)
421
445
 
422
446
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
447
+ - **[Unreleased]**
448
+ - **[0.12.16]** (2026-11-04)
449
+ - **[0.12.15]** (2026-11-03)
450
+ - **[0.12.14]** (2026-11-03)
423
451
  - **[0.12.13]** (2026-11-03)
424
- - **[0.12.12]** (2026-11-03)
425
- - **[0.12.10]** (2026-11-02)
426
- - **[0.12.11]** (2026-11-02)
427
- - **[0.12.9]** (2026-11-02)
428
452
 
429
453
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
430
454
  <!-- CHANGELOG:END -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jeo-code",
3
- "version": "0.12.13",
3
+ "version": "0.12.16",
4
4
  "description": "Clean, highly optimized AI coding agent using spec-first loop",
5
5
  "type": "module",
6
6
 
@@ -46,13 +46,15 @@
46
46
  "scripts": {
47
47
  "start": "bun src/cli.ts",
48
48
  "typecheck": "tsc -p tsconfig.json --noEmit",
49
- "build": "bun build src/cli.ts --compile --keep-names --external chromium-bidi --no-compile-autoload-dotenv --outfile dist/jeo",
49
+ "build": "bun scripts/build-sandbox-worker.ts && bun build src/cli.ts --compile --keep-names --external chromium-bidi --no-compile-autoload-dotenv --outfile dist/jeo",
50
50
  "build:binaries": "bun scripts/ci-release-build-binaries.ts",
51
51
  "build:win": "bun scripts/ci-release-build-binaries.ts --targets win32-x64",
52
52
  "pack:check": "bun scripts/check-package.ts",
53
53
  "publish:npm": "npm publish --access public --registry https://registry.npmjs.org/",
54
54
  "changelog:sync": "bun scripts/sync-changelog.ts",
55
55
  "test": "bun test",
56
+ "build:hands": "bun scripts/build-sandbox-worker.ts",
57
+ "verify:sandbox": "bun scripts/verify-sandbox-isolation.ts",
56
58
  "verify:browser": "bun scripts/verify-browser.ts",
57
59
  "verify:discord": "bun scripts/verify-discord-integration.ts",
58
60
  "verify:tmux": "bash scripts/tmux-verify.sh",
@@ -10,7 +10,7 @@ import * as os from "node:os";
10
10
  import * as path from "node:path";
11
11
  import type { ToolHandler } from "./engine";
12
12
  import type { ToolResult } from "./tools";
13
- import { browserSession, type BrowserSession } from "./browser-session";
13
+ import type { BrowserSession } from "./browser-session";
14
14
  import type { ObservedElement, ExtractFormat } from "./browser-tab";
15
15
  import type { ImageAttachment } from "../ai/types";
16
16
  import { visionVerify } from "./vision-verify";
@@ -149,6 +149,14 @@ async function runActStep(session: BrowserSession, name: string, step: ActStep):
149
149
  export function createBrowserTool(): ToolHandler {
150
150
  return async (args: Record<string, any>, _cwd: string): Promise<ToolResult> => {
151
151
  const action = String(args.action ?? "").trim().toLowerCase();
152
+ let browserSession: BrowserSession;
153
+ try {
154
+ // Optional platform/image capability: minimal Linux hands images need not include Playwright.
155
+ // Loading it eagerly would prevent unrelated filesystem tools from starting.
156
+ ({ browserSession } = await import("./browser-session"));
157
+ } catch (error) {
158
+ return err(`Browser runtime unavailable in this execution environment: ${error instanceof Error ? error.message : String(error)}`);
159
+ }
152
160
 
153
161
  if (action === "open") {
154
162
  const name = tabNameOf(args);