40 Commits
Author SHA1 Message Date
kimandClaude Sonnet 5 e572fc5d76 refactor: table-drive the built-in slash-command dispatch
handleSubmit's ~200-line if/return chain becomes a declarative
{test, run}[] list plus one dispatch loop. Same matching semantics
(exact vs. loose startsWith), same order (/permissions before /perm,
/model before /mode), same handler bodies — just uniform structure and
a single place the command set is registered. Handlers still close over
component state; extracting them into an independently testable module
is a larger follow-up.

Also drops a stale comment claiming Ollama can't report context length
for ":cloud" models — /api/show now returns it for every cloud model
tested (glm-5.2, qwen3.5, kimi-k2, minimax, gemma4).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 16:05:45 +09:00
kimandClaude Sonnet 5 e6795effe1 fix: use width-stable icons in the status bar
⏱ (U+23F1) and ⏵ (U+23F5) are text-default emoji: string-width counts
them as 1 column but Windows Terminal, iTerm and others render them as
2. That makes a status-bar line drift wider than Ink's layout math
expects, so the live region below it under-erases on re-render and
leaves stranded copies of the bar in scrollback. Swap in ⏳ (a full RGI
emoji, unambiguously 2) and ▸ (a plain geometric shape, unambiguously 1).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 16:01:20 +09:00
kimandClaude Sonnet 5 13e6540cdf chore: allow esbuild@0.28.1 install script, note dep constraints
tsx 4.23.13 pulls its own esbuild 0.28.1; allowScripts only listed
0.27.2 (tsup/vite), so npm warned about the blocked postinstall. Also
records the marked<16 / typescript-7 / wrap-ansi-10 constraints in the
memo so a future upgrade pass doesn't have to rediscover them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 15:31:50 +09:00
kimandClaude Sonnet 5 870c30164b chore: upgrade typescript 5.9 -> 7 (native compiler)
tsc --noEmit passes with zero errors and runs noticeably faster. The
project only uses the tsc CLI for typechecking (no programmatic
typescript API imports); tsup/esbuild drives the build and is
unaffected. The native port pulls per-platform @typescript/typescript-*
binaries as optional deps, like esbuild already does.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 15:27:44 +09:00
kimandClaude Sonnet 5 997f2c523e chore: upgrade vitest 4 -> 5
Dev-only. Config (globals/environment/include) and the vi.* API surface
used by the suite are unchanged; all 45 files / 366 tests pass. Node 24
satisfies vitest 5's engine range.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 15:24:48 +09:00
kimandClaude Sonnet 5 a24bc16513 chore: bump wrap-ansi to 10, plus safe minor dependency updates
- wrap-ansi 7 -> 10: matches the copy Ink already installs (shared
  string-width v8 width math) and ships its own types, so the ambient
  src/wrap-ansi.d.ts shim is gone.
- openai 7.5 -> 7.13, react/@types/react 19.2 -> 19.3, zod 4.4 -> 4.6,
  @types/node 26.2 -> 26.5, tsx + vscode-* patch bumps.

marked stays at 15 (marked-terminal@7.3 peer-caps it at <16). typescript
and vitest majors left for separate, tested upgrades.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 15:17:13 +09:00
kimandClaude Sonnet 5 a4da9b9cbd fix: strip stray NUL byte from renderMarkdown cache key
The previous commit's cache key used a raw NUL as the width/text separator,
which made git track src/ui/render.ts as a binary file. Use a plain-text
"<width> <text>" key instead (width is always a bare integer, so the space
delimiter is unambiguous) and add renderMarkdown width-wrapping tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 14:50:42 +09:00
kimandClaude Sonnet 5 c36b45e9d5 fix: hard-wrap history output to terminal width
Lines wider than the terminal (wide markdown tables, long code lines) were
soft-wrapped by the terminal but counted as one row by Ink's <Static> redraw,
desyncing its cursor math and stranding duplicate copies of the status bar and
input box in scrollback on every re-render.

renderMarkdown now wraps its output to the caller's width via wrap-ansi, and
every <Static> item (plus the live streaming line) is held within a
width-bounded Box so nothing the terminal would soft-wrap reaches Ink.

Also sets cli-table3 header color to yellow (red-on-blue themes made table
headers unreadable).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 14:34:47 +09:00
kimandClaude Sonnet 5 c649fdf6ed feat: task persistence, memoized UI components, hook/delete-failure safety
locode-authored (dogfooded) changes:
- TaskStore.toJSON/fromJSON: task_create/list state now survives session
  resume instead of resetting to empty (session.ts, task.ts, sessionStore.ts).
- StatusBar/ChatInput/ThinkingIndicator wrapped in memo() to skip re-renders
  during the ~30fps streaming-text update loop.
- HistoryItemView's todos list keys off t.content instead of array index
  (TodoItem has no id field; content is the best stable key available).
- sessionStore.ts read paths now log (console.warn) on corrupt/unreadable
  files instead of silently swallowing the error.

Fixes on top, found in review:
- safeRunHooksForEvent() wraps the 4 remaining unprotected hook call sites
  (PreToolUse, PermissionRequest, PostToolUse, SubagentStart) — these fire on
  every tool call/sub-agent, far more often than the 3 sites already wrapped
  (Stop/SessionStart/UserPromptSubmit), so a broken hook script was still able
  to abort an in-flight turn.
- deleteSession() no longer reports success when unlinkSync fails for a
  reason other than ENOENT (e.g. EPERM/EBUSY from a Windows file lock) — it
  used to drop the index entry and return true anyway, orphaning the file on
  disk with no way to reference it again. `sessions rm` in cli.ts now
  distinguishes "not found" from "found but couldn't delete" in its message.
- ChatInput's memo() was a no-op: onSubmit={handleSubmit} passed a fresh
  closure every render since handleSubmit isn't wrapped in useCallback.
  Rather than force a ~300-line function (which reads isThinking/streamingText
  — both changing every streaming frame) into a dependency array, added a
  handleSubmitRef + stable wrapper, the same ref-indirection this file
  already uses for phaseRef.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LxiAaGhSD4DRVYYQZ5GJjm
2026-08-24 15:02:34 +09:00
kimandClaude Sonnet 5 5bf60c7921 fix: local/cloud model detection, capability cache TTL, chat cursor offset
- isSmallLocalModel(baseURL, model) replaces isLocalBackendURL() for system-prompt
  branching and context-window fallback defaults: Ollama's cloud-routed models
  (glm-5.2:cloud, qwen3.5:397b-cloud, etc.) share a localhost endpoint with
  genuinely local models, so the base URL alone can't tell them apart. Recomputed
  on /model and /backend switches too, not just at session creation.
- Fixed a latent isLocalBackendURL bug found while testing it: URL.hostname keeps
  the brackets on a literal IPv6 host ("[::1]"), so the old "::1" comparison never
  matched.
- capabilityCache entries now carry a cachedAt timestamp with a 30-day TTL
  (LOCODE_CAPABILITY_CACHE_TTL_DAYS), so a stale "fallback" verdict from a
  transient probe failure doesn't permanently disable native tool calls.
- ChatInput's cursor row offset (+2 -> +1): the extra row was empirical padding
  for a bottomSectionRef wrapper Box and virtual-scroll viewport that no longer
  exist since the Static-based rendering change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LxiAaGhSD4DRVYYQZ5GJjm
2026-08-24 13:51:25 +09:00
kim 09cd992aa6 feat: normal screen architecture, burn rate by model time, dependency upgrades
Major changes:

- Replace alternate screen buffer + app-side mouse tracking/virtual scroll
  with normal screen buffer + Ink <Static> for permanent scrollback.
  Terminal's native scroll/selection/copy just works — no mouseInput.ts needed.
- Fix burn rate (🔥) to use model response time (modelTimeMs) instead of
  session elapsed time, so it reflects actual generation throughput.
- Upgrade dependencies: openai 6→7, commander 13→15, execa 9→10, vitest 3→4,
  node types 22→26, tsup target node20→node22.
- Update upgrade memo with current architecture, all completed upgrades,
  and working tree status.
2026-08-24 12:44:08 +09:00
kimandClaude Sonnet 5 c539995b19 fix: mouse-click crash, CRLF tool-call/edit matching bugs, misleading burn rate
- App.tsx: add the missing effectiveScrollTopRef the prior session's mouse-drag
  fix referenced but never declared — tsc failed (esbuild build doesn't
  type-check, so it looked fine), and any click/drag/release threw
  ReferenceError at runtime.
- partialJson.ts/nativeAdapter.ts: escape raw control characters (literal
  newline/CR/tab) found inside JSON string literals before parsing tool-call
  arguments, and add repair to the non-streaming completion path (previously
  only the streaming path had it) — local models echoing multi-line CRLF file
  content unescaped were failing JSON.parse outright.
- editFile.ts/multiEdit.ts: read_file always shows the model LF-normalized
  content regardless of the file's real line endings, but edit_file/multi_edit
  matched old_string against the raw (CRLF-preserving) file — a systematic
  "old_string not found" on every CRLF file in this project. Now detect the
  file's EOL, match/edit in LF space, and restore the original EOL on write.
- StatusBar.tsx: 🔥 burn rate now uses outputTokens/min instead of
  (inputTokens+outputTokens)/min — inputTokens sums the full resent context on
  every API call (no prompt caching), so it ballooned into misleading hundreds
  of K/min during chatty fallback-mode tool round trips.

339/339 tests passing (+13), tsc clean, build 295.77 KB.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kn75SyL8t8SeyTwmPvADAS
2026-08-22 13:03:29 +09:00
kim 60c6021390 feat: split system prompt by local/cloud, improve mouse tracking, increase retries & iterations
- Separate system prompt for local vs cloud models via isLocal flag
  (isLocalBackendURL, buildSystemPrompt(..., isLocal), propagate through
  createSession/createSessionFromRecord/compactSession/spawnSubAgent)
- Increase MAX_EMPTY_RESPONSE_RETRIES from 1 to 3 for cloud model resilience
- Upgrade mouse input: full SGR-1006 parsing with col/row/pressed/modifiers,
  logicalButton() helper, and copyToClipboard() via OSC 52
- Add temporary mouse debug logging in App.tsx
- Increase DEFAULT_MAX_ITERATIONS from 100 to 300
- Update README mouse/scrollback docs, tweak diff-remove color
2026-08-21 18:01:29 +09:00
kim 47711a9600 docs: record 2026-08-22 session upgrades in memo 2026-08-21 17:11:56 +09:00
kim 506599020c feat: 15 upgrades — system prompt, multiline input, /undo, thinking tokens, and more
Major upgrades (15 items):

1. System prompt overhaul: tool usage guide, local model guidance, error recovery,
   safety guidelines, read/mutating tool categorization (12 tests)
2. Multiline input: Shift+Enter for newlines, bracketed paste support,
   Enter submits single-line, Ctrl+Enter forces submit on multiline
3. /undo command: removes last user turn + assistant/tool messages,
   preserves filesystem changes, shows count (7 tests)
4. Thinking/reasoning token support: capture `reasoning_content` from
   DeepSeek/QwQ-style models, render as dimmed collapsible block
5. @ mention improvements: fuzzy path matching (character-order matching),
   5-minute cache TTL for file list refresh
6. readFile binary guard: 10MB size limit, extension-based binary detection,
   null-byte heuristic, CRLF line-ending fix
7. writeFile atomic write: temp file + rename pattern, explicit ENOENT check
8. bash string accumulation: O(n²) → O(n) with array chunks
9. estimateTokens regex speedup: per-character loop → regex bulk counting
10. handleCompletedMessage: `any` → `Record<string, unknown>`
11. Session restore: persist `allowedTools` in SessionRecord, restore on resume
12. Context window parallel detection: Promise.allSettled for Ollama + LM Studio
13. Streaming text accumulation: O(n²) → O(n) with text chunks array
14. /dashboard & context bar already implemented (no changes needed)
15. Cost estimation already implemented (no changes needed)

Tests: 322 passing across 42 files, typecheck clean, build 289.56 KB
2026-08-21 17:07:49 +09:00
kim 5c3fcfdd57 docs: record this session's work in the upgrade memo
Append the 2026-08-20 session log: 12 commits (6fe9888..dfaf8d1) covering
LSP completion, local-model robustness trio, multi_edit, /mouse, DiffView,
notebook_edit, /export json, the task system, and the streaming-input fix —
with per-commit details, encoding/Python-editing notes, and remaining candidates.
2026-08-21 14:12:39 +09:00
kim dfaf8d1551 fix(ui): keep the input box usable while a response is streaming
While a turn was in flight (isThinking or streamingText), the ChatInput was
unmounted and replaced with a static "Waiting for response…" box — so the user
couldn't type ahead during output, the input field just vanished.

Now ChatInput stays mounted the whole time: the user can type and edit while
output streams in. A one-line status above it ("Waiting… esc to interrupt — type
ahead, Enter sends when done.") replaces the old full-width box. Submitting a
second turn mid-stream is still prevented — handleSubmit now guards on
isThinking/streamingText and returns early, so Enter is a no-op until the
in-flight turn finishes (the typed text stays in the box). The original race this
swap was protecting against (overlapping turns on the same session) is preserved
by that guard.

Verified: typecheck clean, build 274.76 KB, 301 tests pass.
2026-08-21 14:10:21 +09:00
kim dad0915d62 feat: structured task system with dependency graph (task_create/list/get/update)
Replaces the flat todo_write approach for non-trivial multi-step work with a
structured, incrementally-updated task store — dependencies, ownership, status,
metadata — ported from origin/v0.6.0 and adapted to the local Session/ctx.

- src/tools/task.ts: TaskStore (in-memory, per-session) + four read-only tools:
  task_create (returns id), task_list (summaries), task_get (full details),
  task_update (status pending|in_progress|completed|deleted, rename, claim via
  owner, addBlocks/addBlockedBy dependency edges, merge-patch metadata — null
  deletes a key). Self-refs/unknown ids ignored; direct 2-cycles skipped;
  delete prunes dangling refs. Schemas declared before use (fixes a TDZ in the
  v0.6.0 version). todo_write kept alongside for simpler cases.
- types.ts: ctx.taskStore. session.ts: TaskStore on every Session. loop.ts:
  expose session.taskStore in the tool ctx + on sub-agent sessions.
- tools/index.ts: register the four task tools.
- task.test.ts: 9 cases (create/list/get/update status, dependency edges both
  ways, self-ref/unknown/2-cycle guards, delete+prune, metadata merge-patch,
  absent-store error).
- README: document the structured tasks.

Verified: typecheck clean, build 275.10 KB, 301 tests pass (+9).
2026-08-21 14:07:16 +09:00
kim 5438780a55 feat(export): full transcript export + JSON dump (/export json)
The markdown export used to include only plain user/assistant text — every tool
call and tool result was dropped, so a shared transcript lost half the work.

- exportSession.ts: the markdown export now includes tool calls (fenced JSON),
  tool results (fenced), fallback tool_result blocks, and multimodal user
  content (text + [image attached] placeholders). Adds sessionToJson() — the
  full record (messages verbatim + meta + exportedAt) for cross-machine
  replay/sharing. exportSession() takes a format; defaultExportFilename() picks
  .md/.json.
- App.tsx: /export json [file] selects the JSON dump; /export [file] stays the
  markdown transcript. The export prompt remembers the chosen format.
- exportSession.test.ts: 8 cases (markdown includes tool calls/results, tool-
  only assistant turn, JSON shape, filename extensions, file writes, auto-name).
- /help + README: document /export json.

Verified: typecheck clean, build 268.34 KB, 292 tests pass (+8).
2026-08-21 13:56:05 +09:00
kim b2a7d1a0f0 feat: notebook_edit tool — cell-aware Jupyter (.ipynb) editing
Ported from origin/v0.6.0, adapted to resolveWithinCwd (no setLastEdit).

- src/tools/notebookEdit.ts: replace/insert/delete a cell by cell_id or
  cell_index. Converts the model's single-string new_source to/from nbformat's
  source line-array (trailing-newline convention), so the model never hand-writes
  the array quirk. Switching a code cell to markdown drops execution_count/outputs;
  switching to code adds them. Atomic temp+rename write, JSON validated.
- tools/index.ts: register notebook_edit.
- notebookEdit.test.ts: 9 cases (replace by id/index, insert at position/append,
  delete, missing-id failure, insert-without-cell_type, replace-without-
  new_source, diff preview, code→markdown field drop, path containment).

Prefer this over edit_file/write_file for .ipynb so the JSON structure stays
valid (and avoids regenerating a whole notebook JSON in the model's output —
a context-window win for local models).

Verified: typecheck clean, build 266.91 KB, 284 tests pass (+9).
2026-08-21 13:52:16 +09:00
kim ee695a64af feat(ui): color-coded diff rendering in the permission prompt
The pre-confirmation preview for mutating tools (edit_file/multi_edit/write_file/
bash/git_commit) was rendered as plain monochrome text, throwing away the most
useful signal in the thing the user evaluates before approving.

- src/utils/diff.ts: looksLikeDiff() — distinguishes a unified diff from a plain
  preview (bash command, 'Create new file ...', JSON args).
- src/ui/ink/DiffView.tsx: renders a unified diff with additions green, removals
  red, hunk/file headers dimmed. Optional side-by-side mode (pairHunk + clip,
  both exported for testing). Ported from origin/v0.6.0.
- src/ui/theme.ts: DIFF_ADD_HEX / DIFF_REMOVE_HEX.
- PermissionPrompt.tsx: use DiffView when the preview is a diff, keep the
  line-by-line render for non-diff previews.

Tests: diff.test.ts (4), DiffView.test.ts (8) for pairHunk/clip/looksLikeDiff.

Verified: typecheck clean, build 261.50 KB, 275 tests pass (+12).
2026-08-21 13:50:50 +09:00
kim e3520b8eb1 feat(ui): /mouse toggle — enable terminal text selection/copy by default
Mouse-wheel tracking (xterm ?1000h) was always on so the wheel could scroll
the transcript, but it also captures mouse events, which prevents the
terminal's native text selection/drag-to-copy — a frequent ask.

Now mouse tracking defaults to OFF, so selecting/dragging output to copy
works out of the box. PageUp/PageDown still scroll (they always did). Toggle
the wheel back on with '/mouse on' (re-captures mouse events, so selection
won't work while on); '/mouse off' restores selection. '/mouse' shows the
current state. Documented in the /help list and README.

Verified: typecheck clean, build 257.55 KB, 263 tests pass.
2026-08-21 13:48:41 +09:00
kim c8cc78e8f1 docs+test: document LSP code intelligence; cover the LSP tools
- README.md: list definition/references/diagnostics + multi_edit in the tools
  overview; add a Code intelligence (LSP) section (lazy per-language servers,
  required binaries, post-edit sync, freshness wait); add a lspServers config
  example.
- src/tools/codeIntel.test.ts: mock lspManager and assert each tool dispatches
  the right args (path/cwd, 1-indexed positions, includeDeclaration default true)
  and returns the manager result verbatim — no servers spawned. 7 tests.

Verified: typecheck clean, 263 tests pass (+7).
2026-08-21 13:45:40 +09:00
kim f2ca1543d2 feat(lsp): configurable language servers + merge C/C++ into one clangd
The built-in LANGUAGE_SPECS were hardcoded — users couldn't add Java/Ruby/Lua
servers or override a built-in's command/args, and the file's own comment
flagged this as a future extension. Also, c and cpp were separate specs both
spawning clangd, so a mixed C/C++ project ran two indexing the same headers.

- lspManager.ts: LANGUAGE_SPECS is now mutable; configureLanguageSpecs(overrides)
  merges user entries (keyed by languageId) into the built-ins. A built-in id
  override replaces command/args and, if extensions is given, rewrites routing.
  A new id adds a mapping but REQUIRES extensions (ignored otherwise — can't
  route files to it). C and C++ collapse into one 'c' clangd spec (all
  .c/.h/.cpp/.cc/.cxx/.hpp/.hh/.hxx route to a single clangd).
- config.ts: resolveLspServers() reads stored.lspServers.
- store.ts: StoredConfig.lspServers field.
- cli.ts: 'locode config set lspServers <json>' (JSON object value, validated).
- ui/ink/index.tsx: configureLanguageSpecs(resolveLspServers()) at startup.
- lspManager.test.ts: 6 tests for the merge (add, override, rewrite exts,
  ignore-without-extensions, c/cpp collapse) via _specsForTests/_resetSpecsForTests
  — no servers spawned.

Verified: typecheck clean, build 256.60 KB, 256 tests pass (+6).
2026-08-21 13:43:05 +09:00
kim b7233afd77 fix(lsp): await publishDiagnostics instead of a single event-loop turn
getDiagnostics used to sync the document then wait one macrotask
(setTimeout 0) before reading the cached snapshot. tsserver/pyright on a
large file publish asynchronously and often hadn't fired yet, so the call
returned a stale (or empty) snapshot right after an edit — exactly when the
model asks for diagnostics to verify its change.

Now: clear the stale URI snapshot, sync, then race the next
publishDiagnostics for that URI against a 1500ms timeout via a per-URI
waiter map woken by the publish handler. A slow server gets a real chance
to compute fresh diagnostics; on timeout we fall through to whatever's
cached (possibly empty). _resetForTests clears the waiter map too.

Verified: typecheck clean, 250 tests pass.
2026-08-21 13:34:51 +09:00
kim a458b3c478 feat: multi_edit tool — batched edits to one file in a single atomic write
Ported from origin/v0.6.0 and adapted to the local codebase (resolveWithinCwd
path guard, no setLastEdit). Reuses edit_file's applyEdit/countOccurrences.

- src/tools/multiEdit.ts: applies an ordered batch of {old_string, new_string,
  replace_all?} edits to one file. Each edit validates against the running
  result (an earlier edit can shift the text a later edit matches), so a
  mismatch names its edit index. One confirmation + one temp+rename atomic
  write instead of N edit_file round-trips — the biggest local-model win,
  since each edit_file is a full generation + permission prompt.
- editFile.ts: export applyEdit + countOccurrences for reuse.
- tools/index.ts: register multi_edit alongside edit_file.
- multiEdit.test.ts: 8 cases (ordered batch, chained edits, error on first
  mismatch w/ index, ambiguity guard, replace_all, path containment, diff
  preview, preview warning).

Verified: typecheck clean, build 254.81 KB, 250 tests pass (+8).
2026-08-21 13:32:51 +09:00
kim 0ddc822238 fix: local-model tool-call robustness (partial-JSON repair, fallback parser, empty-response retry)
Three upgrades targeting the most common local-model failure modes where a
tool call is intended but never executes:

#2 Partial/truncated-JSON recovery (native streaming):
- src/toolcalling/partialJson.ts: cheap structural repair for tool-call
  arguments that fail JSON.parse — close unterminated strings, balance
  open braces/brackets (max_tokens truncation), strip trailing commas and
  stray trailing tokens. Never invents keys/values; a repaired call still
  goes through schema validation.
- agent/loop.ts: on a parse failure in accumulated native tool calls, try
  repair before falling back to a full non-streaming regeneration (which
  is expensive on a local backend and fails identically when the cause was
  max_tokens). Repaired args replace the broken ones; only unrepairable
  calls trigger the retry.
- partialJson.test.ts: 10 cases (truncation, trailing comma nesting,
  braces inside strings, escaped quotes, stray trailing content).

#3 Fallback tool-call parser robustness:
- src/toolcalling/fallbackParser.ts: now accepts ```tool_call blocks
  (multiline-anchored so an inner ```json fence isn't read as the
  terminator), ```json blocks, AND bare unfenced tool-call objects in
  prose. Inner ```json fences are stripped. Every candidate runs through
  partial-JSON repair, so a truncated fence (no closing ```) still
  recovers. Only accepts bare braces that contain "name"+"arguments" keys.
- fallbackParser.test.ts: 11 cases.

#6 Empty-response retry:
- agent/loop.ts: a bare empty `stop` (no text, no tool calls — common from
  small/quantized local models) now retries once with a nudge instead of
  returning "" or hard-erroring. After the retry budget is exhausted, a
  genuine empty response throws "Empty response from model."
- loop.test.ts: retry-then-succeed and retry-exhausted-throws cases.

Verified: typecheck clean, build 251.48 KB, 242 tests pass (+22).
2026-08-21 13:31:37 +09:00
kim cb950891d5 feat: LSP code intelligence + parallel sub-agent mutation gate
Code intelligence (LSP):
- src/codeintel/lspManager.ts: lazy per-language LSP server lifecycle
  (tsserver/pyright/gopls/clangd/rust-analyzer), didOpen/didChange sync,
  definition/references/diagnostics, notifyFileChanged, shutdownAll.
  Fix stream typing (StreamMessageReader/Writer) + MarkupContent→string.
- src/tools/codeIntel.ts: definition/references/diagnostics read-only tools.
- tools/index.ts: register the three LSP tools.
- agent/loop.ts: wire notifyFileChanged into the FileChanged hook path
  (fire-and-forget, best-effort) so live servers stay in sync with disk.
- ui/ink/index.tsx: shutdownAll on exit so spawned servers aren't orphaned.

Parallel sub-agent orchestration:
- agent/session.ts: session.mutationGate promise chain serializes every
  mutating tool call across the session (incl. parallel sub-agents that
  share the parent session) so they can't race on the single permission
  slot or interleave filesystem writes. Read-only tools stay concurrent.
- agent/loop.ts: runUnderMutationGate wraps mutating tool execution;
  sub-agents inherit the parent's gate.
- tools/agentTool.ts: `tasks` array runs N sub-agents in parallel; one
  failure surfaces as that task's error, not a rejected batch.
- tools/types.ts: SubAgentResult type.

Other:
- cli.ts + mcp/client.ts: read version from package.json instead of
  hardcoding "0.3.1".
- agent/parallelAgents.test.ts: parallel batch + mutation-gate tests.
- Remove scratch guardtest2.mjs.

Verified: typecheck clean, build 245.85 KB, 219 tests pass.
2026-08-21 13:22:34 +09:00
kim 6fe98887d5 v0.6.0: complete all 12 local-model upgrades + raise maxIterations to 100
Upgrade candidates (all 12 done):
- #1 parallel read-only tool execution (runToolBatch, 4 loop sites)
- #2 configurable retry policy (maxRetries + exponential backoff via SDK)
- #3 script-aware token estimation (CJK/symbol/structure-aware heuristic)
- #4 head+tail output capping (truncate.ts), auto-applies to bash/git/etc
- #5 partial-history compaction (preserve recent tail, summarize older prefix)
- #6 dynamic max_tokens (resolveMaxTokens)
- #7 richer tool descriptions with "use when" guidance
- #8 MCP reconnect retry + /mcp reconnect command + session toolset refresh
- #9 context-window cache TTL (cachedAt timestamp, default 7 days)
- #10 edit_file similar-match suggestion on old_string miss (bounded Levenshtein)
- #11 git_status output head+tail (resolved via #4)
- #12 auto-accept now approves all mutating tools; auto-edit stays edit-only

Other:
- DEFAULT_MAX_ITERATIONS 50 -> 100 (local models issue one tool call per step)
- MaxIterationsError message guides resume + config override
- plus prior known-issues work (grep -e/--, session id sanitization, MCP content
  types, 12 hook events, plugin collisions, skill references, git ops expansion,
  configurable autoCompactThreshold, bashGuard, pathGuard, FilePanel, replay)
2026-08-20 16:52:03 +09:00
kimandClaude Sonnet 5 6677ac1e9e v0.5.2: full-screen alt-screen mode with a scrollable, bottom-pinned layout
- Switch to the terminal's alternate screen buffer once a session starts,
  clearing and homing the cursor so content starts flush at (0,0)
- Replace <Static> scrollback with a fixed-height viewport (overflowY hidden)
  that top-aligns short conversations and auto-scrolls to the latest message
  once content exceeds the screen, keeping the input pinned to the bottom
- Rewrite ChatInput's cursor handling: track cursor offset directly instead of
  relying on ink-text-input, and report the real terminal cursor position via
  useCursor + a Yoga-tree walk (absolutePosition.ts) so CJK IME composition
  windows anchor at the actual caret instead of the terminal's fallback corner
- Print a "resume this conversation" hint after leaving the alt screen on exit

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 18:16:51 +09:00
kimandClaude Sonnet 5 ba0a7e44d1 v0.5.1: print a resume hint on exit
Every exit path (Ctrl+C, /exit, SIGTERM/SIGHUP) now prints "Resume this
conversation later with: locode --resume <id>" once Ink has released the
terminal, so the session id isn't lost the moment the process ends.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 16:11:07 +09:00
kim 0bc9480c1d v0.5.0: reduce system prompt token usage by ~35-40%
- Compress systemPrompt.ts guidelines (7 → 5 rules, remove redundancy)
- Shorten tool descriptions: read_file, agent, git_status, git_commit, todo_write, edit_file, web_search, web_fetch
- Shorten parameter descriptions in readFile, agentTool, git, editFile, todoWrite
- Simplify fallbackPrompt.ts (remove example, shorten instructions)
- All 123 tests pass
2026-07-13 16:05:00 +09:00
kim eb03d0bd14 feat: todo lists, permission modes, project instructions, and agent robustness
- Add todo_write tool with live checklist rendering in the UI
- Add permission modes (default/plan/auto-edit/auto-accept) cycled via Shift+Tab or /perm
- Load CLAUDE.md/AGENTS.md as project instructions into the system prompt
- Forward sub-agent tool calls/results to parent UI panel
- Auto-compact context mid-turn and keep model moving with a synthetic user turn
- Prune older image_url parts to cap vision token cost
- Idle-abort guard for stalled streaming backends
- Improve background bash jobs, kill whole process trees on timeout/exit
- Expand hooks: command/http hooks, blocking, JSON outputSchema
- MCP: JSON schema → Zod conversion, parallel connection, duplicate server detection
- Add tests for loop, confirmFn, projectInstructions, readFile, bash, todoWrite, contextWindowCache
2026-07-13 15:47:48 +09:00
kimandClaude Sonnet 5 f01898a9c3 Bump to 0.3.1: fix backend hangs, UI layout, and dead-code type error
A day of dogfooding locode on itself surfaced several real bugs, most centered on
the backend request lifecycle silently hanging with no error surfaced to the user:

- bash/hooks: execa's shell:true defaulted to cmd.exe on Windows, which lacks
  Unix tools (cat, sed, grep, ...) and fails a whole pipe with an unhelpful exit
  255 if any stage isn't found. Added src/utils/shell.ts to resolve real Git Bash
  when available.
- agent/loop: the backend client's own `timeout` only bounds time-to-first-byte —
  once a streaming response starts, a backend that goes silent mid-stream (or one
  that sends periodic content-less "heartbeat" chunks to keep the connection alive
  through a proxy) hung the request forever with zero output. Added an idle-abort
  guard that pokes only on chunks carrying real progress (usage/text/tool-calls/
  finish_reason), covering the main stream, the non-streaming malformed-JSON
  retry, and compactSession.
- agent/loop: a single tool-call-heavy turn (e.g. reading dozens of files) had no
  auto-compaction safety net at all — shouldAutoCompact was only ever checked
  between turns, never during one, so a long turn could blow past the context
  window with no mid-turn correction. Now checked every iteration; re-anchors
  mutationCommitLength afterward since compaction replaces session.messages
  wholesale.
- agent/loop: hitting the iteration cap surfaced as an opaque "Request failed"
  even when every prior tool call (including file edits) had actually succeeded.
  Added MaxIterationsError, rendered as a calm "paused, send another message to
  continue" notice instead, and raised the default cap 25 -> 50.
- agent/loop: removed a dead-code branch (fallback-malformed retry handling
  placed inside a native-mode-only code path, so session.mode === "fallback"
  could never be true there) that TypeScript correctly flagged as a type error.
- ui/App: text_done fires with an empty fullText for pure tool-call turns (no
  preceding prose); the assistant-message push had no guard, rendering a blank
  line every time. Now skipped when fullText is empty.
- ui/App+index: alternate-screen-buffer mode and StatusBar-above-ChatInput
  layout, fixing the reported "prompt starts below the dashboard, jumps up on
  space" glitch. Added input history (up/down recall).
- utils/writeFileAtomic: random temp-file suffix instead of a fixed `<file>.tmp`,
  so concurrent writes to the same directory can't collide.

Also fixes two hardcoded version strings (cli.ts --version, mcp/client.ts's
self-reported MCP identity) that were left stale at 0.2.0 through the 0.3.0
bump, and a stale maxIterations default noted in the README.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 18:06:52 +09:00
kimandClaude Sonnet 5 4652ecb87b Add bash_kill, background job eviction, and configurable request timeout
Bump to 0.3.0.

- bash_kill tool lets the model terminate a still-running backgrounded
  job (Ctrl+B); all running jobs are killed on process exit so they
  don't outlive locode as orphans.
- Finished background jobs are now evicted from the registry after a
  10-minute TTL instead of accumulating for the life of the session.
- The chat completion request timeout is now configurable
  (requestTimeoutMs / LOCODE_REQUEST_TIMEOUT_MS, default 180000) so
  backends that queue requests behind a concurrency limit (e.g.
  Ollama's OLLAMA_NUM_PARALLEL) under multi-session load aren't forced
  into a hard failure at a fixed 3 minutes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 12:48:32 +09:00
kimandClaude Sonnet 5 2a35f40c73 Add plugin/hook/skill support, images, @mentions, dashboard, and stability fixes
Builds out locode's Claude Code plugin parity: MCP servers, slash commands,
sub-agents, hooks (12 lifecycle events), and skills, all loadable from a
local path or git URL. Also adds image support (read_file, /import), @-mention
file autocomplete, a /dashboard stats view, /export with an editable filename
prompt, an expanded git tool (reset/stash/merge/rebase/delete_branch), and a
redesigned status bar.

On top of that, a review pass found and fixed 10 correctness/stability bugs:
argument-injection in git reset/merge/rebase (a ref like "--hard" was parsed
as a flag), tool-call image results interleaving with native tool_call_id
messages and breaking OpenAI-compatible message ordering, backgrounded bash
jobs still being silently killed by their original timeout with the kill
masked as a clean exit, a SubagentStart hook's block being ignored, the
sub-agent timeout clock starting before the hook it should exclude, a
template-expansion bug that could re-substitute $1..$9 placeholders,
unbounded background-job output buffers, a missing directory-target fallback
in /export, and image size caps checked after reading the whole file instead
of before.

Session saves and exports now go through a shared atomic
write-then-rename helper so a crash can't leave a truncated file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 18:17:45 +09:00
kimandClaude Sonnet 5 265f8930a3 Remove accidentally committed terminal transcript
2026-07-03-134954.txt was a stray captured session log containing an
email address, tracked since the initial commit. Ignore future files
matching the same timestamped naming pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 12:52:19 +09:00
kimandClaude Sonnet 5 4e6c0bb216 Bump to 0.2.0 and make max tool-call iterations configurable
The 8-iteration-per-turn cap was hardcoded from before this session's feature work and
too tight for genuine multi-file tasks, causing "Max tool-call iterations reached" on
legitimately multi-step work. Raised the default to 25 and made it configurable via
`locode config set maxIterations <n>` / $LOCODE_MAX_ITERATIONS, mirroring the existing
contextWindow config pattern.

Also bumps the version to 0.2.0 (package.json, --version, and the MCP client's
self-reported identity) to reflect the substantial feature additions since 0.1.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 18:08:49 +09:00
kimandClaude Sonnet 5 3f1527375c Add web search/fetch, sub-agents, MCP support, git tools, session persistence, and context compaction
Substantially expands locode's tool loop toward feature parity with Claude Code:

- web_search / web_fetch: DuckDuckGo-backed search and URL fetching (HTML stripped to text)
- git_status / git_commit: structured git tools with real diff/status/commit-list previews
  before any mutating operation is confirmed
- agent tool: sub-agents run an isolated, headless tool loop sharing the parent's permissions;
  bounded by an explicit depth guard (no nested sub-agents) and a 120s hard timeout, independent
  of the toolset already excluding `agent` for sub-agent sessions
- MCP client support (stdio + streamable HTTP transports) via the official SDK: locode connects
  to servers configured in `.mcp.json` or via `locode mcp add`, and exposes their tools alongside
  the built-in ones, namespaced as `mcp__<server>__<tool>`
- Session persistence: conversations auto-save after every turn; `--continue`/`--resume`,
  `locode sessions list/rm`, and an interactive resume picker
- Context compaction: tracks real token usage via `stream_options.include_usage` (falling back to
  a char-based estimate), auto-detects the model's context window (Ollama/LM Studio native APIs,
  cached, else a configurable default), and auto-summarizes the conversation at 85% usage or on
  demand via /compact
- Status bar and welcome banner: added a context-usage indicator and fixed several dim-by-default
  colors that were hard to read

Session/loop architecture changed to support all of this: Session now carries its own toolset
(tools + registry + OpenAI schemas) built from local + MCP tools, so runTurn no longer depends on
a fixed global tool list — the same mechanism that lets sub-agents inherit MCP tools while
excluding `agent` itself.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 17:53:27 +09:00
kim 68f2242984 first commit 2026-07-06 12:41:15 +09:00