Skip to content
Open
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
1 change: 1 addition & 0 deletions cmd/gc/cmd_lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ func lintPromptContext(packDir string, agentCfg config.Agent, providers map[stri
SlingQuery: agentCfg.EffectiveSlingQuery(),
ProviderKey: providerKey,
ProviderDisplayName: providerDisplayNameFor(providerKey, providers),
ConfigDir: promptConfigDir(packDir, agentCfg.SourceDir),
Env: env,
}
}
Expand Down
1 change: 1 addition & 0 deletions cmd/gc/cmd_prime.go
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@ func buildPrimeContextForBeads(cityPath, cityName string, a *config.Agent, rigs
TemplateName: a.Name,
BindingName: a.BindingName,
BindingPrefix: a.BindingPrefix(),
ConfigDir: promptConfigDir(cityPath, a.SourceDir),
Env: a.Env,
}

Expand Down
22 changes: 21 additions & 1 deletion cmd/gc/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,26 @@ type PromptContext struct {
// Templates use {{ .InstructionsFile }} as a provider-aware fallback when
// pack-specific guidance (e.g. quality-gate commands) is missing or empty.
InstructionsFile string
Env map[string]string // from Agent.Env — custom vars
// ConfigDir is the directory this agent's config was defined in — the pack
// directory for a pack-supplied agent, the city root otherwise. Prompts use
// {{ .ConfigDir }} to reach assets the pack ships alongside the agent, most
// often helper scripts under {{ .ConfigDir }}/assets/scripts/. It carries the
// same value SessionSetupContext carries for command / pre_start /
// session_live expansion, so the variable means the same thing in a prompt as
// it does in a session-setup command.
ConfigDir string
Env map[string]string // from Agent.Env — custom vars
}

// promptConfigDir resolves the ConfigDir for a prompt: the directory the agent's
// config was defined in, falling back to the city root when the agent is defined
// by the city itself rather than by an imported pack. Mirrors the fallback
// resolveTemplate applies when building SessionSetupContext.
func promptConfigDir(cityPath, sourceDir string) string {
if sourceDir != "" {
return sourceDir
}
return cityPath
}

// PromptRenderResult holds the rendered text plus the version and rendered
Expand Down Expand Up @@ -340,6 +359,7 @@ func buildTemplateData(ctx PromptContext) map[string]string {
m["ProviderKey"] = ctx.ProviderKey
m["ProviderDisplayName"] = ctx.ProviderDisplayName
m["InstructionsFile"] = ctx.InstructionsFile
m["ConfigDir"] = ctx.ConfigDir
return m
}

Expand Down
47 changes: 47 additions & 0 deletions cmd/gc/prompt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,53 @@ func TestRenderPromptEnvMerge(t *testing.T) {
}
}

// TestRenderPromptConfigDir covers the shape that regressed: a pack ships helper
// scripts under assets/scripts/ and its prompts reach them via {{ .ConfigDir }}.
// Before ConfigDir was wired into the prompt context, the key was absent from the
// template data map and missingkey=zero rendered it as "", turning every such
// invocation into a bare /assets/scripts/... path that exits 127.
func TestRenderPromptConfigDir(t *testing.T) {
f := fsys.NewFake()
f.Files["/city/prompts/test.template.md"] = []byte("{{ .ConfigDir }}/assets/scripts/slack.py send hi")
ctx := PromptContext{
CityRoot: "/home/user/my-city",
ConfigDir: "/home/user/packs/local-core",
}
got := renderPrompt(f, "/city", "", "prompts/test.template.md", ctx, "", io.Discard, nil, nil, nil)
want := "/home/user/packs/local-core/assets/scripts/slack.py send hi"
if got != want {
t.Errorf("renderPrompt(ConfigDir) = %q, want %q", got, want)
}
}

func TestPromptConfigDir(t *testing.T) {
tests := []struct {
name string
cityPath string
sourceDir string
want string
}{
{
name: "pack-supplied agent uses its pack dir",
cityPath: "/home/user/my-city",
sourceDir: "/home/user/packs/local-core",
want: "/home/user/packs/local-core",
},
{
name: "city-defined agent falls back to the city root",
cityPath: "/home/user/my-city",
want: "/home/user/my-city",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := promptConfigDir(tt.cityPath, tt.sourceDir); got != tt.want {
t.Errorf("promptConfigDir(%q, %q) = %q, want %q", tt.cityPath, tt.sourceDir, got, tt.want)
}
})
}
}

func TestRenderPromptDefaultBranch(t *testing.T) {
f := fsys.NewFake()
f.Files["/city/prompts/test.md.tmpl"] = []byte("Branch: {{ .DefaultBranch }}")
Expand Down
10 changes: 5 additions & 5 deletions cmd/gc/template_resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ func resolveTemplate(p *agentBuildParams, cfgAgent *config.Agent, qualifiedName
ProviderKey: providerKey,
ProviderDisplayName: providerDisplayName,
InstructionsFile: instructionsFileForAgent(cfgAgent, p.workspace, p.providers),
ConfigDir: promptConfigDir(p.cityPath, cfgAgent.SourceDir),
Env: cfgAgent.Env,
}, p.sessionTemplate, p.stderr, packDirs, fragments, p.beadStore)
hasHooks := config.AgentHasHooks(cfgAgent, p.workspace, resolved.Name, p.providers)
Expand Down Expand Up @@ -499,11 +500,10 @@ func resolveTemplate(p *agentBuildParams, cfgAgent *config.Agent, qualifiedName
// re-enable product metrics; Beads telemetry remains independent.
env[execenv.UsageMetricsDisableEnv] = execenv.UsageMetricsDisableValue

// Step 11: Expand session setup templates.
configDir := p.cityPath
if cfgAgent.SourceDir != "" {
configDir = cfgAgent.SourceDir
}
// Step 11: Expand session setup templates. ConfigDir comes from the same
// helper the prompt context uses, so {{.ConfigDir}} resolves identically in a
// prompt and in a session-setup command and the two scopes cannot drift.
configDir := promptConfigDir(p.cityPath, cfgAgent.SourceDir)
setupCtx := SessionSetupContext{
Session: sessName,
Agent: qualifiedName,
Expand Down
Loading