mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-19 16:03:43 +00:00
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>
This commit is contained in:
parent
14d5413f8a
commit
689d968397
17 changed files with 2569 additions and 479 deletions
|
|
@ -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);
|
||||
|
|
|
|||
29
foundry/packages/shared/test/logging.test.ts
Normal file
29
foundry/packages/shared/test/logging.test.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createFoundryLogger } from "../src/logging.js";
|
||||
|
||||
describe("createFoundryLogger", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("emits logfmt output when requested", () => {
|
||||
const writes: string[] = [];
|
||||
const write = vi.fn((chunk: string | Uint8Array) => {
|
||||
writes.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
|
||||
return true;
|
||||
});
|
||||
vi.spyOn(process.stdout, "write").mockImplementation(write as typeof process.stdout.write);
|
||||
|
||||
const logger = createFoundryLogger({
|
||||
service: "foundry-backend",
|
||||
format: "logfmt",
|
||||
}).child({
|
||||
requestId: "req-123",
|
||||
});
|
||||
|
||||
logger.info({ count: 2, nested: { ok: true } }, "backend started");
|
||||
|
||||
expect(write).toHaveBeenCalledTimes(1);
|
||||
expect(writes[0]).toMatch(/^time=\S+ level=info service=foundry-backend requestId=req-123 count=2 nested="\{\\"ok\\":true\}" msg="backend started"\n$/);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue