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

@ -4,10 +4,14 @@ export interface FoundryLoggerOptions {
service: string;
bindings?: Record<string, unknown>;
level?: string;
format?: "json" | "logfmt";
}
type ProcessLike = {
env?: Record<string, string | undefined>;
stdout?: {
write?: (chunk: string) => unknown;
};
};
function resolveEnvVar(name: string): string | undefined {
@ -28,6 +32,116 @@ function isBrowserRuntime(): boolean {
return typeof window !== "undefined" && typeof document !== "undefined";
}
function serializeLogValue(value: unknown): string | number | boolean | null {
if (value === undefined || value === null) {
return null;
}
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
return value;
}
if (typeof value === "bigint") {
return value.toString();
}
if (value instanceof Error) {
return JSON.stringify({
name: value.name,
message: value.message,
stack: value.stack,
});
}
try {
return JSON.stringify(value);
} catch {
return "[unserializable]";
}
}
function formatLogfmtValue(value: string | number | boolean | null): string {
if (typeof value === "number" || typeof value === "boolean") {
return String(value);
}
const raw = value ?? "null";
if (raw.length > 0 && !/[\s="\\]/.test(raw)) {
return raw;
}
return `"${raw.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
}
function formatLogfmtLine(record: Record<string, unknown>): string {
return Object.entries(record)
.filter(([, value]) => value !== undefined)
.map(([key, value]) => `${key}=${formatLogfmtValue(serializeLogValue(value))}`)
.join(" ");
}
function stringifyMessagePart(value: unknown): string {
if (typeof value === "string") {
return value;
}
const serialized = serializeLogValue(value);
return typeof serialized === "string" ? serialized : String(serialized);
}
function buildLogRecord(level: string, bindings: Record<string, unknown>, args: Parameters<Logger["info"]>): Record<string, unknown> {
const record: Record<string, unknown> = {
time: new Date().toISOString(),
level,
};
for (const [key, value] of Object.entries(bindings)) {
if (key !== "time" && key !== "level" && key !== "msg" && value !== undefined) {
record[key] = value;
}
}
if (args.length === 0) {
return record;
}
const [first, ...rest] = args;
if (first && typeof first === "object") {
if (first instanceof Error) {
record.err = {
name: first.name,
message: first.message,
stack: first.stack,
};
} else {
for (const [key, value] of Object.entries(first)) {
if (key !== "time" && key !== "level" && key !== "msg" && value !== undefined) {
record[key] = value;
}
}
}
if (rest.length > 0) {
record.msg = rest.map(stringifyMessagePart).join(" ");
}
return record;
}
record.msg = [first, ...rest].map(stringifyMessagePart).join(" ");
return record;
}
function writeLogfmtLine(line: string): void {
const processLike = (globalThis as { process?: ProcessLike }).process;
if (processLike?.stdout?.write) {
processLike.stdout.write(`${line}\n`);
return;
}
console.log(line);
}
export function createFoundryLogger(options: FoundryLoggerOptions): Logger {
const browser = isBrowserRuntime();
const loggerOptions: LoggerOptions = {
@ -44,6 +158,15 @@ export function createFoundryLogger(options: FoundryLoggerOptions): Logger {
};
} else {
loggerOptions.timestamp = pino.stdTimeFunctions.isoTime;
if (options.format === "logfmt") {
loggerOptions.hooks = {
logMethod(this: Logger, args, _method, level) {
const levelLabel = this.levels.labels[level] ?? "info";
const record = buildLogRecord(levelLabel, this.bindings(), args);
writeLogfmtLine(formatLogfmtLine(record));
},
};
}
}
return pino(loggerOptions);