fix(agents): stabilize cli creds cache + bash cwd
This commit is contained in:
@@ -114,6 +114,7 @@ export type BashToolDetails =
|
|||||||
sessionId: string;
|
sessionId: string;
|
||||||
pid?: number;
|
pid?: number;
|
||||||
startedAt: number;
|
startedAt: number;
|
||||||
|
cwd?: string;
|
||||||
tail?: string;
|
tail?: string;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
@@ -121,6 +122,7 @@ export type BashToolDetails =
|
|||||||
exitCode: number | null;
|
exitCode: number | null;
|
||||||
durationMs: number;
|
durationMs: number;
|
||||||
aggregated: string;
|
aggregated: string;
|
||||||
|
cwd?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createBashTool(
|
export function createBashTool(
|
||||||
@@ -316,6 +318,7 @@ export function createBashTool(
|
|||||||
sessionId,
|
sessionId,
|
||||||
pid: session.pid ?? undefined,
|
pid: session.pid ?? undefined,
|
||||||
startedAt,
|
startedAt,
|
||||||
|
cwd: session.cwd,
|
||||||
tail: session.tail,
|
tail: session.tail,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -356,6 +359,7 @@ export function createBashTool(
|
|||||||
sessionId,
|
sessionId,
|
||||||
pid: session.pid ?? undefined,
|
pid: session.pid ?? undefined,
|
||||||
startedAt,
|
startedAt,
|
||||||
|
cwd: session.cwd,
|
||||||
tail: session.tail,
|
tail: session.tail,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@@ -431,6 +435,7 @@ export function createBashTool(
|
|||||||
exitCode: code ?? 0,
|
exitCode: code ?? 0,
|
||||||
durationMs,
|
durationMs,
|
||||||
aggregated,
|
aggregated,
|
||||||
|
cwd: session.cwd,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -136,10 +136,12 @@ describe("cli credentials", () => {
|
|||||||
const first = readClaudeCliCredentialsCached({
|
const first = readClaudeCliCredentialsCached({
|
||||||
allowKeychainPrompt: true,
|
allowKeychainPrompt: true,
|
||||||
ttlMs: 15 * 60 * 1000,
|
ttlMs: 15 * 60 * 1000,
|
||||||
|
platform: "darwin",
|
||||||
});
|
});
|
||||||
const second = readClaudeCliCredentialsCached({
|
const second = readClaudeCliCredentialsCached({
|
||||||
allowKeychainPrompt: false,
|
allowKeychainPrompt: false,
|
||||||
ttlMs: 15 * 60 * 1000,
|
ttlMs: 15 * 60 * 1000,
|
||||||
|
platform: "darwin",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(first).toBeTruthy();
|
expect(first).toBeTruthy();
|
||||||
@@ -167,6 +169,7 @@ describe("cli credentials", () => {
|
|||||||
const first = readClaudeCliCredentialsCached({
|
const first = readClaudeCliCredentialsCached({
|
||||||
allowKeychainPrompt: true,
|
allowKeychainPrompt: true,
|
||||||
ttlMs: 15 * 60 * 1000,
|
ttlMs: 15 * 60 * 1000,
|
||||||
|
platform: "darwin",
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.advanceTimersByTime(15 * 60 * 1000 + 1);
|
vi.advanceTimersByTime(15 * 60 * 1000 + 1);
|
||||||
@@ -174,6 +177,7 @@ describe("cli credentials", () => {
|
|||||||
const second = readClaudeCliCredentialsCached({
|
const second = readClaudeCliCredentialsCached({
|
||||||
allowKeychainPrompt: true,
|
allowKeychainPrompt: true,
|
||||||
ttlMs: 15 * 60 * 1000,
|
ttlMs: 15 * 60 * 1000,
|
||||||
|
platform: "darwin",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(first).toBeTruthy();
|
expect(first).toBeTruthy();
|
||||||
|
|||||||
@@ -111,8 +111,11 @@ function readClaudeCliKeychainCredentials(): ClaudeCliCredential | null {
|
|||||||
|
|
||||||
export function readClaudeCliCredentials(options?: {
|
export function readClaudeCliCredentials(options?: {
|
||||||
allowKeychainPrompt?: boolean;
|
allowKeychainPrompt?: boolean;
|
||||||
|
platform?: NodeJS.Platform;
|
||||||
|
homeDir?: string;
|
||||||
}): ClaudeCliCredential | null {
|
}): ClaudeCliCredential | null {
|
||||||
if (process.platform === "darwin" && options?.allowKeychainPrompt !== false) {
|
const platform = options?.platform ?? process.platform;
|
||||||
|
if (platform === "darwin" && options?.allowKeychainPrompt !== false) {
|
||||||
const keychainCreds = readClaudeCliKeychainCredentials();
|
const keychainCreds = readClaudeCliKeychainCredentials();
|
||||||
if (keychainCreds) {
|
if (keychainCreds) {
|
||||||
log.info("read anthropic credentials from claude cli keychain", {
|
log.info("read anthropic credentials from claude cli keychain", {
|
||||||
@@ -122,7 +125,7 @@ export function readClaudeCliCredentials(options?: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const credPath = resolveClaudeCliCredentialsPath();
|
const credPath = resolveClaudeCliCredentialsPath(options?.homeDir);
|
||||||
const raw = loadJsonFile(credPath);
|
const raw = loadJsonFile(credPath);
|
||||||
if (!raw || typeof raw !== "object") return null;
|
if (!raw || typeof raw !== "object") return null;
|
||||||
|
|
||||||
@@ -158,10 +161,12 @@ export function readClaudeCliCredentials(options?: {
|
|||||||
export function readClaudeCliCredentialsCached(options?: {
|
export function readClaudeCliCredentialsCached(options?: {
|
||||||
allowKeychainPrompt?: boolean;
|
allowKeychainPrompt?: boolean;
|
||||||
ttlMs?: number;
|
ttlMs?: number;
|
||||||
|
platform?: NodeJS.Platform;
|
||||||
|
homeDir?: string;
|
||||||
}): ClaudeCliCredential | null {
|
}): ClaudeCliCredential | null {
|
||||||
const ttlMs = options?.ttlMs ?? 0;
|
const ttlMs = options?.ttlMs ?? 0;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const cacheKey = resolveClaudeCliCredentialsPath();
|
const cacheKey = resolveClaudeCliCredentialsPath(options?.homeDir);
|
||||||
if (
|
if (
|
||||||
ttlMs > 0 &&
|
ttlMs > 0 &&
|
||||||
claudeCliCache &&
|
claudeCliCache &&
|
||||||
@@ -172,6 +177,8 @@ export function readClaudeCliCredentialsCached(options?: {
|
|||||||
}
|
}
|
||||||
const value = readClaudeCliCredentials({
|
const value = readClaudeCliCredentials({
|
||||||
allowKeychainPrompt: options?.allowKeychainPrompt,
|
allowKeychainPrompt: options?.allowKeychainPrompt,
|
||||||
|
platform: options?.platform,
|
||||||
|
homeDir: options?.homeDir,
|
||||||
});
|
});
|
||||||
if (ttlMs > 0) {
|
if (ttlMs > 0) {
|
||||||
claudeCliCache = { value, readAt: now, cacheKey };
|
claudeCliCache = { value, readAt: now, cacheKey };
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ import path from "node:path";
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { createClawdbotCodingTools } from "./pi-tools.js";
|
import { createClawdbotCodingTools } from "./pi-tools.js";
|
||||||
|
|
||||||
const normalizeText = (value?: string) =>
|
|
||||||
(value ?? "").replace(/\r\n/g, "\n").trim();
|
|
||||||
|
|
||||||
async function withTempDir<T>(prefix: string, fn: (dir: string) => Promise<T>) {
|
async function withTempDir<T>(prefix: string, fn: (dir: string) => Promise<T>) {
|
||||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
|
||||||
try {
|
try {
|
||||||
@@ -120,11 +117,17 @@ describe("workspace path resolution", () => {
|
|||||||
expect(bashTool).toBeDefined();
|
expect(bashTool).toBeDefined();
|
||||||
|
|
||||||
const result = await bashTool?.execute("ws-bash", {
|
const result = await bashTool?.execute("ws-bash", {
|
||||||
command: 'node -e "console.log(process.cwd())"',
|
command: "echo ok",
|
||||||
});
|
});
|
||||||
const output = normalizeText(getTextContent(result));
|
const cwd =
|
||||||
|
result?.details &&
|
||||||
|
typeof result.details === "object" &&
|
||||||
|
"cwd" in result.details
|
||||||
|
? (result.details as { cwd?: string }).cwd
|
||||||
|
: undefined;
|
||||||
|
expect(cwd).toBeTruthy();
|
||||||
const [resolvedOutput, resolvedWorkspace] = await Promise.all([
|
const [resolvedOutput, resolvedWorkspace] = await Promise.all([
|
||||||
fs.realpath(output),
|
fs.realpath(String(cwd)),
|
||||||
fs.realpath(workspaceDir),
|
fs.realpath(workspaceDir),
|
||||||
]);
|
]);
|
||||||
expect(resolvedOutput).toBe(resolvedWorkspace);
|
expect(resolvedOutput).toBe(resolvedWorkspace);
|
||||||
@@ -139,12 +142,18 @@ describe("workspace path resolution", () => {
|
|||||||
expect(bashTool).toBeDefined();
|
expect(bashTool).toBeDefined();
|
||||||
|
|
||||||
const result = await bashTool?.execute("ws-bash-override", {
|
const result = await bashTool?.execute("ws-bash-override", {
|
||||||
command: 'node -e "console.log(process.cwd())"',
|
command: "echo ok",
|
||||||
workdir: overrideDir,
|
workdir: overrideDir,
|
||||||
});
|
});
|
||||||
const output = normalizeText(getTextContent(result));
|
const cwd =
|
||||||
|
result?.details &&
|
||||||
|
typeof result.details === "object" &&
|
||||||
|
"cwd" in result.details
|
||||||
|
? (result.details as { cwd?: string }).cwd
|
||||||
|
: undefined;
|
||||||
|
expect(cwd).toBeTruthy();
|
||||||
const [resolvedOutput, resolvedOverride] = await Promise.all([
|
const [resolvedOutput, resolvedOverride] = await Promise.all([
|
||||||
fs.realpath(output),
|
fs.realpath(String(cwd)),
|
||||||
fs.realpath(overrideDir),
|
fs.realpath(overrideDir),
|
||||||
]);
|
]);
|
||||||
expect(resolvedOutput).toBe(resolvedOverride);
|
expect(resolvedOutput).toBe(resolvedOverride);
|
||||||
|
|||||||
Reference in New Issue
Block a user