Skip to content

feat(M7+M8): inline approval, /rewind, effort env+selector, vim mode, cron daemon scripts#68

Merged
oratis merged 5 commits into
mainfrom
feat/m7-m8-rest
May 28, 2026
Merged

feat(M7+M8): inline approval, /rewind, effort env+selector, vim mode, cron daemon scripts#68
oratis merged 5 commits into
mainfrom
feat/m7-m8-rest

Conversation

@oratis

@oratis oratis commented May 28, 2026

Copy link
Copy Markdown
Owner

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

  • ApprovalCallback return type extended: boolean | 'always'
  • New appendAllowMatcher() helper in @deepcode/core/config — idempotent, creates file + permissions object if absent
  • CLI prompt now accepts [y]es / [n]o / [a]lways
  • Desktop: inline 3-button panel (Approve / Reject / Always allow) above the composer, with a Rust command that targets ~/.deepcode/settings.json
  • 5 new vitest cases for appendAllowMatcher

2. /rewind slash command — 5 ops over session snapshots

  • /rewind lists snapshots in reverse chrono
  • /rewind <seq> {code | conversation | both | summarize-from | summarize-up-to} applies one of the five operations from §3.15.9
  • Backed by existing listSnapshots / restoreSnapshot / compact core APIs
  • SessionContext gains optional history / provider / newHistory fields; REPL pre-populates + applies
  • 5 new vitest cases

M8 — Polish (effort + vim + daemon)

3. DEEPCODE_EFFORT_LEVEL env var + desktop effort selector

  • Pure resolveEffort() helper covers the §3.13c precedence chain (cli flag → env → settings → default)
  • Desktop adds an effort <select> above the composer; choice persists to ~/.deepcode/settings.json#effortLevel
  • mac-agent.ts plumbs effort through runAgent so maxTokens + temperature actually scale per tier
  • 7 new vitest cases

4. cron daemon install/uninstall scripts

  • scripts/install-cron-daemon.sh + scripts/uninstall-cron-daemon.sh
  • Auto-detects the deepcode binary, idempotent on re-run, generates plist inline (no Node dep at runtime)
  • Matches the format produced by core/launchd/buildPlist()
  • macOS-only with a graceful pointer at the Linux systemd alternative
  • Smoke-tested with stubbed launchctl + tempdir HOME

5. Vim mode wired into desktop composer

  • Refactored packages/core/src/keybindings/ into a pure ./vim.ts + IO ./index.ts so the renderer can import VimState + DEFAULT_KEYBINDINGS without dragging node:fs
  • Added subpath export ./dist/keybindings/vim.js
  • New Tauri commands load_keybindings / save_keybindings
  • Repl composer is now a <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.
  • CLI vim wiring stays a follow-up (readline rewrite scope)

Test plan

  • pnpm typecheck — 7 workspaces clean
  • pnpm test — 533+ passing including 17 new
  • bash -n on both daemon scripts; tempdir smoke test verifies plist content matches buildPlist()
  • Manual: install rebuilt DMG → talk to DeepSeek → trigger Bash → click "Always allow" → verify ~/.deepcode/settings.json#permissions.allow includes "Bash" and the rule survives restart
  • Manual: /vim → toggle on in CLI (persists to keybindings.json) → restart desktop → composer shows mode chip

🤖 Generated with Claude Code

oratis and others added 5 commits May 28, 2026 18:57
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>
@oratis oratis merged commit 1296aa6 into main May 28, 2026
1 of 3 checks passed
@oratis oratis deleted the feat/m7-m8-rest branch May 28, 2026 13:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant