Fix Foundry UI bugs: org names, sessions, and repo selection (#250)

* Fix Foundry auth: migrate to Better Auth adapter, fix access token retrieval

- Remove @ts-nocheck from better-auth.ts, auth-user/index.ts, app-shell.ts
  and fix all type errors
- Fix getAccessTokenForSession: read GitHub token directly from account
  record instead of calling Better Auth's internal /get-access-token
  endpoint which returns 403 on server-side calls
- Re-implement workspaceAuth helper functions (workspaceAuthColumn,
  normalizeAuthValue, workspaceAuthClause, workspaceAuthWhere) that were
  accidentally deleted
- Remove all retry logic (withRetries, isRetryableAppActorError)
- Implement CORS origin allowlist from configured environment
- Document cachedAppWorkspace singleton pattern
- Add inline org sync fallback in buildAppSnapshot for post-OAuth flow
- Add no-retry rule to CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add Foundry dev panel from fix-git-data branch

Port the dev panel component that was left out when PR #243 was replaced
by PR #247. Adapted to remove runtime/mock-debug references that don't
exist on the current branch.

- Toggle with Shift+D, persists visibility to localStorage
- Shows context, session, GitHub sync status sections
- Dev-only (import.meta.env.DEV)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add full Docker image defaults, fix actor deadlocks, and improve dev experience

- Add Dockerfile.full and --all flag to install-agent CLI for pre-built images
- Centralize Docker image constant (FULL_IMAGE) pinned to 0.3.1-full
- Remove examples/shared/Dockerfile{,.dev} and daytona snapshot example
- Expand Docker docs with full runnable Dockerfile
- Fix self-deadlock in createWorkbenchSession (fire-and-forget provisioning)
- Audit and convert 12 task actions from wait:true to wait:false
- Add bun --hot for dev backend hot reload
- Remove --force from pnpm install in dev Dockerfile for faster startup
- Add env_file support to compose.dev.yaml for automatic credential loading
- Add mock frontend compose config and dev panel
- Update CLAUDE.md with wait:true policy and dev environment setup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* WIP: async action fixes and interest manager

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix Foundry UI bugs: org names, hanging sessions, and wrong repo creation

- Fix org display name using GitHub description instead of name field
- Fix createWorkbenchSession hanging when sandbox is provisioning
- Fix auto-session creation retry storm on errors
- Fix task creation using wrong repo due to React state race conditions
- Remove Bun hot-reload from backend Dockerfile (causes port drift)
- Add GitHub sync/install status to dev panel

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nathan Flurry 2026-03-13 20:48:22 -07:00 committed by GitHub
parent 58c54156f1
commit d8b8b49f37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
88 changed files with 9252 additions and 1933 deletions

View file

@ -79,7 +79,7 @@ pub enum Command {
Opencode(OpencodeArgs),
/// Manage the sandbox-agent background daemon.
Daemon(DaemonArgs),
/// Install or reinstall an agent without running the server.
/// Install or reinstall one agent, or `all` supported agents, without running the server.
InstallAgent(InstallAgentArgs),
/// Inspect locally discovered credentials.
Credentials(CredentialsArgs),
@ -295,7 +295,10 @@ pub struct AcpCloseArgs {
#[derive(Args, Debug)]
pub struct InstallAgentArgs {
agent: String,
#[arg(required_unless_present = "all", conflicts_with = "all")]
agent: Option<String>,
#[arg(long, conflicts_with = "agent")]
all: bool,
#[arg(long, short = 'r')]
reinstall: bool,
#[arg(long = "agent-version")]
@ -946,24 +949,73 @@ fn load_json_payload(
}
fn install_agent_local(args: &InstallAgentArgs) -> Result<(), CliError> {
let agent_id = AgentId::parse(&args.agent)
.ok_or_else(|| CliError::Server(format!("unsupported agent: {}", args.agent)))?;
if args.all && (args.agent_version.is_some() || args.agent_process_version.is_some()) {
return Err(CliError::Server(
"--agent-version and --agent-process-version are only supported for single-agent installs"
.to_string(),
));
}
let agents = resolve_install_agents(args)?;
let manager = AgentManager::new(default_install_dir())
.map_err(|err| CliError::Server(err.to_string()))?;
let result = manager
.install(
agent_id,
InstallOptions {
reinstall: args.reinstall,
version: args.agent_version.clone(),
agent_process_version: args.agent_process_version.clone(),
},
)
.map_err(|err| CliError::Server(err.to_string()))?;
if agents.len() == 1 {
let result = manager
.install(
agents[0],
InstallOptions {
reinstall: args.reinstall,
version: args.agent_version.clone(),
agent_process_version: args.agent_process_version.clone(),
},
)
.map_err(|err| CliError::Server(err.to_string()))?;
let output = install_result_json(result);
return write_stdout_line(&serde_json::to_string_pretty(&output)?);
}
let output = json!({
let mut results = Vec::with_capacity(agents.len());
for agent_id in agents {
let result = manager
.install(
agent_id,
InstallOptions {
reinstall: args.reinstall,
version: None,
agent_process_version: None,
},
)
.map_err(|err| CliError::Server(err.to_string()))?;
results.push(json!({
"agent": agent_id.as_str(),
"result": install_result_json(result),
}));
}
write_stdout_line(&serde_json::to_string_pretty(
&json!({ "agents": results }),
)?)
}
fn resolve_install_agents(args: &InstallAgentArgs) -> Result<Vec<AgentId>, CliError> {
if args.all {
return Ok(AgentId::all().to_vec());
}
let agent = args
.agent
.as_deref()
.ok_or_else(|| CliError::Server("missing agent: provide <AGENT> or --all".to_string()))?;
AgentId::parse(agent)
.map(|agent_id| vec![agent_id])
.ok_or_else(|| CliError::Server(format!("unsupported agent: {agent}")))
}
fn install_result_json(result: sandbox_agent_agent_management::agents::InstallResult) -> Value {
json!({
"alreadyInstalled": result.already_installed,
"artifacts": result.artifacts.into_iter().map(|artifact| json!({
"kind": format!("{:?}", artifact.kind),
@ -971,9 +1023,7 @@ fn install_agent_local(args: &InstallAgentArgs) -> Result<(), CliError> {
"source": format!("{:?}", artifact.source),
"version": artifact.version,
})).collect::<Vec<_>>()
});
write_stdout_line(&serde_json::to_string_pretty(&output)?)
})
}
#[derive(Serialize)]
@ -1416,6 +1466,60 @@ fn write_stderr_line(text: &str) -> Result<(), CliError> {
mod tests {
use super::*;
#[test]
fn resolve_install_agents_expands_all() {
assert_eq!(
resolve_install_agents(&InstallAgentArgs {
agent: None,
all: true,
reinstall: false,
agent_version: None,
agent_process_version: None,
})
.unwrap(),
AgentId::all().to_vec()
);
}
#[test]
fn resolve_install_agents_supports_single_agent() {
assert_eq!(
resolve_install_agents(&InstallAgentArgs {
agent: Some("codex".to_string()),
all: false,
reinstall: false,
agent_version: None,
agent_process_version: None,
})
.unwrap(),
vec![AgentId::Codex]
);
}
#[test]
fn resolve_install_agents_rejects_unknown_agent() {
assert!(resolve_install_agents(&InstallAgentArgs {
agent: Some("nope".to_string()),
all: false,
reinstall: false,
agent_version: None,
agent_process_version: None,
})
.is_err());
}
#[test]
fn resolve_install_agents_rejects_positional_all() {
assert!(resolve_install_agents(&InstallAgentArgs {
agent: Some("all".to_string()),
all: false,
reinstall: false,
agent_version: None,
agent_process_version: None,
})
.is_err());
}
#[test]
fn apply_last_event_id_header_sets_header_when_provided() {
let client = HttpClient::builder().build().expect("build client");