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 <steipete@gmail.com>
This commit is contained in:
keshavbotagent
2026-05-31 01:33:49 +05:30
committed by GitHub
parent 005da57957
commit 371a8abe9d
17 changed files with 383 additions and 164 deletions

View File

@@ -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":

View File

@@ -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;
}

View File

@@ -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<T = unknown> {
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;
}

View File

@@ -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<typeof convertToLlm>[0] = [

View File

@@ -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;
}

View File

@@ -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(

View File

@@ -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<T = unknown> {
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<never, never> {
export interface CustomAgentMessages {
bashExecution: BashExecutionMessage;
custom: CustomMessage;
branchSummary: BranchSummaryMessage;

View File

@@ -0,0 +1,3 @@
export declare const TSDOWN_PACKAGE_OUTPUT_ROOTS: string[];
export declare function tsdownPackageOutputRoot(packageName: string): string;

View File

@@ -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`;
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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");

View File

@@ -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({

View File

@@ -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: [
{

View File

@@ -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,

View File

@@ -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");

View File

@@ -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<NonNullable<UserConfig["inputOptions"]>, 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,
},