fix(ci): make ci green (#1409)

This commit is contained in:
Charles Packer
2026-03-16 14:23:44 -07:00
committed by GitHub
parent f65a751ff0
commit 8ecf39798c
7 changed files with 110 additions and 29 deletions

View File

@@ -6,7 +6,7 @@ import { APIError } from "@letta-ai/letta-client/core/error";
import type { AgentState } from "@letta-ai/letta-client/resources/agents/agents";
import type { Message } from "@letta-ai/letta-client/resources/agents/messages";
import type { ApprovalRequest } from "../cli/helpers/stream";
import { debugWarn, isDebugEnabled } from "../utils/debug";
import { debugLog, debugWarn, isDebugEnabled } from "../utils/debug";
// Backfill should feel like "the last turn(s)", not "the last N raw messages".
// Tool-heavy turns can generate many tool_call/tool_return messages that would
@@ -485,8 +485,11 @@ export async function getResumeData(
messages = sortChronological(messagesPage.getPaginatedItems());
if (isDebugEnabled()) {
console.log(
`[DEBUG] conversations.messages.list(default, agent_id=${agent.id}) returned ${messages.length} messages`,
debugLog(
"check-approval",
"conversations.messages.list(default, agent_id=%s) returned %d messages",
agent.id,
messages.length,
);
}
} catch (backfillError) {

View File

@@ -139,23 +139,30 @@ export async function sendMessageStream(
);
if (isDebugEnabled()) {
console.log(
`[DEBUG] sendMessageStream: conversationId=${conversationId}, agentId=${opts.agentId ?? "(none)"}`,
debugLog(
"agent-message",
"sendMessageStream: conversationId=%s, agentId=%s",
conversationId,
opts.agentId ?? "(none)",
);
const formattedSkills = clientSkills.map(
(skill) => `${skill.name} (${skill.location})`,
);
console.log(
`[DEBUG] sendMessageStream: client_skills (${clientSkills.length}) ${
formattedSkills.length > 0 ? formattedSkills.join(", ") : "(none)"
}`,
debugLog(
"agent-message",
"sendMessageStream: client_skills (%d) %s",
clientSkills.length,
formattedSkills.length > 0 ? formattedSkills.join(", ") : "(none)",
);
if (clientSkillDiscoveryErrors.length > 0) {
for (const error of clientSkillDiscoveryErrors) {
console.warn(
`[DEBUG] sendMessageStream: client_skills discovery error at ${error.path}: ${error.message}`,
debugWarn(
"agent-message",
"sendMessageStream: client_skills discovery error at %s: %s",
error.path,
error.message,
);
}
}

View File

@@ -3056,8 +3056,11 @@ export default function App({
const isResumingConversation =
resumedExistingConversation || messageHistory.length > 0;
if (isDebugEnabled()) {
console.log(
`[DEBUG] Header: resumedExistingConversation=${resumedExistingConversation}, messageHistory.length=${messageHistory.length}`,
debugLog(
"app",
"Header: resumedExistingConversation=%o, messageHistory.length=%d",
resumedExistingConversation,
messageHistory.length,
);
}
const headerMessage = isResumingConversation

View File

@@ -1825,10 +1825,12 @@ async function main(): Promise<void> {
// Debug: log resume flag status
if (isDebugEnabled()) {
console.log(`[DEBUG] shouldContinue=${shouldContinue}`);
console.log(`[DEBUG] shouldResume=${shouldResume}`);
console.log(
`[DEBUG] specifiedConversationId=${specifiedConversationId}`,
debugLog("startup", "shouldContinue=%o", shouldContinue);
debugLog("startup", "shouldResume=%o", shouldResume);
debugLog(
"startup",
"specifiedConversationId=%s",
specifiedConversationId,
);
}
@@ -1866,8 +1868,8 @@ async function main(): Promise<void> {
settingsManager.getGlobalLastSession();
if (isDebugEnabled()) {
console.log(`[DEBUG] lastSession=${JSON.stringify(lastSession)}`);
console.log(`[DEBUG] agent.id=${agent.id}`);
debugLog("startup", "lastSession=%s", JSON.stringify(lastSession));
debugLog("startup", "agent.id=%s", agent.id);
}
let resumedSuccessfully = false;

View File

@@ -133,6 +133,7 @@ describe("reflectionTranscript helper", () => {
test("buildParentMemorySnapshot renders tree descriptions and system <memory> blocks", async () => {
const memoryDir = join(testRoot, "memory");
const normalizedMemoryDir = memoryDir.replace(/\\/g, "/");
await mkdir(join(memoryDir, "system"), { recursive: true });
await mkdir(join(memoryDir, "reference"), { recursive: true });
await mkdir(join(memoryDir, "skills", "bird"), { recursive: true });
@@ -165,12 +166,14 @@ describe("reflectionTranscript helper", () => {
expect(snapshot).toContain("SKILL.md (X/Twitter CLI for posting)");
expect(snapshot).toContain("<memory>");
expect(snapshot).toContain(`<path>${memoryDir}/system/human.md</path>`);
expect(snapshot).toContain(
`<path>${normalizedMemoryDir}/system/human.md</path>`,
);
expect(snapshot).toContain("Dr. Wooders prefers direct answers.");
expect(snapshot).toContain("</memory>");
expect(snapshot).not.toContain(
`<path>${memoryDir}/reference/project.md</path>`,
`<path>${normalizedMemoryDir}/reference/project.md</path>`,
);
expect(snapshot).not.toContain("letta-code CLI details");
expect(snapshot).not.toContain(

View File

@@ -34,6 +34,22 @@ describe("shell codex tool", () => {
expect(result.output).toContain("hello from bash");
});
test.skipIf(isWindows)(
"falls back when env-wrapped shell launcher is missing",
async () => {
const result = await shell({
command: [
"/definitely-missing/env",
"bash",
"-lc",
"echo env-fallback",
],
});
expect(result.output).toContain("env-fallback");
},
);
test("handles arguments with spaces correctly", async () => {
// This is the key test for execvp semantics - args with spaces
// should NOT be split
@@ -136,6 +152,18 @@ describe("shell codex tool", () => {
}
});
test.skipIf(isWindows)(
"falls back to the default cwd when workdir does not exist",
async () => {
const result = await shell({
command: ["pwd"],
workdir: "/definitely/missing/path",
});
expect(result.output).toBe(process.env.USER_CWD || process.cwd());
},
);
test.skipIf(isWindows)(
"handles command that produces multi-line output",
async () => {

View File

@@ -1,3 +1,4 @@
import { existsSync, statSync } from "node:fs";
import * as path from "node:path";
import { getShellEnv } from "./shellEnv.js";
import { buildShellLaunchers } from "./shellLaunchers.js";
@@ -76,11 +77,13 @@ export async function shell(args: ShellArgs): Promise<ShellResult> {
}
const timeout = timeout_ms ?? DEFAULT_TIMEOUT;
const cwd = workdir
const defaultCwd = process.env.USER_CWD || process.cwd();
const requestedCwd = workdir
? path.isAbsolute(workdir)
? workdir
: path.resolve(process.env.USER_CWD || process.cwd(), workdir)
: process.env.USER_CWD || process.cwd();
: path.resolve(defaultCwd, workdir)
: defaultCwd;
const cwd = isUsableDirectory(requestedCwd) ? requestedCwd : defaultCwd;
const context: SpawnContext = {
command,
@@ -115,10 +118,9 @@ export async function shell(args: ShellArgs): Promise<ShellResult> {
function buildFallbackCommands(command: string[]): string[][] {
if (!command.length) return [];
const first = command[0];
if (!first) return [];
if (!isShellExecutableName(first)) return [];
const script = extractShellScript(command);
const shellIndex = findShellExecutableIndex(command);
if (shellIndex === null) return [];
const script = extractShellScript(command, shellIndex);
if (!script) return [];
const launchers = buildShellLaunchers(script);
return launchers.filter((launcher) => !arraysEqual(launcher, command));
@@ -132,6 +134,14 @@ function arraysEqual(a: string[], b: string[]): boolean {
return true;
}
function isUsableDirectory(candidate: string): boolean {
try {
return existsSync(candidate) && statSync(candidate).isDirectory();
} catch {
return false;
}
}
function isShellExecutableName(name: string): boolean {
const normalized = name.replace(/\\/g, "/").toLowerCase();
if (/(^|\/)(ba|z|a|da)?sh$/.test(normalized)) {
@@ -149,8 +159,33 @@ function isShellExecutableName(name: string): boolean {
return false;
}
function extractShellScript(command: string[]): string | null {
function isEnvExecutableName(name: string): boolean {
const normalized = name.replace(/\\/g, "/").toLowerCase();
return normalized === "env" || normalized.endsWith("/env");
}
function findShellExecutableIndex(command: string[]): number | null {
const first = command[0];
if (!first) return null;
if (isShellExecutableName(first)) return 0;
if (!isEnvExecutableName(first)) return null;
for (let i = 1; i < command.length; i += 1) {
const token = command[i];
if (!token) continue;
if (token.startsWith("-")) continue;
if (/^[A-Za-z_][A-Za-z0-9_]*=.*/.test(token)) continue;
return isShellExecutableName(token) ? i : null;
}
return null;
}
function extractShellScript(
command: string[],
shellIndex: number,
): string | null {
for (let i = shellIndex + 1; i < command.length; i += 1) {
const token = command[i];
if (!token) continue;
const normalized = token.toLowerCase();