dirge-agent 0.6.1

Minimalistic coding agent written in Rust, optimized for memory footprint and performance
# dap-repl — interactive debug REPL driven by DAP Janet FFI
#
# Registers a /dap-repl slash command that opens a sub-mode where you
# type gdb/lldb-like commands directly. Every command calls the DAP
# Janet bindings (dap/launch, dap/step, dap/eval, dap/bp, etc.)
# and returns the JSON result.
#
# The REPL runs as a Janet coroutine: the harness/request-prompt
# mechanism injects each command's prompt into the chat. This means
# you get agent assistance between commands — you can ask "why is
# this variable null?" and the agent can call dap/stack-trace, etc.
#
# Architecture:
#   Plugin (this file) → dap/launch, dap/step, etc. (Janet FFI)
#   → C function (src/dap/janet_bindings.rs) → DAP_TX channel
#   → tokio bridge task → DapSessionManager (src/dap/session.rs)
#   → DapClient (src/dap/client.rs) → real adapter process

(def hooks ["on-prompt"])

# ── REPL state ──────────────────────────────────────────────────────

(var repl-active false)
(var last-file "")
(var last-adapter nil)

# ── Command dispatch ────────────────────────────────────────────────

(defn- repl-dispatch [cmd-str]
  (def parts (string/split " " cmd-str))
  (def head (get parts 0))
  (match head
    # Lifecycle
    "launch" (do
      (def file (get parts 1))
      (def adapter (get parts 2))
      (if (not file)
        "usage: launch <file> [adapter]"
        (do
          (set last-file file)
          (set last-adapter adapter)
          (def res (if adapter
                     (dap/launch file adapter)
                     (dap/launch file)))
          (or res "launch failed — check adapter"))))

    "attach" (do
      (def pid-str (get parts 1))
      (def adapter (get parts 2))
      (if (not pid-str)
        "usage: attach <pid> [adapter]"
        (do
          (def pid (math/parse-int pid-str))
          (if (not pid)
            (string "invalid pid: " pid-str)
            (do
              (def res (if adapter
                         (dap/attach pid adapter)
                         (dap/attach pid)))
              (or res "attach failed — check adapter and pid"))))))

    "terminate" (do
      (def res (dap/terminate))
      (set repl-active false)
      (or res "session terminated"))

    # Execution control
    "c" (dap/continue)
    "continue" (dap/continue)
    "n" (dap/step)
    "next" (dap/step)
    "step" (dap/step)
    "s" (dap/step-in)
    "step-in" (dap/step-in)
    "fin" (dap/step-out)
    "finish" (dap/step-out)
    "step-out" (dap/step-out)

    # Inspection
    "p" (dap/eval (string/join (array/slice parts 1) " "))
    "print" (dap/eval (string/join (array/slice parts 1) " "))
    "eval" (dap/eval (string/join (array/slice parts 1) " "))
    "bt" (dap/stack-trace)
    "backtrace" (dap/stack-trace)
    "info threads" (dap/threads)
    "threads" (dap/threads)
    "sessions" (dap/sessions)

    # Breakpoints
    "bp" (do
      (def file (get parts 1))
      (def line (get parts 2))
      (if (and file line)
        (dap/bp file line)
        "usage: bp <file> <line>"))

    "break" (do
      (def file (get parts 1))
      (def line (get parts 2))
      (if (and file line)
        (dap/bp file line)
        "usage: break <file> <line>"))

    # Variables
    "vars" (do
      (def ref (get parts 1))
      (if ref
        (dap/vars ref)
        "usage: vars <var-ref>"))

    # Meta
    "help" "
DAP REPL commands:
  launch <file> [adapter]  Start debugging a program
  attach <pid> [adapter]   Attach to a running process
  terminate                 End debug session
  c / continue              Resume execution
  n / next / step           Step over current line
  s / step-in               Step into function call
  fin / finish / step-out   Step out of current function
  p / print / eval <expr>   Evaluate expression
  bt / backtrace            Show call stack
  info threads              List threads
  bp / break <file> <line>  Set breakpoint
  sessions                  Show session status
  vars <var-ref>            Show variables
  q / quit                  Exit REPL
  help                      Show this message"

    "q" (do
      (set repl-active false)
      "REPL exited — session still active. Use `terminate` to end it.")
    "quit" (do
      (set repl-active false)
      "REPL exited — session still active. Use `terminate` to end it.")

    # Unknown
    (string "unknown command: " head " — try 'help'")))

# ── Hook — intercept prompts when REPL is active ────────────────────

(defn on-prompt [ctx]
  (if (not repl-active)
    nil
    (do
      (def prompt (ctx :prompt))
      # Strip REPL prefix if present
      (def clean (if (string/find "dap> " prompt)
                   (string/slice prompt 5)
                   prompt))
      (if (empty? clean)
        (do
          (harness/request-prompt "dap> ")
          "Type a command or 'help'")
        (do
          (def result (repl-dispatch clean))
          (if repl-active
            (harness/request-prompt "dap> "))
          result)))))

# ── Slash command entry point ───────────────────────────────────────

(defn repl-start [args]
  (if (not (dap/available?))
    "DAP not available — build with --features dap,plugin and install a debug adapter"
    (do
      (set repl-active true)
      (harness/request-prompt "dap> ")
      "
DAP REPL started. Commands:
  launch <file> [adapter]   — start debugging
  attach <pid> [adapter]    — attach to process
  c/continue, n/next/step, s/step-in, fin/step-out
  p/eval <expr>, bt/backtrace, bp/break <file> <line>
  sessions, vars <ref>, terminate, q/quit
")))

(harness/register-command "dap-repl" "repl-start")