Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions internal/chat/command_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (

// Each stream holds at most one bounded lexical token. Tokens spanning writes
// are scrubbed together; PEM bodies and oversized secret tokens are discarded before
// reaching storage. No raw output is spooled to disk.
// reaching storage or the agent. No raw output is spooled to disk. The agent
// always gets the scrubbed head and tail of the command it asked to run; only
// snippets capture stores the full scrubbed output.
type outputStream struct {
parent *commandOutput
name, token string
Expand Down Expand Up @@ -50,10 +52,12 @@ func (o *commandOutput) emit(ctx context.Context, stream, data string) {
if data == "" || o.err != nil {
return
}
if o.err = o.st.AppendCommandOutput(ctx, o.action, stream, o.seq, []byte(data)); o.err != nil {
return
if o.snippets {
if o.err = o.st.AppendCommandOutput(ctx, o.action, stream, o.seq, []byte(data)); o.err != nil {
return
}
o.seq++
}
o.seq++
o.total += len(data)
if len(o.head) < 2048 {
n := min(2048-len(o.head), len(data))
Expand Down Expand Up @@ -97,9 +101,6 @@ func (s *outputStream) Write(p []byte) (int, error) {
if o.finished {
return 0, errors.New("command output closed")
}
if !o.snippets {
return len(p), nil
}
var safe strings.Builder
for _, b := range p {
if b == ' ' || b == '\n' || b == '\r' || b == '\t' {
Expand Down Expand Up @@ -140,9 +141,7 @@ func (o *commandOutput) Finish(ctx context.Context) (string, error) {
defer o.mu.Unlock()
if !o.finished {
for _, s := range o.streams {
if o.snippets {
o.emit(ctx, s.name, s.flush())
}
o.emit(ctx, s.name, s.flush())
}
o.finished = true
}
Expand Down
6 changes: 5 additions & 1 deletion internal/chat/command_output_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@ func TestCommandOutputCapture(t *testing.T) {
if err = st.ReadCommandOutput(context.Background(), "a", &full); err != nil {
t.Fatal(err)
}
// The agent gets the scrubbed head and tail under every policy.
if strings.Contains(summary, "ghp_") || !strings.Contains(summary, "HEAD") || !strings.Contains(summary, "[redacted]") || !strings.Contains(summary, "TAIL") || len(summary) > 5000 {
t.Fatal("agent summary", summary)
}
if !snippets {
if full.Len() != 0 || summary != "" {
if full.Len() != 0 {
t.Fatal("titles retained output")
}
return
Expand Down
10 changes: 6 additions & 4 deletions site/src/content/docs/guides/chat.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -180,15 +180,17 @@ interrupted work is marked interrupted after a restart and never replayed to
the agent.

Your question is scrubbed and sent to Claude. Context and tool results pass the
capture policy first. Under the default `capture_text = "titles"` Driga keeps
capture policy first, except the output of commands you approve, described
below. Under the default `capture_text = "titles"` Driga keeps
no chat message bodies; `snippets` keeps scrubbed 200-character snippets.
Claude's own session history is managed by Claude and is not controlled by
Driga's capture policy.

Command results join that action log under the same capture policy and
30-day retention. Long command output keeps its head and tail in the
conversation; retained full output is scrubbed and follows the capture
policy (`titles` does not retain command output). Process result counts and
30-day retention. Claude gets the scrubbed head and tail of each command you
approve, up to 2 KB of each, under every capture policy, since reading that
output is why it asked to run the command. Retained full output is scrubbed
and follows the capture policy (`titles` does not retain command output). Process result counts and
per-target outcomes appear on the card. External `driga mcp` clients and
`driga ask` have no command actions.

Expand Down
Loading