From 371a8abe9d4a6ca0bc12e7d28e0bb42c7b48dc77 Mon Sep 17 00:00:00 2001 From: keshavbotagent Date: Sun, 31 May 2026 01:33:49 +0530 Subject: [PATCH] fix(build): avoid stale agent-core dts warnings (#87915) * fix(build): avoid stale agent-core dts warnings * test(secrets): secure plugin entrypoint fixtures * fix(agent-core): normalize compaction summary timestamps * test(secrets): secure platform preset fixture * fix(build): preserve tracked package dts on skip builds * test(secrets): secure platform preset resolver fixture * fix(build): keep declarations during skip dts clean --------- Co-authored-by: Peter Steinberger --- .../compaction/branch-summarization.ts | 23 +++-- .../src/harness/compaction/compaction.ts | 46 ++++++---- .../agent-core/src/harness/message-types.ts | 39 -------- .../agent-core/src/harness/messages.test.ts | 1 - packages/agent-core/src/harness/messages.ts | 64 ++++++++----- .../agent-core/src/harness/session/session.ts | 29 +++--- packages/agent-core/src/types.ts | 60 ++++++++---- scripts/lib/tsdown-output-roots.d.mts | 3 + scripts/lib/tsdown-output-roots.mjs | 27 ++++++ scripts/tsdown-build.mjs | 92 ++++++++++++++++++- src/agents/sessions/messages.ts | 14 --- src/secrets/configure.test.ts | 2 + src/secrets/provider-integrations.test.ts | 31 +++++-- src/secrets/resolve.manifest-registry.test.ts | 5 +- .../runtime.loadable-plugin-origins.test.ts | 5 +- test/scripts/tsdown-build.test.ts | 81 ++++++++++++++++ tsdown.config.ts | 25 ++--- 17 files changed, 383 insertions(+), 164 deletions(-) delete mode 100644 packages/agent-core/src/harness/message-types.ts create mode 100644 scripts/lib/tsdown-output-roots.d.mts create mode 100644 scripts/lib/tsdown-output-roots.mjs diff --git a/packages/agent-core/src/harness/compaction/branch-summarization.ts b/packages/agent-core/src/harness/compaction/branch-summarization.ts index 35cbf2e5915..318c1ce370c 100644 --- a/packages/agent-core/src/harness/compaction/branch-summarization.ts +++ b/packages/agent-core/src/harness/compaction/branch-summarization.ts @@ -5,6 +5,7 @@ import { } from "../../runtime-deps.js"; import type { AgentMessage } from "../../types.js"; import { + asAgentMessage, convertToLlm, createBranchSummaryMessage, createCompactionSummaryMessage, @@ -127,19 +128,25 @@ function getMessageFromEntry(entry: SessionTreeEntry): AgentMessage | undefined return entry.message; case "custom_message": - return createCustomMessage( - entry.customType, - entry.content, - entry.display, - entry.details, - entry.timestamp, + return asAgentMessage( + createCustomMessage( + entry.customType, + entry.content, + entry.display, + entry.details, + entry.timestamp, + ), ); case "branch_summary": - return createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp); + return asAgentMessage( + createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp), + ); case "compaction": - return createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp); + return asAgentMessage( + createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp), + ); case "thinking_level_change": case "model_change": case "custom": diff --git a/packages/agent-core/src/harness/compaction/compaction.ts b/packages/agent-core/src/harness/compaction/compaction.ts index 9dfda002841..42d4ed895d5 100644 --- a/packages/agent-core/src/harness/compaction/compaction.ts +++ b/packages/agent-core/src/harness/compaction/compaction.ts @@ -12,10 +12,12 @@ import { } from "../../runtime-deps.js"; import type { AgentMessage, ThinkingLevel } from "../../types.js"; import { + asAgentMessage, convertToLlm, createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, + type HarnessMessage, } from "../messages.js"; import { buildSessionContext } from "../session/session.js"; import { @@ -83,19 +85,23 @@ function getMessageFromEntry(entry: SessionTreeEntry): AgentMessage | undefined return entry.message; } if (entry.type === "custom_message") { - return createCustomMessage( - entry.customType, - entry.content, - entry.display, - entry.details, - entry.timestamp, + return asAgentMessage( + createCustomMessage( + entry.customType, + entry.content, + entry.display, + entry.details, + entry.timestamp, + ), ); } if (entry.type === "branch_summary") { - return createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp); + return asAgentMessage(createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp)); } if (entry.type === "compaction") { - return createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp); + return asAgentMessage( + createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp), + ); } return undefined; } @@ -238,11 +244,13 @@ export function shouldCompact( /** Estimate token count for one message using a conservative character heuristic. */ export function estimateTokens(message: AgentMessage): number { let chars = 0; + const harnessMessage = message as HarnessMessage; - switch (message.role) { + switch (harnessMessage.role) { case "user": { - const content = (message as { content: string | Array<{ type: string; text?: string }> }) - .content; + const content = ( + harnessMessage as { content: string | Array<{ type: string; text?: string }> } + ).content; if (typeof content === "string") { chars = content.length; } else if (Array.isArray(content)) { @@ -255,7 +263,7 @@ export function estimateTokens(message: AgentMessage): number { return Math.ceil(chars / 4); } case "assistant": { - const assistant = message; + const assistant = harnessMessage; for (const block of assistant.content) { if (block.type === "text") { chars += block.text.length; @@ -269,10 +277,10 @@ export function estimateTokens(message: AgentMessage): number { } case "custom": case "toolResult": { - if (typeof message.content === "string") { - chars = message.content.length; + if (typeof harnessMessage.content === "string") { + chars = harnessMessage.content.length; } else { - for (const block of message.content) { + for (const block of harnessMessage.content) { if (block.type === "text" && block.text) { chars += block.text.length; } @@ -284,12 +292,12 @@ export function estimateTokens(message: AgentMessage): number { return Math.ceil(chars / 4); } case "bashExecution": { - chars = message.command.length + message.output.length; + chars = harnessMessage.command.length + harnessMessage.output.length; return Math.ceil(chars / 4); } case "branchSummary": case "compactionSummary": { - chars = message.summary.length; + chars = harnessMessage.summary.length; return Math.ceil(chars / 4); } } @@ -306,7 +314,7 @@ function findValidCutPoints( const entry = entries[i]; switch (entry.type) { case "message": { - const role = entry.message.role; + const role = (entry.message as HarnessMessage).role; switch (role) { case "bashExecution": case "custom": @@ -351,7 +359,7 @@ export function findTurnStartIndex( return i; } if (entry.type === "message") { - const role = entry.message.role; + const role = (entry.message as HarnessMessage).role; if (role === "user" || role === "bashExecution") { return i; } diff --git a/packages/agent-core/src/harness/message-types.ts b/packages/agent-core/src/harness/message-types.ts deleted file mode 100644 index 64e3b24d9ab..00000000000 --- a/packages/agent-core/src/harness/message-types.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { ImageContent, TextContent } from "../../../llm-core/src/index.js"; - -export interface BashExecutionMessage { - role: "bashExecution"; - command: string; - output: string; - exitCode: number | undefined; - cancelled: boolean; - truncated: boolean; - fullOutputPath?: string; - timestamp: number; - excludeFromContext?: boolean; -} - -export interface CustomMessage { - role: "custom"; - customType: string; - content: string | (TextContent | ImageContent)[]; - display: boolean; - details?: T; - timestamp: number; -} - -export interface BranchSummaryMessage { - role: "branchSummary"; - summary: string; - fromId: string; - timestamp: number; -} - -export interface CompactionSummaryMessage { - role: "compactionSummary"; - summary: string; - tokensBefore: number; - timestamp: number | string; - tokensAfter?: number; - firstKeptEntryId?: string; - details?: unknown; -} diff --git a/packages/agent-core/src/harness/messages.test.ts b/packages/agent-core/src/harness/messages.test.ts index 4a9e1fbea6a..74c22746b36 100644 --- a/packages/agent-core/src/harness/messages.test.ts +++ b/packages/agent-core/src/harness/messages.test.ts @@ -7,7 +7,6 @@ describe("harness message timestamps", () => { "custom message timestamp must be a valid timestamp", ); }); - it("normalizes persisted compaction summary timestamp strings", () => { const timestamp = "2026-05-30T17:00:00.000Z"; const persistedMessages: Parameters[0] = [ diff --git a/packages/agent-core/src/harness/messages.ts b/packages/agent-core/src/harness/messages.ts index 8f39059dae3..bc0ffb6f93d 100644 --- a/packages/agent-core/src/harness/messages.ts +++ b/packages/agent-core/src/harness/messages.ts @@ -1,11 +1,11 @@ import type { ImageContent, Message, TextContent } from "../../../llm-core/src/index.js"; -import type { AgentMessage } from "../types.js"; import type { + AgentMessage, BashExecutionMessage, BranchSummaryMessage, CompactionSummaryMessage, CustomMessage, -} from "./message-types.js"; +} from "../types.js"; import { parseSessionTimestampMs, requireSessionTimestampMs } from "./session/timestamps.js"; export type { @@ -13,7 +13,29 @@ export type { BranchSummaryMessage, CompactionSummaryMessage, CustomMessage, -} from "./message-types.js"; +} from "../types.js"; + +export type HarnessMessage = + | AgentMessage + | BashExecutionMessage + | CustomMessage + | BranchSummaryMessage + | CompactionSummaryMessage; + +// Internal session paths keep call sites explicit about this harness-owned +// boundary even though these message roles are part of AgentMessage. +export function asAgentMessage(message: HarnessMessage): AgentMessage { + return message as AgentMessage; +} + +function normalizeCompactionSummaryTimestamp(timestamp: number | string): number { + if (typeof timestamp === "number") { + return timestamp; + } + const parsed = parseSessionTimestampMs(timestamp); + // Corrupt persisted rows should not abort context conversion; session order is already preserved. + return parsed ?? 0; +} export const COMPACTION_SUMMARY_PREFIX = `The conversation history before this point was compacted into the following summary: @@ -91,37 +113,29 @@ export function createCustomMessage( }; } -function normalizeCompactionSummaryTimestamp(timestamp: number | string): number { - if (typeof timestamp === "number") { - return timestamp; - } - const parsed = parseSessionTimestampMs(timestamp); - // Corrupt persisted rows should not abort context conversion; session order is already preserved. - return parsed ?? 0; -} - export function convertToLlm(messages: AgentMessage[]): Message[] { return messages .map((m): Message | undefined => { - switch (m.role) { + const message = m as HarnessMessage; + switch (message.role) { case "bashExecution": - if (m.excludeFromContext) { + if (message.excludeFromContext) { return undefined; } return { role: "user", - content: [{ type: "text", text: bashExecutionToText(m) }], - timestamp: m.timestamp, + content: [{ type: "text", text: bashExecutionToText(message) }], + timestamp: message.timestamp, }; case "custom": { const content = - typeof m.content === "string" - ? [{ type: "text" as const, text: m.content }] - : m.content; + typeof message.content === "string" + ? [{ type: "text" as const, text: message.content }] + : message.content; return { role: "user", content, - timestamp: m.timestamp, + timestamp: message.timestamp, }; } case "branchSummary": @@ -130,10 +144,10 @@ export function convertToLlm(messages: AgentMessage[]): Message[] { content: [ { type: "text" as const, - text: BRANCH_SUMMARY_PREFIX + m.summary + BRANCH_SUMMARY_SUFFIX, + text: BRANCH_SUMMARY_PREFIX + message.summary + BRANCH_SUMMARY_SUFFIX, }, ], - timestamp: m.timestamp, + timestamp: message.timestamp, }; case "compactionSummary": return { @@ -141,15 +155,15 @@ export function convertToLlm(messages: AgentMessage[]): Message[] { content: [ { type: "text" as const, - text: COMPACTION_SUMMARY_PREFIX + m.summary + COMPACTION_SUMMARY_SUFFIX, + text: COMPACTION_SUMMARY_PREFIX + message.summary + COMPACTION_SUMMARY_SUFFIX, }, ], - timestamp: normalizeCompactionSummaryTimestamp(m.timestamp), + timestamp: normalizeCompactionSummaryTimestamp(message.timestamp), }; case "user": case "assistant": case "toolResult": - return m; + return message; default: return undefined; } diff --git a/packages/agent-core/src/harness/session/session.ts b/packages/agent-core/src/harness/session/session.ts index 801f28c6522..08f6429c459 100644 --- a/packages/agent-core/src/harness/session/session.ts +++ b/packages/agent-core/src/harness/session/session.ts @@ -1,6 +1,7 @@ import type { ImageContent, TextContent } from "../../../../llm-core/src/index.js"; import type { AgentMessage } from "../../types.js"; import { + asAgentMessage, createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage, @@ -45,25 +46,31 @@ export function buildSessionContext(pathEntries: SessionTreeEntry[]): SessionCon messages.push(entry.message); } else if (entry.type === "custom_message") { messages.push( - createCustomMessage( - entry.customType, - entry.content, - entry.display, - entry.details, - entry.timestamp, + asAgentMessage( + createCustomMessage( + entry.customType, + entry.content, + entry.display, + entry.details, + entry.timestamp, + ), ), ); } else if (entry.type === "branch_summary" && entry.summary) { - messages.push(createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp)); + messages.push( + asAgentMessage(createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp)), + ); } }; if (compaction) { messages.push( - createCompactionSummaryMessage( - compaction.summary, - compaction.tokensBefore, - compaction.timestamp, + asAgentMessage( + createCompactionSummaryMessage( + compaction.summary, + compaction.tokensBefore, + compaction.timestamp, + ), ), ); const compactionIdx = pathEntries.findIndex( diff --git a/packages/agent-core/src/types.ts b/packages/agent-core/src/types.ts index 9b90e139c69..5e2e95b1937 100644 --- a/packages/agent-core/src/types.ts +++ b/packages/agent-core/src/types.ts @@ -11,12 +11,6 @@ import type { Tool, ToolResultMessage, } from "../../llm-core/src/index.js"; -import type { - BashExecutionMessage, - BranchSummaryMessage, - CompactionSummaryMessage, - CustomMessage, -} from "./harness/message-types.js"; /** * Stream function used by the agent loop. @@ -293,21 +287,49 @@ export interface AgentLoopConfig extends SimpleStreamOptions { */ export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"; +export interface BashExecutionMessage { + role: "bashExecution"; + command: string; + output: string; + exitCode: number | undefined; + cancelled: boolean; + truncated: boolean; + fullOutputPath?: string; + timestamp: number; + excludeFromContext?: boolean; +} + +export interface CustomMessage { + role: "custom"; + customType: string; + content: string | (TextContent | ImageContent)[]; + display: boolean; + details?: T; + timestamp: number; +} + +export interface BranchSummaryMessage { + role: "branchSummary"; + summary: string; + fromId: string; + timestamp: number; +} + +export interface CompactionSummaryMessage { + role: "compactionSummary"; + summary: string; + tokensBefore: number; + timestamp: number | string; + tokensAfter?: number; + firstKeptEntryId?: string; + details?: unknown; +} + /** - * Extensible interface for custom app messages. - * Apps can extend via declaration merging: - * - * @example - * ```typescript - * declare module "@mariozechner/agent" { - * interface CustomAgentMessages { - * artifact: ArtifactMessage; - * notification: NotificationMessage; - * } - * } - * ``` + * Extensible interface for custom app and harness messages. + * Apps can extend via declaration merging. */ -export interface CustomAgentMessages extends Record { +export interface CustomAgentMessages { bashExecution: BashExecutionMessage; custom: CustomMessage; branchSummary: BranchSummaryMessage; diff --git a/scripts/lib/tsdown-output-roots.d.mts b/scripts/lib/tsdown-output-roots.d.mts new file mode 100644 index 00000000000..f4d6072e7ab --- /dev/null +++ b/scripts/lib/tsdown-output-roots.d.mts @@ -0,0 +1,3 @@ +export declare const TSDOWN_PACKAGE_OUTPUT_ROOTS: string[]; + +export declare function tsdownPackageOutputRoot(packageName: string): string; diff --git a/scripts/lib/tsdown-output-roots.mjs b/scripts/lib/tsdown-output-roots.mjs new file mode 100644 index 00000000000..e58d9967a80 --- /dev/null +++ b/scripts/lib/tsdown-output-roots.mjs @@ -0,0 +1,27 @@ +const TSDOWN_PACKAGE_NAMES = [ + "agent-core", + "gateway-client", + "gateway-protocol", + "llm-core", + "llm-runtime", + "markdown-core", + "media-generation-core", + "media-understanding-common", + "model-catalog-core", + "net-policy", + "speech-core", + "terminal-core", +]; + +export const TSDOWN_PACKAGE_OUTPUT_ROOTS = TSDOWN_PACKAGE_NAMES.map(packageOutputRoot); + +export function tsdownPackageOutputRoot(packageName) { + if (!TSDOWN_PACKAGE_NAMES.includes(packageName)) { + throw new Error(`Unknown tsdown package output root: ${packageName}`); + } + return packageOutputRoot(packageName); +} + +function packageOutputRoot(packageName) { + return `packages/${packageName}/dist`; +} diff --git a/scripts/tsdown-build.mjs b/scripts/tsdown-build.mjs index 64e54b9f6c7..e20dd8eea2f 100644 --- a/scripts/tsdown-build.mjs +++ b/scripts/tsdown-build.mjs @@ -5,6 +5,7 @@ import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { BUNDLED_PLUGIN_PATH_PREFIX } from "./lib/bundled-plugin-paths.mjs"; +import { TSDOWN_PACKAGE_OUTPUT_ROOTS } from "./lib/tsdown-output-roots.mjs"; import { resolvePnpmRunner } from "./pnpm-runner.mjs"; import { isSourceCheckoutRoot, @@ -28,9 +29,11 @@ const CGROUP_MEMORY_LIMIT_PATHS = [ ]; const PROC_MEMINFO_PATH = "/proc/meminfo"; const TERMINATION_GRACE_MS = 5_000; -const TSDOWN_OUTPUT_ROOTS = ["dist", "dist-runtime"]; +const ROOT_TSDOWN_OUTPUT_ROOTS = ["dist", "dist-runtime"]; const GENERATED_SOURCE_DECLARATION_PATHSPEC = ":(glob)extensions/**/*.d.ts"; +const DECLARATION_EXTENSIONS = [".d.ts", ".d.mts", ".d.cts"]; const SOURCE_DECLARATION_SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs"]; +const RUN_NODE_SKIP_DTS_BUILD_ENV = "OPENCLAW_RUN_NODE_SKIP_DTS_BUILD"; function removeDistPluginNodeModulesSymlinks(rootDir) { const extensionsDir = path.join(rootDir, "extensions"); @@ -65,20 +68,97 @@ function pruneStaleRuntimeSymlinks() { export function cleanTsdownOutputRoots(params = {}) { const cwd = params.cwd ?? process.cwd(); const fsImpl = params.fs ?? fs; - for (const root of TSDOWN_OUTPUT_ROOTS) { + const env = params.env ?? process.env; + const roots = listTsdownOutputRoots(); + const protectedDeclarationPaths = + env[RUN_NODE_SKIP_DTS_BUILD_ENV] === "1" + ? listExistingDeclarationOutputPaths({ + cwd, + fs: fsImpl, + roots, + }) + : new Set(); + for (const root of roots) { const rootPath = path.join(cwd, root); try { - fsImpl.rmSync(rootPath, { force: true, recursive: true }); + if (hasProtectedChild({ rootPath, protectedPaths: protectedDeclarationPaths })) { + cleanOutputRootExcept(rootPath, protectedDeclarationPaths, fsImpl); + } else { + fsImpl.rmSync(rootPath, { force: true, recursive: true }); + } } catch { // Best-effort cleanup. tsdown will recreate the output tree it needs. } } } +function hasProtectedChild({ rootPath, protectedPaths }) { + const rootWithSeparator = `${path.resolve(rootPath)}${path.sep}`; + for (const protectedPath of protectedPaths) { + if (protectedPath.startsWith(rootWithSeparator)) { + return true; + } + } + return false; +} + +function cleanOutputRootExcept(rootPath, protectedPaths, fsImpl) { + let entries = []; + try { + entries = fsImpl.readdirSync(rootPath, { withFileTypes: true }); + } catch { + return; + } + + for (const entry of entries) { + const entryPath = path.join(rootPath, entry.name); + const resolvedEntryPath = path.resolve(entryPath); + if (protectedPaths.has(resolvedEntryPath)) { + continue; + } + try { + if (entry.isDirectory()) { + cleanOutputRootExcept(entryPath, protectedPaths, fsImpl); + fsImpl.rmdirSync(entryPath); + } else { + fsImpl.rmSync(entryPath, { force: true }); + } + } catch { + // Keep best-effort semantics; protected declaration children can keep a directory non-empty. + } + } +} + +function listExistingDeclarationOutputPaths({ cwd, fs: fsImpl, roots }) { + const protectedPaths = new Set(); + for (const root of roots) { + collectDeclarationOutputPaths(path.join(cwd, root), protectedPaths, fsImpl); + } + return protectedPaths; +} + +function collectDeclarationOutputPaths(rootPath, protectedPaths, fsImpl) { + let entries = []; + try { + entries = fsImpl.readdirSync(rootPath, { withFileTypes: true }); + } catch { + return; + } + + for (const entry of entries) { + const entryPath = path.join(rootPath, entry.name); + if (entry.isDirectory()) { + collectDeclarationOutputPaths(entryPath, protectedPaths, fsImpl); + } else if (DECLARATION_EXTENSIONS.some((extension) => entry.name.endsWith(extension))) { + protectedPaths.add(path.resolve(entryPath)); + } + } +} + export function pruneStaleRootChunkFiles(params = {}) { const cwd = params.cwd ?? process.cwd(); const fsImpl = params.fs ?? fs; - const roots = TSDOWN_OUTPUT_ROOTS.map((root) => path.join(cwd, root)); + const roots = listTsdownOutputRoots({ cwd, fs: fsImpl }).map((root) => path.join(cwd, root)); for (const root of roots) { let entries = []; try { @@ -103,6 +183,10 @@ export function pruneStaleRootChunkFiles(params = {}) { } } +export function listTsdownOutputRoots() { + return [...ROOT_TSDOWN_OUTPUT_ROOTS, ...TSDOWN_PACKAGE_OUTPUT_ROOTS]; +} + export function pruneUntrackedGeneratedSourceDeclarations(params = {}) { const cwd = params.cwd ?? process.cwd(); const fsImpl = params.fs ?? fs; diff --git a/src/agents/sessions/messages.ts b/src/agents/sessions/messages.ts index b57024aed4b..4035a72e4fe 100644 --- a/src/agents/sessions/messages.ts +++ b/src/agents/sessions/messages.ts @@ -1,9 +1,3 @@ -import type { - BashExecutionMessage, - BranchSummaryMessage, - CustomMessage, -} from "../../../packages/agent-core/src/harness/messages.js"; - export { bashExecutionToText, BRANCH_SUMMARY_PREFIX, @@ -22,11 +16,3 @@ export type { CompactionSummaryMessage, CustomMessage, } from "../../../packages/agent-core/src/harness/messages.js"; - -declare module "openclaw/plugin-sdk/agent-core" { - interface CustomAgentMessages { - bashExecution: BashExecutionMessage; - custom: CustomMessage; - branchSummary: BranchSummaryMessage; - } -} diff --git a/src/secrets/configure.test.ts b/src/secrets/configure.test.ts index 42646950829..ed47b65a0fa 100644 --- a/src/secrets/configure.test.ts +++ b/src/secrets/configure.test.ts @@ -37,6 +37,7 @@ const { runSecretsConfigureInteractive } = await import("./configure.js"); function makeTempDir(): string { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-secrets-configure-")); + fs.chmodSync(dir, 0o700); tempDirs.push(dir); return dir; } @@ -97,6 +98,7 @@ describe("runSecretsConfigureInteractive", () => { const pluginRoot = makeTempDir(); const resolverPath = path.join(pluginRoot, "vault-secret-ref-resolver.js"); fs.writeFileSync(resolverPath, "process.stdin.resume();\n"); + fs.chmodSync(resolverPath, 0o600); selectMock.mockResolvedValueOnce("preset"); selectMock.mockResolvedValueOnce("vault:vault:vault"); selectMock.mockResolvedValueOnce("continue"); diff --git a/src/secrets/provider-integrations.test.ts b/src/secrets/provider-integrations.test.ts index 504ecff5ae8..a5df4e43d85 100644 --- a/src/secrets/provider-integrations.test.ts +++ b/src/secrets/provider-integrations.test.ts @@ -18,10 +18,21 @@ const tempDirs: string[] = []; function makeTempDir(): string { const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-secret-provider-integrations-")); + fs.chmodSync(dir, 0o700); tempDirs.push(dir); return dir; } +function makeSecureDir(dir: string): void { + fs.mkdirSync(dir); + fs.chmodSync(dir, 0o700); +} + +function writeSecureFile(file: string, contents: string): void { + fs.writeFileSync(file, contents, "utf8"); + fs.chmodSync(file, 0o600); +} + function createCandidate( rootDir: string, idHint: string, @@ -55,8 +66,8 @@ describe("secret provider integration presets", () => { it("materializes plugin manifest exec providers without provider-specific core code", () => { const rootDir = makeTempDir(); fs.writeFileSync(path.join(rootDir, "index.ts"), "export default {};\n", "utf8"); - fs.mkdirSync(path.join(rootDir, "bin")); - fs.writeFileSync(path.join(rootDir, "bin", "resolve.mjs"), "process.stdin.resume();\n"); + makeSecureDir(path.join(rootDir, "bin")); + writeSecureFile(path.join(rootDir, "bin", "resolve.mjs"), "process.stdin.resume();\n"); fs.writeFileSync( path.join(rootDir, "openclaw.plugin.json"), JSON.stringify({ @@ -133,7 +144,7 @@ describe("secret provider integration presets", () => { it("normalizes manifest exec provider options to SecretRef provider schema limits", () => { const rootDir = makeTempDir(); fs.writeFileSync(path.join(rootDir, "index.ts"), "export default {};\n", "utf8"); - fs.writeFileSync(path.join(rootDir, "resolve.mjs"), "process.stdin.resume();\n"); + writeSecureFile(path.join(rootDir, "resolve.mjs"), "process.stdin.resume();\n"); fs.writeFileSync( path.join(rootDir, "openclaw.plugin.json"), JSON.stringify({ @@ -322,7 +333,7 @@ describe("secret provider integration presets", () => { it("skips presets from disabled installed plugins", () => { const rootDir = makeTempDir(); fs.writeFileSync(path.join(rootDir, "index.ts"), "export default {};\n", "utf8"); - fs.writeFileSync(path.join(rootDir, "resolve.mjs"), "process.stdin.resume();\n"); + writeSecureFile(path.join(rootDir, "resolve.mjs"), "process.stdin.resume();\n"); fs.writeFileSync( path.join(rootDir, "openclaw.plugin.json"), JSON.stringify({ @@ -376,7 +387,7 @@ describe("secret provider integration presets", () => { it("applies plugin id aliases when filtering disabled presets", () => { const rootDir = makeTempDir(); fs.writeFileSync(path.join(rootDir, "index.ts"), "export default {};\n", "utf8"); - fs.writeFileSync(path.join(rootDir, "resolve.mjs"), "process.stdin.resume();\n"); + writeSecureFile(path.join(rootDir, "resolve.mjs"), "process.stdin.resume();\n"); fs.writeFileSync( path.join(rootDir, "openclaw.plugin.json"), JSON.stringify({ @@ -419,7 +430,7 @@ describe("secret provider integration presets", () => { it("exposes bundled presets enabled by platform default", () => { const rootDir = makeTempDir(); fs.writeFileSync(path.join(rootDir, "index.ts"), "export default {};\n", "utf8"); - fs.writeFileSync(path.join(rootDir, "resolve.mjs"), "process.stdin.resume();\n"); + writeSecureFile(path.join(rootDir, "resolve.mjs"), "process.stdin.resume();\n"); fs.writeFileSync( path.join(rootDir, "openclaw.plugin.json"), JSON.stringify({ @@ -463,7 +474,7 @@ describe("secret provider integration presets", () => { const linkParent = makeTempDir(); const linkRoot = path.join(linkParent, "plugin-link"); fs.writeFileSync(path.join(rootDir, "index.ts"), "export default {};\n", "utf8"); - fs.writeFileSync(path.join(rootDir, "resolve.mjs"), "process.stdin.resume();\n"); + writeSecureFile(path.join(rootDir, "resolve.mjs"), "process.stdin.resume();\n"); fs.writeFileSync( path.join(rootDir, "openclaw.plugin.json"), JSON.stringify({ @@ -776,11 +787,11 @@ describe("secret provider integration presets", () => { const parentDir = makeTempDir(); const realRoot = path.join(parentDir, "real-plugin"); const linkedRoot = path.join(parentDir, "linked-plugin"); - fs.mkdirSync(realRoot); + makeSecureDir(realRoot); fs.symlinkSync(realRoot, linkedRoot, "dir"); fs.writeFileSync(path.join(realRoot, "index.ts"), "export default {};\n", "utf8"); - fs.mkdirSync(path.join(realRoot, "bin")); - fs.writeFileSync(path.join(realRoot, "bin", "resolve.mjs"), "process.stdin.resume();\n"); + makeSecureDir(path.join(realRoot, "bin")); + writeSecureFile(path.join(realRoot, "bin", "resolve.mjs"), "process.stdin.resume();\n"); fs.writeFileSync( path.join(realRoot, "openclaw.plugin.json"), JSON.stringify({ diff --git a/src/secrets/resolve.manifest-registry.test.ts b/src/secrets/resolve.manifest-registry.test.ts index b9e8e67dd76..08dfb67e47a 100644 --- a/src/secrets/resolve.manifest-registry.test.ts +++ b/src/secrets/resolve.manifest-registry.test.ts @@ -27,9 +27,11 @@ function createPluginManagedSecretProviderFixture(): { rootDir: string; } { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "oc-secret-provider-")); + fs.chmodSync(rootDir, 0o700); fs.writeFileSync(path.join(rootDir, "index.ts"), "export default {};\n", "utf8"); + const resolverPath = path.join(rootDir, "resolve.mjs"); fs.writeFileSync( - path.join(rootDir, "resolve.mjs"), + resolverPath, [ "import process from 'node:process';", "let input = '';", @@ -43,6 +45,7 @@ function createPluginManagedSecretProviderFixture(): { ].join("\n"), "utf8", ); + fs.chmodSync(resolverPath, 0o600); const manifestRegistry = { plugins: [ { diff --git a/src/secrets/runtime.loadable-plugin-origins.test.ts b/src/secrets/runtime.loadable-plugin-origins.test.ts index cee9f348d4c..b058d8487e1 100644 --- a/src/secrets/runtime.loadable-plugin-origins.test.ts +++ b/src/secrets/runtime.loadable-plugin-origins.test.ts @@ -102,9 +102,11 @@ describe("prepareSecretsRuntimeSnapshot loadable plugin origins", () => { it("carries the shared manifest registry into plugin-managed SecretRef resolution", async () => { const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "oc-runtime-secret-provider-")); + fs.chmodSync(rootDir, 0o700); fs.writeFileSync(path.join(rootDir, "index.ts"), "export default {};\n", "utf8"); + const resolverPath = path.join(rootDir, "resolve.mjs"); fs.writeFileSync( - path.join(rootDir, "resolve.mjs"), + resolverPath, [ "import process from 'node:process';", "let input = '';", @@ -118,6 +120,7 @@ describe("prepareSecretsRuntimeSnapshot loadable plugin origins", () => { ].join("\n"), "utf8", ); + fs.chmodSync(resolverPath, 0o600); const plugin: PluginManifestRecord = { id: "vault-secrets", rootDir, diff --git a/test/scripts/tsdown-build.test.ts b/test/scripts/tsdown-build.test.ts index 3bc2b754d1a..17489019e91 100644 --- a/test/scripts/tsdown-build.test.ts +++ b/test/scripts/tsdown-build.test.ts @@ -6,6 +6,7 @@ import { describe, expect, it, vi } from "vitest"; import { cleanTsdownOutputRoots, createTsdownOutputScanner, + listTsdownOutputRoots, parseTsdownBuildArgs, pruneSourceCheckoutBundledPluginNodeModules, pruneStaleRootChunkFiles, @@ -276,24 +277,104 @@ describe("resolveTsdownBuildInvocation", () => { const distFile = path.join(rootDir, "dist", "stale.js"); const pluginGeneratedFile = path.join(rootDir, "dist", "extensions", "telegram", "index.js"); const distRuntimeFile = path.join(rootDir, "dist-runtime", "stale.js"); + const agentCorePackageFile = path.join(rootDir, "packages", "agent-core", "dist", "stale.js"); + const netPolicyPackageFile = path.join(rootDir, "packages", "net-policy", "dist", "stale.js"); + const pluginSdkPackageFile = path.join(rootDir, "packages", "plugin-sdk", "dist", "keep.js"); + const packageSourceFile = path.join(rootDir, "packages", "agent-core", "src", "keep.ts"); const unrelatedFile = path.join(rootDir, "tmp", "keep.js"); await fsPromises.mkdir(path.dirname(distFile), { recursive: true }); await fsPromises.mkdir(path.dirname(pluginGeneratedFile), { recursive: true }); await fsPromises.mkdir(path.dirname(distRuntimeFile), { recursive: true }); + await fsPromises.mkdir(path.dirname(agentCorePackageFile), { recursive: true }); + await fsPromises.mkdir(path.dirname(netPolicyPackageFile), { recursive: true }); + await fsPromises.mkdir(path.dirname(pluginSdkPackageFile), { recursive: true }); + await fsPromises.mkdir(path.dirname(packageSourceFile), { recursive: true }); await fsPromises.mkdir(path.dirname(unrelatedFile), { recursive: true }); await fsPromises.writeFile(distFile, "stale\n"); await fsPromises.writeFile(pluginGeneratedFile, "generated\n"); await fsPromises.writeFile(distRuntimeFile, "stale\n"); + await fsPromises.writeFile(agentCorePackageFile, "stale\n"); + await fsPromises.writeFile(netPolicyPackageFile, "stale\n"); + await fsPromises.writeFile(pluginSdkPackageFile, "keep\n"); + await fsPromises.writeFile(packageSourceFile, "keep\n"); await fsPromises.writeFile(unrelatedFile, "keep\n"); + const outputRoots = listTsdownOutputRoots(); + expect(outputRoots).toEqual( + expect.arrayContaining([ + path.join("packages", "agent-core", "dist"), + path.join("packages", "net-policy", "dist"), + ]), + ); + expect(outputRoots).not.toContain(path.join("packages", "plugin-sdk", "dist")); + cleanTsdownOutputRoots({ cwd: rootDir }); await expectPathMissing(distFile); await expectPathMissing(pluginGeneratedFile); await expectPathMissing(path.join(rootDir, "dist-runtime")); + await expectPathMissing(path.join(rootDir, "packages", "agent-core", "dist")); + await expectPathMissing(path.join(rootDir, "packages", "net-policy", "dist")); + await expect(fsPromises.readFile(pluginSdkPackageFile, "utf8")).resolves.toBe("keep\n"); + await expect(fsPromises.readFile(packageSourceFile, "utf8")).resolves.toBe("keep\n"); await expect(fsPromises.readFile(unrelatedFile, "utf8")).resolves.toBe("keep\n"); }); + it("preserves existing package declarations when tsdown DTS output is skipped", async () => { + const rootDir = createTempDir("openclaw-tsdown-clean-skip-dts-"); + const declarationFile = path.join( + rootDir, + "packages", + "media-understanding-common", + "dist", + "index.d.mts", + ); + const nestedDeclarationFile = path.join( + rootDir, + "packages", + "media-understanding-common", + "dist", + "nested", + "types.d.ts", + ); + const staleJsFile = path.join( + rootDir, + "packages", + "media-understanding-common", + "dist", + "index.mjs", + ); + const nestedStaleFile = path.join( + rootDir, + "packages", + "media-understanding-common", + "dist", + "chunks", + "old.js", + ); + const agentCorePackageFile = path.join(rootDir, "packages", "agent-core", "dist", "stale.js"); + await fsPromises.mkdir(path.dirname(declarationFile), { recursive: true }); + await fsPromises.mkdir(path.dirname(nestedDeclarationFile), { recursive: true }); + await fsPromises.mkdir(path.dirname(nestedStaleFile), { recursive: true }); + await fsPromises.mkdir(path.dirname(agentCorePackageFile), { recursive: true }); + await fsPromises.writeFile(declarationFile, "export {};\n"); + await fsPromises.writeFile(nestedDeclarationFile, "export {};\n"); + await fsPromises.writeFile(staleJsFile, "stale\n"); + await fsPromises.writeFile(nestedStaleFile, "old\n"); + await fsPromises.writeFile(agentCorePackageFile, "stale\n"); + + cleanTsdownOutputRoots({ + cwd: rootDir, + env: { OPENCLAW_RUN_NODE_SKIP_DTS_BUILD: "1" }, + }); + + await expect(fsPromises.readFile(declarationFile, "utf8")).resolves.toBe("export {};\n"); + await expect(fsPromises.readFile(nestedDeclarationFile, "utf8")).resolves.toBe("export {};\n"); + await expectPathMissing(staleJsFile); + await expectPathMissing(nestedStaleFile); + await expectPathMissing(path.join(rootDir, "packages", "agent-core", "dist")); + }); + it("prunes untracked generated declaration files that shadow source entries", async () => { const rootDir = createTempDir("openclaw-tsdown-source-dts-"); const signalDir = path.join(rootDir, "extensions", "signal"); diff --git a/tsdown.config.ts b/tsdown.config.ts index 7d126abebe6..b1252af3465 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -10,6 +10,7 @@ import { pluginSdkEntrypoints, publicPluginSdkEntrypoints, } from "./scripts/lib/plugin-sdk-entries.mjs"; +import { tsdownPackageOutputRoot } from "./scripts/lib/tsdown-output-roots.mjs"; type InputOptionsFactory = Extract, Function>; type InputOptionsArg = InputOptionsFactory extends ( @@ -584,7 +585,7 @@ export default defineConfig([ clean: true, dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, entry: buildAgentCoreDistEntries(), - outDir: "packages/agent-core/dist", + outDir: tsdownPackageOutputRoot("agent-core"), deps: { neverBundle: shouldExternalizeAgentCoreDependency, }, @@ -593,7 +594,7 @@ export default defineConfig([ clean: true, dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, entry: buildGatewayProtocolDistEntries(), - outDir: "packages/gateway-protocol/dist", + outDir: tsdownPackageOutputRoot("gateway-protocol"), deps: { neverBundle: shouldExternalizeGatewayProtocolDependency, }, @@ -602,7 +603,7 @@ export default defineConfig([ clean: true, dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, entry: buildGatewayClientDistEntries(), - outDir: "packages/gateway-client/dist", + outDir: tsdownPackageOutputRoot("gateway-client"), deps: { neverBundle: shouldExternalizeGatewayClientDependency, }, @@ -611,7 +612,7 @@ export default defineConfig([ clean: true, dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, entry: buildNetPolicyDistEntries(), - outDir: "packages/net-policy/dist", + outDir: tsdownPackageOutputRoot("net-policy"), deps: { neverBundle: shouldExternalizeNetPolicyDependency, }, @@ -620,19 +621,19 @@ export default defineConfig([ clean: true, dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, entry: buildMediaGenerationCoreDistEntries(), - outDir: "packages/media-generation-core/dist", + outDir: tsdownPackageOutputRoot("media-generation-core"), }), nodeWorkspacePackageBuildConfig({ clean: true, dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, entry: buildMediaUnderstandingCoreDistEntries(), - outDir: "packages/media-understanding-common/dist", + outDir: tsdownPackageOutputRoot("media-understanding-common"), }), nodeWorkspacePackageBuildConfig({ clean: true, dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, entry: buildMarkdownCoreDistEntries(), - outDir: "packages/markdown-core/dist", + outDir: tsdownPackageOutputRoot("markdown-core"), deps: { neverBundle: shouldExternalizeMarkdownCoreDependency, }, @@ -641,7 +642,7 @@ export default defineConfig([ clean: true, dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, entry: buildTerminalCoreDistEntries(), - outDir: "packages/terminal-core/dist", + outDir: tsdownPackageOutputRoot("terminal-core"), deps: { neverBundle: shouldExternalizeTerminalCoreDependency, }, @@ -650,7 +651,7 @@ export default defineConfig([ clean: true, dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, entry: buildSpeechCoreDistEntries(), - outDir: "packages/speech-core/dist", + outDir: tsdownPackageOutputRoot("speech-core"), deps: { neverBundle: shouldExternalizeSpeechCoreDependency, }, @@ -659,7 +660,7 @@ export default defineConfig([ clean: true, dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, entry: buildLlmCoreDistEntries(), - outDir: "packages/llm-core/dist", + outDir: tsdownPackageOutputRoot("llm-core"), deps: { neverBundle: shouldExternalizeLlmCoreDependency, }, @@ -668,13 +669,13 @@ export default defineConfig([ clean: true, dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, entry: buildModelCatalogCoreDistEntries(), - outDir: "packages/model-catalog-core/dist", + outDir: tsdownPackageOutputRoot("model-catalog-core"), }), nodeWorkspacePackageBuildConfig({ clean: true, dts: RUN_NODE_SKIP_DTS_BUILD ? false : undefined, entry: buildLlmRuntimeDistEntries(), - outDir: "packages/llm-runtime/dist", + outDir: tsdownPackageOutputRoot("llm-runtime"), deps: { neverBundle: shouldExternalizeLlmRuntimeDependency, },