426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
487 lines
14 KiB
JavaScript
487 lines
14 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Exports the LifeOps planner action manifest as JSON for the LifeOps bench.
|
|
*
|
|
* Loads the personal-assistant plus its connector plugins (bluebubbles,
|
|
* contacts, imessage, phone, todos), builds the planner tool definitions from
|
|
* their actions, and emits each as a function-tool entry annotated with the
|
|
* source plugin and planner metadata (tags, contexts, priority, surfaces,
|
|
* risk/cost). The bench consumes the manifest to drive and score action selection.
|
|
*/
|
|
import { spawnSync } from "node:child_process";
|
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
import { dirname, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { buildPlannerToolsFromActions } from "../../packages/core/src/actions/to-tool.ts";
|
|
import type { Action } from "../../packages/core/src/types/components.ts";
|
|
import type { Plugin } from "../../packages/core/src/types/plugin.ts";
|
|
import blueBubblesPlugin from "../../plugins/plugin-bluebubbles/src/index.ts";
|
|
import { appContactsPlugin } from "../../plugins/plugin-contacts/src/plugin.ts";
|
|
import imessagePlugin from "../../plugins/plugin-imessage/src/index.ts";
|
|
import { personalAssistantPlugin } from "../../plugins/plugin-personal-assistant/src/plugin.ts";
|
|
import { appPhonePlugin } from "../../plugins/plugin-phone/src/plugin.ts";
|
|
import { todosPlugin } from "../../plugins/plugin-todos/src/index.ts";
|
|
|
|
type JsonObject = Record<string, unknown>;
|
|
|
|
type ManifestEntry = {
|
|
type: "function";
|
|
function: {
|
|
name: string;
|
|
description: string;
|
|
parameters: JsonObject;
|
|
strict: true;
|
|
};
|
|
_plugin: string;
|
|
_tags: string[];
|
|
_contexts: string[];
|
|
_priority: number;
|
|
_examples_count: number;
|
|
_domain: string | null;
|
|
_capabilities: string[];
|
|
_surfaces: string[];
|
|
_risk: string | null;
|
|
_cost: string | null;
|
|
};
|
|
|
|
type Manifest = {
|
|
schemaVersion: 1;
|
|
generator: string;
|
|
sourcePlugins: string[];
|
|
filters: {
|
|
domains: string[];
|
|
capabilities: string[];
|
|
surfaces: string[];
|
|
excludeRisks: string[];
|
|
benchUmbrellaAugment: boolean;
|
|
};
|
|
actions: ManifestEntry[];
|
|
};
|
|
|
|
type CliOptions = {
|
|
out: string;
|
|
summaryOut: string | null;
|
|
domains: string[];
|
|
capabilities: string[];
|
|
surfaces: string[];
|
|
excludeRisks: string[];
|
|
benchUmbrellaAugment: boolean;
|
|
};
|
|
|
|
const scriptPath = fileURLToPath(import.meta.url);
|
|
const repoRoot = resolve(dirname(scriptPath), "../..");
|
|
const defaultManifestPath = resolve(
|
|
repoRoot,
|
|
"packages/benchmarks/lifeops-bench/manifests/actions.manifest.json",
|
|
);
|
|
const defaultSummaryPath = resolve(
|
|
repoRoot,
|
|
"packages/benchmarks/lifeops-bench/manifests/actions.summary.md",
|
|
);
|
|
const benchmarkPackageRoot = resolve(
|
|
repoRoot,
|
|
"packages/benchmarks/lifeops-bench",
|
|
);
|
|
|
|
function usage(): string {
|
|
return [
|
|
"Usage: node --conditions=eliza-source --conditions=development --import tsx scripts/lifeops-bench/export-action-manifest.ts [options]",
|
|
"",
|
|
"Options:",
|
|
" --out <path> Output manifest JSON path.",
|
|
" --summary-out <path> Output summary Markdown path. Use 'none' to skip.",
|
|
" --domain <name> Keep actions with domain:<name>. Repeatable.",
|
|
" --capability <name> Keep actions with capability:<name>. Repeatable.",
|
|
" --surface <name> Keep actions with surface:<name>. Repeatable.",
|
|
" --exclude-risk <name> Drop actions with risk:<name>. Repeatable.",
|
|
" --skip-bench-augment Do not add LifeOpsBench-only umbrella verbs.",
|
|
" --help Print this help.",
|
|
].join("\n");
|
|
}
|
|
|
|
function readValue(args: string[], index: number, flag: string): string {
|
|
const value = args[index + 1];
|
|
if (!value || value.startsWith("--")) {
|
|
throw new Error(`${flag} requires a value`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function parseArgs(argv: string[]): CliOptions {
|
|
const options: CliOptions = {
|
|
out: defaultManifestPath,
|
|
summaryOut: defaultSummaryPath,
|
|
domains: [],
|
|
capabilities: [],
|
|
surfaces: [],
|
|
excludeRisks: [],
|
|
benchUmbrellaAugment: true,
|
|
};
|
|
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const arg = argv[index];
|
|
switch (arg) {
|
|
case "--help":
|
|
case "-h":
|
|
console.log(usage());
|
|
process.exit(0);
|
|
break;
|
|
case "--out":
|
|
options.out = resolve(process.cwd(), readValue(argv, index, arg));
|
|
index += 1;
|
|
break;
|
|
case "--summary-out": {
|
|
const value = readValue(argv, index, arg);
|
|
options.summaryOut =
|
|
value === "none" ? null : resolve(process.cwd(), value);
|
|
index += 1;
|
|
break;
|
|
}
|
|
case "--domain":
|
|
options.domains.push(
|
|
normalizeTaggedValue("domain", readValue(argv, index, arg)),
|
|
);
|
|
index += 1;
|
|
break;
|
|
case "--capability":
|
|
options.capabilities.push(
|
|
normalizeTaggedValue("capability", readValue(argv, index, arg)),
|
|
);
|
|
index += 1;
|
|
break;
|
|
case "--surface":
|
|
options.surfaces.push(
|
|
normalizeTaggedValue("surface", readValue(argv, index, arg)),
|
|
);
|
|
index += 1;
|
|
break;
|
|
case "--exclude-risk":
|
|
options.excludeRisks.push(
|
|
normalizeTaggedValue("risk", readValue(argv, index, arg)),
|
|
);
|
|
index += 1;
|
|
break;
|
|
case "--skip-bench-augment":
|
|
options.benchUmbrellaAugment = false;
|
|
break;
|
|
default:
|
|
throw new Error(`Unknown argument: ${arg}\n\n${usage()}`);
|
|
}
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
function normalizeTaggedValue(prefix: string, value: string): string {
|
|
const trimmed = value.trim();
|
|
if (!trimmed) {
|
|
throw new Error(`Empty ${prefix} filter`);
|
|
}
|
|
return trimmed.includes(":") ? trimmed : `${prefix}:${trimmed}`;
|
|
}
|
|
|
|
function partitionTags(
|
|
tags: string[],
|
|
): Pick<
|
|
ManifestEntry,
|
|
"_domain" | "_capabilities" | "_surfaces" | "_risk" | "_cost"
|
|
> {
|
|
return {
|
|
_domain: tags.find((tag) => tag.startsWith("domain:")) ?? null,
|
|
_capabilities: tags.filter((tag) => tag.startsWith("capability:")),
|
|
_surfaces: tags.filter((tag) => tag.startsWith("surface:")),
|
|
_risk: tags.find((tag) => tag.startsWith("risk:")) ?? null,
|
|
_cost: tags.find((tag) => tag.startsWith("cost:")) ?? null,
|
|
};
|
|
}
|
|
|
|
function examplesCount(action: Action): number {
|
|
return Array.isArray(action.examples) ? action.examples.length : 0;
|
|
}
|
|
|
|
function actionToEntry(action: Action, pluginName: string): ManifestEntry {
|
|
const tool = buildPlannerToolsFromActions([action])[0];
|
|
if (!tool) {
|
|
throw new Error(`Failed to render planner tool for ${action.name}`);
|
|
}
|
|
const tags = [...(action.tags ?? [])];
|
|
const taxonomy = partitionTags(tags);
|
|
return {
|
|
type: "function",
|
|
function: {
|
|
name: tool.name,
|
|
description: tool.description,
|
|
parameters: tool.parameters as JsonObject,
|
|
strict: true,
|
|
},
|
|
_plugin: pluginName,
|
|
_tags: tags,
|
|
_contexts: [...(action.contexts ?? ["general"])],
|
|
_priority: action.priority ?? 100,
|
|
_examples_count: examplesCount(action),
|
|
...taxonomy,
|
|
};
|
|
}
|
|
|
|
function collectEntries(plugins: Plugin[]): ManifestEntry[] {
|
|
const entries: ManifestEntry[] = [];
|
|
const seen = new Set<string>();
|
|
for (const plugin of plugins) {
|
|
for (const action of plugin.actions ?? []) {
|
|
const entry = actionToEntry(action, plugin.name);
|
|
const name = entry.function.name;
|
|
if (seen.has(name)) {
|
|
throw new Error(`Duplicate action name in exported registry: ${name}`);
|
|
}
|
|
seen.add(name);
|
|
entries.push(entry);
|
|
}
|
|
}
|
|
entries.sort((left, right) =>
|
|
left.function.name.localeCompare(right.function.name),
|
|
);
|
|
return entries;
|
|
}
|
|
|
|
function matchesAnyFilter(value: string | null, filters: string[]): boolean {
|
|
return filters.length === 0 || (value !== null && filters.includes(value));
|
|
}
|
|
|
|
function includesAllFilters(values: string[], filters: string[]): boolean {
|
|
return (
|
|
filters.length === 0 || filters.every((filter) => values.includes(filter))
|
|
);
|
|
}
|
|
|
|
function filterEntries(
|
|
entries: ManifestEntry[],
|
|
options: CliOptions,
|
|
): ManifestEntry[] {
|
|
return entries.filter((entry) => {
|
|
if (!matchesAnyFilter(entry._domain, options.domains)) {
|
|
return false;
|
|
}
|
|
if (!includesAllFilters(entry._capabilities, options.capabilities)) {
|
|
return false;
|
|
}
|
|
if (!includesAllFilters(entry._surfaces, options.surfaces)) {
|
|
return false;
|
|
}
|
|
if (entry._risk !== null && options.excludeRisks.includes(entry._risk)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
function writeJson(path: string, value: unknown): void {
|
|
mkdirSync(dirname(path), { recursive: true });
|
|
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
}
|
|
|
|
function augmentWithBenchUmbrellas(path: string): void {
|
|
const result = spawnSync(
|
|
"python3",
|
|
["-m", "eliza_lifeops_bench.manifest_export", path],
|
|
{
|
|
cwd: benchmarkPackageRoot,
|
|
encoding: "utf8",
|
|
stdio: "pipe",
|
|
},
|
|
);
|
|
if (result.status !== 0) {
|
|
throw new Error(
|
|
[
|
|
"LifeOpsBench umbrella manifest augment failed.",
|
|
result.stdout.trim(),
|
|
result.stderr.trim(),
|
|
]
|
|
.filter(Boolean)
|
|
.join("\n"),
|
|
);
|
|
}
|
|
if (result.stdout.trim()) {
|
|
console.log(result.stdout.trim());
|
|
}
|
|
}
|
|
|
|
function formatJsonWithBiome(path: string): void {
|
|
const result = spawnSync(
|
|
"bunx",
|
|
["biome", "format", "--stdin-file-path", "actions.manifest.json"],
|
|
{
|
|
cwd: repoRoot,
|
|
encoding: "utf8",
|
|
input: readFileSync(path, "utf8"),
|
|
stdio: "pipe",
|
|
},
|
|
);
|
|
if (result.status !== 0) {
|
|
throw new Error(
|
|
[
|
|
`Biome failed to format generated manifest at ${path}.`,
|
|
result.stdout.trim(),
|
|
result.stderr.trim(),
|
|
]
|
|
.filter(Boolean)
|
|
.join("\n"),
|
|
);
|
|
}
|
|
writeFileSync(path, result.stdout, "utf8");
|
|
}
|
|
|
|
function renderList(values: string[]): string {
|
|
return values.length === 0 ? "none" : values.join(", ");
|
|
}
|
|
|
|
function renderSummary(manifest: Manifest): string {
|
|
const actions = [...manifest.actions].sort((left, right) => {
|
|
const leftDomain = left._domain ?? "(untagged)";
|
|
const rightDomain = right._domain ?? "(untagged)";
|
|
return (
|
|
leftDomain.localeCompare(rightDomain) ||
|
|
left.function.name.localeCompare(right.function.name)
|
|
);
|
|
});
|
|
|
|
const byPlugin = countBy(actions, (entry) => entry._plugin);
|
|
const byDomain = countBy(actions, (entry) => entry._domain ?? "(untagged)");
|
|
const byRisk = countBy(actions, (entry) => entry._risk ?? "(no risk)");
|
|
const domains = [...byDomain.keys()].sort();
|
|
|
|
const lines: string[] = [
|
|
"# LifeOps Action Manifest - Summary",
|
|
"",
|
|
"Generated by `scripts/lifeops-bench/export-action-manifest.ts`.",
|
|
`Filter: domains=[${renderList(manifest.filters.domains)}] capabilities=[${renderList(
|
|
manifest.filters.capabilities,
|
|
)}] surfaces=[${renderList(manifest.filters.surfaces)}] excludeRisks=[${renderList(
|
|
manifest.filters.excludeRisks,
|
|
)}] benchUmbrellaAugment=${manifest.filters.benchUmbrellaAugment}`,
|
|
`Total actions: ${actions.length}`,
|
|
"",
|
|
"## Plugin breakdown",
|
|
"",
|
|
"| Plugin | Actions |",
|
|
"| --- | ---: |",
|
|
...renderCountRows(byPlugin),
|
|
"",
|
|
"## Domain breakdown",
|
|
"",
|
|
"| Domain | Actions |",
|
|
"| --- | ---: |",
|
|
...renderCountRows(byDomain),
|
|
"",
|
|
"## Risk breakdown",
|
|
"",
|
|
"| Risk | Actions |",
|
|
"| --- | ---: |",
|
|
...renderCountRows(byRisk),
|
|
"",
|
|
"## Actions by domain",
|
|
"",
|
|
];
|
|
|
|
for (const domain of domains) {
|
|
lines.push(`### ${domain}`, "");
|
|
lines.push(
|
|
"| Action | Plugin | Risk | Capabilities | Surfaces | Description |",
|
|
);
|
|
lines.push("| --- | --- | :---: | --- | --- | --- |");
|
|
for (const entry of actions.filter(
|
|
(action) => (action._domain ?? "(untagged)") === domain,
|
|
)) {
|
|
lines.push(
|
|
[
|
|
`\`${entry.function.name}\``,
|
|
entry._plugin,
|
|
entry._risk ?? "-",
|
|
entry._capabilities
|
|
.map((tag) => tag.replace("capability:", ""))
|
|
.join(", "),
|
|
entry._surfaces.map((tag) => tag.replace("surface:", "")).join(", "),
|
|
truncateForTable(entry.function.description),
|
|
]
|
|
.join(" | ")
|
|
.replace(/^/, "| ")
|
|
.replace(/$/, " |"),
|
|
);
|
|
}
|
|
lines.push("");
|
|
}
|
|
|
|
while (lines.at(-1) === "") {
|
|
lines.pop();
|
|
}
|
|
return `${lines.join("\n")}\n`;
|
|
}
|
|
|
|
function countBy<T>(
|
|
items: T[],
|
|
keyFn: (item: T) => string,
|
|
): Map<string, number> {
|
|
const counts = new Map<string, number>();
|
|
for (const item of items) {
|
|
const key = keyFn(item);
|
|
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
}
|
|
return counts;
|
|
}
|
|
|
|
function renderCountRows(counts: Map<string, number>): string[] {
|
|
return [...counts.entries()]
|
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
.map(([key, count]) => `| ${key} | ${count} |`);
|
|
}
|
|
|
|
function truncateForTable(value: string): string {
|
|
const escaped = value.replace(/\s+/g, " ").replace(/\|/g, "\\|").trim();
|
|
return escaped.length <= 96 ? escaped : `${escaped.slice(0, 95)}...`;
|
|
}
|
|
|
|
function main(): void {
|
|
const options = parseArgs(process.argv.slice(2));
|
|
const sourcePlugins = [
|
|
appContactsPlugin,
|
|
personalAssistantPlugin,
|
|
appPhonePlugin,
|
|
blueBubblesPlugin,
|
|
imessagePlugin,
|
|
todosPlugin,
|
|
];
|
|
const entries = filterEntries(collectEntries(sourcePlugins), options);
|
|
const manifest: Manifest = {
|
|
schemaVersion: 1,
|
|
generator: "scripts/lifeops-bench/export-action-manifest.ts",
|
|
sourcePlugins: sourcePlugins.map((plugin) => plugin.name),
|
|
filters: {
|
|
domains: options.domains,
|
|
capabilities: options.capabilities,
|
|
surfaces: options.surfaces,
|
|
excludeRisks: options.excludeRisks,
|
|
benchUmbrellaAugment: options.benchUmbrellaAugment,
|
|
},
|
|
actions: entries,
|
|
};
|
|
|
|
writeJson(options.out, manifest);
|
|
if (options.benchUmbrellaAugment) {
|
|
augmentWithBenchUmbrellas(options.out);
|
|
}
|
|
formatJsonWithBiome(options.out);
|
|
|
|
const finalManifest = JSON.parse(
|
|
readFileSync(options.out, "utf8"),
|
|
) as Manifest;
|
|
if (options.summaryOut) {
|
|
mkdirSync(dirname(options.summaryOut), { recursive: true });
|
|
writeFileSync(options.summaryOut, renderSummary(finalManifest), "utf8");
|
|
}
|
|
console.log(
|
|
`wrote ${options.out} (${finalManifest.actions.length} actions)` +
|
|
(options.summaryOut ? ` and ${options.summaryOut}` : ""),
|
|
);
|
|
}
|
|
|
|
main();
|