all: merge thin packages and small files

- Merge detach/ into toolsrv/detach.go (single consumer)
  Rename Process → DetachedProcess to avoid collision with spawn.go
- Merge agent/state.go into loop.go (types used only there)
- Merge agent/runtime.go into runtime.go (formerly build_runtime.go)
- Merge agent/compaction.go + models.go into history.go
- Merge agent/new.go into agent.go (constructor lives with struct)
- Rename ResultTier → MemoryTier (TierHot/Warm/Cold → MemoryHot/Warm/Cold)
- Rename build_runtime.go → runtime.go
This commit is contained in:
Ollie Agent 2026-08-08 15:56:26 +02:00
parent 8152dec075
commit 705a7226a2
13 changed files with 329 additions and 363 deletions

View File

@ -621,3 +621,62 @@ func (ag *Agent) DismissDetached(pid int) bool {
}
return ag.runtime.ToolServer.DismissDetached(pid)
}
// AgentCfg is the configuration for constructing a new Agent.
type AgentCfg struct {
ID string // unique agent identity (uname)
SessionID string // id of session the agent belongs to
Profile string // config profile name (e.g. "default" → agents/default.json)
History *History
Runtime *Runtime
AgentsDir string
Cwd string // working directory for tool execution
SystemPrompt string
EnvBlock string
PromptEnvExtra []string
NewToolServer func() *toolsrv.Conn
NewBackend func(string) (backend.Backend, error)
Output EventHandler
Log *olog.Logger
AuditLog *olog.Logger
StartupMsgs []string
ReadPlanStep func() string
Save func()
Flush func()
}
// NewAgent constructs an Agent from the given configuration.
func NewAgent(cfg AgentCfg) *Agent {
auditLog := cfg.AuditLog
if auditLog == nil && cfg.Log != nil {
cfg.Log.Warn("AuditLog not provided for agent %s, deriving from Log", cfg.ID)
auditLog = cfg.Log.Sub("audit")
}
ag := &Agent{
history: cfg.History,
runtime: cfg.Runtime,
profile: cfg.Profile,
name: cfg.ID, // display name defaults to uname
agentsDir: cfg.AgentsDir,
id: cfg.ID,
cwd: cfg.Cwd,
systemPrompt: cfg.SystemPrompt,
envBlock: cfg.EnvBlock,
promptEnvExtra: cfg.PromptEnvExtra,
newToolServer: cfg.NewToolServer,
newBackend: cfg.NewBackend,
output: cfg.Output,
log: cfg.Log,
auditLog: auditLog,
sessionID: cfg.SessionID,
startupMessages: cfg.StartupMsgs,
readPlanStep: cfg.ReadPlanStep,
save: cfg.Save,
flush: cfg.Flush,
state: "idle",
}
ag.changeCond = sync.NewCond(&ag.changeMu)
ag.wireToolsChanged()
return ag
}

View File

@ -1,194 +0,0 @@
package agent
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"ollie/backend"
"ollie/prompts"
"ollie/toolsrv"
)
// BuildRuntime constructs a Runtime from a pre-configured Dispatcher and
// optional agent config. cwd sets the working directory reported in the
// system prompt; if empty, the process working directory is used.
// env provides additional environment variables injected into prompt resolution
// subprocesses (e.g. OLLIE_SESSION_ID=xxx).
// The caller is responsible for registering all servers on d before calling this.
func BuildRuntime(cfg *AgentConfig, srv *toolsrv.Conn, cwd string, env []string, systemPrompt, envBlock string) *Runtime {
var messages []string
var allToolInfos []toolsrv.ToolInfo
var allTools []backend.Tool
if srv != nil && (cfg == nil || cfg.ToolsEnabled()) {
var listErr error
allToolInfos, listErr = srv.ListTools()
if listErr != nil {
messages = append(messages, fmt.Sprintf("list tools: %v", listErr))
}
// Only loaded tools become backend callable tools.
allTools = toolInfosToBackend(allToolInfos)
}
var agentPrompt string
var genParams backend.GenerationParams
var maxSteps int
if cfg != nil {
if resolved, err := resolvePrompt(cfg.Prompt, cwd, env); err != nil {
fmt.Fprintf(os.Stderr, "resolve prompt: %v\n", err)
} else {
agentPrompt = resolved
}
genParams = cfg.GenParams()
maxSteps = cfg.MaxSteps
if len(cfg.AllowTools) > 0 && srv != nil {
srv.SetAllowTools(cfg.AllowTools)
}
}
exec := func(ctx context.Context, name string, args json.RawMessage) (string, []backend.ContentBlock, error) {
if srv == nil {
return "", nil, fmt.Errorf("no tool server available")
}
raw, err := srv.CallTool(ctx, name, args)
if err != nil {
return "", nil, err
}
text, blocks, isErr := extractToolResult(raw)
if isErr {
return "", nil, fmt.Errorf("%s", text)
}
return text, blocks, nil
}
// Build ToolMeta lookup from the tool listing — single source of truth
// for all per-tool metadata (tier, readOnly, outputFormat, resetsCounter).
toolMeta := make(map[string]toolsrv.ToolInfo, len(allToolInfos))
for _, ti := range allToolInfos {
toolMeta[ti.Name] = ti
}
var compactionModel string
if cfg != nil {
compactionModel = cfg.CompactionModel
}
preamble := buildPreamble(systemPrompt, envBlock, agentPrompt, allToolInfos)
return &Runtime{
ToolServer: srv,
Tools: allTools,
ToolMeta: toolMeta,
Exec: exec,
Preamble: preamble,
GenParams: genParams,
MaxSteps: maxSteps,
CompactionModel: compactionModel,
Messages: messages,
}
}
// buildPreamble constructs the complete system prompt from its components.
// The preamble evolves when:
// - Working directory changes (envBlock updates)
// - Tools are loaded/unloaded (allToolInfos changes)
// - Agent profile switches (agentPrompt changes)
// This function should be called whenever any component changes.
func buildPreamble(systemPrompt, envBlock, agentPrompt string, allToolInfos []toolsrv.ToolInfo) string {
var sb strings.Builder
// 1. Core system prompt (identity, rules, operating principles)
sb.WriteString(systemPrompt)
sb.WriteByte('\n')
// 2. Environment context (working directory, platform, date, etc.)
sb.WriteString(envBlock)
sb.WriteByte('\n')
// 3. Agent-specific prompt (role, task instructions from config)
sb.WriteString(agentPrompt)
sb.WriteByte('\n')
// 4. Compact tool surface listing (name + description)
var toolListing strings.Builder
for _, ti := range allToolInfos {
if ti.Description != "" && ti.Server == "" {
fmt.Fprintf(&toolListing, "- **%s** — %s\n", ti.Name, ti.Description)
}
}
// 5. Full tool documentation (prompts, schemas, examples)
var toolDocs strings.Builder
for _, ti := range allToolInfos {
if ti.Prompt != "" && ti.Server == "" {
toolDocs.WriteString(ti.Prompt)
toolDocs.WriteString("\n\n")
if len(ti.InputSchema) > 0 && string(ti.InputSchema) != "{}" {
fmt.Fprintf(&toolDocs, "**Schema**:\n```json\n%s\n```\n\n", ti.InputSchema)
}
}
}
// 6. Render tool sections
if toolListing.Len() > 0 || toolDocs.Len() > 0 {
toolSections := prompts.ToolSections(toolListing.String(), toolDocs.String())
if toolSections != "" {
sb.WriteString("\n")
sb.WriteString(toolSections)
}
}
// 6. Tool discovery instructions (9P filesystem)
sb.WriteString("\n")
sb.WriteString(prompts.ToolDiscoveryPrompt)
return sb.String()
}
func toolInfosToBackend(infos []toolsrv.ToolInfo) []backend.Tool {
out := make([]backend.Tool, len(infos))
for i, t := range infos {
out[i] = backend.Tool{
Name: t.Name,
Description: t.Description,
Parameters: t.InputSchema,
}
}
return out
}
func extractToolResult(raw json.RawMessage) (text string, contentBlocks []backend.ContentBlock, isError bool) {
var result struct {
IsError bool `json:"isError"`
Content []struct {
Type string `json:"type"`
Text string `json:"text"`
MediaType string `json:"media_type"`
Data string `json:"data"`
} `json:"content"`
}
if err := json.Unmarshal(raw, &result); err != nil {
return string(raw), nil, false
}
var parts []string
for _, c := range result.Content {
switch c.Type {
case "text":
parts = append(parts, c.Text)
case "image":
contentBlocks = append(contentBlocks, backend.ContentBlock{
Type: "image",
ImageSource: &backend.ImageSource{
Type: "base64",
MediaType: c.MediaType,
Data: c.Data,
},
})
}
}
return strings.Join(parts, "\n"), contentBlocks, result.IsError
}

View File

@ -1,26 +0,0 @@
package agent
import (
"os"
"ollie/backend"
)
// resolveCompactionModel returns the model to use for compaction.
// Priority: agent config > OLLIE_COMPACTION_MODEL env > per-backend default from models.yaml > session's current model.
// Returns "" if no override is configured (use the session's current model).
func resolveCompactionModel(cfgModel string, b backend.Backend) string {
if cfgModel != "" {
return cfgModel
}
if env := os.Getenv("OLLIE_COMPACTION_MODEL"); env != "" {
return env
}
cfg := loadModelsConfig()
if b != nil {
if m, ok := cfg.Compaction[b.Name()]; ok {
return m
}
}
return ""
}

View File

@ -5,9 +5,13 @@ import (
"crypto/rand"
"encoding/json"
"fmt"
"os"
"strings"
"ollie/backend"
"ollie/paths"
"gopkg.in/yaml.v3"
)
const (
@ -539,3 +543,45 @@ func NewResponseID() string {
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}
// resolveCompactionModel returns the model to use for compaction.
// Priority: agent config > OLLIE_COMPACTION_MODEL env > per-backend default from models.yaml > session's current model.
// Returns "" if no override is configured (use the session's current model).
func resolveCompactionModel(cfgModel string, b backend.Backend) string {
if cfgModel != "" {
return cfgModel
}
if env := os.Getenv("OLLIE_COMPACTION_MODEL"); env != "" {
return env
}
cfg := loadModelsConfig()
if b != nil {
if m, ok := cfg.Compaction[b.Name()]; ok {
return m
}
}
return ""
}
// modelsConfig holds the parsed contents of ~/.config/ollie/models.yaml.
type modelsConfig struct {
Compaction map[string]string `yaml:"compaction"`
Completion struct {
Model string `yaml:"model"`
Backend string `yaml:"backend"`
} `yaml:"completion"`
}
// loadModelsConfig reads and parses the models config file.
// Returns zero value if the file doesn't exist or is invalid.
func loadModelsConfig() modelsConfig {
data, err := os.ReadFile(paths.CfgDir() + "/models.yaml")
if err != nil {
return modelsConfig{}
}
var cfg modelsConfig
if err := yaml.Unmarshal(data, &cfg); err != nil {
return modelsConfig{}
}
return cfg
}

View File

@ -105,16 +105,16 @@ func toolOutputFormat(rt *Runtime, name string) string {
return ""
}
func toolMemoryTier(rt *Runtime, name string) ResultTier {
func toolMemoryTier(rt *Runtime, name string) MemoryTier {
if ti, ok := rt.ToolMeta[name]; ok {
switch ti.Tier {
case "cold":
return TierCold
return MemoryCold
case "warm":
return TierWarm
return MemoryWarm
}
}
return TierHot
return MemoryHot
}
// TurnCtx holds per-turn closures and state that vary between turns within
@ -431,7 +431,7 @@ func run(rt *Runtime, ctx TurnCtx, h *History) error {
// if the agent turn ends or is interrupted.
emit(ctx, Event{Role: "tool", Name: call.Name, OutputFormat: toolOutputFormat(rt, call.Name)})
}
tier := TierHot
tier := MemoryHot
if !isErr {
tier = toolMemoryTier(rt, call.Name)
}
@ -1092,3 +1092,22 @@ func retryCountdown(ctx TurnCtx, wait time.Duration) error {
const defaultToolResultMaxBytes = 131072
// MemoryTier classifies how long a tool result stays in the hot message list.
type MemoryTier int
const (
MemoryHot MemoryTier = iota // stays verbatim in messages (default)
MemoryWarm // summarized on next compaction pass
MemoryCold // immediately summarized at update time
)
// toolResult holds the output of a single tool call.
type toolResult struct {
ToolCallID string // may be empty (Ollama does not always set this)
Name string
Content string
ContentBlocks []backend.ContentBlock // image blocks extracted from tool output
IsError bool
Tier MemoryTier // default MemoryHot; tools may self-classify
}

View File

@ -1,32 +0,0 @@
package agent
import (
"os"
"ollie/paths"
"gopkg.in/yaml.v3"
)
// modelsConfig holds the parsed contents of ~/.config/ollie/models.yaml.
type modelsConfig struct {
Compaction map[string]string `yaml:"compaction"`
Completion struct {
Model string `yaml:"model"`
Backend string `yaml:"backend"`
} `yaml:"completion"`
}
// loadModelsConfig reads and parses the models config file.
// Returns zero value if the file doesn't exist or is invalid.
func loadModelsConfig() modelsConfig {
data, err := os.ReadFile(paths.CfgDir() + "/models.yaml")
if err != nil {
return modelsConfig{}
}
var cfg modelsConfig
if err := yaml.Unmarshal(data, &cfg); err != nil {
return modelsConfig{}
}
return cfg
}

View File

@ -1,68 +0,0 @@
package agent
import (
"sync"
"ollie/backend"
olog "ollie/log"
"ollie/toolsrv"
)
// AgentCfg is the configuration for constructing a new Agent.
type AgentCfg struct {
ID string // unique agent identity (uname)
SessionID string // id of session the agent belongs to
Profile string // config profile name (e.g. "default" → agents/default.json)
History *History
Runtime *Runtime
AgentsDir string
Cwd string // working directory for tool execution
SystemPrompt string
EnvBlock string
PromptEnvExtra []string
NewToolServer func() *toolsrv.Conn
NewBackend func(string) (backend.Backend, error)
Output EventHandler
Log *olog.Logger
AuditLog *olog.Logger
StartupMsgs []string
ReadPlanStep func() string
Save func()
Flush func()
}
// NewAgent constructs an Agent from the given configuration.
func NewAgent(cfg AgentCfg) *Agent {
auditLog := cfg.AuditLog
if auditLog == nil && cfg.Log != nil {
cfg.Log.Warn("AuditLog not provided for agent %s, deriving from Log", cfg.ID)
auditLog = cfg.Log.Sub("audit")
}
ag := &Agent{
history: cfg.History,
runtime: cfg.Runtime,
profile: cfg.Profile,
name: cfg.ID, // display name defaults to uname
agentsDir: cfg.AgentsDir,
id: cfg.ID,
cwd: cfg.Cwd,
systemPrompt: cfg.SystemPrompt,
envBlock: cfg.EnvBlock,
promptEnvExtra: cfg.PromptEnvExtra,
newToolServer: cfg.NewToolServer,
newBackend: cfg.NewBackend,
output: cfg.Output,
log: cfg.Log,
auditLog: auditLog,
sessionID: cfg.SessionID,
startupMessages: cfg.StartupMsgs,
readPlanStep: cfg.ReadPlanStep,
save: cfg.Save,
flush: cfg.Flush,
state: "idle",
}
ag.changeCond = sync.NewCond(&ag.changeMu)
ag.wireToolsChanged()
return ag
}

View File

@ -1,10 +1,198 @@
package agent
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"ollie/backend"
"ollie/prompts"
"ollie/toolsrv"
)
// BuildRuntime constructs a Runtime from a pre-configured Dispatcher and
// optional agent config. cwd sets the working directory reported in the
// system prompt; if empty, the process working directory is used.
// env provides additional environment variables injected into prompt resolution
// subprocesses (e.g. OLLIE_SESSION_ID=xxx).
// The caller is responsible for registering all servers on d before calling this.
func BuildRuntime(cfg *AgentConfig, srv *toolsrv.Conn, cwd string, env []string, systemPrompt, envBlock string) *Runtime {
var messages []string
var allToolInfos []toolsrv.ToolInfo
var allTools []backend.Tool
if srv != nil && (cfg == nil || cfg.ToolsEnabled()) {
var listErr error
allToolInfos, listErr = srv.ListTools()
if listErr != nil {
messages = append(messages, fmt.Sprintf("list tools: %v", listErr))
}
// Only loaded tools become backend callable tools.
allTools = toolInfosToBackend(allToolInfos)
}
var agentPrompt string
var genParams backend.GenerationParams
var maxSteps int
if cfg != nil {
if resolved, err := resolvePrompt(cfg.Prompt, cwd, env); err != nil {
fmt.Fprintf(os.Stderr, "resolve prompt: %v\n", err)
} else {
agentPrompt = resolved
}
genParams = cfg.GenParams()
maxSteps = cfg.MaxSteps
if len(cfg.AllowTools) > 0 && srv != nil {
srv.SetAllowTools(cfg.AllowTools)
}
}
exec := func(ctx context.Context, name string, args json.RawMessage) (string, []backend.ContentBlock, error) {
if srv == nil {
return "", nil, fmt.Errorf("no tool server available")
}
raw, err := srv.CallTool(ctx, name, args)
if err != nil {
return "", nil, err
}
text, blocks, isErr := extractToolResult(raw)
if isErr {
return "", nil, fmt.Errorf("%s", text)
}
return text, blocks, nil
}
// Build ToolMeta lookup from the tool listing — single source of truth
// for all per-tool metadata (tier, readOnly, outputFormat, resetsCounter).
toolMeta := make(map[string]toolsrv.ToolInfo, len(allToolInfos))
for _, ti := range allToolInfos {
toolMeta[ti.Name] = ti
}
var compactionModel string
if cfg != nil {
compactionModel = cfg.CompactionModel
}
preamble := buildPreamble(systemPrompt, envBlock, agentPrompt, allToolInfos)
return &Runtime{
ToolServer: srv,
Tools: allTools,
ToolMeta: toolMeta,
Exec: exec,
Preamble: preamble,
GenParams: genParams,
MaxSteps: maxSteps,
CompactionModel: compactionModel,
Messages: messages,
}
}
// buildPreamble constructs the complete system prompt from its components.
// The preamble evolves when:
// - Working directory changes (envBlock updates)
// - Tools are loaded/unloaded (allToolInfos changes)
// - Agent profile switches (agentPrompt changes)
// This function should be called whenever any component changes.
func buildPreamble(systemPrompt, envBlock, agentPrompt string, allToolInfos []toolsrv.ToolInfo) string {
var sb strings.Builder
// 1. Core system prompt (identity, rules, operating principles)
sb.WriteString(systemPrompt)
sb.WriteByte('\n')
// 2. Environment context (working directory, platform, date, etc.)
sb.WriteString(envBlock)
sb.WriteByte('\n')
// 3. Agent-specific prompt (role, task instructions from config)
sb.WriteString(agentPrompt)
sb.WriteByte('\n')
// 4. Compact tool surface listing (name + description)
var toolListing strings.Builder
for _, ti := range allToolInfos {
if ti.Description != "" && ti.Server == "" {
fmt.Fprintf(&toolListing, "- **%s** — %s\n", ti.Name, ti.Description)
}
}
// 5. Full tool documentation (prompts, schemas, examples)
var toolDocs strings.Builder
for _, ti := range allToolInfos {
if ti.Prompt != "" && ti.Server == "" {
toolDocs.WriteString(ti.Prompt)
toolDocs.WriteString("\n\n")
if len(ti.InputSchema) > 0 && string(ti.InputSchema) != "{}" {
fmt.Fprintf(&toolDocs, "**Schema**:\n```json\n%s\n```\n\n", ti.InputSchema)
}
}
}
// 6. Render tool sections
if toolListing.Len() > 0 || toolDocs.Len() > 0 {
toolSections := prompts.ToolSections(toolListing.String(), toolDocs.String())
if toolSections != "" {
sb.WriteString("\n")
sb.WriteString(toolSections)
}
}
// 6. Tool discovery instructions (9P filesystem)
sb.WriteString("\n")
sb.WriteString(prompts.ToolDiscoveryPrompt)
return sb.String()
}
func toolInfosToBackend(infos []toolsrv.ToolInfo) []backend.Tool {
out := make([]backend.Tool, len(infos))
for i, t := range infos {
out[i] = backend.Tool{
Name: t.Name,
Description: t.Description,
Parameters: t.InputSchema,
}
}
return out
}
func extractToolResult(raw json.RawMessage) (text string, contentBlocks []backend.ContentBlock, isError bool) {
var result struct {
IsError bool `json:"isError"`
Content []struct {
Type string `json:"type"`
Text string `json:"text"`
MediaType string `json:"media_type"`
Data string `json:"data"`
} `json:"content"`
}
if err := json.Unmarshal(raw, &result); err != nil {
return string(raw), nil, false
}
var parts []string
for _, c := range result.Content {
switch c.Type {
case "text":
parts = append(parts, c.Text)
case "image":
contentBlocks = append(contentBlocks, backend.ContentBlock{
Type: "image",
ImageSource: &backend.ImageSource{
Type: "base64",
MediaType: c.MediaType,
Data: c.Data,
},
})
}
}
return strings.Join(parts, "\n"), contentBlocks, result.IsError
}
// Runtime holds the swappable per-agent configuration. It contains everything
// that changes on an /agent switch but is stable across turns within the same
// agent. The agent struct stores a pointer to the active Runtime; switching

View File

@ -1,24 +0,0 @@
package agent
import (
"ollie/backend"
)
// ResultTier classifies how long a tool result stays in the hot message list.
type ResultTier int
const (
TierHot ResultTier = iota // stays verbatim in messages (default)
TierWarm // summarized on next compaction pass
TierCold // immediately summarized at update time
)
// toolResult holds the output of a single tool call.
type toolResult struct {
ToolCallID string // may be empty (Ollama does not always set this)
Name string
Content string
ContentBlocks []backend.ContentBlock // image blocks extracted from tool output
IsError bool
Tier ResultTier // default TierHot; tools may self-classify
}

View File

@ -1,4 +1,4 @@
package detach
package toolsrv
import (
"sync"
@ -7,9 +7,9 @@ import (
const RingBufSize = 64 * 1024 // 64KB ring buffer per detached process
// Process represents a process that the agent has detached from
// DetachedProcess represents a process that the agent has detached from
// but which continues running. The user can view its output and signal it.
type Process struct {
type DetachedProcess struct {
PID int
Command string
Started time.Time
@ -30,7 +30,7 @@ type InfoData struct {
ExitCode int
}
func (p *Process) Info() InfoData {
func (p *DetachedProcess) Info() InfoData {
p.Mu.Lock()
defer p.Mu.Unlock()
return InfoData{
@ -43,7 +43,7 @@ func (p *Process) Info() InfoData {
}
// Output returns the current contents of the ring buffer.
func (p *Process) Output() string {
func (p *DetachedProcess) Output() string {
p.Mu.Lock()
defer p.Mu.Unlock()
return p.Ring.String()

View File

@ -9,7 +9,6 @@ import (
"syscall"
"time"
"ollie/detach"
"ollie/paths"
)
@ -73,7 +72,7 @@ type Server struct {
// Detached process management
detachMu sync.Mutex
detachCh chan struct{} // signal to detach the currently running process
detached []*detach.Process
detached []*DetachedProcess
OnDetach func(pid int, cmd string) // hook: called when a process is detached
OnExit func(pid int, exitCode int) // hook: called when a detached process exits
}

View File

@ -17,7 +17,6 @@ import (
"syscall"
"time"
"ollie/detach"
"ollie/paths"
"ollie/sandbox"
)
@ -241,10 +240,10 @@ func (s *Server) executeElevatedOpts(ctx context.Context, cmd, dir string, timeo
if len(cmdStr) > 80 {
cmdStr = cmdStr[:77] + "..."
}
ring := detach.NewRingBuffer(detach.RingBufSize)
ring := NewRingBuffer(RingBufSize)
pid := int(time.Now().UnixNano() & 0x7FFFFFFF) // synthetic PID
proc := &detach.Process{
proc := &DetachedProcess{
PID: pid,
Command: cmdStr,
Started: time.Now(),
@ -327,7 +326,7 @@ func (s *Server) executeElevatedOpts(ctx context.Context, cmd, dir string, timeo
if len(cmdStr) > 80 {
cmdStr = cmdStr[:77] + "..."
}
ring := detach.NewRingBuffer(detach.RingBufSize)
ring := NewRingBuffer(RingBufSize)
pid := int(time.Now().UnixNano() & 0x7FFFFFFF)
// Splice: future output goes to ring buffer, stop streaming
@ -336,7 +335,7 @@ func (s *Server) executeElevatedOpts(ctx context.Context, cmd, dir string, timeo
lw.stream = nil
lw.mu.Unlock()
proc := &detach.Process{
proc := &DetachedProcess{
PID: pid,
Command: cmdStr,
Started: time.Now(),
@ -601,14 +600,14 @@ func (s *Server) executeWithStdin(ctx context.Context, code, language string, ti
if len(cmdStr) > 80 {
cmdStr = cmdStr[:77] + "..."
}
ring := detach.NewRingBuffer(detach.RingBufSize)
ring := NewRingBuffer(RingBufSize)
// Splice: future output goes to ring buffer instead of outputBuf.
lw.mu.Lock()
lw.w = ring
lw.stream = nil
lw.mu.Unlock()
proc := &detach.Process{
proc := &DetachedProcess{
PID: cmd.Process.Pid,
Command: cmdStr,
Started: time.Now(),