feat(M7+M8): inline approval, /rewind, effort env+selector, vim mode, cron daemon scripts#68
Merged
Conversation
Permission prompts now surface in the desktop UI as an inline panel above
the composer with three buttons: Approve / Reject / Always allow. "Always
allow" persists a bare-tool matcher to ~/.deepcode/settings.json so the
rule survives across sessions.
**Core changes:**
- ApprovalCallback return type extended: boolean | 'always'
- agent.ts treats 'always' same as true (allow-this-call); host is
responsible for the persistence side
- New appendAllowMatcher(path, matcher) helper in config/loader.ts —
idempotent, creates file + permissions object if absent
- Re-exported from @deepcode/core root index + config/index.ts
**CLI:**
- repl.ts prompt now accepts [y]es / [n]o / [a]lways
- On 'a', calls appendAllowMatcher against project-local settings
**Desktop (Tauri):**
- New Rust command append_allow_matcher(matcher) targeting user-level
~/.deepcode/settings.json (renderer doesn't have a stable cwd concept)
- mac-agent.ts onApproval expanded to return 'allow'|'deny'|'always'
- window-shim.ts mints requestId per ask, emits permission_request event
carrying { requestId, toolName, reason }, stashes resolver in a Map
- agent.approve({ requestId, decision }) resolves the awaiting promise
- ReplScreen.tsx renders inline panel above composer; locks input while
pending; on 'always' calls appendAllowMatcher before dispatching
**Tests:** 5 new vitest cases for appendAllowMatcher (create / preserve /
idempotent / whitespace / pre-existing allow absent). 13 loader tests
pass; all 533 workspace tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds /rewind to the CLI command set. Backed by the existing snapshot
machinery from packages/core/src/sessions/snapshots.ts.
Forms:
/rewind list snapshots (latest first)
/rewind <seq> code restore the file from that snapshot
/rewind <seq> conversation trim history to messages before its
capturedAt timestamp
/rewind <seq> both code + conversation
/rewind <seq> summarize-from keep head up to snap; compact the tail
/rewind <seq> summarize-up-to compact the head; keep tail (= /compact
pinned to a snapshot point)
Implementation:
- SessionContext gains optional history / provider / newHistory fields
- REPL pre-populates ctx.history before each command call and applies
ctx.newHistory after if the command set it
- compact() reused from packages/core/src/compaction for both summarize ops
- 5 new vitest cases (list empty, list populated, restore code, trim
conversation by timestamp, reject bad seq) — 52/52 CLI tests pass
- Also exports ApprovalDecision type (sibling to ApprovalCallback)
Desktop UI for /rewind is deferred — the same listSnapshots /
restoreSnapshot APIs are available from @deepcode/core/dist/sessions
and can be surfaced via the existing FilePanel History tab in a
follow-up. CLI is the primary surface for this milestone.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLI: /effort and --effort already existed; this adds the third tier of the precedence chain from docs/DEVELOPMENT_PLAN.md §3.13c: --effort flag → DEEPCODE_EFFORT_LEVEL env → settings.effortLevel → 'medium' Pulled the precedence logic into a pure helper `resolveEffort()` in parse-args.ts so it's tested independently of REPL bootstrap. 7 new vitest cases cover cli-wins, env-wins, settings-wins, default, invalid env, trim, empty. Desktop: adds a small effort dropdown above the composer in REPL screen. Choice persists to ~/.deepcode/settings.json#effortLevel via the existing save_settings_file Tauri command, so it survives restart. Loaded on mount. Backend wiring: mac-agent.ts now looks up EFFORT_PARAMS[effort] and passes maxTokens + temperature to runAgent (previously relied on provider defaults). startAgentTurn() and window-shim agent.start() both accept the new effort parameter. All 59 CLI tests + workspace typecheck pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds scripts/install-cron-daemon.sh and scripts/uninstall-cron-daemon.sh
to wire up the DeepCode scheduled-tasks LaunchAgent (referenced from
packages/core/src/launchd/index.ts).
install-cron-daemon.sh:
- Locates the deepcode binary (DEEPCODE_BIN env, then $PATH, then
apps/cli/dist/index.js)
- Writes ~/Library/LaunchAgents/dev.deepcode.scheduler.plist inline
(avoids a runtime Node dep — plist format mirrors buildPlist() in
the core launchd module)
- Idempotent: unloads any existing copy first
- Configurable interval (DEEPCODE_INTERVAL, default 60s) and subcommand
(DEEPCODE_SUBCMD, default "scheduler run")
- launchctl load -w to make launchd start firing
uninstall-cron-daemon.sh:
- launchctl unload -w + rm plist
- Idempotent — runs cleanly when nothing is installed
- Keeps log files under ~/.deepcode/scheduler.{log,err.log}
Both are macOS-only (early-exit with a helpful pointer at the Linux
systemd alternative described in DEVELOPMENT_PLAN.md §3.15.4).
Smoke-tested with stubbed launchctl + tempdir HOME: plist content
matches the format produced by core's buildPlist() exactly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VimState class + DEFAULT_KEYBINDINGS already lived in
packages/core/src/keybindings/index.ts but couldn't be imported from
the Tauri renderer because the same file pulled in node:fs. Split the
pure half (types, VimState, resolveKeyAction, normalizeChord,
DEFAULT_KEYBINDINGS) into ./vim.ts, kept loadKeybindings/saveKeybindings
in index.ts, and added a subpath export so the renderer can import
just the pure module.
Desktop wiring:
- New Tauri commands load_keybindings / save_keybindings backed by
~/.deepcode/keybindings.json (mirrors core's loader)
- Repl.tsx loads the user's config on mount; if `vim: true`,
instantiates a VimState
- Composer changed from <input> to <textarea> (multi-line, cursor
control). Enter submits; Shift+Enter inserts newline. Vim handles
Enter differently — left to default text behavior.
- onKeyDown synthesizes a chord (ctrl/shift/alt/meta + key, with
Escape → "esc"), feeds VimState, and for resolved actions applies
the host effect: cursor-line-start/end, kill-{line,to-end,to-start},
yank-line, paste-after, plus the vim-{normal,insert,append,visual}-mode
switches that VimState handles itself
- In NORMAL mode, untranslated keys are swallowed (preventDefault) so
navigation keys like h/j/k/l don't insert text. Arrow keys + Backspace
are still allowed for safety.
- Mode indicator chip ("-- NORMAL --" / "-- INSERT --" / "-- VISUAL --")
appears in the composer header when vim is on
CLI vim wiring stays a follow-up — node:readline doesn't expose raw
keystrokes without a full rewrite of the editing loop. The /vim
command continues to toggle the flag persistently; the CLI editor
just behaves emacs-style until the editor rewrite lands.
All 18 keybindings + 533 workspace tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the M7 + M8 polish backlog. Five focused commits, one milestone each.
M7 — Polish (file panel + composer)
1. Inline Approval UI with Always-allow persistence
ApprovalCallbackreturn type extended:boolean | 'always'appendAllowMatcher()helper in@deepcode/core/config— idempotent, creates file + permissions object if absent[y]es / [n]o / [a]lways~/.deepcode/settings.jsonappendAllowMatcher2.
/rewindslash command — 5 ops over session snapshots/rewindlists snapshots in reverse chrono/rewind <seq> {code | conversation | both | summarize-from | summarize-up-to}applies one of the five operations from §3.15.9listSnapshots / restoreSnapshot / compactcore APIsSessionContextgains optionalhistory / provider / newHistoryfields; REPL pre-populates + appliesM8 — Polish (effort + vim + daemon)
3.
DEEPCODE_EFFORT_LEVELenv var + desktop effort selectorresolveEffort()helper covers the §3.13c precedence chain (cli flag → env → settings → default)<select>above the composer; choice persists to~/.deepcode/settings.json#effortLevelmac-agent.tsplumbseffortthroughrunAgentsomaxTokens+temperatureactually scale per tier4. cron daemon install/uninstall scripts
scripts/install-cron-daemon.sh+scripts/uninstall-cron-daemon.shcore/launchd/buildPlist()HOME5. Vim mode wired into desktop composer
packages/core/src/keybindings/into a pure./vim.ts+ IO./index.tsso the renderer can importVimState+DEFAULT_KEYBINDINGSwithout draggingnode:fs./dist/keybindings/vim.jsload_keybindings/save_keybindings<textarea>; in vim mode the keydown handler builds a chord, feeds VimState, and applies host actions (cursor moves, kill, yank, paste). Mode indicator chip ("-- NORMAL --" / etc.) appears in the header.Test plan
pnpm typecheck— 7 workspaces cleanpnpm test— 533+ passing including 17 newbash -non both daemon scripts; tempdir smoke test verifies plist content matchesbuildPlist()~/.deepcode/settings.json#permissions.allowincludes"Bash"and the rule survives restart/vim→ toggle on in CLI (persists to keybindings.json) → restart desktop → composer shows mode chip🤖 Generated with Claude Code