mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-19 22:06:31 +00:00
fix(ci): release workflow checks fail on macOS Bash 3.2 (#121669)
* fix(ci): keep release workflow checks portable on macOS * fix(ci): repair current main compact shard regressions * fix(ci): repair latest main validation drift * fix(approvals): restore native account ownership gates * fix(ci): repair skill workshop validation drift * fix(ci): align approval route selection with main * fix(ci): remove stale skill workshop test exports * fix(ci): align repairs with latest main
This commit is contained in:
parent
d6317094a9
commit
3cd034f7a8
10 changed files with 101 additions and 69 deletions
|
|
@ -507,7 +507,7 @@ jobs:
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ "$qa_filter_seen" == "true" ]]; then
|
if [[ "$qa_filter_seen" == "true" ]]; then
|
||||||
repo_live_suite_filter="$(IFS=,; printf '%s' "${repo_filter_tokens[*]:-}")"
|
repo_live_suite_filter="$(IFS=,; printf '%s' "${repo_filter_tokens[*]-}")"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${#disabled_required_lanes[@]}" -gt 0 ]]; then
|
if [[ "${#disabled_required_lanes[@]}" -gt 0 ]]; then
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,12 @@ describe("executeAgentTurn: lifecycle progress", () => {
|
||||||
name: "read",
|
name: "read",
|
||||||
phase: "start",
|
phase: "start",
|
||||||
status: "running",
|
status: "running",
|
||||||
|
summary: undefined,
|
||||||
|
progressText: undefined,
|
||||||
|
meta: undefined,
|
||||||
commandBearing: false,
|
commandBearing: false,
|
||||||
|
approvalId: undefined,
|
||||||
|
approvalSlug: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -227,7 +232,12 @@ describe("executeAgentTurn: lifecycle progress", () => {
|
||||||
name: "bash",
|
name: "bash",
|
||||||
phase: "start",
|
phase: "start",
|
||||||
status: "running",
|
status: "running",
|
||||||
|
summary: undefined,
|
||||||
|
progressText: undefined,
|
||||||
|
meta: undefined,
|
||||||
commandBearing: false,
|
commandBearing: false,
|
||||||
|
approvalId: undefined,
|
||||||
|
approvalSlug: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import { CronService } from "./service.js";
|
import { CronService } from "./service.js";
|
||||||
import { setupCronServiceSuite } from "./service.test-harness.js";
|
import { setupCronServiceSuite } from "./service.test-harness.js";
|
||||||
|
import type { CronServiceDeps } from "./service/state.js";
|
||||||
|
|
||||||
const { logger, makeStorePath } = setupCronServiceSuite({ prefix: "cron-stream-trigger-" });
|
const { logger, makeStorePath } = setupCronServiceSuite({ prefix: "cron-stream-trigger-" });
|
||||||
|
|
||||||
|
|
@ -194,7 +195,9 @@ describe("cron stream trigger composition", () => {
|
||||||
it("reports a failed payload batch without reporting it fired", async () => {
|
it("reports a failed payload batch without reporting it fired", async () => {
|
||||||
const { storePath } = await makeStorePath();
|
const { storePath } = await makeStorePath();
|
||||||
const onTriggerDisposition = vi.fn();
|
const onTriggerDisposition = vi.fn();
|
||||||
const sendCronFailureAlert = vi.fn(async () => undefined);
|
const sendCronFailureAlert = vi.fn<NonNullable<CronServiceDeps["sendCronFailureAlert"]>>(
|
||||||
|
async () => undefined,
|
||||||
|
);
|
||||||
const cron = new CronService({
|
const cron = new CronService({
|
||||||
storePath,
|
storePath,
|
||||||
cronEnabled: true,
|
cronEnabled: true,
|
||||||
|
|
@ -239,15 +242,15 @@ describe("cron stream trigger composition", () => {
|
||||||
consecutiveErrors: 1,
|
consecutiveErrors: 1,
|
||||||
});
|
});
|
||||||
expect(sendCronFailureAlert).toHaveBeenCalledOnce();
|
expect(sendCronFailureAlert).toHaveBeenCalledOnce();
|
||||||
expect(sendCronFailureAlert).toHaveBeenCalledWith(
|
const alert = sendCronFailureAlert.mock.calls[0]?.[0];
|
||||||
expect.objectContaining({
|
expect(alert?.channel).toBe("telegram");
|
||||||
payload: expect.objectContaining({
|
expect(alert?.to).toBe("19098680");
|
||||||
text:
|
expect(alert?.payload).toEqual({
|
||||||
'Automation "failing stream payload" failed 1 times\n' +
|
text:
|
||||||
"Check automation history for details.",
|
'Automation "failing stream payload" failed 1 times\n' +
|
||||||
}),
|
"Check automation history for details.",
|
||||||
}),
|
});
|
||||||
);
|
expect(alert?.job.state.lastError).toBe("boom");
|
||||||
} finally {
|
} finally {
|
||||||
cron.stop();
|
cron.stop();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import type {
|
||||||
CronAgentExecutionStarted,
|
CronAgentExecutionStarted,
|
||||||
CronJob,
|
CronJob,
|
||||||
} from "../types.js";
|
} from "../types.js";
|
||||||
import { createCronServiceState } from "./state.js";
|
import { createCronServiceState, type CronServiceDeps } from "./state.js";
|
||||||
import { onTimer } from "./timer.test-support.js";
|
import { onTimer } from "./timer.test-support.js";
|
||||||
|
|
||||||
const timerRegressionFixtures = setupCronRegressionFixtures({
|
const timerRegressionFixtures = setupCronRegressionFixtures({
|
||||||
|
|
@ -723,7 +723,9 @@ describe("cron service timer regressions", () => {
|
||||||
const started = createDeferred();
|
const started = createDeferred();
|
||||||
let abortObserved = false;
|
let abortObserved = false;
|
||||||
const cleanupTimedOutAgentRun = vi.fn(async () => {});
|
const cleanupTimedOutAgentRun = vi.fn(async () => {});
|
||||||
const sendCronFailureAlert = vi.fn(async () => {});
|
const sendCronFailureAlert = vi.fn<NonNullable<CronServiceDeps["sendCronFailureAlert"]>>(
|
||||||
|
async () => {},
|
||||||
|
);
|
||||||
const state = createCronServiceState({
|
const state = createCronServiceState({
|
||||||
cronEnabled: true,
|
cronEnabled: true,
|
||||||
storePath: store.storePath,
|
storePath: store.storePath,
|
||||||
|
|
@ -775,23 +777,29 @@ describe("cron service timer regressions", () => {
|
||||||
await timerPromise;
|
await timerPromise;
|
||||||
|
|
||||||
const job = requireJob(state, "isolated-before-agent-reply-unhandled-82811");
|
const job = requireJob(state, "isolated-before-agent-reply-unhandled-82811");
|
||||||
|
const diagnostic =
|
||||||
|
"cron: isolated agent run stalled before execution start (last phase: runtime-plugins)";
|
||||||
expect(abortObserved).toBe(true);
|
expect(abortObserved).toBe(true);
|
||||||
expect(job.state.lastStatus).toBe("error");
|
expect(job.state.lastStatus).toBe("error");
|
||||||
expect(job.state.lastError).toContain("stalled before execution start");
|
expect(job.state.lastError).toBe(diagnostic);
|
||||||
expect(job.state.lastError).toContain("runtime-plugins");
|
expect(job.state.lastDiagnosticSummary).toBe(diagnostic);
|
||||||
|
expect(job.state.lastDiagnostics).toEqual({
|
||||||
|
summary: diagnostic,
|
||||||
|
entries: [
|
||||||
|
{ source: "cron-setup", severity: "error", message: diagnostic, ts: scheduledAt },
|
||||||
|
],
|
||||||
|
});
|
||||||
expect(cleanupTimedOutAgentRun).toHaveBeenCalledTimes(1);
|
expect(cleanupTimedOutAgentRun).toHaveBeenCalledTimes(1);
|
||||||
expect(sendCronFailureAlert).toHaveBeenCalledTimes(1);
|
expect(sendCronFailureAlert).toHaveBeenCalledTimes(1);
|
||||||
expect(sendCronFailureAlert).toHaveBeenCalledWith(
|
const alert = sendCronFailureAlert.mock.calls[0]?.[0];
|
||||||
expect.objectContaining({
|
expect(alert?.channel).toBe("telegram");
|
||||||
channel: "telegram",
|
expect(alert?.to).toBe("12345");
|
||||||
to: "12345",
|
expect(alert?.payload).toEqual({
|
||||||
payload: expect.objectContaining({
|
text:
|
||||||
text:
|
'Automation "before agent reply unhandled regression" failed 1 times\n' +
|
||||||
'Automation "before agent reply unhandled regression" failed 1 times\n' +
|
"Check automation history for details.",
|
||||||
"Check automation history for details.",
|
});
|
||||||
}),
|
expect(alert?.job.state.lastDiagnosticSummary).toBe(diagnostic);
|
||||||
}),
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ export async function runExclusiveSystemAgentSetupActivation<T>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Resolves after both the wizard runner and its setup-target admission have settled. */
|
||||||
export function whenAdmittedWizardSessionSettled(session: {
|
export function whenAdmittedWizardSessionSettled(session: {
|
||||||
whenSettled(): Promise<unknown>;
|
whenSettled(): Promise<unknown>;
|
||||||
}): Promise<unknown> {
|
}): Promise<unknown> {
|
||||||
|
|
|
||||||
|
|
@ -250,26 +250,12 @@ describe("resolveApprovalOverGateway", () => {
|
||||||
|
|
||||||
it("sends channel custody to an injected canonical runtime", async () => {
|
it("sends channel custody to an injected canonical runtime", async () => {
|
||||||
const injectedRequest = vi.fn(async () => ({ applied: true, approval: recordedApproval }));
|
const injectedRequest = vi.fn(async () => ({ applied: true, approval: recordedApproval }));
|
||||||
const scopedRequest: GatewayNativeApprovalRuntime["request"] = vi.fn(
|
const scopedRequest = vi.fn();
|
||||||
async <T = unknown>(method: string): Promise<T> => {
|
|
||||||
const fixture =
|
|
||||||
method === "exec.approval.list"
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
id: "approval-1",
|
|
||||||
request: {
|
|
||||||
command: "printf approval",
|
|
||||||
turnSourceChannel: "imessage",
|
|
||||||
turnSourceAccountId: "personal",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: { applied: true, approval: recordedApproval };
|
|
||||||
return fixture as T;
|
|
||||||
},
|
|
||||||
) as GatewayNativeApprovalRuntime["request"];
|
|
||||||
const runtime = {
|
const runtime = {
|
||||||
request: scopedRequest,
|
request: async <T>(): Promise<T> => {
|
||||||
|
scopedRequest();
|
||||||
|
throw new Error("unexpected scoped approval request");
|
||||||
|
},
|
||||||
requestRoute: vi.fn(),
|
requestRoute: vi.fn(),
|
||||||
routeCoordinator: { doesAccountHandleRequest: () => true } as never,
|
routeCoordinator: { doesAccountHandleRequest: () => true } as never,
|
||||||
subscribe: vi.fn(),
|
subscribe: vi.fn(),
|
||||||
|
|
|
||||||
|
|
@ -773,6 +773,8 @@ export function createNativeApprovalChannelRouteGates<TTarget extends NativeAppr
|
||||||
approvalKind: ApprovalKind;
|
approvalKind: ApprovalKind;
|
||||||
request: ApprovalRequest;
|
request: ApprovalRequest;
|
||||||
}): boolean => {
|
}): boolean => {
|
||||||
|
// Per-account runtimes report raw candidates here. The route coordinator rejects
|
||||||
|
// unbound multi-account groups as ambiguous before any runtime can deliver.
|
||||||
const accountId = input.accountId ?? params.resolveDefaultAccountId(input.cfg);
|
const accountId = input.accountId ?? params.resolveDefaultAccountId(input.cfg);
|
||||||
const eligibleAccountIds = params.isTransportEnabled({ cfg: input.cfg, accountId })
|
const eligibleAccountIds = params.isTransportEnabled({ cfg: input.cfg, accountId })
|
||||||
? [accountId]
|
? [accountId]
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,28 @@ import { getArchivedSkillFiles } from "./curator.js";
|
||||||
import { readSkillProposalTargetTreeSha256 } from "./proposal-bundle.js";
|
import { readSkillProposalTargetTreeSha256 } from "./proposal-bundle.js";
|
||||||
import { withSkillCollectionLock } from "./target-lock.js";
|
import { withSkillCollectionLock } from "./target-lock.js";
|
||||||
|
|
||||||
|
type CopyDirectoryHook = (
|
||||||
|
source: unknown,
|
||||||
|
destination: unknown,
|
||||||
|
options?: unknown,
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
|
const copyDirectoryBefore = vi.hoisted(() => vi.fn<CopyDirectoryHook>(async () => {}));
|
||||||
|
const copyDirectoryAfter = vi.hoisted(() => vi.fn<CopyDirectoryHook>(async () => {}));
|
||||||
const dispatchCommittedSkillChangeBestEffort = vi.hoisted(() =>
|
const dispatchCommittedSkillChangeBestEffort = vi.hoisted(() =>
|
||||||
vi.fn(async (_event: { action: string }) => {}),
|
vi.fn(async (_event: { action: string }) => {}),
|
||||||
);
|
);
|
||||||
const snapshotCommittedSkillArtifactBestEffort = vi.hoisted(() => vi.fn(async () => undefined));
|
const snapshotCommittedSkillArtifactBestEffort = vi.hoisted(() => vi.fn(async () => undefined));
|
||||||
|
vi.mock("node:fs/promises", async () => {
|
||||||
|
const actual = await vi.importActual<typeof import("node:fs/promises")>("node:fs/promises");
|
||||||
|
const cp: typeof actual.cp = async (source, destination, options) => {
|
||||||
|
await copyDirectoryBefore(source, destination, options);
|
||||||
|
await actual.cp(source, destination, options);
|
||||||
|
await copyDirectoryAfter(source, destination, options);
|
||||||
|
};
|
||||||
|
const patched = { ...actual, cp };
|
||||||
|
return { ...patched, default: patched };
|
||||||
|
});
|
||||||
vi.mock("../lifecycle/skill-change-hook.js", () => ({
|
vi.mock("../lifecycle/skill-change-hook.js", () => ({
|
||||||
hasCommittedSkillChangeHooks: () => true,
|
hasCommittedSkillChangeHooks: () => true,
|
||||||
snapshotCommittedSkillArtifactBestEffort,
|
snapshotCommittedSkillArtifactBestEffort,
|
||||||
|
|
@ -35,6 +53,10 @@ let testState: OpenClawTestState;
|
||||||
let workspaceDir: string;
|
let workspaceDir: string;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
copyDirectoryBefore.mockReset();
|
||||||
|
copyDirectoryBefore.mockResolvedValue(undefined);
|
||||||
|
copyDirectoryAfter.mockReset();
|
||||||
|
copyDirectoryAfter.mockResolvedValue(undefined);
|
||||||
dispatchCommittedSkillChangeBestEffort.mockClear();
|
dispatchCommittedSkillChangeBestEffort.mockClear();
|
||||||
snapshotCommittedSkillArtifactBestEffort.mockReset();
|
snapshotCommittedSkillArtifactBestEffort.mockReset();
|
||||||
snapshotCommittedSkillArtifactBestEffort.mockResolvedValue(undefined);
|
snapshotCommittedSkillArtifactBestEffort.mockResolvedValue(undefined);
|
||||||
|
|
@ -239,9 +261,7 @@ describe("skill collection reconciliation", () => {
|
||||||
await fs.mkdir(path.dirname(supportFile), { recursive: true });
|
await fs.mkdir(path.dirname(supportFile), { recursive: true });
|
||||||
await fs.writeFile(supportFile, "Before\n", "utf8");
|
await fs.writeFile(supportFile, "Before\n", "utf8");
|
||||||
const receipt = await readCollectionReceipt();
|
const receipt = await readCollectionReceipt();
|
||||||
const copy = fs.cp.bind(fs);
|
copyDirectoryAfter.mockImplementationOnce(async () => {
|
||||||
const copySpy = vi.spyOn(fs, "cp").mockImplementation(async (source, destination, options) => {
|
|
||||||
await copy(source, destination, options);
|
|
||||||
await fs.appendFile(supportFile, "External edit\n", "utf8");
|
await fs.appendFile(supportFile, "External edit\n", "utf8");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -260,7 +280,7 @@ describe("skill collection reconciliation", () => {
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow("Skill tree changed before collection mutation: procedure");
|
).rejects.toThrow("Skill tree changed before collection mutation: procedure");
|
||||||
copySpy.mockRestore();
|
copyDirectoryAfter.mockReset();
|
||||||
|
|
||||||
await expect(fs.readFile(path.join(skillDir, "SKILL.md"), "utf8")).resolves.toContain(
|
await expect(fs.readFile(path.join(skillDir, "SKILL.md"), "utf8")).resolves.toContain(
|
||||||
"# Original",
|
"# Original",
|
||||||
|
|
@ -450,12 +470,16 @@ describe("skill collection reconciliation", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const skillDir = path.join(workspaceDir, "skills", "procedure");
|
const canonicalWorkspaceDir = await fs.realpath(workspaceDir);
|
||||||
|
const skillDir = path.join(canonicalWorkspaceDir, "skills", "procedure");
|
||||||
const skillFile = path.join(skillDir, "SKILL.md");
|
const skillFile = path.join(skillDir, "SKILL.md");
|
||||||
const backupRoot = path.join(testState.stateDir, "skill-workshop", "collection-backups");
|
const backupRoot = path.join(
|
||||||
const originalCopy = fs.cp.bind(fs);
|
await fs.realpath(testState.stateDir),
|
||||||
|
"skill-workshop",
|
||||||
|
"collection-backups",
|
||||||
|
);
|
||||||
let failed = false;
|
let failed = false;
|
||||||
const copySpy = vi.spyOn(fs, "cp").mockImplementation(async (source, destination, options) => {
|
copyDirectoryBefore.mockImplementation(async (source, destination) => {
|
||||||
if (
|
if (
|
||||||
!failed &&
|
!failed &&
|
||||||
String(source).startsWith(backupRoot) &&
|
String(source).startsWith(backupRoot) &&
|
||||||
|
|
@ -465,7 +489,6 @@ describe("skill collection reconciliation", () => {
|
||||||
failed = true;
|
failed = true;
|
||||||
throw new Error("forced restore copy failure");
|
throw new Error("forced restore copy failure");
|
||||||
}
|
}
|
||||||
await originalCopy(source, destination, options);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -473,7 +496,7 @@ describe("skill collection reconciliation", () => {
|
||||||
restoreLatestSkillCollectionBackup({ workspaceDir, env: testState.env }),
|
restoreLatestSkillCollectionBackup({ workspaceDir, env: testState.env }),
|
||||||
).rejects.toThrow("forced restore copy failure");
|
).rejects.toThrow("forced restore copy failure");
|
||||||
} finally {
|
} finally {
|
||||||
copySpy.mockRestore();
|
copyDirectoryBefore.mockReset();
|
||||||
}
|
}
|
||||||
await expect(fs.readFile(skillFile, "utf8")).resolves.toContain("# Clean");
|
await expect(fs.readFile(skillFile, "utf8")).resolves.toContain("# Clean");
|
||||||
|
|
||||||
|
|
@ -498,14 +521,12 @@ describe("skill collection reconciliation", () => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const skillDir = path.join(workspaceDir, "skills", "procedure");
|
const skillDir = path.join(await fs.realpath(workspaceDir), "skills", "procedure");
|
||||||
const beforeVersion = getSkillsSnapshotVersion();
|
const beforeVersion = getSkillsSnapshotVersion();
|
||||||
const originalCopy = fs.cp.bind(fs);
|
copyDirectoryBefore.mockImplementation(async (source, destination) => {
|
||||||
const copySpy = vi.spyOn(fs, "cp").mockImplementation(async (source, destination, options) => {
|
|
||||||
if (path.resolve(String(destination)) === path.resolve(skillDir)) {
|
if (path.resolve(String(destination)) === path.resolve(skillDir)) {
|
||||||
throw new Error(`forced restore copy failure: ${String(source)}`);
|
throw new Error(`forced restore copy failure: ${String(source)}`);
|
||||||
}
|
}
|
||||||
await originalCopy(source, destination, options);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -513,7 +534,7 @@ describe("skill collection reconciliation", () => {
|
||||||
restoreLatestSkillCollectionBackup({ workspaceDir, env: testState.env }),
|
restoreLatestSkillCollectionBackup({ workspaceDir, env: testState.env }),
|
||||||
).rejects.toThrow("current collection was not restored");
|
).rejects.toThrow("current collection was not restored");
|
||||||
} finally {
|
} finally {
|
||||||
copySpy.mockRestore();
|
copyDirectoryBefore.mockReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(getSkillsSnapshotVersion()).toBeGreaterThan(beforeVersion);
|
expect(getSkillsSnapshotVersion()).toBeGreaterThan(beforeVersion);
|
||||||
|
|
|
||||||
|
|
@ -367,6 +367,7 @@ describe("skill collection review", () => {
|
||||||
|
|
||||||
it("groups symlink aliases before comparing shared-workspace identities", async () => {
|
it("groups symlink aliases before comparing shared-workspace identities", async () => {
|
||||||
const workspaceDir = await tempDirs.make("openclaw-collection-review-real-workspace-");
|
const workspaceDir = await tempDirs.make("openclaw-collection-review-real-workspace-");
|
||||||
|
const canonicalWorkspaceDir = await fs.realpath(workspaceDir);
|
||||||
const aliasParent = await tempDirs.make("openclaw-collection-review-alias-parent-");
|
const aliasParent = await tempDirs.make("openclaw-collection-review-alias-parent-");
|
||||||
const workspaceAlias = path.join(aliasParent, "workspace-alias");
|
const workspaceAlias = path.join(aliasParent, "workspace-alias");
|
||||||
await fs.symlink(
|
await fs.symlink(
|
||||||
|
|
@ -400,7 +401,7 @@ describe("skill collection review", () => {
|
||||||
onError,
|
onError,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(onError).toHaveBeenCalledWith(expect.any(Error), workspaceDir);
|
expect(onError).toHaveBeenCalledWith(expect.any(Error), canonicalWorkspaceDir);
|
||||||
expect(runWithGatewayIndependentRootWorkAdmission).not.toHaveBeenCalled();
|
expect(runWithGatewayIndependentRootWorkAdmission).not.toHaveBeenCalled();
|
||||||
expect(runEmbeddedAgent).not.toHaveBeenCalled();
|
expect(runEmbeddedAgent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
@ -450,6 +451,7 @@ describe("skill collection review", () => {
|
||||||
|
|
||||||
it("admits and reports each workspace independently", async () => {
|
it("admits and reports each workspace independently", async () => {
|
||||||
const oversizedWorkspace = await tempDirs.make("openclaw-collection-review-failed-");
|
const oversizedWorkspace = await tempDirs.make("openclaw-collection-review-failed-");
|
||||||
|
const canonicalOversizedWorkspace = await fs.realpath(oversizedWorkspace);
|
||||||
const healthyWorkspace = await tempDirs.make("openclaw-collection-review-healthy-");
|
const healthyWorkspace = await tempDirs.make("openclaw-collection-review-healthy-");
|
||||||
await writeWorkspaceSkills(oversizedWorkspace, [
|
await writeWorkspaceSkills(oversizedWorkspace, [
|
||||||
{ name: "oversized", description: "Oversized", body: "x".repeat(240_001) },
|
{ name: "oversized", description: "Oversized", body: "x".repeat(240_001) },
|
||||||
|
|
@ -489,7 +491,7 @@ describe("skill collection review", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(runWithGatewayIndependentRootWorkAdmission).toHaveBeenCalledTimes(2);
|
expect(runWithGatewayIndependentRootWorkAdmission).toHaveBeenCalledTimes(2);
|
||||||
expect(onError).toHaveBeenCalledWith(expect.any(Error), oversizedWorkspace);
|
expect(onError).toHaveBeenCalledWith(expect.any(Error), canonicalOversizedWorkspace);
|
||||||
expect(runEmbeddedAgent).toHaveBeenCalledTimes(1);
|
expect(runEmbeddedAgent).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3871,7 +3871,7 @@ describe("package artifact reuse", () => {
|
||||||
expect(workflow).toContain("repo_live_suite_filter:");
|
expect(workflow).toContain("repo_live_suite_filter:");
|
||||||
expect(workflow).toContain('repo_filter_tokens+=("$token")');
|
expect(workflow).toContain('repo_filter_tokens+=("$token")');
|
||||||
expect(workflow).toContain(
|
expect(workflow).toContain(
|
||||||
'repo_live_suite_filter="$(IFS=,; printf \'%s\' "${repo_filter_tokens[*]:-}")"',
|
'repo_live_suite_filter="$(IFS=,; printf \'%s\' "${repo_filter_tokens[*]-}")"',
|
||||||
);
|
);
|
||||||
expect(workflow).toContain("cross_os_suite_filter:");
|
expect(workflow).toContain("cross_os_suite_filter:");
|
||||||
expect(workflow).toContain("advisory: false");
|
expect(workflow).toContain("advisory: false");
|
||||||
|
|
@ -6356,12 +6356,11 @@ wait_for_run plugin-clawhub-new.yml 123 "${expectedSha}" || status=$?
|
||||||
const releaseChecksParent = workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "release_checks");
|
const releaseChecksParent = workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "release_checks");
|
||||||
expect(releaseChecksParent["runs-on"]).toBe("blacksmith-4vcpu-ubuntu-2404");
|
expect(releaseChecksParent["runs-on"]).toBe("blacksmith-4vcpu-ubuntu-2404");
|
||||||
expect(releaseChecksParent["timeout-minutes"]).toBe(420);
|
expect(releaseChecksParent["timeout-minutes"]).toBe(420);
|
||||||
const releasePackageTimeouts = Object.fromEntries(
|
const releasePackageTimeouts = {
|
||||||
profiles.map((profile) => [
|
beta: releasePackagePaths.beta.reduce((total, timeout) => total + timeout, 0),
|
||||||
profile,
|
stable: releasePackagePaths.stable.reduce((total, timeout) => total + timeout, 0),
|
||||||
releasePackagePaths[profile].reduce((total, timeout) => total + timeout, 0),
|
full: releasePackagePaths.full.reduce((total, timeout) => total + timeout, 0),
|
||||||
]),
|
};
|
||||||
) as Record<(typeof profiles)[number], number>;
|
|
||||||
expect(releasePackageTimeouts).toEqual({ beta: 280, stable: 280, full: 310 });
|
expect(releasePackageTimeouts).toEqual({ beta: 280, stable: 280, full: 310 });
|
||||||
for (const [profile, childTimeout] of Object.entries(releasePackageTimeouts)) {
|
for (const [profile, childTimeout] of Object.entries(releasePackageTimeouts)) {
|
||||||
expect(childTimeout, `release-package:${profile}`).toBeLessThanOrEqual(420);
|
expect(childTimeout, `release-package:${profile}`).toBeLessThanOrEqual(420);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue