Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

379 lines
12 KiB
TypeScript

/** Unit tests for the WORKFLOW action's op dispatch against a mocked WorkflowService (deterministic). */
import { describe, expect, mock, test } from 'bun:test';
import type { HandlerCallback, HandlerOptions, IAgentRuntime, Memory } from '@elizaos/core';
import { workflowAction } from '../../src/actions/workflow';
import { WORKFLOW_SERVICE_TYPE, type WorkflowService } from '../../src/services/workflow-service';
function makeRuntime(service: Partial<WorkflowService>): IAgentRuntime {
return {
agentId: 'agent-test',
getService: (type: string) => (type === WORKFLOW_SERVICE_TYPE ? service : null),
} as IAgentRuntime;
}
const message = {
entityId: 'user-test',
} as Memory;
async function runAction(
service: Partial<WorkflowService>,
parameters: Record<string, unknown>,
callback?: HandlerCallback
) {
if (!workflowAction.handler) throw new Error('workflow action missing handler');
return workflowAction.handler(
makeRuntime(service),
message,
undefined,
{ parameters } as HandlerOptions,
callback
);
}
describe('workflowAction chat operations', () => {
test('lists workflows for chat review and selection', async () => {
const listWorkflows = mock(() =>
Promise.resolve([
{
id: 'wf-1',
versionId: 'v-1',
name: 'Daily summary',
active: true,
nodes: [{ id: 'n1', name: 'Manual Trigger', type: 'manual', parameters: {} }],
connections: {},
createdAt: '2026-06-20T12:00:00.000Z',
updatedAt: '2026-06-20T12:00:00.000Z',
},
])
);
const result = await runAction({ listWorkflows } as Partial<WorkflowService>, {
action: 'list',
limit: 5,
});
expect(listWorkflows).toHaveBeenCalledWith('user-test');
expect(result.success).toBe(true);
expect(result.values).toEqual({ count: 1 });
expect(result.data).toEqual({
workflows: [{ id: 'wf-1', name: 'Daily summary', active: true, nodeCount: 1 }],
total: 1,
});
});
test('gets a workflow definition for chat review', async () => {
const getWorkflow = mock(() =>
Promise.resolve({
id: 'wf-1',
versionId: 'v-1',
name: 'Daily summary',
active: true,
nodes: [
{ id: 'trigger', name: 'Manual Trigger', type: 'manual', parameters: {} },
{ id: 'set', name: 'Set Summary', type: 'set', parameters: {} },
],
connections: {},
createdAt: '2026-06-20T12:00:00.000Z',
updatedAt: '2026-06-20T12:00:00.000Z',
})
);
const result = await runAction({ getWorkflow } as Partial<WorkflowService>, {
action: 'get',
workflowId: 'wf-1',
});
expect(getWorkflow).toHaveBeenCalledWith('wf-1');
expect(result.success).toBe(true);
expect(result.values).toEqual({
workflowId: 'wf-1',
workflowName: 'Daily summary',
active: true,
nodeCount: 2,
});
expect(result.data).toEqual({
workflow: expect.objectContaining({ id: 'wf-1', name: 'Daily summary' }),
});
});
test('runs a workflow immediately and returns execution details', async () => {
const runWorkflow = mock(() =>
Promise.resolve({
id: 'exec-1',
workflowId: 'wf-1',
mode: 'manual',
startedAt: '2026-06-20T12:00:00.000Z',
stoppedAt: '2026-06-20T12:00:01.000Z',
finished: true,
status: 'success',
})
);
const callback = mock(() => Promise.resolve());
const result = await runAction(
{ runWorkflow } as Partial<WorkflowService>,
{ action: 'run', workflowId: 'wf-1' },
callback as HandlerCallback
);
expect(runWorkflow).toHaveBeenCalledWith('wf-1', { throwOnError: false });
expect(result.success).toBe(true);
expect(result.values).toEqual({
workflowId: 'wf-1',
executionId: 'exec-1',
status: 'success',
});
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
metadata: expect.objectContaining({ workflowId: 'wf-1', executionId: 'exec-1' }),
})
);
});
test('lists revisions so chat can offer rollback choices', async () => {
const listWorkflowRevisions = mock(() =>
Promise.resolve([
{
id: 'rev-1',
workflowId: 'wf-1',
versionId: 'v-1',
name: 'Previous workflow',
active: true,
workflow: { name: 'Previous workflow', nodes: [], connections: {} },
createdAt: '2026-06-20T12:00:00.000Z',
updatedAt: '2026-06-20T12:00:00.000Z',
capturedAt: '2026-06-20T12:01:00.000Z',
operation: 'update' as const,
},
])
);
const result = await runAction({ listWorkflowRevisions } as Partial<WorkflowService>, {
action: 'revisions',
workflowId: 'wf-1',
limit: 5,
});
expect(listWorkflowRevisions).toHaveBeenCalledWith('wf-1', 5);
expect(result.success).toBe(true);
expect(result.values).toEqual({ workflowId: 'wf-1', count: 1 });
expect(result.data).toEqual({
revisions: expect.arrayContaining([expect.objectContaining({ versionId: 'v-1' })]),
});
});
test('restores a selected workflow revision', async () => {
const restoreWorkflowRevision = mock(() =>
Promise.resolve({
id: 'wf-1',
versionId: 'v-restored',
name: 'Restored workflow',
active: true,
nodes: [],
connections: {},
createdAt: '2026-06-20T12:00:00.000Z',
updatedAt: '2026-06-20T12:02:00.000Z',
})
);
const result = await runAction({ restoreWorkflowRevision } as Partial<WorkflowService>, {
action: 'restore',
workflowId: 'wf-1',
versionId: 'v-old',
});
expect(restoreWorkflowRevision).toHaveBeenCalledWith('wf-1', 'v-old');
expect(result.success).toBe(true);
expect(result.values).toEqual({
workflowId: 'wf-1',
workflowName: 'Restored workflow',
versionId: 'v-old',
});
expect(result.data).toEqual({
workflow: {
id: 'wf-1',
name: 'Restored workflow',
active: true,
nodeCount: 0,
},
});
});
test('diagnoses the latest failed execution for chat troubleshooting', async () => {
const listExecutions = mock(() =>
Promise.resolve({
data: [
{
id: 'exec-failed',
workflowId: 'wf-1',
mode: 'manual' as const,
startedAt: '2026-06-20T12:00:00.000Z',
stoppedAt: '2026-06-20T12:00:01.000Z',
finished: true,
status: 'error' as const,
data: {
resultData: {
lastNodeExecuted: 'Send Slack',
engine: {
provider: 'smithers' as const,
nodes: 3,
levels: 2,
maxConcurrency: 2,
started: 3,
finished: 2,
failed: 1,
skipped: 0,
retries: 1,
},
error: { message: 'Missing Slack credential' },
runData: {
'Send Slack': [
{
executionTime: 12,
error: { message: 'Missing Slack credential' },
data: { main: [] },
},
],
},
},
},
},
],
})
);
const callback = mock(() => Promise.resolve());
const result = await runAction(
{ listExecutions } as Partial<WorkflowService>,
{ action: 'diagnose', workflowId: 'wf-1' },
callback as HandlerCallback
);
expect(listExecutions).toHaveBeenCalledWith({ workflowId: 'wf-1', limit: 10 });
expect(result.success).toBe(true);
expect(result.values).toEqual({
workflowId: 'wf-1',
executionId: 'exec-failed',
status: 'error',
error: 'Missing Slack credential',
});
expect(result.text).toContain('Missing Slack credential');
expect(result.data).toEqual({
execution: expect.objectContaining({ id: 'exec-failed' }),
summary: expect.objectContaining({ statusLabel: 'Failed' }),
diagnostics: expect.stringContaining('Engine: 3 nodes / 2 levels / 2 max parallel'),
});
expect(String((result.data as { diagnostics: string }).diagnostics)).toContain(
'Send Slack: error; 0 items; 12 ms; error=Missing Slack credential'
);
expect(callback).toHaveBeenCalledWith(
expect.objectContaining({
metadata: expect.objectContaining({
workflowId: 'wf-1',
executionId: 'exec-failed',
status: 'error',
}),
})
);
});
test('diagnoses an execution directly by id', async () => {
const getExecutionDetail = mock(() =>
Promise.resolve({
id: 'exec-1',
workflowId: 'wf-1',
mode: 'manual' as const,
startedAt: '2026-06-20T12:00:00.000Z',
stoppedAt: '2026-06-20T12:00:01.000Z',
finished: true,
status: 'success' as const,
data: { resultData: { runData: {} } },
})
);
const result = await runAction({ getExecutionDetail } as Partial<WorkflowService>, {
action: 'diagnose',
executionId: 'exec-1',
});
expect(getExecutionDetail).toHaveBeenCalledWith('exec-1');
expect(result.success).toBe(true);
expect(result.values).toEqual({
workflowId: 'wf-1',
executionId: 'exec-1',
status: 'success',
});
expect(result.data).toEqual({
execution: expect.objectContaining({ id: 'exec-1' }),
summary: expect.objectContaining({ statusLabel: 'Succeeded' }),
diagnostics: expect.stringContaining('Nodes: none recorded'),
});
});
test('generates evaluation samples from workflow executions for chat optimization', async () => {
const getWorkflowEvaluationSuite = mock(() =>
Promise.resolve({
workflowId: 'wf-1',
workflowName: 'Daily summary',
workflowVersionId: 'v-1',
generatedAt: '2026-06-20T12:00:00.000Z',
sampleCount: 1,
samples: [
{
id: 'wf-1:exec-1',
workflowId: 'wf-1',
workflowName: 'Daily summary',
workflowVersionId: 'v-1',
executionId: 'exec-1',
createdAt: '2026-06-20T12:00:00.000Z',
input: { mode: 'manual' as const },
expected: { status: 'success' as const, passed: true, nodes: [] },
score: { pass: true, value: 1, reason: 'Execution completed successfully.' },
tags: ['smithers'],
},
],
jsonl: '{"id":"wf-1:exec-1"}',
optimizer: {
engine: 'smithers-gepa' as const,
target: 'workflow-generation' as const,
suiteName: 'daily-summary',
caseFile: 'evals/daily-summary.jsonl',
recommendedCommand:
'bunx smithers-orchestrator eval <workflow.tsx> --cases evals/daily-summary.jsonl --suite daily-summary',
recommendedEvalCommand:
'bunx smithers-orchestrator eval <workflow.tsx> --cases evals/daily-summary.jsonl --suite daily-summary',
recommendedOptimizeCommand: 'bunx smithers-orchestrator optimize',
recommendedObservabilityCommand: 'bunx smithers-orchestrator observability --detach',
recommendedMetricsCommand:
'bunx smithers-orchestrator up <workflow.tsx> --serve --metrics',
notes: [],
},
})
);
const result = await runAction({ getWorkflowEvaluationSuite } as Partial<WorkflowService>, {
action: 'eval_samples',
workflowId: 'wf-1',
limit: 5,
});
expect(getWorkflowEvaluationSuite).toHaveBeenCalledWith('wf-1', 5);
expect(result.success).toBe(true);
expect(result.values).toEqual({
workflowId: 'wf-1',
count: 1,
caseFile: 'evals/daily-summary.jsonl',
suiteName: 'daily-summary',
});
expect(result.text).toContain('Save cases to evals/daily-summary.jsonl.');
expect(result.text).toContain('Optimize: bunx smithers-orchestrator optimize');
expect(result.data).toEqual({
suite: expect.objectContaining({
workflowId: 'wf-1',
sampleCount: 1,
jsonl: '{"id":"wf-1:exec-1"}',
}),
});
});
});