fix(ci): make ci green (#1409)
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
14
src/index.ts
14
src/index.ts
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user