16 agent tools, event handler for auto-linking, sync job every 15m. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
700 lines
30 KiB
JavaScript
700 lines
30 KiB
JavaScript
import { randomUUID } from "node:crypto";
|
|
function normalizeScope(input) {
|
|
return {
|
|
scopeKind: input.scopeKind,
|
|
scopeId: input.scopeId,
|
|
namespace: input.namespace ?? "default",
|
|
stateKey: input.stateKey,
|
|
};
|
|
}
|
|
function stateMapKey(input) {
|
|
const normalized = normalizeScope(input);
|
|
return `${normalized.scopeKind}|${normalized.scopeId ?? ""}|${normalized.namespace}|${normalized.stateKey}`;
|
|
}
|
|
function allowsEvent(filter, event) {
|
|
if (!filter)
|
|
return true;
|
|
if (filter.companyId && filter.companyId !== String(event.payload?.companyId ?? ""))
|
|
return false;
|
|
if (filter.projectId && filter.projectId !== String(event.payload?.projectId ?? ""))
|
|
return false;
|
|
if (filter.agentId && filter.agentId !== String(event.payload?.agentId ?? ""))
|
|
return false;
|
|
return true;
|
|
}
|
|
function requireCapability(manifest, allowed, capability) {
|
|
if (allowed.has(capability))
|
|
return;
|
|
throw new Error(`Plugin '${manifest.id}' is missing required capability '${capability}' in test harness`);
|
|
}
|
|
function requireCompanyId(companyId) {
|
|
if (!companyId)
|
|
throw new Error("companyId is required for this operation");
|
|
return companyId;
|
|
}
|
|
function isInCompany(record, companyId) {
|
|
return Boolean(record && record.companyId === companyId);
|
|
}
|
|
/**
|
|
* Create an in-memory host harness for plugin worker tests.
|
|
*
|
|
* The harness enforces declared capabilities and simulates host APIs, so tests
|
|
* can validate plugin behavior without spinning up the Paperclip server runtime.
|
|
*/
|
|
export function createTestHarness(options) {
|
|
const manifest = options.manifest;
|
|
const capabilitySet = new Set(options.capabilities ?? manifest.capabilities);
|
|
let currentConfig = { ...(options.config ?? {}) };
|
|
const logs = [];
|
|
const activity = [];
|
|
const metrics = [];
|
|
const state = new Map();
|
|
const entities = new Map();
|
|
const entityExternalIndex = new Map();
|
|
const companies = new Map();
|
|
const projects = new Map();
|
|
const issues = new Map();
|
|
const issueComments = new Map();
|
|
const agents = new Map();
|
|
const goals = new Map();
|
|
const projectWorkspaces = new Map();
|
|
const sessions = new Map();
|
|
const sessionEventCallbacks = new Map();
|
|
const events = [];
|
|
const jobs = new Map();
|
|
const launchers = new Map();
|
|
const dataHandlers = new Map();
|
|
const actionHandlers = new Map();
|
|
const toolHandlers = new Map();
|
|
const ctx = {
|
|
manifest,
|
|
config: {
|
|
async get() {
|
|
return { ...currentConfig };
|
|
},
|
|
},
|
|
events: {
|
|
on(name, filterOrFn, maybeFn) {
|
|
requireCapability(manifest, capabilitySet, "events.subscribe");
|
|
let registration;
|
|
if (typeof filterOrFn === "function") {
|
|
registration = { name, fn: filterOrFn };
|
|
}
|
|
else {
|
|
if (!maybeFn)
|
|
throw new Error("event handler is required");
|
|
registration = { name, filter: filterOrFn, fn: maybeFn };
|
|
}
|
|
events.push(registration);
|
|
return () => {
|
|
const idx = events.indexOf(registration);
|
|
if (idx !== -1)
|
|
events.splice(idx, 1);
|
|
};
|
|
},
|
|
async emit(name, companyId, payload) {
|
|
requireCapability(manifest, capabilitySet, "events.emit");
|
|
await harness.emit(`plugin.${manifest.id}.${name}`, payload, { companyId });
|
|
},
|
|
},
|
|
jobs: {
|
|
register(key, fn) {
|
|
requireCapability(manifest, capabilitySet, "jobs.schedule");
|
|
jobs.set(key, fn);
|
|
},
|
|
},
|
|
launchers: {
|
|
register(launcher) {
|
|
launchers.set(launcher.id, launcher);
|
|
},
|
|
},
|
|
http: {
|
|
async fetch(url, init) {
|
|
requireCapability(manifest, capabilitySet, "http.outbound");
|
|
return fetch(url, init);
|
|
},
|
|
},
|
|
secrets: {
|
|
async resolve(secretRef) {
|
|
requireCapability(manifest, capabilitySet, "secrets.read-ref");
|
|
return `resolved:${secretRef}`;
|
|
},
|
|
},
|
|
activity: {
|
|
async log(entry) {
|
|
requireCapability(manifest, capabilitySet, "activity.log.write");
|
|
activity.push(entry);
|
|
},
|
|
},
|
|
state: {
|
|
async get(input) {
|
|
requireCapability(manifest, capabilitySet, "plugin.state.read");
|
|
return state.has(stateMapKey(input)) ? state.get(stateMapKey(input)) : null;
|
|
},
|
|
async set(input, value) {
|
|
requireCapability(manifest, capabilitySet, "plugin.state.write");
|
|
state.set(stateMapKey(input), value);
|
|
},
|
|
async delete(input) {
|
|
requireCapability(manifest, capabilitySet, "plugin.state.write");
|
|
state.delete(stateMapKey(input));
|
|
},
|
|
},
|
|
entities: {
|
|
async upsert(input) {
|
|
const externalKey = input.externalId
|
|
? `${input.entityType}|${input.scopeKind}|${input.scopeId ?? ""}|${input.externalId}`
|
|
: null;
|
|
const existingId = externalKey ? entityExternalIndex.get(externalKey) : undefined;
|
|
const existing = existingId ? entities.get(existingId) : undefined;
|
|
const now = new Date().toISOString();
|
|
const previousExternalKey = existing?.externalId
|
|
? `${existing.entityType}|${existing.scopeKind}|${existing.scopeId ?? ""}|${existing.externalId}`
|
|
: null;
|
|
const record = existing
|
|
? {
|
|
...existing,
|
|
entityType: input.entityType,
|
|
scopeKind: input.scopeKind,
|
|
scopeId: input.scopeId ?? null,
|
|
externalId: input.externalId ?? null,
|
|
title: input.title ?? null,
|
|
status: input.status ?? null,
|
|
data: input.data,
|
|
updatedAt: now,
|
|
}
|
|
: {
|
|
id: randomUUID(),
|
|
entityType: input.entityType,
|
|
scopeKind: input.scopeKind,
|
|
scopeId: input.scopeId ?? null,
|
|
externalId: input.externalId ?? null,
|
|
title: input.title ?? null,
|
|
status: input.status ?? null,
|
|
data: input.data,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
entities.set(record.id, record);
|
|
if (previousExternalKey && previousExternalKey !== externalKey) {
|
|
entityExternalIndex.delete(previousExternalKey);
|
|
}
|
|
if (externalKey)
|
|
entityExternalIndex.set(externalKey, record.id);
|
|
return record;
|
|
},
|
|
async list(query) {
|
|
let out = [...entities.values()];
|
|
if (query.entityType)
|
|
out = out.filter((r) => r.entityType === query.entityType);
|
|
if (query.scopeKind)
|
|
out = out.filter((r) => r.scopeKind === query.scopeKind);
|
|
if (query.scopeId)
|
|
out = out.filter((r) => r.scopeId === query.scopeId);
|
|
if (query.externalId)
|
|
out = out.filter((r) => r.externalId === query.externalId);
|
|
if (query.offset)
|
|
out = out.slice(query.offset);
|
|
if (query.limit)
|
|
out = out.slice(0, query.limit);
|
|
return out;
|
|
},
|
|
},
|
|
projects: {
|
|
async list(input) {
|
|
requireCapability(manifest, capabilitySet, "projects.read");
|
|
const companyId = requireCompanyId(input?.companyId);
|
|
let out = [...projects.values()];
|
|
out = out.filter((project) => project.companyId === companyId);
|
|
if (input?.offset)
|
|
out = out.slice(input.offset);
|
|
if (input?.limit)
|
|
out = out.slice(0, input.limit);
|
|
return out;
|
|
},
|
|
async get(projectId, companyId) {
|
|
requireCapability(manifest, capabilitySet, "projects.read");
|
|
const project = projects.get(projectId);
|
|
return isInCompany(project, companyId) ? project : null;
|
|
},
|
|
async listWorkspaces(projectId, companyId) {
|
|
requireCapability(manifest, capabilitySet, "project.workspaces.read");
|
|
if (!isInCompany(projects.get(projectId), companyId))
|
|
return [];
|
|
return projectWorkspaces.get(projectId) ?? [];
|
|
},
|
|
async getPrimaryWorkspace(projectId, companyId) {
|
|
requireCapability(manifest, capabilitySet, "project.workspaces.read");
|
|
if (!isInCompany(projects.get(projectId), companyId))
|
|
return null;
|
|
const workspaces = projectWorkspaces.get(projectId) ?? [];
|
|
return workspaces.find((workspace) => workspace.isPrimary) ?? null;
|
|
},
|
|
async getWorkspaceForIssue(issueId, companyId) {
|
|
requireCapability(manifest, capabilitySet, "project.workspaces.read");
|
|
const issue = issues.get(issueId);
|
|
if (!isInCompany(issue, companyId))
|
|
return null;
|
|
const projectId = issue?.projectId;
|
|
if (!projectId)
|
|
return null;
|
|
if (!isInCompany(projects.get(projectId), companyId))
|
|
return null;
|
|
const workspaces = projectWorkspaces.get(projectId) ?? [];
|
|
return workspaces.find((workspace) => workspace.isPrimary) ?? null;
|
|
},
|
|
},
|
|
companies: {
|
|
async list(input) {
|
|
requireCapability(manifest, capabilitySet, "companies.read");
|
|
let out = [...companies.values()];
|
|
if (input?.offset)
|
|
out = out.slice(input.offset);
|
|
if (input?.limit)
|
|
out = out.slice(0, input.limit);
|
|
return out;
|
|
},
|
|
async get(companyId) {
|
|
requireCapability(manifest, capabilitySet, "companies.read");
|
|
return companies.get(companyId) ?? null;
|
|
},
|
|
},
|
|
issues: {
|
|
async list(input) {
|
|
requireCapability(manifest, capabilitySet, "issues.read");
|
|
const companyId = requireCompanyId(input?.companyId);
|
|
let out = [...issues.values()];
|
|
out = out.filter((issue) => issue.companyId === companyId);
|
|
if (input?.projectId)
|
|
out = out.filter((issue) => issue.projectId === input.projectId);
|
|
if (input?.assigneeAgentId)
|
|
out = out.filter((issue) => issue.assigneeAgentId === input.assigneeAgentId);
|
|
if (input?.status)
|
|
out = out.filter((issue) => issue.status === input.status);
|
|
if (input?.offset)
|
|
out = out.slice(input.offset);
|
|
if (input?.limit)
|
|
out = out.slice(0, input.limit);
|
|
return out;
|
|
},
|
|
async get(issueId, companyId) {
|
|
requireCapability(manifest, capabilitySet, "issues.read");
|
|
const issue = issues.get(issueId);
|
|
return isInCompany(issue, companyId) ? issue : null;
|
|
},
|
|
async create(input) {
|
|
requireCapability(manifest, capabilitySet, "issues.create");
|
|
const now = new Date();
|
|
const record = {
|
|
id: randomUUID(),
|
|
companyId: input.companyId,
|
|
projectId: input.projectId ?? null,
|
|
projectWorkspaceId: null,
|
|
goalId: input.goalId ?? null,
|
|
parentId: input.parentId ?? null,
|
|
title: input.title,
|
|
description: input.description ?? null,
|
|
status: "todo",
|
|
priority: input.priority ?? "medium",
|
|
assigneeAgentId: input.assigneeAgentId ?? null,
|
|
assigneeUserId: null,
|
|
checkoutRunId: null,
|
|
executionRunId: null,
|
|
executionAgentNameKey: null,
|
|
executionLockedAt: null,
|
|
createdByAgentId: null,
|
|
createdByUserId: null,
|
|
issueNumber: null,
|
|
identifier: null,
|
|
requestDepth: 0,
|
|
billingCode: null,
|
|
assigneeAdapterOverrides: null,
|
|
executionWorkspaceId: null,
|
|
executionWorkspacePreference: null,
|
|
executionWorkspaceSettings: null,
|
|
startedAt: null,
|
|
completedAt: null,
|
|
cancelledAt: null,
|
|
hiddenAt: null,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
issues.set(record.id, record);
|
|
return record;
|
|
},
|
|
async update(issueId, patch, companyId) {
|
|
requireCapability(manifest, capabilitySet, "issues.update");
|
|
const record = issues.get(issueId);
|
|
if (!isInCompany(record, companyId))
|
|
throw new Error(`Issue not found: ${issueId}`);
|
|
const updated = {
|
|
...record,
|
|
...patch,
|
|
updatedAt: new Date(),
|
|
};
|
|
issues.set(issueId, updated);
|
|
return updated;
|
|
},
|
|
async listComments(issueId, companyId) {
|
|
requireCapability(manifest, capabilitySet, "issue.comments.read");
|
|
if (!isInCompany(issues.get(issueId), companyId))
|
|
return [];
|
|
return issueComments.get(issueId) ?? [];
|
|
},
|
|
async createComment(issueId, body, companyId) {
|
|
requireCapability(manifest, capabilitySet, "issue.comments.create");
|
|
const parentIssue = issues.get(issueId);
|
|
if (!isInCompany(parentIssue, companyId)) {
|
|
throw new Error(`Issue not found: ${issueId}`);
|
|
}
|
|
const now = new Date();
|
|
const comment = {
|
|
id: randomUUID(),
|
|
companyId: parentIssue.companyId,
|
|
issueId,
|
|
authorAgentId: null,
|
|
authorUserId: null,
|
|
body,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
const current = issueComments.get(issueId) ?? [];
|
|
current.push(comment);
|
|
issueComments.set(issueId, current);
|
|
return comment;
|
|
},
|
|
documents: {
|
|
async list(issueId, companyId) {
|
|
requireCapability(manifest, capabilitySet, "issue.documents.read");
|
|
if (!isInCompany(issues.get(issueId), companyId))
|
|
return [];
|
|
return [];
|
|
},
|
|
async get(issueId, _key, companyId) {
|
|
requireCapability(manifest, capabilitySet, "issue.documents.read");
|
|
if (!isInCompany(issues.get(issueId), companyId))
|
|
return null;
|
|
return null;
|
|
},
|
|
async upsert(input) {
|
|
requireCapability(manifest, capabilitySet, "issue.documents.write");
|
|
const parentIssue = issues.get(input.issueId);
|
|
if (!isInCompany(parentIssue, input.companyId)) {
|
|
throw new Error(`Issue not found: ${input.issueId}`);
|
|
}
|
|
throw new Error("documents.upsert is not implemented in test context");
|
|
},
|
|
async delete(issueId, _key, companyId) {
|
|
requireCapability(manifest, capabilitySet, "issue.documents.write");
|
|
const parentIssue = issues.get(issueId);
|
|
if (!isInCompany(parentIssue, companyId)) {
|
|
throw new Error(`Issue not found: ${issueId}`);
|
|
}
|
|
},
|
|
},
|
|
},
|
|
agents: {
|
|
async list(input) {
|
|
requireCapability(manifest, capabilitySet, "agents.read");
|
|
const companyId = requireCompanyId(input?.companyId);
|
|
let out = [...agents.values()];
|
|
out = out.filter((agent) => agent.companyId === companyId);
|
|
if (input?.status)
|
|
out = out.filter((agent) => agent.status === input.status);
|
|
if (input?.offset)
|
|
out = out.slice(input.offset);
|
|
if (input?.limit)
|
|
out = out.slice(0, input.limit);
|
|
return out;
|
|
},
|
|
async get(agentId, companyId) {
|
|
requireCapability(manifest, capabilitySet, "agents.read");
|
|
const agent = agents.get(agentId);
|
|
return isInCompany(agent, companyId) ? agent : null;
|
|
},
|
|
async pause(agentId, companyId) {
|
|
requireCapability(manifest, capabilitySet, "agents.pause");
|
|
const cid = requireCompanyId(companyId);
|
|
const agent = agents.get(agentId);
|
|
if (!isInCompany(agent, cid))
|
|
throw new Error(`Agent not found: ${agentId}`);
|
|
if (agent.status === "terminated")
|
|
throw new Error("Cannot pause terminated agent");
|
|
const updated = { ...agent, status: "paused", updatedAt: new Date() };
|
|
agents.set(agentId, updated);
|
|
return updated;
|
|
},
|
|
async resume(agentId, companyId) {
|
|
requireCapability(manifest, capabilitySet, "agents.resume");
|
|
const cid = requireCompanyId(companyId);
|
|
const agent = agents.get(agentId);
|
|
if (!isInCompany(agent, cid))
|
|
throw new Error(`Agent not found: ${agentId}`);
|
|
if (agent.status === "terminated")
|
|
throw new Error("Cannot resume terminated agent");
|
|
if (agent.status === "pending_approval")
|
|
throw new Error("Pending approval agents cannot be resumed");
|
|
const updated = { ...agent, status: "idle", updatedAt: new Date() };
|
|
agents.set(agentId, updated);
|
|
return updated;
|
|
},
|
|
async invoke(agentId, companyId, opts) {
|
|
requireCapability(manifest, capabilitySet, "agents.invoke");
|
|
const cid = requireCompanyId(companyId);
|
|
const agent = agents.get(agentId);
|
|
if (!isInCompany(agent, cid))
|
|
throw new Error(`Agent not found: ${agentId}`);
|
|
if (agent.status === "paused" ||
|
|
agent.status === "terminated" ||
|
|
agent.status === "pending_approval") {
|
|
throw new Error(`Agent is not invokable in its current state: ${agent.status}`);
|
|
}
|
|
return { runId: randomUUID() };
|
|
},
|
|
sessions: {
|
|
async create(agentId, companyId, opts) {
|
|
requireCapability(manifest, capabilitySet, "agent.sessions.create");
|
|
const cid = requireCompanyId(companyId);
|
|
const agent = agents.get(agentId);
|
|
if (!isInCompany(agent, cid))
|
|
throw new Error(`Agent not found: ${agentId}`);
|
|
const session = {
|
|
sessionId: randomUUID(),
|
|
agentId,
|
|
companyId: cid,
|
|
status: "active",
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
sessions.set(session.sessionId, session);
|
|
return session;
|
|
},
|
|
async list(agentId, companyId) {
|
|
requireCapability(manifest, capabilitySet, "agent.sessions.list");
|
|
const cid = requireCompanyId(companyId);
|
|
return [...sessions.values()].filter((s) => s.agentId === agentId && s.companyId === cid && s.status === "active");
|
|
},
|
|
async sendMessage(sessionId, companyId, opts) {
|
|
requireCapability(manifest, capabilitySet, "agent.sessions.send");
|
|
const session = sessions.get(sessionId);
|
|
if (!session || session.status !== "active")
|
|
throw new Error(`Session not found or closed: ${sessionId}`);
|
|
if (session.companyId !== companyId)
|
|
throw new Error(`Session not found: ${sessionId}`);
|
|
if (opts.onEvent) {
|
|
sessionEventCallbacks.set(sessionId, opts.onEvent);
|
|
}
|
|
return { runId: randomUUID() };
|
|
},
|
|
async close(sessionId, companyId) {
|
|
requireCapability(manifest, capabilitySet, "agent.sessions.close");
|
|
const session = sessions.get(sessionId);
|
|
if (!session)
|
|
throw new Error(`Session not found: ${sessionId}`);
|
|
if (session.companyId !== companyId)
|
|
throw new Error(`Session not found: ${sessionId}`);
|
|
session.status = "closed";
|
|
sessionEventCallbacks.delete(sessionId);
|
|
},
|
|
},
|
|
},
|
|
goals: {
|
|
async list(input) {
|
|
requireCapability(manifest, capabilitySet, "goals.read");
|
|
const companyId = requireCompanyId(input?.companyId);
|
|
let out = [...goals.values()];
|
|
out = out.filter((goal) => goal.companyId === companyId);
|
|
if (input?.level)
|
|
out = out.filter((goal) => goal.level === input.level);
|
|
if (input?.status)
|
|
out = out.filter((goal) => goal.status === input.status);
|
|
if (input?.offset)
|
|
out = out.slice(input.offset);
|
|
if (input?.limit)
|
|
out = out.slice(0, input.limit);
|
|
return out;
|
|
},
|
|
async get(goalId, companyId) {
|
|
requireCapability(manifest, capabilitySet, "goals.read");
|
|
const goal = goals.get(goalId);
|
|
return isInCompany(goal, companyId) ? goal : null;
|
|
},
|
|
async create(input) {
|
|
requireCapability(manifest, capabilitySet, "goals.create");
|
|
const now = new Date();
|
|
const record = {
|
|
id: randomUUID(),
|
|
companyId: input.companyId,
|
|
title: input.title,
|
|
description: input.description ?? null,
|
|
level: input.level ?? "task",
|
|
status: input.status ?? "planned",
|
|
parentId: input.parentId ?? null,
|
|
ownerAgentId: input.ownerAgentId ?? null,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
goals.set(record.id, record);
|
|
return record;
|
|
},
|
|
async update(goalId, patch, companyId) {
|
|
requireCapability(manifest, capabilitySet, "goals.update");
|
|
const record = goals.get(goalId);
|
|
if (!isInCompany(record, companyId))
|
|
throw new Error(`Goal not found: ${goalId}`);
|
|
const updated = {
|
|
...record,
|
|
...patch,
|
|
updatedAt: new Date(),
|
|
};
|
|
goals.set(goalId, updated);
|
|
return updated;
|
|
},
|
|
},
|
|
data: {
|
|
register(key, handler) {
|
|
dataHandlers.set(key, handler);
|
|
},
|
|
},
|
|
actions: {
|
|
register(key, handler) {
|
|
actionHandlers.set(key, handler);
|
|
},
|
|
},
|
|
streams: (() => {
|
|
const channelCompanyMap = new Map();
|
|
return {
|
|
open(channel, companyId) {
|
|
channelCompanyMap.set(channel, companyId);
|
|
},
|
|
emit(_channel, _event) {
|
|
// No-op in test harness — events are not forwarded
|
|
},
|
|
close(channel) {
|
|
channelCompanyMap.delete(channel);
|
|
},
|
|
};
|
|
})(),
|
|
tools: {
|
|
register(name, _decl, fn) {
|
|
requireCapability(manifest, capabilitySet, "agent.tools.register");
|
|
toolHandlers.set(name, fn);
|
|
},
|
|
},
|
|
metrics: {
|
|
async write(name, value, tags) {
|
|
requireCapability(manifest, capabilitySet, "metrics.write");
|
|
metrics.push({ name, value, tags });
|
|
},
|
|
},
|
|
logger: {
|
|
info(message, meta) {
|
|
logs.push({ level: "info", message, meta });
|
|
},
|
|
warn(message, meta) {
|
|
logs.push({ level: "warn", message, meta });
|
|
},
|
|
error(message, meta) {
|
|
logs.push({ level: "error", message, meta });
|
|
},
|
|
debug(message, meta) {
|
|
logs.push({ level: "debug", message, meta });
|
|
},
|
|
},
|
|
};
|
|
const harness = {
|
|
ctx,
|
|
seed(input) {
|
|
for (const row of input.companies ?? [])
|
|
companies.set(row.id, row);
|
|
for (const row of input.projects ?? [])
|
|
projects.set(row.id, row);
|
|
for (const row of input.issues ?? [])
|
|
issues.set(row.id, row);
|
|
for (const row of input.issueComments ?? []) {
|
|
const list = issueComments.get(row.issueId) ?? [];
|
|
list.push(row);
|
|
issueComments.set(row.issueId, list);
|
|
}
|
|
for (const row of input.agents ?? [])
|
|
agents.set(row.id, row);
|
|
for (const row of input.goals ?? [])
|
|
goals.set(row.id, row);
|
|
},
|
|
setConfig(config) {
|
|
currentConfig = { ...config };
|
|
},
|
|
async emit(eventType, payload, base) {
|
|
const event = {
|
|
eventId: base?.eventId ?? randomUUID(),
|
|
eventType,
|
|
companyId: base?.companyId ?? "test-company",
|
|
occurredAt: base?.occurredAt ?? new Date().toISOString(),
|
|
actorId: base?.actorId,
|
|
actorType: base?.actorType,
|
|
entityId: base?.entityId,
|
|
entityType: base?.entityType,
|
|
payload,
|
|
};
|
|
for (const handler of events) {
|
|
const exactMatch = handler.name === event.eventType;
|
|
const wildcardPluginAll = handler.name === "plugin.*" && String(event.eventType).startsWith("plugin.");
|
|
const wildcardPluginOne = String(handler.name).endsWith(".*")
|
|
&& String(event.eventType).startsWith(String(handler.name).slice(0, -1));
|
|
if (!exactMatch && !wildcardPluginAll && !wildcardPluginOne)
|
|
continue;
|
|
if (!allowsEvent(handler.filter, event))
|
|
continue;
|
|
await handler.fn(event);
|
|
}
|
|
},
|
|
async runJob(jobKey, partial = {}) {
|
|
const handler = jobs.get(jobKey);
|
|
if (!handler)
|
|
throw new Error(`No job handler registered for '${jobKey}'`);
|
|
await handler({
|
|
jobKey,
|
|
runId: partial.runId ?? randomUUID(),
|
|
trigger: partial.trigger ?? "manual",
|
|
scheduledAt: partial.scheduledAt ?? new Date().toISOString(),
|
|
});
|
|
},
|
|
async getData(key, params = {}) {
|
|
const handler = dataHandlers.get(key);
|
|
if (!handler)
|
|
throw new Error(`No data handler registered for '${key}'`);
|
|
return await handler(params);
|
|
},
|
|
async performAction(key, params = {}) {
|
|
const handler = actionHandlers.get(key);
|
|
if (!handler)
|
|
throw new Error(`No action handler registered for '${key}'`);
|
|
return await handler(params);
|
|
},
|
|
async executeTool(name, params, runCtx = {}) {
|
|
const handler = toolHandlers.get(name);
|
|
if (!handler)
|
|
throw new Error(`No tool handler registered for '${name}'`);
|
|
const ctxToPass = {
|
|
agentId: runCtx.agentId ?? "agent-test",
|
|
runId: runCtx.runId ?? randomUUID(),
|
|
companyId: runCtx.companyId ?? "company-test",
|
|
projectId: runCtx.projectId ?? "project-test",
|
|
};
|
|
return await handler(params, ctxToPass);
|
|
},
|
|
getState(input) {
|
|
return state.get(stateMapKey(input));
|
|
},
|
|
simulateSessionEvent(sessionId, event) {
|
|
const cb = sessionEventCallbacks.get(sessionId);
|
|
if (!cb)
|
|
throw new Error(`No active session event callback for session: ${sessionId}`);
|
|
cb({ ...event, sessionId });
|
|
},
|
|
logs,
|
|
activity,
|
|
metrics,
|
|
};
|
|
return harness;
|
|
}
|
|
//# sourceMappingURL=testing.js.map
|