Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
40563ed
fix(cli): resolve development ports and prompt setup context consiste…
moshloop Jul 28, 2026
646fcca
feat(ai): Expose caller tools to agent runtimes through authenticated…
moshloop Jul 31, 2026
58ef865
feat(cli): Expose canonical prompt runtimes and explicit save-as flow
moshloop Jul 31, 2026
d104e5d
feat(execution): add durable authority for chat executions and caller…
moshloop Jul 31, 2026
0706ce5
fix(ai): fix catalog capabilities and disabled selection consistency
moshloop Jul 31, 2026
8f3824b
feat(cmux): Add cmux process inspection command
moshloop Aug 2, 2026
88000b1
feat(transcript): Normalize wrapped shell commands in rendered transc…
moshloop Aug 2, 2026
80ad5fe
fix(ai): Normalize shell history and unify live event rendering
moshloop Aug 2, 2026
7c2a4f3
feat(aichat): unify session-backed chat lifecycle
moshloop Aug 3, 2026
161069c
build(webapp): Upgrade React type definitions to v19
moshloop Aug 3, 2026
72c0cae
feat(cmux): add cmux process inspection command
moshloop Aug 3, 2026
94c7029
test(webapp): avoid unsupported Array.at in stream hook test
moshloop Aug 3, 2026
de0dc0a
chore(cli): Prepare serve command for numeric parsing
moshloop Aug 3, 2026
48c6185
fix(db): Enforce model-call identity for tool approvals
moshloop Aug 3, 2026
0cfb94a
feat(aichat): Add runtime availability surfaces and endpoint for mode…
moshloop Aug 4, 2026
bb1eea7
fix(cli): hardens serve port handling and HTTP trace capture
moshloop Aug 4, 2026
2567969
fix(db): Fix session merge migration and release lock after verification
moshloop Aug 4, 2026
4e3b866
fix(ai): honor no-cache probing and support System tool mapping
moshloop Aug 4, 2026
2c554c2
feat(db): support database contexts and authoritative model costs
moshloop Aug 5, 2026
988d064
test(aichat): Use fixed thread stores in service tests
moshloop Aug 5, 2026
6a58eb0
fix(cli): resolve cross-source plans and render complete session trees
moshloop Aug 5, 2026
e77b2f6
fix(ai): preserve agent edits and provider identity across runtime seams
moshloop Aug 7, 2026
7f7707e
feat(aichat): Add session titles and atomic execution admission
moshloop Aug 7, 2026
1808536
chore: update generated and lock files
moshloop Aug 7, 2026
21fe87d
fix(session): Unify session projections and changed-file attribution
moshloop Aug 7, 2026
f648195
feat(agent): surface lifecycle hook activity in run transcripts
moshloop Aug 7, 2026
90e82ff
fix(agent): Record all files mutated by tool events
moshloop Aug 7, 2026
8fa7207
fix(aichat): Wait for suspended assistant message before resuming app…
moshloop Aug 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
28 changes: 28 additions & 0 deletions .gavel.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
ai: {}
checks: {}
commit:
allow:
- pkg/cli/webapp/dist/.gitkeep
grouping: {}
lint: {}
message: {}
precommit: {}
summary: {}
tidy: {}
fixtures: {}
lint:
fix: {}
pr:
content: {}
fix: {}
procfile: {}
secrets: {}
ssh: {}
status:
summary: {}
test:
outlineSummary: {}
todos:
plan: {}
run: {}
verify: {}
2 changes: 1 addition & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dev: go run ./cmd/captain serve --dev
dev: killport 9020; go run ./cmd/captain serve --dev --port 9020
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ Supported backends are inferred from code and dependencies, including:

### 8. Utility commands

- `cmux info <copy-id-lines|pid>` — resolves all processes in a cmux surface, pane, or workspace and reports runtime, CPU, memory, listening TCP ports, and optional Go stacks
- `cmux screenshot` — captures a screenshot of the active browser surface in cmux and copies the path to the clipboard
- `port kill <port>` — finds and kills the process listening on a TCP port

Expand Down Expand Up @@ -184,7 +185,7 @@ captain/
├── pkg/claude/ # Claude history, sessions, parsing, formatting
├── pkg/cli/ # Cobra/clicky command implementations
├── pkg/cli/webapp/ # Embedded React web UI (served by captain serve)
├── pkg/cmux/ # Terminal multiplexer integration (screenshot)
├── pkg/cmux/ # Terminal multiplexer integration (processes and screenshots)
├── pkg/collections/ # Generic collection utilities
├── pkg/container/ # Sandbox discovery, generation, build/run logic
├── pkg/dod/ # Definition of Done persistence and execution
Expand Down Expand Up @@ -491,10 +492,15 @@ Important generate/build flags:
captain whoami
captain whoami --backend anthropic
captain whoami --models=false
captain whoami --no-cache
```

Lists every AI adapter (API providers and CLI agents: `anthropic`, `openai`, `gemini`, `claude-cli`, `claude-agent`, `codex-cli`, `gemini-cli`), their authentication method, binary availability, and a live model listing.

Provider model listings are resolved through the persisted cache in `~/.config/captain/models.json` (24h TTL, invalidated when the set of configured API keys changes), and priced from the OpenRouter snapshot in `~/Library/Caches/flanksource/openrouter-pricing.json` (24h TTL). `--no-cache` skips both, re-queries every provider's model endpoint plus OpenRouter pricing, and rewrites each cache with the fresh result. Add `-v` to see an access line per request, `-vv` for headers and query params, `-vvv` for request bodies, `-vvvv` for response bodies; credentials are redacted at every rung. Failed requests (status >= 400 or a transport error) are logged at the default verbosity. `-Plog.level.http=<level>` raises HTTP logging alone, and `-Phttp.log.base-level=<level>` (or `HTTP_LOG_BASE_LEVEL`) shifts the whole ladder.

To keep the traffic instead of watching it scroll past, `-Phttp.har=<path>` writes every request/response pair — including redirect hops and retries — to a HAR 1.2 archive you can open in browser DevTools. It applies to any command, not just `whoami`, and the file is written even when the command fails. `-Phttp.har.level=metadata` records headers, query strings and timings without bodies; `-Phttp.har.maxBodySize=<bytes>` changes the 64 KB per-body cap (`0` for none). Credentials are masked the same way as in the wire log, which means the archive is safe to attach to a bug report but cannot be replayed — `-Phttp.har.sensitive=true` keeps them verbatim and writes the file `0600`. Use `-Phttp.captain.har=<path>` to capture captain's own traffic when a shared `http.har` is already set.

### Configure

```bash
Expand Down Expand Up @@ -523,7 +529,7 @@ task www:dev
task www:build
```

Starts an HTTP API and embedded web UI. The UI launches `captain ai agent` operations and opens follow-up chat windows that resume the returned session. `--dev` starts the Vite dev server from `pkg/cli/webapp`, binds both the Go API and Vite UI to random free ports, and proxies `/api` to the API. Pass `--port` or `--ui-port` to use a specific development port. Use `task www:dev` for the local Go-backed Vite proxy with the browser opened, and `task www:build` to rebuild the embedded web UI assets.
Starts an HTTP API and embedded web UI. The UI launches `captain ai agent` operations and opens follow-up chat windows that resume the returned session. `--dev` keeps the Go API on the configured `--port` (`9020` by default), starts Vite on a random free port, and proxies `/api` to the API. Pass `--ui-port` to use a specific Vite port. Use `task www:dev` for the local Go-backed Vite proxy with the browser opened, and `task www:build` to rebuild the embedded web UI assets.

### MCP server

Expand All @@ -536,6 +542,17 @@ Exposes captain commands as MCP tools. Auto-exposes all commands except `sandbox
### Utility commands

```bash
# Inspect every process attributed to a copied cmux surface
captain cmux info \
surface_ref=surface:21 \
surface_id=65BB4725-B785-48DE-B3FD-31167ECB8300

# Pipe the Copy IDs block directly from the clipboard
pbpaste | captain cmux info

# Inspect a PID and request a Go goroutine stack through gops
captain cmux info 33745 --stack

# Screenshot active browser surface in cmux
captain cmux screenshot

Expand Down
33 changes: 33 additions & 0 deletions cmd/captain/help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"fmt"

"github.com/flanksource/clicky"
"github.com/flanksource/commons/help"
"github.com/spf13/cobra"
)

// installRootHelp appends commons' documentation of the runtime knobs captain
// inherits — log verbosity, HTTP wire logging, HAR capture and output
// formatting — to `captain --help`.
//
// Cobra resolves a command's help function by walking up to the root, so this
// one runs for every command; the block is emitted only when the target is the
// root itself, leaving subcommand help as cobra renders it and leaving the
// sandbox/container SetHelpFunc overrides untouched.
func installRootHelp(root *cobra.Command) {
cobraHelp := root.HelpFunc()
root.SetHelpFunc(func(cmd *cobra.Command, args []string) {
cobraHelp(cmd, args)
if cmd != root {
return
}
text := help.Help()
out := text.ANSI()
if clicky.Flags.NoColor {
out = text.String()
}
fmt.Fprintf(cmd.OutOrStdout(), "\n%s\n", out)
})
}
59 changes: 59 additions & 0 deletions cmd/captain/help_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package main

import (
"bytes"

"github.com/flanksource/clicky"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/spf13/cobra"
)

var _ = Describe("root help", func() {
// commonsMarker is a property only the commons help block documents, so its
// presence distinguishes the appended block from cobra's own output.
const commonsMarker = "http.har.maxBodySize"

newRoot := func() (*cobra.Command, *cobra.Command, *bytes.Buffer) {
out := &bytes.Buffer{}
root := &cobra.Command{Use: "captain", Short: "test root"}
child := &cobra.Command{Use: "child", Short: "test child", Run: func(*cobra.Command, []string) {}}
root.AddCommand(child)
root.SetOut(out)
root.SetErr(out)
installRootHelp(root)
return root, child, out
}

It("appends the commons runtime knobs to the root help", func() {
root, _, out := newRoot()
root.SetArgs([]string{"--help"})

Expect(root.Execute()).To(Succeed())
Expect(out.String()).To(ContainSubstring("Usage:"), "cobra's own help must still render")
Expect(out.String()).To(ContainSubstring("Available Commands:"))
Expect(out.String()).To(ContainSubstring(commonsMarker))
Expect(out.String()).To(ContainSubstring("HTTP wire logging"))
})

It("leaves subcommand help untouched", func() {
root, _, out := newRoot()
root.SetArgs([]string{"child", "--help"})

Expect(root.Execute()).To(Succeed())
Expect(out.String()).To(ContainSubstring("Usage:"))
Expect(out.String()).ToNot(ContainSubstring(commonsMarker))
})

It("drops ANSI escapes when --no-color is set", func() {
defer func(previous bool) { clicky.Flags.NoColor = previous }(clicky.Flags.NoColor)
clicky.Flags.NoColor = true

root, _, out := newRoot()
root.SetArgs([]string{"--help"})

Expect(root.Execute()).To(Succeed())
Expect(out.String()).To(ContainSubstring(commonsMarker))
Expect(out.String()).ToNot(ContainSubstring("\x1b["))
})
})
52 changes: 42 additions & 10 deletions cmd/captain/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,43 @@ func main() {
// respect it (cobra walks up the tree), so a runtime error from any
// command prints just the error.
SilenceUsage: true,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
clicky.Flags.UseFlags()
cli.EnableHTTPWireLogging()
// A malformed -Phttp.har.level is a hard stop rather than a run
// that silently captures nothing.
if err := cli.EnableHTTPWireLogging(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// Every model/effort resolution path reads a process-wide opt-out set;
// a malformed config is a hard stop rather than a silent full catalog.
if err := cli.InstallDisabledSelections(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// Bind the database context every command reads from, so an unknown
// --context fails before the command runs rather than at first query.
name, err := cli.ResolveDatabaseContextName(cmd.Context())
if err != nil {
return err
}
cmd.SetContext(cli.ContextWithDatabaseContext(cmd.Context(), name))
return nil
},
}
configureVersion(rootCmd, currentBuildInfo())

clicky.BindAllFlags(rootCmd.PersistentFlags(), "format")
cli.BindDatabaseURLFlag(rootCmd.PersistentFlags())
cli.BindDatabaseFlags(rootCmd.PersistentFlags())
// Bind commons' -P/--properties flag so per-subsystem log levels and HTTP
// wire logging can be toggled, e.g. -Plog.level.http=trace3.
// wire logging can be toggled, e.g. -Plog.level.http=trace or
// -Phttp.log.base-level=info.
properties.BindFlags(rootCmd.PersistentFlags())

// Document those properties where they are discoverable: appended to the
// root --help, after the flags they complement.
installRootHelp(rootCmd)

// Bind HistoryOptions directly on rootCmd so 'captain' IS 'captain history'.
// All history flags (--tool, --category, --since, --limit, -f, ...) work
// at the root. RunHistory auto-detects piped stdin when File is empty.
Expand All @@ -67,7 +85,7 @@ func main() {
infoCmd.Short = "Show the current agent session or discovered project sessions"
infoCmd.Long = "Detect the current Codex, Claude, Gemini, or Captain session from its environment, including matching Captain database sessions. Explicit discovery flags show project session history instead."

costCmd := clicky.AddNamedCommand("cost", rootCmd, cli.CostOptions{}, cli.RunCost)
costCmd := clicky.AddNamedCommandWithContext("cost", rootCmd, cli.CostOptions{}, cli.RunCost)
costCmd.Short = "Show token usage and estimated costs"
costCmd.Long = "Display token consumption (input, output, cache read/write) and estimated costs across Claude Code sessions."

Expand All @@ -79,6 +97,10 @@ func main() {
planCmd.Short = "Show the exit-plan-mode plan for a session"
planCmd.Long = "Determine the plan file path and content for a Claude Code or Codex session. Pass a session ID (exact or prefix) to target a specific session; otherwise the most recent session with a plan in the current directory is used. Claude plans resolve to a ~/.claude/plans/<slug>.md file; Codex plans are inline update_plan checklists. Use --path to print only the plan file path."

contextsCmd := clicky.AddNamedCommandWithContext("contexts", rootCmd, cli.ContextsOptions{}, cli.RunContexts)
contextsCmd.Short = "List the databases captain can read"
contextsCmd.Long = "List the configured database contexts. Only the default context is monitored and written to; every other context is read-only and is selected per command with --context or per browser session from the UI's project picker. Pass --check to connect to each one and report whether it is reachable."

sessionsCmd := &cobra.Command{Use: "sessions", Short: "Browse Claude and Codex sessions"}
rootCmd.AddCommand(sessionsCmd)
clicky.AddNamedCommandWithContext("list", sessionsCmd, cli.SessionListOptions{}, cli.RunSessionList).Short = "List discovered sessions"
Expand Down Expand Up @@ -106,9 +128,11 @@ func main() {
Short: "AI provider commands",
Long: "AI provider commands.\n\n" +
"Logging: increase application verbosity with -v/-vv or --log-level=debug. " +
"To log HTTP requests/responses to the provider APIs (with sensitive headers " +
"redacted), set -Plog.level.http=trace3 for headers and timing, or trace4 to " +
"also include request/response bodies.",
"HTTP calls to the provider APIs are logged on the same ladder (with credentials " +
"redacted): failed requests are logged by default, -v adds an access line per " +
"request, -vv adds headers and query params, -vvv request bodies, -vvvv response " +
"bodies. Use -Plog.level.http=<level> to raise only HTTP logging, or " +
"-Phttp.har=<path> to write the exchanges to a HAR archive instead.",
}
rootCmd.AddCommand(aiCmd)
aiCmd.AddCommand(cli.NewCommandAlias(cli.CommandAliasOptions{
Expand All @@ -125,7 +149,7 @@ func main() {

whoamiCmd := clicky.AddNamedCommand("whoami", rootCmd, cli.WhoamiOptions{}, cli.RunWhoami)
whoamiCmd.Short = "List agent adapters, auth methods, and available models"
whoamiCmd.Long = "Show every AI agent adapter (API providers and CLI agents), how each is authenticated (Captain vault, API-key env var, or CLI login), whether its CLI binary is installed, and the models each provider exposes via a live API call. Pass --models=false to skip the network probes, or --backend to inspect a single adapter."
whoamiCmd.Long = "Show every AI agent adapter (API providers and CLI agents), how each is authenticated (Captain vault, API-key env var, or CLI login), whether its CLI binary is installed, and the models each provider exposes via a live API call. Disabled models are hidden by default; pass --disabled=true to include them. Pass --models=false to skip the network probes, --backend to inspect a single adapter, or --no-cache to bypass the persisted model and pricing caches and re-query both live."

configureCmd := clicky.AddNamedCommandWithContext("configure", rootCmd, cli.ConfigureOptions{}, cli.RunConfigure)
configureCmd.Use = "configure [provider]"
Expand Down Expand Up @@ -248,6 +272,10 @@ func main() {

cmuxCmd := &cobra.Command{Use: "cmux", Short: "Cmux terminal multiplexer commands"}
rootCmd.AddCommand(cmuxCmd)
cmuxInfoCmd := clicky.AddNamedCommandWithContext("info", cmuxCmd, cli.CmuxInfoOptions{}, cli.RunCmuxInfo)
cmuxInfoCmd.Use = "info [copy-id-lines|pid]..."
cmuxInfoCmd.Short = "Inspect processes running in a cmux target"
cmuxInfoCmd.Long = "Resolve copied cmux workspace, pane, or surface IDs (or a PID) and report each process, runtime, CPU, memory, and listening TCP ports. Use --stack to request Go goroutine stacks through gops."
screenshotCmd := clicky.AddNamedCommand("screenshot", cmuxCmd, cli.CmuxScreenshotOptions{}, cli.RunCmuxScreenshot)
screenshotCmd.Short = "Take a screenshot of the active browser surface"
screenshotCmd.Long = "Capture a screenshot of the currently focused browser panel in cmux and copy the file path to the clipboard."
Expand All @@ -258,7 +286,11 @@ func main() {
portKillCmd.Short = "Kill the process listening on a TCP port"
portKillCmd.Long = "Find the process bound to the specified TCP port using lsof and kill it with SIGKILL. Reports the process name and PID before killing."

if err := rootCmd.Execute(); err != nil {
err := rootCmd.Execute()
// Flush before exiting: PersistentPostRun does not run when a command
// fails, and a failed run is the one whose HAR you want.
cli.FlushHAR()
if err != nil {
os.Exit(1)
}
}
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/firebase/genkit/go v1.11.0
github.com/flanksource/clicky v1.21.52
github.com/flanksource/clicky/aichat v1.21.48
github.com/flanksource/commons v1.54.0
github.com/flanksource/commons v1.55.0
github.com/flanksource/sandbox-runtime v1.0.2
github.com/fsnotify/fsnotify v1.9.0
github.com/google/dotprompt/go v0.0.0-20260502013637-5cd4a8405ca3
Expand All @@ -23,6 +23,7 @@ require (
github.com/samber/lo v1.53.0
github.com/segmentio/encoding v0.5.4
github.com/sergi/go-diff v1.4.0
github.com/shirou/gopsutil/v3 v3.24.5
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1
github.com/t14raptor/go-fast v0.1.0
Expand Down Expand Up @@ -60,7 +61,7 @@ require (
github.com/nukilabs/ftoa v1.0.0 // indirect
github.com/nukilabs/unicodeid v0.1.0 // indirect
github.com/pgplex/pgparser v0.2.0 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/zclconf/go-cty v1.14.4 // indirect
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
Expand Down Expand Up @@ -136,7 +137,7 @@ require (
github.com/charmbracelet/bubbletea v1.3.10 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/ansi v0.11.6
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
Expand Down Expand Up @@ -292,7 +293,6 @@ require (
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/samber/oops v1.21.0 // indirect
github.com/segmentio/asm v1.2.1 // indirect
github.com/shirou/gopsutil/v3 v3.24.5 // indirect
github.com/shoenig/go-m1cpu v0.1.7 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@ github.com/flanksource/clicky v1.21.52 h1:JtcBD05mIbE0cLuhu218Z9uO6O2mFARmgrtEIB
github.com/flanksource/clicky v1.21.52/go.mod h1:eonv42hF6W1IPjQXOL6roBm/nHCYNomqc3Kps2E3RZE=
github.com/flanksource/clicky/aichat v1.21.48 h1:f8Kvl96Lfp1qcqPuve1zsjaYN8ZcK1/FYqnkGA7VN30=
github.com/flanksource/clicky/aichat v1.21.48/go.mod h1:PGN/lVAgxpchRctciUCpR4YIuqWoDwRxDh339A6wi3w=
github.com/flanksource/commons v1.54.0 h1:tXTJ9rzko94HdiZe9vcG857i5+02TyZ3qkl+GVElWOs=
github.com/flanksource/commons v1.54.0/go.mod h1:gupTCRqGpgD8dd2ooE7bMDJxkfcVKvkPVuHg3cWkW+Q=
github.com/flanksource/commons v1.55.0 h1:gj9zBY3V1qgAAnEiLaeGbkqCmNK0p1tJVQCDurdTZ2k=
github.com/flanksource/commons v1.55.0/go.mod h1:gupTCRqGpgD8dd2ooE7bMDJxkfcVKvkPVuHg3cWkW+Q=
github.com/flanksource/commons-db v0.1.26 h1:NXAP0WvMs4ufyDfl1L2ryRxBv5qxV67GiI1nINd4YIw=
github.com/flanksource/commons-db v0.1.26/go.mod h1:i378WIxy8g9xOeLBvhh1y3FO99oCumUqNmfhuDF79kM=
github.com/flanksource/gomplate/v3 v3.24.84 h1:UOE0yCJsczTIKRaHUvhD6tjCYrbNvOugAizuy0FVlhE=
Expand Down
3 changes: 3 additions & 0 deletions migrations/02_merge_duplicate_sessions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ BEGIN
UPDATE captain_prompt_runs r SET execution_session_id = m.winner
FROM captain_duplicate_session_map m WHERE r.execution_session_id = m.loser;

UPDATE captain_session_processes p SET session_id = m.winner
FROM captain_duplicate_session_map m WHERE p.session_id = m.loser;

-- captain_sessions.parent_session_id and root_session_id are self-referential
-- and ON DELETE CASCADE, so a ghost that any subagent row named as its parent
-- takes that whole subtree -- sessions, messages, turns, artifacts -- down with
Expand Down
Loading
Loading