426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
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) Has been cancelled
279 lines
9.4 KiB
TypeScript
279 lines
9.4 KiB
TypeScript
/**
|
||
* Asserts the canonical LifeOps action structure: retired source action names stay
|
||
* unregistered, the owner-operation parent umbrellas register, and each exposes `action` as
|
||
* its public discriminator. Pure plugin-shape asserts, no model.
|
||
*/
|
||
import { ShortcutRegistry } from "@elizaos/core";
|
||
import { describe, expect, it } from "vitest";
|
||
import { isDarwin } from "../src/platform/host.js";
|
||
import {
|
||
APPROVAL_REJECT_SHORTCUT_ID,
|
||
personalAssistantPlugin,
|
||
} from "../src/plugin.js";
|
||
|
||
// OWNER_SCREENTIME is only registered on darwin because the native activity
|
||
// tracker is macOS-only. See `platformGatedActionUmbrellas` in src/plugin.ts
|
||
// and `b56fb4edf6` (graceful Windows fallbacks for darwin-only features).
|
||
const DARWIN_ONLY_PARENTS = new Set(["OWNER_SCREENTIME"]);
|
||
|
||
const RETIRED_REGISTERED_NAMES = [
|
||
"LIFE",
|
||
"PROFILE",
|
||
"RELATIONSHIP",
|
||
"MONEY",
|
||
"PAYMENTS",
|
||
"SUBSCRIPTIONS",
|
||
"CHECKIN",
|
||
"SCHEDULE",
|
||
"BOOK_TRAVEL",
|
||
"SCHEDULING_NEGOTIATION",
|
||
"FIRST_RUN",
|
||
"TOGGLE_FEATURE",
|
||
"DEVICE_INTENT",
|
||
"MESSAGE_HANDOFF",
|
||
"APP_BLOCK",
|
||
"WEBSITE_BLOCK",
|
||
"AUTOFILL",
|
||
"PASSWORD_MANAGER",
|
||
"GOOGLE_CALENDAR",
|
||
"LIFEOPS",
|
||
"LIFEOPS_THREAD_CONTROL",
|
||
"SCHEDULED_TASK",
|
||
// Scheduling sub-handlers converted to plain functions in Task F teardown;
|
||
// dispatched from CALENDAR umbrella (action=propose_times|check_availability|update_preferences).
|
||
"PROPOSE_MEETING_TIMES",
|
||
"CHECK_AVAILABILITY",
|
||
"UPDATE_MEETING_PREFERENCES",
|
||
] as const;
|
||
|
||
const CANONICAL_OWNER_PARENTS = [
|
||
"OWNER_REMINDERS",
|
||
"OWNER_ALARMS",
|
||
"OWNER_GOALS",
|
||
"OWNER_TODOS",
|
||
"OWNER_ROUTINES",
|
||
"OWNER_HEALTH",
|
||
"OWNER_SCREENTIME",
|
||
"OWNER_FINANCES",
|
||
"PERSONAL_ASSISTANT",
|
||
"BLOCK",
|
||
"CREDENTIALS",
|
||
"CALENDAR",
|
||
"CONNECTOR",
|
||
"RESOLVE_REQUEST",
|
||
"VOICE_CALL",
|
||
"SCHEDULED_TASKS",
|
||
"WORK_THREAD",
|
||
] as const;
|
||
|
||
describe("LifeOps canonical action structure", () => {
|
||
it("does not register retired LifeOps source action names", () => {
|
||
const actionNames = new Set(
|
||
(personalAssistantPlugin.actions ?? []).map((a) => a.name),
|
||
);
|
||
for (const retired of RETIRED_REGISTERED_NAMES) {
|
||
expect(actionNames.has(retired), retired).toBe(false);
|
||
}
|
||
});
|
||
|
||
it("registers canonical owner-operation parents", () => {
|
||
const actionNames = new Set(
|
||
(personalAssistantPlugin.actions ?? []).map((a) => a.name),
|
||
);
|
||
const darwin = isDarwin();
|
||
for (const expected of CANONICAL_OWNER_PARENTS) {
|
||
if (!darwin && DARWIN_ONLY_PARENTS.has(expected)) continue;
|
||
expect(actionNames.has(expected), expected).toBe(true);
|
||
}
|
||
});
|
||
|
||
it("uses action as the public discriminator on canonical owner-operation parents", () => {
|
||
const actionNames = new Set(CANONICAL_OWNER_PARENTS);
|
||
const failures = (personalAssistantPlugin.actions ?? [])
|
||
.filter((action) =>
|
||
actionNames.has(
|
||
action.name as (typeof CANONICAL_OWNER_PARENTS)[number],
|
||
),
|
||
)
|
||
.filter((action) => {
|
||
const names = new Set(
|
||
(action.parameters ?? []).map((parameter) => parameter.name),
|
||
);
|
||
return names.has("subaction") && !names.has("action");
|
||
})
|
||
.map((action) => action.name);
|
||
|
||
expect(failures).toEqual([]);
|
||
});
|
||
|
||
it("routes work-thread Stage-1 behavior through threadOps field evaluator only", () => {
|
||
expect(
|
||
(personalAssistantPlugin.responseHandlerFieldEvaluators ?? []).map(
|
||
(evaluator) => evaluator.name,
|
||
),
|
||
).toContain("threadOps");
|
||
expect(
|
||
(personalAssistantPlugin.responseHandlerEvaluators ?? []).map(
|
||
(evaluator) => evaluator.name,
|
||
),
|
||
).not.toContain("lifeops.work_thread_router");
|
||
});
|
||
|
||
it("routes explicit pending-approval rejection phrasing to RESOLVE_REQUEST_REJECT", () => {
|
||
const registry = new ShortcutRegistry();
|
||
registry.registerMany(personalAssistantPlugin.shortcuts ?? []);
|
||
const actionNames = (personalAssistantPlugin.actions ?? []).map(
|
||
(action) => action.name,
|
||
);
|
||
|
||
const match = registry.match(
|
||
"Wait - which Chris? There are two. Don't send it, reject that for now until I confirm the right person.",
|
||
{
|
||
actions: actionNames,
|
||
allowNatural: true,
|
||
isElevated: true,
|
||
},
|
||
);
|
||
|
||
expect(match?.shortcut.id).toBe(APPROVAL_REJECT_SHORTCUT_ID);
|
||
expect(match?.shortcut.target).toEqual({
|
||
kind: "action",
|
||
name: "RESOLVE_REQUEST_REJECT",
|
||
});
|
||
const matchOwner = (text: string) =>
|
||
registry.match(text, {
|
||
actions: actionNames,
|
||
allowNatural: true,
|
||
isElevated: true,
|
||
});
|
||
for (const positive of [
|
||
"Don’t send it.",
|
||
"reject that for now",
|
||
"Decline the request.",
|
||
"deny the pending approval",
|
||
]) {
|
||
expect(matchOwner(positive)?.shortcut.id, positive).toBe(
|
||
APPROVAL_REJECT_SHORTCUT_ID,
|
||
);
|
||
}
|
||
// Rejection verbs aimed at other objects stay with the planner, which can
|
||
// weigh conversation context; a shortcut misfire terminally rejects a
|
||
// queued approval with zero inference.
|
||
for (const negative of [
|
||
"hold that for now",
|
||
"Decline the meeting request from Bob",
|
||
"Reject the draft and write a new one",
|
||
"deny his request for access",
|
||
]) {
|
||
expect(matchOwner(negative), negative).toBeNull();
|
||
}
|
||
expect(
|
||
registry.match("Don't send it, reject that request.", {
|
||
actions: actionNames,
|
||
allowNatural: true,
|
||
isElevated: false,
|
||
}),
|
||
).toBeNull();
|
||
});
|
||
});
|
||
|
||
// Live gemma-4-31b brush-teeth trajectory fences (#9950/#10722): the habit
|
||
// save flow only routes correctly when (1) each owner-life umbrella pins its
|
||
// backing kind (a planner-supplied kind:"goal" silently rerouted a habit into
|
||
// the goals store), (2) the umbrellas expose the `confirmed` preview->confirm
|
||
// handshake, and (3) SCHEDULED_TASKS de-claims new-habit creation so a
|
||
// habit-shaped ask reaches OWNER_ROUTINES/OWNER_REMINDERS instead of the raw
|
||
// scheduler surface. Evidence: test-results/evidence/
|
||
// 9950-gemma4-31b-live-trajectories/brush-teeth-basic-after-fix*.report.json.
|
||
describe("brush-teeth habit-save routing contract (#9950/#10722)", () => {
|
||
const findAction = (name: string) =>
|
||
(personalAssistantPlugin.actions ?? []).find(
|
||
(action) => action.name === name,
|
||
);
|
||
|
||
const PINNED_KINDS: Record<string, string> = {
|
||
OWNER_REMINDERS: "definition",
|
||
OWNER_ALARMS: "definition",
|
||
OWNER_TODOS: "definition",
|
||
OWNER_ROUTINES: "definition",
|
||
OWNER_GOALS: "goal",
|
||
};
|
||
|
||
it("pins each owner-life umbrella's kind parameter to its backing store", () => {
|
||
for (const [name, kind] of Object.entries(PINNED_KINDS)) {
|
||
const action = findAction(name);
|
||
expect(action, name).toBeDefined();
|
||
const kindParameter = (action?.parameters ?? []).find(
|
||
(parameter) => parameter.name === "kind",
|
||
);
|
||
expect(kindParameter, `${name} kind parameter`).toBeDefined();
|
||
const schema = kindParameter?.schema as {
|
||
enum?: string[];
|
||
default?: string;
|
||
};
|
||
expect(schema?.enum, `${name} kind enum`).toEqual([kind]);
|
||
expect(schema?.default, `${name} kind default`).toBe(kind);
|
||
}
|
||
});
|
||
|
||
it("exposes the create-only confirmed handshake on every owner-life umbrella", () => {
|
||
for (const name of Object.keys(PINNED_KINDS)) {
|
||
const action = findAction(name);
|
||
const confirmedParameter = (action?.parameters ?? []).find(
|
||
(parameter) => parameter.name === "confirmed",
|
||
);
|
||
expect(confirmedParameter, `${name} confirmed parameter`).toBeDefined();
|
||
expect(
|
||
(confirmedParameter?.schema as { type?: string })?.type,
|
||
`${name} confirmed type`,
|
||
).toBe("boolean");
|
||
}
|
||
});
|
||
|
||
it("claims habit phrasing on OWNER_ROUTINES and de-claims it on SCHEDULED_TASKS(+_CREATE)", () => {
|
||
const routines = findAction("OWNER_ROUTINES");
|
||
expect(routines?.description).toContain("habit");
|
||
expect(routines?.similes).toContain("CREATE_HABIT");
|
||
expect(routines?.similes).toContain("RECURRING_TASK");
|
||
|
||
const scheduledTasks = findAction("SCHEDULED_TASKS");
|
||
expect(scheduledTasks?.description).toContain("OWNER_ROUTINES");
|
||
expect(scheduledTasks?.routingHint).toContain("OWNER_ROUTINES");
|
||
|
||
const scheduledTasksCreate = findAction("SCHEDULED_TASKS_CREATE");
|
||
expect(
|
||
scheduledTasksCreate,
|
||
"SCHEDULED_TASKS_CREATE virtual",
|
||
).toBeDefined();
|
||
expect(scheduledTasksCreate?.description).toContain(
|
||
"OWNER_ROUTINES_CREATE",
|
||
);
|
||
});
|
||
|
||
it("claims one-off deadline reminders on OWNER_REMINDERS and de-claims them on SCHEDULED_TASKS", () => {
|
||
const reminders = findAction("OWNER_REMINDERS");
|
||
expect(reminders?.description).toContain("deadline");
|
||
expect(reminders?.description).toContain("by the 20th");
|
||
expect(reminders?.descriptionCompressed).toContain("deadlines");
|
||
|
||
const scheduledTasks = findAction("SCHEDULED_TASKS");
|
||
expect(scheduledTasks?.description).toContain(
|
||
"NOT the flow for saving a new owner reminder",
|
||
);
|
||
expect(scheduledTasks?.description).toContain("by the 20th");
|
||
expect(scheduledTasks?.routingHint).toContain(
|
||
"NEW owner reminders/deadlines",
|
||
);
|
||
expect(scheduledTasks?.routingHint).toContain(
|
||
"OWNER_REMINDERS action=create",
|
||
);
|
||
});
|
||
|
||
it("declares the productivity context on owner-life umbrellas (Stage-1 boost parity with SCHEDULED_TASKS)", () => {
|
||
for (const name of Object.keys(PINNED_KINDS)) {
|
||
const action = findAction(name);
|
||
expect(action?.contexts, `${name} contexts`).toContain("productivity");
|
||
}
|
||
});
|
||
});
|