jeo-code 0.6.40 → 0.7.0
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 +34 -0
- package/README.ja.md +31 -38
- package/README.ko.md +31 -38
- package/README.md +32 -38
- package/README.zh.md +31 -38
- package/package.json +4 -2
- package/src/agent/compaction.ts +2 -1
- package/src/agent/computer-supervisor.ts +59 -0
- package/src/agent/config-schema.ts +6 -0
- package/src/agent/engine.ts +9 -4
- package/src/agent/json.ts +75 -8
- package/src/agent/loop-guards.ts +16 -5
- package/src/agent/memory-okf.ts +2 -0
- package/src/agent/memory.ts +25 -8
- package/src/agent/state.ts +39 -7
- package/src/agent/task-tool.ts +12 -3
- package/src/agent/tool-schemas.ts +19 -0
- package/src/ai/model-catalog.ts +4 -0
- package/src/ai/model-manager.ts +23 -5
- package/src/ai/providers/anthropic.ts +10 -6
- package/src/ai/providers/openai-compatible-catalog.ts +2 -0
- package/src/ai/types.ts +2 -1
- package/src/auth/index.ts +1 -1
- package/src/auth/refresh.ts +47 -2
- package/src/auth/storage.ts +11 -5
- package/src/cli/runner.ts +9 -0
- package/src/cli.ts +16 -0
- package/src/commands/approve.ts +17 -1
- package/src/commands/computer.ts +334 -0
- package/src/commands/launch/input.ts +19 -6
- package/src/commands/launch.ts +56 -8
- package/src/commands/ralplan.ts +4 -0
- package/src/commands/team.ts +13 -0
- package/src/index.ts +1 -0
- package/src/prompts/agents/executor.md +6 -4
- package/src/prompts/skills/ralplan/SKILL.md +5 -0
- package/src/prompts/skills/team/SKILL.md +5 -0
- package/src/tui/components/slash.ts +1 -0
- package/src/util/terminal-restore.ts +41 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
The README mirrors the latest 5 entries — regenerate with `bun run changelog:sync`.
|
|
8
8
|
|
|
9
|
+
## [0.7.0] - 2026-06-22
|
|
10
|
+
_Adds an opt-in, fail-closed **computer-use** capability (a `computer` tool + `jeo computer` CLI for screenshot/click/type/keypress/scroll/drag/wait/batch), hardens the spec-first workflow against silent post-consensus plan edits and PID-reuse lock starvation, makes OAuth refresh degrade cleanly when the refresh token is dead, and teaches tool-arg JSON repair to tolerate unescaped control characters. Verified leak-free (`mem-probe`, 2000 turns, −447 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6)._
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Computer-use (opt-in, disabled by default).** A new `computer` agent tool and `jeo computer <action>` CLI execute desktop automation — `screenshot`, `click`, `double_click`, `move`, `drag`, `scroll`, `type`, `keypress`, `wait`, and `batch` — via native OS tools (`screencapture`/`scrot`, `cliclick`/`xdotool`). It is gated behind `computer.enabled: true` in `~/.jeo/config.json` (off unless explicitly enabled) and a **fail-closed `ComputerSupervisor`**: side-effecting actions are blocked unless a live kill switch, a fresh heartbeat, and a non-suspended state all hold; read-only `screenshot`/`wait` are allowed. A new mid-turn **`Ctrl+\` kill switch** binding lets the operator suspend automation instantly, and every action is appended to `.jeo/computer-audit.jsonl`.
|
|
14
|
+
- **Tencent Cloud MaaS provider** (`tencent`, `TENCENT_API_KEY`), served over the Anthropic Messages wire format (`deepseek-v4-pro`/`-flash`, `minimax-m3`).
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- **A plan can no longer be silently edited after the consensus critic's `[OKAY]` verdict (round-13).** `ralplan` now records a SHA-256 `consensus_hash` of the exact plan content the critic gated; `jeo approve` and `jeo team` recompute the on-disk plan's hash and refuse to proceed if it differs, directing the operator to re-run `ralplan` for a fresh review. A schema-valid post-verdict edit can no longer ride through approval/execution unreviewed.
|
|
18
|
+
- **The team/ultragoal run-lock no longer starves on PID reuse (round-13).** `process.kill(pid, 0)` alone treats a recycled dead pid as "alive" forever; the holder now heartbeats the lock's `at` timestamp on an unref'd interval and a contender treats the lock as stale when the pid is dead **or** the timestamp is older than a TTL (`JEO_RUN_LOCK_TTL_MS`, default 60s).
|
|
19
|
+
- **A dead OAuth refresh token now degrades cleanly instead of looping on a doomed refresh.** `refreshOAuthToken` classifies a refresh failure as *definitive* (`invalid_grant`/revoked/401-403) vs *transient* (timeout/network); a definitive failure clears the credential and falls back to an API key (or a logged-out state that prompts re-login), while a transient blip leaves the stale token untouched for the next sweep.
|
|
20
|
+
- **Tool-argument JSON repair now tolerates unescaped control characters.** A model that emits a literal newline/tab inside a string value (a frequent slip when an `edit`/`write` payload carries multi-line code) previously made the whole tool call unparseable; `tryParse` now layers a control-char escaper with the existing trailing-comma repair, never altering already-valid JSON.
|
|
21
|
+
- **A background crash no longer leaves the shell mute.** `src/cli.ts` installs `uncaughtException`/`unhandledRejection` handlers that synchronously restore the terminal (raw mode, bracketed paste) via a new `restoreTerminalState()` and print one clean error line instead of a raw stack dump over a broken REPL.
|
|
22
|
+
- Anthropic streaming now captures `stop_reason` from `message_start`.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- **Memory and prompts now preserve failed approaches.** OKF memory gains a `FailedAttempt` concept type (one dead-end + its cause per entry), compaction is told to retain tried-and-failed approaches and unconfirmed hypotheses, and the executor/loop prompts add a reflect-on-failure directive ("state what the failure taught you, then change the tool/arguments"), a running task-state instruction, and an explicit "a passing test is not a met requirement" guard.
|
|
26
|
+
|
|
27
|
+
### Verified
|
|
28
|
+
- `bun run typecheck` clean; full suite **1798 pass / 0 fail** (221 files), including the new `computer`, `computer-supervisor`, `terminal-restore`, and `tencent-provider` suites plus consensus-hash and refresh-classification coverage.
|
|
29
|
+
- `scripts/mem-probe.ts` (2000 turns × 40 tools): no long-term leak (per-turn slope **−447 bytes/turn**, exit-listeners flat at 1, heap returns to baseline).
|
|
30
|
+
- `scripts/tmux-verify.sh battery`: **6/6 PASSED** on a fresh `jeo --tmux` boot (boot, `/help`, unknown `$skill`, `/agents`, `$ultragoal`, unresolved `/command`).
|
|
31
|
+
|
|
32
|
+
## [0.6.41] - 2026-06-21
|
|
33
|
+
_The non-streaming model path now inherits the v0.6.40 300s window: an unattended long-reasoning turn (compaction, ralplan, deep-interview, memory distill, goal-verify, subagent/autopilot steps) is no longer false-aborted at the old 120s wall cap. Re-verified leak-free (`mem-probe`, 2000 turns, −610 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6)._
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
- **The non-streaming `call()` path no longer false-aborts a long completion at 120s — it now matches the 300s streaming idle window.** v0.6.40's wire heartbeat only re-armed the streaming watchdog (`manager.stream()`); the non-streaming twin `manager.call()` — taken by every `callLlm` WITHOUT an `onToken` consumer (`compaction`, `ralplan`, `deep-interview`, `memory` distill, `goal-verifier`, and subagent/autopilot engine steps) — kept a hard 120s wall cap, so the exact unattended long-reasoning turns the release meant to protect still aborted, exhausted the attempt budget, and stopped the turn. The cap is now 300s, matching `STREAM_IDLE_TIMEOUT_MS`, via a new env-overridable `callTimeoutMs()` helper (`JEO_CALL_TIMEOUT_MS`) mirroring the existing `streamIdleMs()`. A wire heartbeat can't help here — the path collects an opaque buffered body with no per-chunk signal — so a wall clock is the only correct lever; non-streaming `readSse` sites and `call()`'s buffered reads are deliberately left without an idle watchdog (it would false-abort a healthy opaque read).
|
|
37
|
+
|
|
38
|
+
### Verified
|
|
39
|
+
- `bun run typecheck` clean; full suite **1756 pass / 0 fail** (216 files), including the new `callTimeoutMs` default + `JEO_CALL_TIMEOUT_MS` override parsing tests in `test/round-b.test.ts`.
|
|
40
|
+
- `scripts/mem-probe.ts` (2000 turns × 40 tools): no long-term leak (per-turn slope **−610 bytes/turn**, exit-listeners flat at 1, heap returns to baseline).
|
|
41
|
+
- `scripts/tmux-verify.sh battery`: **6/6 PASSED** on a fresh `jeo --tmux` boot (boot, `/help`, unknown `$skill`, `/agents`, `$ultragoal`, unresolved `/command`).
|
|
42
|
+
|
|
9
43
|
## [0.6.40] - 2026-06-21
|
|
10
44
|
_Wire-level stream heartbeat: ANY bytes from the provider (SSE keepalive/ping, filtered events) now re-arm the idle watchdog — a connected-but-quiet stream can no longer trip a false `stream idle` retry. Default idle window raised to 300s so Ollama/llama.cpp model-load silence no longer exhausts retries and stops the turn._
|
|
11
45
|
|
package/README.ja.md
CHANGED
|
@@ -96,6 +96,31 @@ jeo approve <プランパス>
|
|
|
96
96
|
jeo team
|
|
97
97
|
jeo ultragoal
|
|
98
98
|
```
|
|
99
|
+
```
|
|
100
|
+
┌──────────────────────┐
|
|
101
|
+
│ deep-interview │ Socratic ambiguity gate · seed frozen when concrete
|
|
102
|
+
└──────────┬───────────┘
|
|
103
|
+
│ .jeo/state/<seed>.json
|
|
104
|
+
▼
|
|
105
|
+
┌──────────────────────┐
|
|
106
|
+
│ ralplan │ Draft + repo-grounded critic → [OKAY] persisted
|
|
107
|
+
└──────────┬───────────┘
|
|
108
|
+
│ requires [OKAY] verdict
|
|
109
|
+
▼
|
|
110
|
+
┌──────────────────────┐
|
|
111
|
+
│ approve │ Schema + roles + [OKAY] — unlocks execution
|
|
112
|
+
└──────────┬───────────┘
|
|
113
|
+
│
|
|
114
|
+
▼
|
|
115
|
+
┌──────────────────────┐
|
|
116
|
+
│ team │ Serial executor · run lock · mutation audit
|
|
117
|
+
└──────────┬───────────┘
|
|
118
|
+
│ all tasks done
|
|
119
|
+
▼
|
|
120
|
+
┌──────────────────────┐
|
|
121
|
+
│ ultragoal │ Honest verification — suite once, no fabrication
|
|
122
|
+
└──────────────────────┘
|
|
123
|
+
```
|
|
99
124
|
|
|
100
125
|
- **deep-interview** — 曖昧度スコアリングのソクラテス式ループ。基準が具体的な場合のみシードを凍結(曖昧のみの基準は拒否)、シードは自身のパーサをラウンドトリップで通過する必要があります。新しいアイデアが完了済みインタビューを黙って再利用することはありません。
|
|
101
126
|
- **ralplan** — ドラフトパス + **リポジトリを実際に読む critic サブエージェントゲート**: `[OKAY]`/`[ITERATE]`/`[REJECT]` 評決が強制・永続化されます。無効なプラン(スキーマ・未知ロール)は complete になりません。
|
|
@@ -123,42 +148,6 @@ jeo ultragoal
|
|
|
123
148
|
|
|
124
149
|
`jeo` は `.jeo/memory/` 配下に **ローカルファースト・蒸留されたプロジェクトメモリ** を保持します(リモートバックエンドなし、ネイティブ依存ゼロ)。過去のセッションは [OKF](docs/okf_mem/) コンセプトバンドルへ蒸留され、次のセッションは関連性の高い予算内のスライスだけをシステムプロンプトへ再注入します — 指示ではなく DATA として堅牢化されます。`JEO_NO_MEMORY=1` ですべて無効化。
|
|
125
150
|
|
|
126
|
-
📐 **編集可能な図:** [`docs/diagrams/memory-flow.drawio`](docs/diagrams/memory-flow.drawio)([draw.io](https://app.diagrams.net) / デスクトップアプリで開く) — 書き込み/保存/読み込み/移行の全スイムレーン。概要:
|
|
127
|
-
|
|
128
|
-
```mermaid
|
|
129
|
-
flowchart LR
|
|
130
|
-
subgraph WRITE["WRITE — session-end distill (detached, best-effort)"]
|
|
131
|
-
direction TB
|
|
132
|
-
W1["session exit / ^C^C"] --> W2["spawnDetachedDistill()<br/>payload + detached child, returns instantly"]
|
|
133
|
-
W2 --> W3["distillSessionMemory()<br/>load bundle · transcriptTail · ONE LLM call (JSON)"]
|
|
134
|
-
W3 --> WD{"concepts JSON<br/>parsed?"}
|
|
135
|
-
WD -->|yes| WY["per concept: upsert by title,<br/>atomic write into facts/ commands/<br/>gotchas/ preferences/"]
|
|
136
|
-
WD -->|no| WN["plain text →<br/>legacy MEMORY.md"]
|
|
137
|
-
WY --> WR["rebuildIndex() index.md<br/>updateLog() log.md"]
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
subgraph STORE[".jeo/memory/ — OKF concept bundle"]
|
|
141
|
-
direction TB
|
|
142
|
-
S1["facts/ · commands/ · gotchas/ · preferences/<br/>(YAML frontmatter + body)"]
|
|
143
|
-
S2["index.md · log.md · cross-link graph (Sprint 04)"]
|
|
144
|
-
S3["MEMORY.md (legacy fallback)<br/>MEMORY.md.bak (rollback)"]
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
subgraph READ["READ — memoryPromptSection(cwd, query)"]
|
|
148
|
-
direction TB
|
|
149
|
-
R1["session start (query = task text)"] --> R2{"bundle has<br/>concepts?"}
|
|
150
|
-
R2 -->|yes| R3["selectWithinBudget()<br/>core → query relevance → 1-hop graph<br/>≤ MEMORY_INJECT_MAX_CHARS (3000)"]
|
|
151
|
-
R2 -->|no| R3B["legacy loadMemory()"]
|
|
152
|
-
R3 --> R4["frameMemory()<br/>hard cap · fence-neutralize · DATA framing"]
|
|
153
|
-
R3B --> R4
|
|
154
|
-
R4 --> R5["<project_memory> … injected into system prompt"]
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
WR -->|atomic| STORE
|
|
158
|
-
WN -->|fallback| S3
|
|
159
|
-
STORE -.->|loadConcepts / loadMemory| READ
|
|
160
|
-
```
|
|
161
|
-
|
|
162
151
|
**移行(`jeo memory-migrate`、ワンショット・冪等).** レガシーの単一ドキュメント `MEMORY.md` をロスレスでバンドルへ変換します: `## 見出し → タイプ`、各箇条書き → タイプ別コンセプト、インデント行 → 本文; `index.md`/`log.md` を再構築し、元ファイルを `MEMORY.md.bak` にリネームします。バンドルにコンセプトができた後の再実行は no-op です。**ロールバック:** `JEO_MEMORY_LEGACY=1` はバンドルを無視し、同じ注入堅牢化を通して `MEMORY.md`/`.bak` を読みます(`JEO_NO_MEMORY=1` がすべてに優先)。
|
|
163
152
|
|
|
164
153
|
## ローカルモデル
|
|
@@ -197,14 +186,18 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
|
|
|
197
186
|
- `jeo-code` パッケージへの Read/Write 権限を持つ **Granular Access Token**、またはクラシック **Automation** トークン
|
|
198
187
|
- 「公開時の **bypass 2FA**」許可が必須 — Automation トークンは常にバイパス、granular トークンはオプションの有効化が必要
|
|
199
188
|
|
|
189
|
+
## 謝辞 (Acknowledgements)
|
|
190
|
+
|
|
191
|
+
[gajae-code](https://github.com/Yeachan-Heo/gajae-code) に多大な感謝を。
|
|
192
|
+
|
|
200
193
|
## 変更履歴 (Changelog)
|
|
201
194
|
|
|
202
195
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
196
|
+
- **[0.7.0]** (2026-06-22) — Adds an opt-in, fail-closed **computer-use** capability (a `computer` tool + `jeo computer` CLI for screenshot/click/type/keypress/scroll/drag/wait/batch), hardens the spec-first workflow against silent post-consensus plan edits and PID-reuse lock starvation, makes OAuth refresh degrade cleanly when the refresh token is dead, and teaches tool-arg JSON repair to tolerate unescaped control characters. Verified leak-free (`mem-probe`, 2000 turns, −447 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
197
|
+
- **[0.6.41]** (2026-06-21) — The non-streaming model path now inherits the v0.6.40 300s window: an unattended long-reasoning turn (compaction, ralplan, deep-interview, memory distill, goal-verify, subagent/autopilot steps) is no longer false-aborted at the old 120s wall cap. Re-verified leak-free (`mem-probe`, 2000 turns, −610 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
203
198
|
- **[0.6.40]** (2026-06-21) — Wire-level stream heartbeat: ANY bytes from the provider (SSE keepalive/ping, filtered events) now re-arm the idle watchdog — a connected-but-quiet stream can no longer trip a false `stream idle` retry. Default idle window raised to 300s so Ollama/llama.cpp model-load silence no longer exhausts retries and stops the turn.
|
|
204
199
|
- **[0.6.39]** (2026-06-21) — A long "thinking" phase no longer trips a false stream-idle retry: reasoning/thinking deltas now act as a stream heartbeat, so a model that streams thought tokens past the idle window before any visible text is no longer mistaken for a stalled stream and retried (which discarded the in-progress reasoning). Re-verified leak-free (`mem-probe`, 2000 turns, −525 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
205
200
|
- **[0.6.38]** (2026-06-21) — The OKF concept-bundle memory no longer silently drops what it learns: a legacy single-doc `MEMORY.md` (or a text-only distill fallback) can no longer shadow the concept bundle, break OKF conformance, or lose a turn's learnings — its content is folded into the concept merge and the stale blob is archived. Re-verified leak-free (`mem-probe`, 2000 turns, −570 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
206
|
-
- **[0.6.37]** (2026-06-20) — Two dead-end fixes: the boxed prompt's ↑/↓ now recalls input history on a soft-wrapped one-liner (only a genuine multi-line draft gets in-box caret nav), and every terminating Spec-first stage (deep-interview, ralplan, team) now surfaces a user-visible answer instead of silently stalling — re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
|
|
207
|
-
- **[0.6.36]** (2026-06-20) — When `jeo --tmux` flips the mouse on so you can drag-select, the drag now actually lands on the system clipboard — the in-session tmux profile sets `set-clipboard on` + a local `copy-command` on the CURRENT session only — plus `/help` documents the drag-to-copy and the Shift/Option-drag escape hatch, re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
|
|
208
201
|
|
|
209
202
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
210
203
|
<!-- CHANGELOG:END -->
|
package/README.ko.md
CHANGED
|
@@ -96,6 +96,31 @@ jeo approve <플랜경로>
|
|
|
96
96
|
jeo team
|
|
97
97
|
jeo ultragoal
|
|
98
98
|
```
|
|
99
|
+
```
|
|
100
|
+
┌──────────────────────┐
|
|
101
|
+
│ deep-interview │ Socratic ambiguity gate · seed frozen when concrete
|
|
102
|
+
└──────────┬───────────┘
|
|
103
|
+
│ .jeo/state/<seed>.json
|
|
104
|
+
▼
|
|
105
|
+
┌──────────────────────┐
|
|
106
|
+
│ ralplan │ Draft + repo-grounded critic → [OKAY] persisted
|
|
107
|
+
└──────────┬───────────┘
|
|
108
|
+
│ requires [OKAY] verdict
|
|
109
|
+
▼
|
|
110
|
+
┌──────────────────────┐
|
|
111
|
+
│ approve │ Schema + roles + [OKAY] — unlocks execution
|
|
112
|
+
└──────────┬───────────┘
|
|
113
|
+
│
|
|
114
|
+
▼
|
|
115
|
+
┌──────────────────────┐
|
|
116
|
+
│ team │ Serial executor · run lock · mutation audit
|
|
117
|
+
└──────────┬───────────┘
|
|
118
|
+
│ all tasks done
|
|
119
|
+
▼
|
|
120
|
+
┌──────────────────────┐
|
|
121
|
+
│ ultragoal │ Honest verification — suite once, no fabrication
|
|
122
|
+
└──────────────────────┘
|
|
123
|
+
```
|
|
99
124
|
|
|
100
125
|
- **deep-interview** — 모호성 스코어링 기반 소크라테스 루프. 기준이 구체적일 때만 시드 동결(vague-only 기준은 거부), 시드는 자체 파서 라운드트립을 통과해야 합니다. 새 아이디어가 완료된 인터뷰를 조용히 재사용하지 않습니다.
|
|
101
126
|
- **ralplan** — 드래프팅 패스 + **저장소를 직접 읽는 critic 서브에이전트 게이트**: `[OKAY]`/`[ITERATE]`/`[REJECT]` 평결이 강제·영속됩니다. 무효 플랜(스키마·미지 역할)은 complete로 마킹되지 않습니다.
|
|
@@ -123,42 +148,6 @@ jeo ultragoal
|
|
|
123
148
|
|
|
124
149
|
`jeo`는 `.jeo/memory/` 아래에 **로컬 우선·증류된 프로젝트 메모리**를 둡니다(원격 백엔드 없음, 네이티브 의존성 0). 지난 세션은 [OKF](docs/okf_mem/) 개념 번들로 증류되고, 다음 세션은 관련성 높은 예산 한도 내 일부만 시스템 프롬프트로 다시 주입합니다 — 지시가 아닌 DATA로 강화 처리됩니다. `JEO_NO_MEMORY=1`로 전체 비활성화.
|
|
125
150
|
|
|
126
|
-
📐 **편집 가능한 다이어그램:** [`docs/diagrams/memory-flow.drawio`](docs/diagrams/memory-flow.drawio) ([draw.io](https://app.diagrams.net) / 데스크톱 앱에서 열기) — 쓰기/저장/읽기/마이그레이션 전체 스윔레인. 요약 보기:
|
|
127
|
-
|
|
128
|
-
```mermaid
|
|
129
|
-
flowchart LR
|
|
130
|
-
subgraph WRITE["WRITE — session-end distill (detached, best-effort)"]
|
|
131
|
-
direction TB
|
|
132
|
-
W1["session exit / ^C^C"] --> W2["spawnDetachedDistill()<br/>payload + detached child, returns instantly"]
|
|
133
|
-
W2 --> W3["distillSessionMemory()<br/>load bundle · transcriptTail · ONE LLM call (JSON)"]
|
|
134
|
-
W3 --> WD{"concepts JSON<br/>parsed?"}
|
|
135
|
-
WD -->|yes| WY["per concept: upsert by title,<br/>atomic write into facts/ commands/<br/>gotchas/ preferences/"]
|
|
136
|
-
WD -->|no| WN["plain text →<br/>legacy MEMORY.md"]
|
|
137
|
-
WY --> WR["rebuildIndex() index.md<br/>updateLog() log.md"]
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
subgraph STORE[".jeo/memory/ — OKF concept bundle"]
|
|
141
|
-
direction TB
|
|
142
|
-
S1["facts/ · commands/ · gotchas/ · preferences/<br/>(YAML frontmatter + body)"]
|
|
143
|
-
S2["index.md · log.md · cross-link graph (Sprint 04)"]
|
|
144
|
-
S3["MEMORY.md (legacy fallback)<br/>MEMORY.md.bak (rollback)"]
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
subgraph READ["READ — memoryPromptSection(cwd, query)"]
|
|
148
|
-
direction TB
|
|
149
|
-
R1["session start (query = task text)"] --> R2{"bundle has<br/>concepts?"}
|
|
150
|
-
R2 -->|yes| R3["selectWithinBudget()<br/>core → query relevance → 1-hop graph<br/>≤ MEMORY_INJECT_MAX_CHARS (3000)"]
|
|
151
|
-
R2 -->|no| R3B["legacy loadMemory()"]
|
|
152
|
-
R3 --> R4["frameMemory()<br/>hard cap · fence-neutralize · DATA framing"]
|
|
153
|
-
R3B --> R4
|
|
154
|
-
R4 --> R5["<project_memory> … injected into system prompt"]
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
WR -->|atomic| STORE
|
|
158
|
-
WN -->|fallback| S3
|
|
159
|
-
STORE -.->|loadConcepts / loadMemory| READ
|
|
160
|
-
```
|
|
161
|
-
|
|
162
151
|
**마이그레이션 (`jeo memory-migrate`, 1회성 · 멱등).** 레거시 단일 문서 `MEMORY.md`를 무손실로 번들로 변환합니다: `## 헤딩 → 타입`, 각 불릿 → 타입별 개념, 들여쓴 줄 → 본문; `index.md`/`log.md`를 재생성하고 원본은 `MEMORY.md.bak`으로 이름을 바꿉니다. 번들에 개념이 생긴 뒤 재실행은 no-op입니다. **롤백:** `JEO_MEMORY_LEGACY=1`은 번들을 무시하고 동일한 주입 강화 처리로 `MEMORY.md`/`.bak`를 읽습니다(`JEO_NO_MEMORY=1`이 모든 것에 우선).
|
|
163
152
|
|
|
164
153
|
## 로컬 모델
|
|
@@ -197,14 +186,18 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
|
|
|
197
186
|
- `jeo-code` 패키지에 Read/Write 권한이 있는 **Granular Access Token**, 또는 클래식 **Automation** 토큰
|
|
198
187
|
- "배포 시 **bypass 2FA**" 허용 필수 — Automation 토큰은 항상 우회, granular 토큰은 옵션 활성화 필요
|
|
199
188
|
|
|
189
|
+
## 감사의 말 (Acknowledgements)
|
|
190
|
+
|
|
191
|
+
[gajae-code](https://github.com/Yeachan-Heo/gajae-code)에 깊은 감사를 드립니다.
|
|
192
|
+
|
|
200
193
|
## 변경 이력 (Changelog)
|
|
201
194
|
|
|
202
195
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
196
|
+
- **[0.7.0]** (2026-06-22) — Adds an opt-in, fail-closed **computer-use** capability (a `computer` tool + `jeo computer` CLI for screenshot/click/type/keypress/scroll/drag/wait/batch), hardens the spec-first workflow against silent post-consensus plan edits and PID-reuse lock starvation, makes OAuth refresh degrade cleanly when the refresh token is dead, and teaches tool-arg JSON repair to tolerate unescaped control characters. Verified leak-free (`mem-probe`, 2000 turns, −447 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
197
|
+
- **[0.6.41]** (2026-06-21) — The non-streaming model path now inherits the v0.6.40 300s window: an unattended long-reasoning turn (compaction, ralplan, deep-interview, memory distill, goal-verify, subagent/autopilot steps) is no longer false-aborted at the old 120s wall cap. Re-verified leak-free (`mem-probe`, 2000 turns, −610 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
203
198
|
- **[0.6.40]** (2026-06-21) — Wire-level stream heartbeat: ANY bytes from the provider (SSE keepalive/ping, filtered events) now re-arm the idle watchdog — a connected-but-quiet stream can no longer trip a false `stream idle` retry. Default idle window raised to 300s so Ollama/llama.cpp model-load silence no longer exhausts retries and stops the turn.
|
|
204
199
|
- **[0.6.39]** (2026-06-21) — A long "thinking" phase no longer trips a false stream-idle retry: reasoning/thinking deltas now act as a stream heartbeat, so a model that streams thought tokens past the idle window before any visible text is no longer mistaken for a stalled stream and retried (which discarded the in-progress reasoning). Re-verified leak-free (`mem-probe`, 2000 turns, −525 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
205
200
|
- **[0.6.38]** (2026-06-21) — The OKF concept-bundle memory no longer silently drops what it learns: a legacy single-doc `MEMORY.md` (or a text-only distill fallback) can no longer shadow the concept bundle, break OKF conformance, or lose a turn's learnings — its content is folded into the concept merge and the stale blob is archived. Re-verified leak-free (`mem-probe`, 2000 turns, −570 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
206
|
-
- **[0.6.37]** (2026-06-20) — Two dead-end fixes: the boxed prompt's ↑/↓ now recalls input history on a soft-wrapped one-liner (only a genuine multi-line draft gets in-box caret nav), and every terminating Spec-first stage (deep-interview, ralplan, team) now surfaces a user-visible answer instead of silently stalling — re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
|
|
207
|
-
- **[0.6.36]** (2026-06-20) — When `jeo --tmux` flips the mouse on so you can drag-select, the drag now actually lands on the system clipboard — the in-session tmux profile sets `set-clipboard on` + a local `copy-command` on the CURRENT session only — plus `/help` documents the drag-to-copy and the Shift/Option-drag escape hatch, re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
|
|
208
201
|
|
|
209
202
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
210
203
|
<!-- CHANGELOG:END -->
|
package/README.md
CHANGED
|
@@ -97,6 +97,32 @@ jeo team
|
|
|
97
97
|
jeo ultragoal
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
```
|
|
101
|
+
┌──────────────────────┐
|
|
102
|
+
│ deep-interview │ Socratic ambiguity gate · seed frozen when concrete
|
|
103
|
+
└──────────┬───────────┘
|
|
104
|
+
│ .jeo/state/<seed>.json
|
|
105
|
+
▼
|
|
106
|
+
┌──────────────────────┐
|
|
107
|
+
│ ralplan │ Draft + repo-grounded critic → [OKAY] persisted
|
|
108
|
+
└──────────┬───────────┘
|
|
109
|
+
│ requires [OKAY] verdict
|
|
110
|
+
▼
|
|
111
|
+
┌──────────────────────┐
|
|
112
|
+
│ approve │ Schema + roles + [OKAY] — unlocks execution
|
|
113
|
+
└──────────┬───────────┘
|
|
114
|
+
│
|
|
115
|
+
▼
|
|
116
|
+
┌──────────────────────┐
|
|
117
|
+
│ team │ Serial executor · run lock · mutation audit
|
|
118
|
+
└──────────┬───────────┘
|
|
119
|
+
│ all tasks done
|
|
120
|
+
▼
|
|
121
|
+
┌──────────────────────┐
|
|
122
|
+
│ ultragoal │ Honest verification — suite once, no fabrication
|
|
123
|
+
└──────────────────────┘
|
|
124
|
+
```
|
|
125
|
+
|
|
100
126
|
- **deep-interview** — Socratic loop with ambiguity scoring; freezes a seed only when criteria are concrete (vague-only criteria are refused) and the seed round-trips its own parser. A new idea never silently reuses a completed interview.
|
|
101
127
|
- **ralplan** — drafting passes plus a **repo-grounded critic subagent gate**: the critic reads the actual repository, must return `[OKAY]`/`[ITERATE]`/`[REJECT]`, and the verdict is persisted. Invalid plans (schema, unknown roles) are never marked complete.
|
|
102
128
|
- **approve** — validates the exact contract `team` executes (schema + roles) *and* requires the persisted `[OKAY]` consensus verdict.
|
|
@@ -123,42 +149,6 @@ Non-zero hook output is appended to the tool result the model reads (deduped per
|
|
|
123
149
|
|
|
124
150
|
`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`.
|
|
125
151
|
|
|
126
|
-
📐 **Editable diagram:** [`docs/diagrams/memory-flow.drawio`](docs/diagrams/memory-flow.drawio) (open in [draw.io](https://app.diagrams.net) / the desktop app) — full write/store/read/migration swimlanes. Quick view:
|
|
127
|
-
|
|
128
|
-
```mermaid
|
|
129
|
-
flowchart LR
|
|
130
|
-
subgraph WRITE["WRITE — session-end distill (detached, best-effort)"]
|
|
131
|
-
direction TB
|
|
132
|
-
W1["session exit / ^C^C"] --> W2["spawnDetachedDistill()<br/>payload + detached child, returns instantly"]
|
|
133
|
-
W2 --> W3["distillSessionMemory()<br/>load bundle · transcriptTail · ONE LLM call (JSON)"]
|
|
134
|
-
W3 --> WD{"concepts JSON<br/>parsed?"}
|
|
135
|
-
WD -->|yes| WY["per concept: upsert by title,<br/>atomic write into facts/ commands/<br/>gotchas/ preferences/"]
|
|
136
|
-
WD -->|no| WN["plain text →<br/>legacy MEMORY.md"]
|
|
137
|
-
WY --> WR["rebuildIndex() index.md<br/>updateLog() log.md"]
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
subgraph STORE[".jeo/memory/ — OKF concept bundle"]
|
|
141
|
-
direction TB
|
|
142
|
-
S1["facts/ · commands/ · gotchas/ · preferences/<br/>(YAML frontmatter + body)"]
|
|
143
|
-
S2["index.md · log.md · cross-link graph (Sprint 04)"]
|
|
144
|
-
S3["MEMORY.md (legacy fallback)<br/>MEMORY.md.bak (rollback)"]
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
subgraph READ["READ — memoryPromptSection(cwd, query)"]
|
|
148
|
-
direction TB
|
|
149
|
-
R1["session start (query = task text)"] --> R2{"bundle has<br/>concepts?"}
|
|
150
|
-
R2 -->|yes| R3["selectWithinBudget()<br/>core → query relevance → 1-hop graph<br/>≤ MEMORY_INJECT_MAX_CHARS (3000)"]
|
|
151
|
-
R2 -->|no| R3B["legacy loadMemory()"]
|
|
152
|
-
R3 --> R4["frameMemory()<br/>hard cap · fence-neutralize · DATA framing"]
|
|
153
|
-
R3B --> R4
|
|
154
|
-
R4 --> R5["<project_memory> … injected into system prompt"]
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
WR -->|atomic| STORE
|
|
158
|
-
WN -->|fallback| S3
|
|
159
|
-
STORE -.->|loadConcepts / loadMemory| READ
|
|
160
|
-
```
|
|
161
|
-
|
|
162
152
|
**Migration (`jeo memory-migrate`, one-shot · idempotent).** A legacy single-doc `MEMORY.md` is converted losslessly into the bundle: `## heading → type`, each bullet → a typed concept, indented lines → body; `index.md`/`log.md` are rebuilt and the original is renamed to `MEMORY.md.bak`. Re-running is a no-op once the bundle has concepts. **Rollback:** `JEO_MEMORY_LEGACY=1` ignores the bundle and reads `MEMORY.md`/`.bak` through the same injection-hardening (`JEO_NO_MEMORY=1` still wins over everything).
|
|
163
153
|
|
|
164
154
|
## Local models
|
|
@@ -198,14 +188,18 @@ Required npm token permissions (repository secret `NPM_TOKEN`):
|
|
|
198
188
|
- A **Granular Access Token** with Read/Write access to the `jeo-code` package, or a classic **Automation** token
|
|
199
189
|
- "**bypass 2FA** for publishing" must be allowed — Automation tokens always bypass; granular tokens need the option enabled
|
|
200
190
|
|
|
191
|
+
## Acknowledgements
|
|
192
|
+
|
|
193
|
+
Huge thanks to [gajae-code](https://github.com/Yeachan-Heo/gajae-code) for the inspiration.
|
|
194
|
+
|
|
201
195
|
## Changelog
|
|
202
196
|
|
|
203
197
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
198
|
+
- **[0.7.0]** (2026-06-22) — Adds an opt-in, fail-closed **computer-use** capability (a `computer` tool + `jeo computer` CLI for screenshot/click/type/keypress/scroll/drag/wait/batch), hardens the spec-first workflow against silent post-consensus plan edits and PID-reuse lock starvation, makes OAuth refresh degrade cleanly when the refresh token is dead, and teaches tool-arg JSON repair to tolerate unescaped control characters. Verified leak-free (`mem-probe`, 2000 turns, −447 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
199
|
+
- **[0.6.41]** (2026-06-21) — The non-streaming model path now inherits the v0.6.40 300s window: an unattended long-reasoning turn (compaction, ralplan, deep-interview, memory distill, goal-verify, subagent/autopilot steps) is no longer false-aborted at the old 120s wall cap. Re-verified leak-free (`mem-probe`, 2000 turns, −610 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
204
200
|
- **[0.6.40]** (2026-06-21) — Wire-level stream heartbeat: ANY bytes from the provider (SSE keepalive/ping, filtered events) now re-arm the idle watchdog — a connected-but-quiet stream can no longer trip a false `stream idle` retry. Default idle window raised to 300s so Ollama/llama.cpp model-load silence no longer exhausts retries and stops the turn.
|
|
205
201
|
- **[0.6.39]** (2026-06-21) — A long "thinking" phase no longer trips a false stream-idle retry: reasoning/thinking deltas now act as a stream heartbeat, so a model that streams thought tokens past the idle window before any visible text is no longer mistaken for a stalled stream and retried (which discarded the in-progress reasoning). Re-verified leak-free (`mem-probe`, 2000 turns, −525 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
206
202
|
- **[0.6.38]** (2026-06-21) — The OKF concept-bundle memory no longer silently drops what it learns: a legacy single-doc `MEMORY.md` (or a text-only distill fallback) can no longer shadow the concept bundle, break OKF conformance, or lose a turn's learnings — its content is folded into the concept merge and the stale blob is archived. Re-verified leak-free (`mem-probe`, 2000 turns, −570 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
207
|
-
- **[0.6.37]** (2026-06-20) — Two dead-end fixes: the boxed prompt's ↑/↓ now recalls input history on a soft-wrapped one-liner (only a genuine multi-line draft gets in-box caret nav), and every terminating Spec-first stage (deep-interview, ralplan, team) now surfaces a user-visible answer instead of silently stalling — re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
|
|
208
|
-
- **[0.6.36]** (2026-06-20) — When `jeo --tmux` flips the mouse on so you can drag-select, the drag now actually lands on the system clipboard — the in-session tmux profile sets `set-clipboard on` + a local `copy-command` on the CURRENT session only — plus `/help` documents the drag-to-copy and the Shift/Option-drag escape hatch, re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
|
|
209
203
|
|
|
210
204
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
211
205
|
<!-- CHANGELOG:END -->
|
package/README.zh.md
CHANGED
|
@@ -96,6 +96,31 @@ jeo approve <计划路径>
|
|
|
96
96
|
jeo team
|
|
97
97
|
jeo ultragoal
|
|
98
98
|
```
|
|
99
|
+
```
|
|
100
|
+
┌──────────────────────┐
|
|
101
|
+
│ deep-interview │ Socratic ambiguity gate · seed frozen when concrete
|
|
102
|
+
└──────────┬───────────┘
|
|
103
|
+
│ .jeo/state/<seed>.json
|
|
104
|
+
▼
|
|
105
|
+
┌──────────────────────┐
|
|
106
|
+
│ ralplan │ Draft + repo-grounded critic → [OKAY] persisted
|
|
107
|
+
└──────────┬───────────┘
|
|
108
|
+
│ requires [OKAY] verdict
|
|
109
|
+
▼
|
|
110
|
+
┌──────────────────────┐
|
|
111
|
+
│ approve │ Schema + roles + [OKAY] — unlocks execution
|
|
112
|
+
└──────────┬───────────┘
|
|
113
|
+
│
|
|
114
|
+
▼
|
|
115
|
+
┌──────────────────────┐
|
|
116
|
+
│ team │ Serial executor · run lock · mutation audit
|
|
117
|
+
└──────────┬───────────┘
|
|
118
|
+
│ all tasks done
|
|
119
|
+
▼
|
|
120
|
+
┌──────────────────────┐
|
|
121
|
+
│ ultragoal │ Honest verification — suite once, no fabrication
|
|
122
|
+
└──────────────────────┘
|
|
123
|
+
```
|
|
99
124
|
|
|
100
125
|
- **deep-interview** — 基于歧义度评分的苏格拉底循环;只有标准足够具体才冻结种子(纯含糊标准会被拒绝),且种子必须通过自身解析器的往返校验。新想法绝不会静默复用已完成的访谈。
|
|
101
126
|
- **ralplan** — 起草阶段 + **真正读取仓库的 critic 子代理门禁**: 强制并持久化 `[OKAY]`/`[ITERATE]`/`[REJECT]` 裁决。无效计划(schema、未知角色)不会被标记为 complete。
|
|
@@ -123,42 +148,6 @@ jeo ultragoal
|
|
|
123
148
|
|
|
124
149
|
`jeo` 在 `.jeo/memory/` 下保存 **本地优先、蒸馏后的项目内存**(无远程后端,零原生依赖)。过往会话被蒸馏为 [OKF](docs/okf_mem/) 概念包,下一次会话仅把相关的、受预算约束的切片重新注入系统提示 —— 作为 DATA 而非指令加固。用 `JEO_NO_MEMORY=1` 完全禁用。
|
|
125
150
|
|
|
126
|
-
📐 **可编辑图示:** [`docs/diagrams/memory-flow.drawio`](docs/diagrams/memory-flow.drawio)(在 [draw.io](https://app.diagrams.net) / 桌面应用中打开)—— 写入/存储/读取/迁移完整泳道。概览:
|
|
127
|
-
|
|
128
|
-
```mermaid
|
|
129
|
-
flowchart LR
|
|
130
|
-
subgraph WRITE["WRITE — session-end distill (detached, best-effort)"]
|
|
131
|
-
direction TB
|
|
132
|
-
W1["session exit / ^C^C"] --> W2["spawnDetachedDistill()<br/>payload + detached child, returns instantly"]
|
|
133
|
-
W2 --> W3["distillSessionMemory()<br/>load bundle · transcriptTail · ONE LLM call (JSON)"]
|
|
134
|
-
W3 --> WD{"concepts JSON<br/>parsed?"}
|
|
135
|
-
WD -->|yes| WY["per concept: upsert by title,<br/>atomic write into facts/ commands/<br/>gotchas/ preferences/"]
|
|
136
|
-
WD -->|no| WN["plain text →<br/>legacy MEMORY.md"]
|
|
137
|
-
WY --> WR["rebuildIndex() index.md<br/>updateLog() log.md"]
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
subgraph STORE[".jeo/memory/ — OKF concept bundle"]
|
|
141
|
-
direction TB
|
|
142
|
-
S1["facts/ · commands/ · gotchas/ · preferences/<br/>(YAML frontmatter + body)"]
|
|
143
|
-
S2["index.md · log.md · cross-link graph (Sprint 04)"]
|
|
144
|
-
S3["MEMORY.md (legacy fallback)<br/>MEMORY.md.bak (rollback)"]
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
subgraph READ["READ — memoryPromptSection(cwd, query)"]
|
|
148
|
-
direction TB
|
|
149
|
-
R1["session start (query = task text)"] --> R2{"bundle has<br/>concepts?"}
|
|
150
|
-
R2 -->|yes| R3["selectWithinBudget()<br/>core → query relevance → 1-hop graph<br/>≤ MEMORY_INJECT_MAX_CHARS (3000)"]
|
|
151
|
-
R2 -->|no| R3B["legacy loadMemory()"]
|
|
152
|
-
R3 --> R4["frameMemory()<br/>hard cap · fence-neutralize · DATA framing"]
|
|
153
|
-
R3B --> R4
|
|
154
|
-
R4 --> R5["<project_memory> … injected into system prompt"]
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
WR -->|atomic| STORE
|
|
158
|
-
WN -->|fallback| S3
|
|
159
|
-
STORE -.->|loadConcepts / loadMemory| READ
|
|
160
|
-
```
|
|
161
|
-
|
|
162
151
|
**迁移(`jeo memory-migrate`,一次性 · 幂等).** 把旧版单文档 `MEMORY.md` 无损转换为概念包: `## 标题 → 类型`,每个项目符号 → 一个类型化概念,缩进行 → 正文; 重建 `index.md`/`log.md`,并把原文件重命名为 `MEMORY.md.bak`。一旦概念包中已有概念,再次运行即为 no-op。**回滚:** `JEO_MEMORY_LEGACY=1` 忽略概念包,通过相同的注入加固读取 `MEMORY.md`/`.bak`(`JEO_NO_MEMORY=1` 仍优先于一切)。
|
|
163
152
|
|
|
164
153
|
## 本地模型
|
|
@@ -197,14 +186,18 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
|
|
|
197
186
|
- 对 `jeo-code` 包具有 Read/Write 权限的 **Granular Access Token**,或经典 **Automation** 令牌
|
|
198
187
|
- 必须允许"发布时 **bypass 2FA**" — Automation 令牌始终绕过,granular 令牌需启用该选项
|
|
199
188
|
|
|
189
|
+
## 致谢 (Acknowledgements)
|
|
190
|
+
|
|
191
|
+
非常感谢 [gajae-code](https://github.com/Yeachan-Heo/gajae-code) 带来的灵感。
|
|
192
|
+
|
|
200
193
|
## 更新日志 (Changelog)
|
|
201
194
|
|
|
202
195
|
<!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
|
|
196
|
+
- **[0.7.0]** (2026-06-22) — Adds an opt-in, fail-closed **computer-use** capability (a `computer` tool + `jeo computer` CLI for screenshot/click/type/keypress/scroll/drag/wait/batch), hardens the spec-first workflow against silent post-consensus plan edits and PID-reuse lock starvation, makes OAuth refresh degrade cleanly when the refresh token is dead, and teaches tool-arg JSON repair to tolerate unescaped control characters. Verified leak-free (`mem-probe`, 2000 turns, −447 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
197
|
+
- **[0.6.41]** (2026-06-21) — The non-streaming model path now inherits the v0.6.40 300s window: an unattended long-reasoning turn (compaction, ralplan, deep-interview, memory distill, goal-verify, subagent/autopilot steps) is no longer false-aborted at the old 120s wall cap. Re-verified leak-free (`mem-probe`, 2000 turns, −610 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
203
198
|
- **[0.6.40]** (2026-06-21) — Wire-level stream heartbeat: ANY bytes from the provider (SSE keepalive/ping, filtered events) now re-arm the idle watchdog — a connected-but-quiet stream can no longer trip a false `stream idle` retry. Default idle window raised to 300s so Ollama/llama.cpp model-load silence no longer exhausts retries and stops the turn.
|
|
204
199
|
- **[0.6.39]** (2026-06-21) — A long "thinking" phase no longer trips a false stream-idle retry: reasoning/thinking deltas now act as a stream heartbeat, so a model that streams thought tokens past the idle window before any visible text is no longer mistaken for a stalled stream and retried (which discarded the in-progress reasoning). Re-verified leak-free (`mem-probe`, 2000 turns, −525 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
205
200
|
- **[0.6.38]** (2026-06-21) — The OKF concept-bundle memory no longer silently drops what it learns: a legacy single-doc `MEMORY.md` (or a text-only distill fallback) can no longer shadow the concept bundle, break OKF conformance, or lose a turn's learnings — its content is folded into the concept merge and the stale blob is archived. Re-verified leak-free (`mem-probe`, 2000 turns, −570 bytes/turn slope) with a fresh `jeo --tmux` boot battery (6/6).
|
|
206
|
-
- **[0.6.37]** (2026-06-20) — Two dead-end fixes: the boxed prompt's ↑/↓ now recalls input history on a soft-wrapped one-liner (only a genuine multi-line draft gets in-box caret nav), and every terminating Spec-first stage (deep-interview, ralplan, team) now surfaces a user-visible answer instead of silently stalling — re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
|
|
207
|
-
- **[0.6.36]** (2026-06-20) — When `jeo --tmux` flips the mouse on so you can drag-select, the drag now actually lands on the system clipboard — the in-session tmux profile sets `set-clipboard on` + a local `copy-command` on the CURRENT session only — plus `/help` documents the drag-to-copy and the Shift/Option-drag escape hatch, re-verified leak-free (`mem-probe`) with a fresh `jeo --tmux` boot.
|
|
208
201
|
|
|
209
202
|
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
210
203
|
<!-- CHANGELOG:END -->
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jeo-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Clean, highly optimized AI coding agent using spec-first loop",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/cli.ts",
|
|
@@ -59,6 +59,8 @@
|
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@types/bun": "^1.1.14",
|
|
62
|
-
"
|
|
62
|
+
"playwright": "^1.61.0",
|
|
63
|
+
"typescript": "^5.6.3",
|
|
64
|
+
"ws": "^8.21.0"
|
|
63
65
|
}
|
|
64
66
|
}
|
package/src/agent/compaction.ts
CHANGED
|
@@ -368,7 +368,8 @@ export async function maybeCompact(
|
|
|
368
368
|
: "";
|
|
369
369
|
|
|
370
370
|
const systemPrompt =
|
|
371
|
-
"Summarize the following coding-agent conversation so work can continue. Capture decisions, files changed, current task state, and open TODOs.
|
|
371
|
+
"Summarize the following coding-agent conversation so work can continue. Capture decisions, files changed, current task state, and open TODOs. " +
|
|
372
|
+
"Also preserve approaches that were tried and FAILED (with the cause) and any unconfirmed candidates/hypotheses still to check, so the next steps do not repeat dead ends or lose open leads. Be concise." +
|
|
372
373
|
touchedNote;
|
|
373
374
|
|
|
374
375
|
// Degradation ladder: (1) RETRY the summary a few times with short, abort-aware
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure-TypeScript supervisor for computer-use safety.
|
|
3
|
+
* Implements a fail-closed supervisor pattern to prevent runaway desktop automation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const HEARTBEAT_FRESH_MS = 2000;
|
|
7
|
+
|
|
8
|
+
export class ComputerSupervisor {
|
|
9
|
+
#suspended = false;
|
|
10
|
+
#killSwitchLive = false;
|
|
11
|
+
#lastHeartbeatMs = 0;
|
|
12
|
+
|
|
13
|
+
constructor(options?: { killSwitchLive?: boolean }) {
|
|
14
|
+
this.#killSwitchLive = options?.killSwitchLive ?? false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get inputAllowed(): boolean {
|
|
18
|
+
// If the kill switch is not live, we fail closed.
|
|
19
|
+
if (!this.#killSwitchLive) return false;
|
|
20
|
+
// If the heartbeat is stale, we fail closed.
|
|
21
|
+
if (Date.now() - this.#lastHeartbeatMs >= HEARTBEAT_FRESH_MS) return false;
|
|
22
|
+
// If suspended by user, we fail closed.
|
|
23
|
+
if (this.#suspended) return false;
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get isSuspended(): boolean {
|
|
28
|
+
return this.#suspended;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get isKillSwitchLive(): boolean {
|
|
32
|
+
return this.#killSwitchLive;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get lastHeartbeatMs(): number {
|
|
36
|
+
return this.#lastHeartbeatMs;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setKillSwitchLive(live: boolean) {
|
|
40
|
+
this.#killSwitchLive = live;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
triggerStop() {
|
|
44
|
+
this.#suspended = true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
reset() {
|
|
48
|
+
// Only human interaction should call this.
|
|
49
|
+
this.#suspended = false;
|
|
50
|
+
this.heartbeat(); // refresh heartbeat on reset
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
heartbeat() {
|
|
54
|
+
this.#lastHeartbeatMs = Date.now();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Global singleton instance for the process
|
|
59
|
+
export const computerSupervisor = new ComputerSupervisor();
|
|
@@ -126,7 +126,13 @@ export const ConfigSchema = z
|
|
|
126
126
|
plan: z.string().optional(),
|
|
127
127
|
})
|
|
128
128
|
.optional(),
|
|
129
|
+
gitAutoCommit: z.boolean().optional(),
|
|
129
130
|
hooks: HookConfigSchema.optional(),
|
|
131
|
+
computer: z
|
|
132
|
+
.object({
|
|
133
|
+
enabled: z.boolean().optional(),
|
|
134
|
+
})
|
|
135
|
+
.optional(),
|
|
130
136
|
})
|
|
131
137
|
.passthrough();
|
|
132
138
|
|
package/src/agent/engine.ts
CHANGED
|
@@ -14,8 +14,9 @@ import { extractJsonObject } from "./json";
|
|
|
14
14
|
import { nativeToolSchemasFor, normalizeNativeToolName } from "./tool-schemas";
|
|
15
15
|
import { readTool, writeTool, editTool, bashTool, findTool, searchTool, lsTool, mkdirTool, deleteTool, type ToolResult } from "./tools";
|
|
16
16
|
import { webSearchTool, setWebSearchActiveModel } from "./web-search";
|
|
17
|
-
import {
|
|
17
|
+
import { executeComputerAction } from "../commands/computer";
|
|
18
18
|
import { isRateLimitError } from "../util/retry";
|
|
19
|
+
import { isContextOverflowError, isRefusalError, friendlyProviderError } from "../util/provider-error";
|
|
19
20
|
import { runPreToolHooks, runPostTurnHooksForBatch } from "./hooks";
|
|
20
21
|
import { truncateToolOutput, formatToolResultBody } from "./tool-output";
|
|
21
22
|
export { TOOL_OUTPUT_MAX, READ_OUTPUT_MAX, TOOL_SPILL_THRESHOLD, MAX_TOOL_ARTIFACTS, truncateToolOutput, spillToolResult } from "./tool-output";
|
|
@@ -79,8 +80,8 @@ export const DEFAULT_TOOLS: Record<string, ToolHandler> = {
|
|
|
79
80
|
mkdir: (a, cwd) => mkdirTool(a.dirPath ?? a.path ?? a.dir, cwd),
|
|
80
81
|
delete: (a, cwd) => deleteTool(a.path ?? a.filePath ?? a.targetPath ?? a.dirPath, cwd, !!(a.recursive ?? a.r)),
|
|
81
82
|
web_search: (a, cwd) => webSearchTool(a, cwd),
|
|
83
|
+
computer: (a) => executeComputerAction(a as any),
|
|
82
84
|
};
|
|
83
|
-
|
|
84
85
|
/** Tool-protocol description injected into the system prompt. */
|
|
85
86
|
export const TOOL_PROTOCOL = [
|
|
86
87
|
"You have these tools (call exactly ONE per step, or batch multiple independent calls):",
|
|
@@ -94,7 +95,8 @@ export const TOOL_PROTOCOL = [
|
|
|
94
95
|
"8. mkdir {dirPath} — create a directory (parents included; idempotent)",
|
|
95
96
|
"9. delete {path, recursive?} — remove a file (or directory with recursive:true)",
|
|
96
97
|
"10. web_search {query, recency?, limit?} — search the web (Anthropic-native: synthesized answer + sources + citations)",
|
|
97
|
-
"11.
|
|
98
|
+
"11. computer {action, x?, y?, text?, key?, deltaX?, deltaY?, duration?, actions?} — execute desktop automation actions (screenshot, click, double_click, move, drag, scroll, type, keypress, wait, batch)",
|
|
99
|
+
"12. done {reason?} — call when the task is fully implemented AND verified",
|
|
98
100
|
"",
|
|
99
101
|
"Reply with STRICT JSON only — no code fences. You MAY include an optional leading",
|
|
100
102
|
'"reasoning" string (one short sentence on your plan) before "tool":',
|
|
@@ -140,7 +142,8 @@ export const WORKING_DISCIPLINE = [
|
|
|
140
142
|
"- Don't fabricate API/library surfaces from memory; check the source or --help for unfamiliar APIs.",
|
|
141
143
|
"- Never ship stubs, placeholders, or TODO-only code as a delivered feature.",
|
|
142
144
|
"- Never substitute the requested problem with an easier adjacent one.",
|
|
143
|
-
"- On a failed tool or test, fix the cause and continue — capture the evidence first; no apology loops, no shrinking the task to dodge it.",
|
|
145
|
+
"- On a failed tool or test, fix the cause and continue — capture the evidence first, state what the failure taught you, and change the next attempt accordingly; no apology loops, no shrinking the task to dodge it.",
|
|
146
|
+
"- Maintain a running task state (goal/constraints, confirmed evidence, failed approaches + cause, open candidates) and update it instead of re-reading the whole history each step.",
|
|
144
147
|
"- Update directly affected callsites, tests, and docs — or state why they are unchanged.",
|
|
145
148
|
"- Reuse existing patterns; parallel conventions are prohibited. Fix problems at their source.",
|
|
146
149
|
"- Not alone in the repo: treat unexpected changes as user work; never revert or delete them.",
|
|
@@ -170,8 +173,10 @@ export const OUTPUT_DISCIPLINE = [
|
|
|
170
173
|
* and launch.ts's interactive prompt (was duplicated verbatim in both). */
|
|
171
174
|
export const VERIFICATION_DIRECTIVE =
|
|
172
175
|
"Before calling done, self-check: did I run the test or command that exercises this change, are directly-affected callsites/tests/docs updated, and does my claim match real output? If any answer is no, keep working — do not call done. " +
|
|
176
|
+
"Distinguish a passing test from a met requirement: never weaken, skip, or narrow a test to make it pass. " +
|
|
173
177
|
"When you write tests, exercise observable behavior, edge values, branch conditions, invariants, and error handling — never assert defaults or tautologies.";
|
|
174
178
|
|
|
179
|
+
|
|
175
180
|
export function executorSystemPrompt(
|
|
176
181
|
role = "Executor Agent, a senior software developer",
|
|
177
182
|
protocol: string = TOOL_PROTOCOL,
|