Skip to content

Commit e87559e

Browse files
committed
v0.54.0 — 6 bug fixes: overlayFile merge, API key re-injection, FactStore TOCTOU race, EpisodeStore Search race, ResumeSession loop var, Bot doJSON retry stop
1 parent db47835 commit e87559e

20 files changed

Lines changed: 467 additions & 25 deletions

cmd/odek/main.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -924,7 +924,7 @@ func run(args []string) error {
924924

925925
// Sandbox setup
926926
var sandboxCleanup func() error
927-
tools := builtinTools(resolved.Dangerous, sm, nil, resolved.MaxConcurrency, resolved.Transcription, nil)
927+
tools := builtinTools(resolved.Dangerous, sm, nil, resolved.MaxConcurrency, resolved.APIKey, resolved.Transcription, nil)
928928

929929
// MCP server tools
930930
var mcpCleanup func()
@@ -1311,7 +1311,7 @@ func injectFilesToSandbox(containerName string, files []string, cwd string) (int
13111311
return injected, nil
13121312
}
13131313

1314-
func builtinTools(dc danger.DangerousConfig, sm *skills.SkillManager, approver danger.Approver, maxConcurrency int, tc config.TranscriptionConfig, store *session.Store) []odek.Tool {
1314+
func builtinTools(dc danger.DangerousConfig, sm *skills.SkillManager, approver danger.Approver, maxConcurrency int, apiKey string, tc config.TranscriptionConfig, store *session.Store) []odek.Tool {
13151315
tools := []odek.Tool{
13161316
&shellTool{
13171317
dangerousConfig: dc,
@@ -1320,6 +1320,7 @@ func builtinTools(dc danger.DangerousConfig, sm *skills.SkillManager, approver d
13201320
&delegateTasksTool{
13211321
maxConcurrency: maxConcurrency,
13221322
odekPath: os.Args[0],
1323+
apiKey: apiKey,
13231324
timeout: 120 * time.Second,
13241325
},
13251326
&readFileTool{dangerousConfig: dc},
@@ -1890,7 +1891,7 @@ func continueCmd(args []string) error {
18901891
"./.odek/skills",
18911892
)
18921893
}
1893-
tools := builtinTools(resolved.Dangerous, sm, nil, resolved.MaxConcurrency, resolved.Transcription, store)
1894+
tools := builtinTools(resolved.Dangerous, sm, nil, resolved.MaxConcurrency, resolved.APIKey, resolved.Transcription, store)
18941895
var sandboxCleanup func() error
18951896

18961897
// MCP server tools

cmd/odek/main_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ func TestRun_NoAPIKey(t *testing.T) {
201201
}
202202

203203
func TestBuiltinTools(t *testing.T) {
204-
tools := builtinTools(danger.DangerousConfig{}, nil, nil, 3, config.TranscriptionConfig{}, nil)
204+
tools := builtinTools(danger.DangerousConfig{}, nil, nil, 3, "", config.TranscriptionConfig{}, nil)
205205
if len(tools) == 0 {
206206
t.Fatal("builtinTools() returned empty slice")
207207
}

cmd/odek/mcp.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ Flags:
7373
}
7474

7575
// Build tools
76-
toolSet := builtinTools(resolved.Dangerous, sm, nil, resolved.MaxConcurrency, config.TranscriptionConfig{}, nil)
76+
toolSet := builtinTools(resolved.Dangerous, sm, nil, resolved.MaxConcurrency, resolved.APIKey, config.TranscriptionConfig{}, nil)
7777

7878
// MCP server tools — connect and discover before sandbox
7979
var mcpCleanup func()

cmd/odek/repl.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func replCmd(args []string) error {
7979
"./.odek/skills",
8080
)
8181
}
82-
tools := builtinTools(resolved.Dangerous, sm, nil, resolved.MaxConcurrency, config.TranscriptionConfig{}, nil)
82+
tools := builtinTools(resolved.Dangerous, sm, nil, resolved.MaxConcurrency, resolved.APIKey, config.TranscriptionConfig{}, nil)
8383
var sandboxCleanup func() error
8484

8585
// MCP server tools

cmd/odek/serve.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ func newServeAgent(resolved config.ResolvedConfig, system string, sendFn func(v
187187
approver := newWSApprover(sendFn)
188188
resolved.Dangerous.Approver = approver
189189

190-
tools := builtinTools(resolved.Dangerous, sm, approver, resolved.MaxConcurrency, config.TranscriptionConfig{}, nil)
190+
tools := builtinTools(resolved.Dangerous, sm, approver, resolved.MaxConcurrency, resolved.APIKey, config.TranscriptionConfig{}, nil)
191191

192192
// Find the delegateTasksTool to wire up sub-agent log streaming
193193
var subagentTool *delegateTasksTool

cmd/odek/subagent.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ func subagentCmd(args []string) error {
361361
"./.odek/skills",
362362
)
363363
}
364-
tools := builtinTools(resolved.Dangerous, sm, nil, resolved.MaxConcurrency, config.TranscriptionConfig{}, nil)
364+
tools := builtinTools(resolved.Dangerous, sm, nil, resolved.MaxConcurrency, resolved.APIKey, config.TranscriptionConfig{}, nil)
365365
var sandboxCleanup func() error
366366

367367
// MCP server tools

cmd/odek/subagent_contract_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ func TestSubagent_ExitCodeThree(t *testing.T) {
320320
// ── 4. delegate_tasks Tool Schema ───────────────────────────────────
321321

322322
func TestDelegateTasksTool_Exists(t *testing.T) {
323-
tools := builtinTools(danger.DangerousConfig{}, nil, nil, 3, config.TranscriptionConfig{}, nil)
323+
tools := builtinTools(danger.DangerousConfig{}, nil, nil, 3, "", config.TranscriptionConfig{}, nil)
324324
if len(tools) == 0 {
325325
t.Fatal("builtinTools() returned empty slice")
326326
}
@@ -338,7 +338,7 @@ func TestDelegateTasksTool_Exists(t *testing.T) {
338338
}
339339

340340
func TestDelegateTasksTool_HasSchema(t *testing.T) {
341-
tools := builtinTools(danger.DangerousConfig{}, nil, nil, 3, config.TranscriptionConfig{}, nil)
341+
tools := builtinTools(danger.DangerousConfig{}, nil, nil, 3, "", config.TranscriptionConfig{}, nil)
342342

343343
var tool odek.Tool
344344
for _, t2 := range tools {
@@ -427,7 +427,7 @@ func TestDelegateTasksTool_HasSchema(t *testing.T) {
427427
}
428428

429429
func TestDelegateTasksTool_Description(t *testing.T) {
430-
tools := builtinTools(danger.DangerousConfig{}, nil, nil, 3, config.TranscriptionConfig{}, nil)
430+
tools := builtinTools(danger.DangerousConfig{}, nil, nil, 3, "", config.TranscriptionConfig{}, nil)
431431

432432
var tool odek.Tool
433433
for _, t2 := range tools {

cmd/odek/subagent_tool.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
type delegateTasksTool struct {
2525
maxConcurrency int
2626
odekPath string // path to the odek binary
27+
apiKey string // re-injected into sub-agent environment
2728
timeout time.Duration
2829

2930
// ctx is the parent agent's context, set by the agent loop before each
@@ -201,6 +202,19 @@ func (t *delegateTasksTool) runTask(taskIdx int, goal, taskContext, system strin
201202
stderrBuf := &strings.Builder{}
202203
cmd.Stderr = stderrBuf
203204

205+
// Re-inject API key into sub-agent environment.
206+
// config.LoadConfig clears ODEK_API_KEY/DEEPSEEK_API_KEY/OPENAI_API_KEY
207+
// from the parent's environment, so the child process inherits nothing.
208+
// Set all three forms so the sub-agent's LoadConfig call finds the key
209+
// regardless of which env var fallback it uses.
210+
if t.apiKey != "" {
211+
cmd.Env = append(os.Environ(),
212+
"ODEK_API_KEY="+t.apiKey,
213+
"DEEPSEEK_API_KEY="+t.apiKey,
214+
"OPENAI_API_KEY="+t.apiKey,
215+
)
216+
}
217+
204218
if err := cmd.Start(); err != nil {
205219
return fmt.Sprintf(`{"error":"start: %v"}`, err)
206220
}

cmd/odek/telegram.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -888,11 +888,14 @@ func spawnChild() error {
888888
}
889889

890890
// Build child environment: parent env + re-injected API key.
891-
// config.LoadConfig clears ODEK_API_KEY from the environment, so it's
892-
// missing from os.Environ(). Re-inject it so the child can start.
891+
// config.LoadConfig clears all API key env vars from the environment,
892+
// so they're missing from os.Environ(). Re-inject all three forms so
893+
// the child process finds the key regardless of which env var it checks.
893894
childEnv := os.Environ()
894895
if resolvedAPIKey != "" {
895896
childEnv = append(childEnv, "ODEK_API_KEY="+resolvedAPIKey)
897+
childEnv = append(childEnv, "DEEPSEEK_API_KEY="+resolvedAPIKey)
898+
childEnv = append(childEnv, "OPENAI_API_KEY="+resolvedAPIKey)
896899
}
897900

898901
attr := &os.ProcAttr{
@@ -975,7 +978,7 @@ func handleChatMessage(
975978
cs.LastActive = time.Now()
976979

977980
// Build the agent with Telegram approver.
978-
tools := builtinTools(resolved.Dangerous, nil, approver, resolved.MaxConcurrency, resolved.Transcription, sessionManager.Store)
981+
tools := builtinTools(resolved.Dangerous, nil, approver, resolved.MaxConcurrency, resolved.APIKey, resolved.Transcription, sessionManager.Store)
979982

980983
modelLabel := odek.ProfileLabel(resolved.Model)
981984
if modelLabel == "" {

internal/config/loader.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,23 @@ func overlayFile(base, override FileConfig) FileConfig {
993993
if override.Telegram != nil {
994994
base.Telegram = override.Telegram
995995
}
996+
if override.PromptCaching != nil {
997+
base.PromptCaching = override.PromptCaching
998+
}
999+
if override.MaxConcurrency > 0 {
1000+
base.MaxConcurrency = override.MaxConcurrency
1001+
}
1002+
if override.MaxToolParallel > 0 {
1003+
base.MaxToolParallel = override.MaxToolParallel
1004+
}
1005+
if override.MCPServers != nil {
1006+
if base.MCPServers == nil {
1007+
base.MCPServers = make(map[string]mcpclient.ServerConfig)
1008+
}
1009+
for k, v := range override.MCPServers {
1010+
base.MCPServers[k] = v
1011+
}
1012+
}
9961013
if override.GithubRepoDirectory != "" {
9971014
base.GithubRepoDirectory = override.GithubRepoDirectory
9981015
}

0 commit comments

Comments
 (0)