Merge branch 'main' into together-ai
This commit is contained in:
commit
74f85fc50b
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -76,7 +76,7 @@ jobs:
|
|||||||
command: pnpm lint
|
command: pnpm lint
|
||||||
- runtime: node
|
- runtime: node
|
||||||
task: test
|
task: test
|
||||||
command: pnpm test
|
command: pnpm canvas:a2ui:bundle && pnpm test
|
||||||
- runtime: node
|
- runtime: node
|
||||||
task: build
|
task: build
|
||||||
command: pnpm build
|
command: pnpm build
|
||||||
@ -88,7 +88,7 @@ jobs:
|
|||||||
command: pnpm format
|
command: pnpm format
|
||||||
- runtime: bun
|
- runtime: bun
|
||||||
task: test
|
task: test
|
||||||
command: bunx vitest run
|
command: pnpm canvas:a2ui:bundle && bunx vitest run
|
||||||
- runtime: bun
|
- runtime: bun
|
||||||
task: build
|
task: build
|
||||||
command: bunx tsc -p tsconfig.json
|
command: bunx tsc -p tsconfig.json
|
||||||
@ -188,6 +188,7 @@ jobs:
|
|||||||
runs-on: blacksmith-4vcpu-windows-2025
|
runs-on: blacksmith-4vcpu-windows-2025
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=4096
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
|
CLAWDBOT_TEST_WORKERS: 1
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
@ -200,7 +201,7 @@ jobs:
|
|||||||
command: pnpm lint
|
command: pnpm lint
|
||||||
- runtime: node
|
- runtime: node
|
||||||
task: test
|
task: test
|
||||||
command: pnpm test
|
command: pnpm canvas:a2ui:bundle && pnpm test
|
||||||
- runtime: node
|
- runtime: node
|
||||||
task: build
|
task: build
|
||||||
command: pnpm build
|
command: pnpm build
|
||||||
|
|||||||
@ -72,6 +72,7 @@ Status: unreleased.
|
|||||||
- Security: pin npm overrides to keep tar@7.5.4 for install toolchains.
|
- Security: pin npm overrides to keep tar@7.5.4 for install toolchains.
|
||||||
- Security: properly test Windows ACL audit for config includes. (#2403) Thanks @dominicnunez.
|
- Security: properly test Windows ACL audit for config includes. (#2403) Thanks @dominicnunez.
|
||||||
- CLI: recognize versioned Node executables when parsing argv. (#2490) Thanks @David-Marsh-Photo.
|
- CLI: recognize versioned Node executables when parsing argv. (#2490) Thanks @David-Marsh-Photo.
|
||||||
|
- CLI: avoid prompting for gateway runtime under the spinner. (#2874)
|
||||||
- BlueBubbles: coalesce inbound URL link preview messages. (#1981) Thanks @tyler6204.
|
- BlueBubbles: coalesce inbound URL link preview messages. (#1981) Thanks @tyler6204.
|
||||||
- Cron: allow payloads containing "heartbeat" in event filter. (#2219) Thanks @dwfinkelstein.
|
- Cron: allow payloads containing "heartbeat" in event filter. (#2219) Thanks @dwfinkelstein.
|
||||||
- CLI: avoid loading config for global help/version while registering plugin commands. (#2212) Thanks @dial481.
|
- CLI: avoid loading config for global help/version while registering plugin commands. (#2212) Thanks @dial481.
|
||||||
|
|||||||
@ -24,7 +24,7 @@ COPY scripts ./scripts
|
|||||||
RUN pnpm install --frozen-lockfile
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN pnpm build
|
RUN CLAWDBOT_A2UI_SKIP_MISSING=1 pnpm build
|
||||||
# Force pnpm for UI build (Bun may fail on ARM/Synology architectures)
|
# Force pnpm for UI build (Bun may fail on ARM/Synology architectures)
|
||||||
ENV CLAWDBOT_PREFER_PNPM=1
|
ENV CLAWDBOT_PREFER_PNPM=1
|
||||||
RUN pnpm ui:install
|
RUN pnpm ui:install
|
||||||
|
|||||||
@ -27,23 +27,49 @@ INPUT_PATHS=(
|
|||||||
"$A2UI_APP_DIR"
|
"$A2UI_APP_DIR"
|
||||||
)
|
)
|
||||||
|
|
||||||
collect_files() {
|
compute_hash() {
|
||||||
local path
|
ROOT_DIR="$ROOT_DIR" node --input-type=module - "${INPUT_PATHS[@]}" <<'NODE'
|
||||||
for path in "${INPUT_PATHS[@]}"; do
|
import { createHash } from "node:crypto";
|
||||||
if [[ -d "$path" ]]; then
|
import { promises as fs } from "node:fs";
|
||||||
find "$path" -type f -print0
|
import path from "node:path";
|
||||||
else
|
|
||||||
printf '%s\0' "$path"
|
const rootDir = process.env.ROOT_DIR ?? process.cwd();
|
||||||
fi
|
const inputs = process.argv.slice(2);
|
||||||
done
|
const files = [];
|
||||||
|
|
||||||
|
async function walk(entryPath) {
|
||||||
|
const st = await fs.stat(entryPath);
|
||||||
|
if (st.isDirectory()) {
|
||||||
|
const entries = await fs.readdir(entryPath);
|
||||||
|
for (const entry of entries) {
|
||||||
|
await walk(path.join(entryPath, entry));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
files.push(entryPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
compute_hash() {
|
for (const input of inputs) {
|
||||||
collect_files \
|
await walk(input);
|
||||||
| LC_ALL=C sort -z \
|
}
|
||||||
| xargs -0 shasum -a 256 \
|
|
||||||
| shasum -a 256 \
|
function normalize(p) {
|
||||||
| awk '{print $1}'
|
return p.split(path.sep).join("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
files.sort((a, b) => normalize(a).localeCompare(normalize(b)));
|
||||||
|
|
||||||
|
const hash = createHash("sha256");
|
||||||
|
for (const filePath of files) {
|
||||||
|
const rel = normalize(path.relative(rootDir, filePath));
|
||||||
|
hash.update(rel);
|
||||||
|
hash.update("\0");
|
||||||
|
hash.update(await fs.readFile(filePath));
|
||||||
|
hash.update("\0");
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write(hash.digest("hex"));
|
||||||
|
NODE
|
||||||
}
|
}
|
||||||
|
|
||||||
current_hash="$(compute_hash)"
|
current_hash="$(compute_hash)"
|
||||||
|
|||||||
@ -19,12 +19,17 @@ export async function copyA2uiAssets({
|
|||||||
srcDir: string;
|
srcDir: string;
|
||||||
outDir: string;
|
outDir: string;
|
||||||
}) {
|
}) {
|
||||||
|
const skipMissing = process.env.CLAWDBOT_A2UI_SKIP_MISSING === "1";
|
||||||
try {
|
try {
|
||||||
await fs.stat(path.join(srcDir, "index.html"));
|
await fs.stat(path.join(srcDir, "index.html"));
|
||||||
await fs.stat(path.join(srcDir, "a2ui.bundle.js"));
|
await fs.stat(path.join(srcDir, "a2ui.bundle.js"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message =
|
const message =
|
||||||
'Missing A2UI bundle assets. Run "pnpm canvas:a2ui:bundle" and retry.';
|
'Missing A2UI bundle assets. Run "pnpm canvas:a2ui:bundle" and retry.';
|
||||||
|
if (skipMissing) {
|
||||||
|
console.warn(`${message} Skipping copy (CLAWDBOT_A2UI_SKIP_MISSING=1).`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
throw new Error(message, { cause: err });
|
throw new Error(message, { cause: err });
|
||||||
}
|
}
|
||||||
await fs.mkdir(path.dirname(outDir), { recursive: true });
|
await fs.mkdir(path.dirname(outDir), { recursive: true });
|
||||||
|
|||||||
@ -18,16 +18,21 @@ const runs = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const parallelRuns = runs.filter((entry) => entry.name !== "gateway");
|
|
||||||
const serialRuns = runs.filter((entry) => entry.name === "gateway");
|
|
||||||
|
|
||||||
const children = new Set();
|
const children = new Set();
|
||||||
const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
||||||
const isMacOS = process.platform === "darwin" || process.env.RUNNER_OS === "macOS";
|
const isMacOS = process.platform === "darwin" || process.env.RUNNER_OS === "macOS";
|
||||||
|
const isWindows = process.platform === "win32" || process.env.RUNNER_OS === "Windows";
|
||||||
|
const isWindowsCi = isCI && isWindows;
|
||||||
|
const shardOverride = Number.parseInt(process.env.CLAWDBOT_TEST_SHARDS ?? "", 10);
|
||||||
|
const shardCount = isWindowsCi ? (Number.isFinite(shardOverride) && shardOverride > 1 ? shardOverride : 2) : 1;
|
||||||
|
const windowsCiArgs = isWindowsCi ? ["--no-file-parallelism", "--dangerouslyIgnoreUnhandledErrors"] : [];
|
||||||
const overrideWorkers = Number.parseInt(process.env.CLAWDBOT_TEST_WORKERS ?? "", 10);
|
const overrideWorkers = Number.parseInt(process.env.CLAWDBOT_TEST_WORKERS ?? "", 10);
|
||||||
const resolvedOverride = Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null;
|
const resolvedOverride = Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null;
|
||||||
|
const parallelRuns = isWindowsCi ? [] : runs.filter((entry) => entry.name !== "gateway");
|
||||||
|
const serialRuns = isWindowsCi ? runs : runs.filter((entry) => entry.name === "gateway");
|
||||||
const localWorkers = Math.max(4, Math.min(16, os.cpus().length));
|
const localWorkers = Math.max(4, Math.min(16, os.cpus().length));
|
||||||
const perRunWorkers = Math.max(1, Math.floor(localWorkers / parallelRuns.length));
|
const parallelCount = Math.max(1, parallelRuns.length);
|
||||||
|
const perRunWorkers = Math.max(1, Math.floor(localWorkers / parallelCount));
|
||||||
const macCiWorkers = isCI && isMacOS ? 1 : perRunWorkers;
|
const macCiWorkers = isCI && isMacOS ? 1 : perRunWorkers;
|
||||||
// Keep worker counts predictable for local runs; trim macOS CI workers to avoid worker crashes/OOM.
|
// Keep worker counts predictable for local runs; trim macOS CI workers to avoid worker crashes/OOM.
|
||||||
// In CI on linux/windows, prefer Vitest defaults to avoid cross-test interference from lower worker counts.
|
// In CI on linux/windows, prefer Vitest defaults to avoid cross-test interference from lower worker counts.
|
||||||
@ -39,9 +44,11 @@ const WARNING_SUPPRESSION_FLAGS = [
|
|||||||
"--disable-warning=DEP0060",
|
"--disable-warning=DEP0060",
|
||||||
];
|
];
|
||||||
|
|
||||||
const run = (entry) =>
|
const runOnce = (entry, extraArgs = []) =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
const args = maxWorkers ? [...entry.args, "--maxWorkers", String(maxWorkers)] : entry.args;
|
const args = maxWorkers
|
||||||
|
? [...entry.args, "--maxWorkers", String(maxWorkers), ...windowsCiArgs, ...extraArgs]
|
||||||
|
: [...entry.args, ...windowsCiArgs, ...extraArgs];
|
||||||
const nodeOptions = process.env.NODE_OPTIONS ?? "";
|
const nodeOptions = process.env.NODE_OPTIONS ?? "";
|
||||||
const nextNodeOptions = WARNING_SUPPRESSION_FLAGS.reduce(
|
const nextNodeOptions = WARNING_SUPPRESSION_FLAGS.reduce(
|
||||||
(acc, flag) => (acc.includes(flag) ? acc : `${acc} ${flag}`.trim()),
|
(acc, flag) => (acc.includes(flag) ? acc : `${acc} ${flag}`.trim()),
|
||||||
@ -59,6 +66,16 @@ const run = (entry) =>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const run = async (entry) => {
|
||||||
|
if (shardCount <= 1) return runOnce(entry);
|
||||||
|
for (let shardIndex = 1; shardIndex <= shardCount; shardIndex += 1) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const code = await runOnce(entry, ["--shard", `${shardIndex}/${shardCount}`]);
|
||||||
|
if (code !== 0) return code;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
const shutdown = (signal) => {
|
const shutdown = (signal) => {
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
child.kill(signal);
|
child.kill(signal);
|
||||||
|
|||||||
@ -66,20 +66,23 @@ export async function maybeInstallDaemon(params: {
|
|||||||
|
|
||||||
if (shouldInstall) {
|
if (shouldInstall) {
|
||||||
let installError: string | null = null;
|
let installError: string | null = null;
|
||||||
|
if (!params.daemonRuntime) {
|
||||||
|
if (GATEWAY_DAEMON_RUNTIME_OPTIONS.length === 1) {
|
||||||
|
daemonRuntime = GATEWAY_DAEMON_RUNTIME_OPTIONS[0]?.value ?? DEFAULT_GATEWAY_DAEMON_RUNTIME;
|
||||||
|
} else {
|
||||||
|
daemonRuntime = guardCancel(
|
||||||
|
await select({
|
||||||
|
message: "Gateway service runtime",
|
||||||
|
options: GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
||||||
|
initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||||
|
}),
|
||||||
|
params.runtime,
|
||||||
|
) as GatewayDaemonRuntime;
|
||||||
|
}
|
||||||
|
}
|
||||||
await withProgress(
|
await withProgress(
|
||||||
{ label: "Gateway service", indeterminate: true, delayMs: 0 },
|
{ label: "Gateway service", indeterminate: true, delayMs: 0 },
|
||||||
async (progress) => {
|
async (progress) => {
|
||||||
if (!params.daemonRuntime) {
|
|
||||||
daemonRuntime = guardCancel(
|
|
||||||
await select({
|
|
||||||
message: "Gateway service runtime",
|
|
||||||
options: GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
|
||||||
initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
|
||||||
}),
|
|
||||||
params.runtime,
|
|
||||||
) as GatewayDaemonRuntime;
|
|
||||||
}
|
|
||||||
|
|
||||||
progress.setLabel("Preparing Gateway service…");
|
progress.setLabel("Preparing Gateway service…");
|
||||||
|
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user