From 663da4557c73e09ca0869eb06c98997421cb337b Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 19:00:48 -0400 Subject: [PATCH 01/26] chore(lint): apply repo formatter fixes --- .../cloud/shared/src/lib/types/cloud-api.ts | 4 +- .../features/trajectories/read-routes.test.ts | 572 ++++++++--------- .../src/features/trajectories/read-routes.ts | 604 +++++++++--------- packages/core/src/services/trajectories.ts | 2 +- .../ui/src/cloud/admin/admin-role.test.ts | 4 +- .../src/cloud/shell/cloud-route-gate.test.tsx | 10 +- .../apps/helpers-catalog-curation.test.ts | 4 +- packages/ui/src/components/apps/helpers.ts | 21 +- .../src/components/settings/ApiKeyConfig.tsx | 2 +- .../src/genui/genui-action-registry.test.ts | 8 +- .../unit/routes/workbench-todos.test.ts | 68 +- .../src/routes/workbench-todos.ts | 73 +-- 12 files changed, 655 insertions(+), 717 deletions(-) diff --git a/packages/cloud/shared/src/lib/types/cloud-api.ts b/packages/cloud/shared/src/lib/types/cloud-api.ts index 31adfb0099fa7..c406337a51c0a 100644 --- a/packages/cloud/shared/src/lib/types/cloud-api.ts +++ b/packages/cloud/shared/src/lib/types/cloud-api.ts @@ -448,9 +448,7 @@ export const ADMIN_ROLE_RANK: Record = { /** Type guard: `value` is a recognized {@link AdminRole}. */ export function isAdminRole(value: unknown): value is AdminRole { - return ( - value === "super_admin" || value === "moderator" || value === "viewer" - ); + return value === "super_admin" || value === "moderator" || value === "viewer"; } /** diff --git a/packages/core/src/features/trajectories/read-routes.test.ts b/packages/core/src/features/trajectories/read-routes.test.ts index 0a0f866b550c0..0189b291992a7 100644 --- a/packages/core/src/features/trajectories/read-routes.test.ts +++ b/packages/core/src/features/trajectories/read-routes.test.ts @@ -5,307 +5,307 @@ import { tryHandleTrajectoryReadRoutes } from "./read-routes"; // Minimal ServerResponse capture — records statusCode + parsed JSON body. function mockRes(): { - res: ServerResponse; - get: () => { status: number; body: unknown }; + res: ServerResponse; + get: () => { status: number; body: unknown }; } { - const state = { status: 0, body: undefined as unknown, ended: false }; - const res = { - statusCode: 0, - setHeader() {}, - end(payload?: string) { - state.status = (this as { statusCode: number }).statusCode; - state.body = payload ? JSON.parse(payload) : undefined; - state.ended = true; - }, - } as unknown as ServerResponse; - return { res, get: () => ({ status: state.status, body: state.body }) }; + const state = { status: 0, body: undefined as unknown, ended: false }; + const res = { + statusCode: 0, + setHeader() {}, + end(payload?: string) { + state.status = (this as { statusCode: number }).statusCode; + state.body = payload ? JSON.parse(payload) : undefined; + state.ended = true; + }, + } as unknown as ServerResponse; + return { res, get: () => ({ status: state.status, body: state.body }) }; } function runtimeWith( - service: unknown, - rooms: Record = {}, + service: unknown, + rooms: Record = {}, ): IAgentRuntime { - return { - getService: (type: string) => (type === "trajectories" ? service : null), - getRoom: async (id: string) => rooms[id] ?? null, - } as unknown as IAgentRuntime; + return { + getService: (type: string) => (type === "trajectories" ? service : null), + getRoom: async (id: string) => rooms[id] ?? null, + } as unknown as IAgentRuntime; } const url = (p: string) => new URL(`http://localhost${p}`); describe("tryHandleTrajectoryReadRoutes", () => { - it("ignores non-trajectory paths and non-GET methods", async () => { - const { res } = mockRes(); - expect( - await tryHandleTrajectoryReadRoutes({ - pathname: "/api/health", - method: "GET", - url: url("/api/health"), - runtime: runtimeWith({}), - res, - }), - ).toBe(false); - expect( - await tryHandleTrajectoryReadRoutes({ - pathname: "/api/trajectories", - method: "DELETE", - url: url("/api/trajectories"), - runtime: runtimeWith({}), - res, - }), - ).toBe(false); - }); + it("ignores non-trajectory paths and non-GET methods", async () => { + const { res } = mockRes(); + expect( + await tryHandleTrajectoryReadRoutes({ + pathname: "/api/health", + method: "GET", + url: url("/api/health"), + runtime: runtimeWith({}), + res, + }), + ).toBe(false); + expect( + await tryHandleTrajectoryReadRoutes({ + pathname: "/api/trajectories", + method: "DELETE", + url: url("/api/trajectories"), + runtime: runtimeWith({}), + res, + }), + ).toBe(false); + }); - it("lists trajectories from the core service (UI shape, timeout→error)", async () => { - const service = { - listTrajectories: async () => ({ - trajectories: [ - { - id: "t1", - status: "completed", - llmCallCount: 3, - source: "discord", - roomId: "room-1", - entityId: "entity-1", - metadata: { roomId: "room-1", entityId: "entity-1" }, - }, - { id: "t2", status: "timeout", llmCallCount: 1 }, - ], - total: 2, - }), - }; - const { res, get } = mockRes(); - const handled = await tryHandleTrajectoryReadRoutes({ - pathname: "/api/trajectories", - method: "GET", - url: url("/api/trajectories?limit=10"), - runtime: runtimeWith(service), - res, - }); - expect(handled).toBe(true); - const { status, body } = get(); - expect(status).toBe(200); - const b = body as { - trajectories: Array>; - total: number; - }; - expect(b.total).toBe(2); - expect(b.trajectories[0]).toMatchObject({ - id: "t1", - status: "completed", - llmCallCount: 3, - }); - // timeout collapses to the viewer's tri-state "error" - expect(b.trajectories[1]).toMatchObject({ id: "t2", status: "error" }); - }); + it("lists trajectories from the core service (UI shape, timeout→error)", async () => { + const service = { + listTrajectories: async () => ({ + trajectories: [ + { + id: "t1", + status: "completed", + llmCallCount: 3, + source: "discord", + roomId: "room-1", + entityId: "entity-1", + metadata: { roomId: "room-1", entityId: "entity-1" }, + }, + { id: "t2", status: "timeout", llmCallCount: 1 }, + ], + total: 2, + }), + }; + const { res, get } = mockRes(); + const handled = await tryHandleTrajectoryReadRoutes({ + pathname: "/api/trajectories", + method: "GET", + url: url("/api/trajectories?limit=10"), + runtime: runtimeWith(service), + res, + }); + expect(handled).toBe(true); + const { status, body } = get(); + expect(status).toBe(200); + const b = body as { + trajectories: Array>; + total: number; + }; + expect(b.total).toBe(2); + expect(b.trajectories[0]).toMatchObject({ + id: "t1", + status: "completed", + llmCallCount: 3, + }); + // timeout collapses to the viewer's tri-state "error" + expect(b.trajectories[1]).toMatchObject({ id: "t2", status: "error" }); + }); - it("forwards the search param to the SQL reader so only matches return", async () => { - const rows = [ - { id: "match-1", status: "completed", llmCallCount: 1 }, - { id: "other-1", status: "completed", llmCallCount: 1 }, - { id: "match-2", status: "completed", llmCallCount: 1 }, - ]; - let receivedSearch: string | undefined; - const service = { - listTrajectories: async (options: { search?: string }) => { - receivedSearch = options.search; - // Emulate the SQL reader: filter + count by the search needle. - const matched = options.search - ? rows.filter((r) => r.id.includes(options.search as string)) - : rows; - return { trajectories: matched, total: matched.length }; - }, - }; - const { res, get } = mockRes(); - const handled = await tryHandleTrajectoryReadRoutes({ - pathname: "/api/trajectories", - method: "GET", - url: url("/api/trajectories?search=match&limit=10"), - runtime: runtimeWith(service), - res, - }); - expect(handled).toBe(true); - // search is threaded through to the service - expect(receivedSearch).toBe("match"); - const { status, body } = get(); - expect(status).toBe(200); - const b = body as { - trajectories: Array<{ id: string }>; - total: number; - }; - // only matching rows return; total reflects the filtered count - expect(b.trajectories.map((t) => t.id)).toEqual(["match-1", "match-2"]); - expect(b.total).toBe(2); - }); + it("forwards the search param to the SQL reader so only matches return", async () => { + const rows = [ + { id: "match-1", status: "completed", llmCallCount: 1 }, + { id: "other-1", status: "completed", llmCallCount: 1 }, + { id: "match-2", status: "completed", llmCallCount: 1 }, + ]; + let receivedSearch: string | undefined; + const service = { + listTrajectories: async (options: { search?: string }) => { + receivedSearch = options.search; + // Emulate the SQL reader: filter + count by the search needle. + const matched = options.search + ? rows.filter((r) => r.id.includes(options.search as string)) + : rows; + return { trajectories: matched, total: matched.length }; + }, + }; + const { res, get } = mockRes(); + const handled = await tryHandleTrajectoryReadRoutes({ + pathname: "/api/trajectories", + method: "GET", + url: url("/api/trajectories?search=match&limit=10"), + runtime: runtimeWith(service), + res, + }); + expect(handled).toBe(true); + // search is threaded through to the service + expect(receivedSearch).toBe("match"); + const { status, body } = get(); + expect(status).toBe(200); + const b = body as { + trajectories: Array<{ id: string }>; + total: number; + }; + // only matching rows return; total reflects the filtered count + expect(b.trajectories.map((t) => t.id)).toEqual(["match-1", "match-2"]); + expect(b.total).toBe(2); + }); - it("maps detail steps into phase-classified llmCalls / providerAccesses / toolEvents", async () => { - const service = { - getTrajectoryDetail: async (id: string) => ({ - trajectoryId: id, - endTime: 1000, - metrics: { finalStatus: "completed" }, - metadata: { source: "discord", roomId: "room-1", entityId: "entity-1" }, - steps: [ - { - stepId: "s0", - llmCalls: [ - { - callId: "c0", - model: "m", - response: "RESPOND", - stepType: "should_respond", - }, - { - callId: "c1", - model: "m", - response: "plan", - stepType: "reasoning", - }, - ], - providerAccesses: [ - { providerId: "p0", providerName: "facts", purpose: "ctx" }, - ], - action: { attemptId: "a0", actionName: "REPLY", success: true }, - }, - ], - }), - }; - const { res, get } = mockRes(); - const handled = await tryHandleTrajectoryReadRoutes({ - pathname: "/api/trajectories/abc", - method: "GET", - url: url("/api/trajectories/abc"), - runtime: runtimeWith(service), - res, - }); - expect(handled).toBe(true); - const { status, body } = get(); - expect(status).toBe(200); - const b = body as { - trajectory: { - id: string; - status: string; - source: string; - roomId: string; - entityId: string; - metadata: Record; - llmCallCount: number; - }; - llmCalls: Array<{ stepType: string }>; - providerAccesses: unknown[]; - toolEvents: Array<{ actionName: string; success: boolean }>; - }; - expect(b.trajectory).toMatchObject({ - id: "abc", - status: "completed", - source: "discord", - roomId: "room-1", - entityId: "entity-1", - metadata: { source: "discord", roomId: "room-1", entityId: "entity-1" }, - llmCallCount: 2, - }); - expect(b.llmCalls.map((c) => c.stepType)).toEqual([ - "should_respond", - "reasoning", - ]); - expect(b.providerAccesses).toHaveLength(1); - expect(b.toolEvents[0]).toMatchObject({ - actionName: "REPLY", - success: true, - type: "tool_result", - }); - }); + it("maps detail steps into phase-classified llmCalls / providerAccesses / toolEvents", async () => { + const service = { + getTrajectoryDetail: async (id: string) => ({ + trajectoryId: id, + endTime: 1000, + metrics: { finalStatus: "completed" }, + metadata: { source: "discord", roomId: "room-1", entityId: "entity-1" }, + steps: [ + { + stepId: "s0", + llmCalls: [ + { + callId: "c0", + model: "m", + response: "RESPOND", + stepType: "should_respond", + }, + { + callId: "c1", + model: "m", + response: "plan", + stepType: "reasoning", + }, + ], + providerAccesses: [ + { providerId: "p0", providerName: "facts", purpose: "ctx" }, + ], + action: { attemptId: "a0", actionName: "REPLY", success: true }, + }, + ], + }), + }; + const { res, get } = mockRes(); + const handled = await tryHandleTrajectoryReadRoutes({ + pathname: "/api/trajectories/abc", + method: "GET", + url: url("/api/trajectories/abc"), + runtime: runtimeWith(service), + res, + }); + expect(handled).toBe(true); + const { status, body } = get(); + expect(status).toBe(200); + const b = body as { + trajectory: { + id: string; + status: string; + source: string; + roomId: string; + entityId: string; + metadata: Record; + llmCallCount: number; + }; + llmCalls: Array<{ stepType: string }>; + providerAccesses: unknown[]; + toolEvents: Array<{ actionName: string; success: boolean }>; + }; + expect(b.trajectory).toMatchObject({ + id: "abc", + status: "completed", + source: "discord", + roomId: "room-1", + entityId: "entity-1", + metadata: { source: "discord", roomId: "room-1", entityId: "entity-1" }, + llmCallCount: 2, + }); + expect(b.llmCalls.map((c) => c.stepType)).toEqual([ + "should_respond", + "reasoning", + ]); + expect(b.providerAccesses).toHaveLength(1); + expect(b.toolEvents[0]).toMatchObject({ + actionName: "REPLY", + success: true, + type: "tool_result", + }); + }); - it("resolves room context only when requested", async () => { - const service = { - listTrajectories: async () => ({ - trajectories: [ - { - id: "t1", - status: "completed", - llmCallCount: 1, - metadata: { roomId: "room-1" }, - }, - ], - total: 1, - }), - }; - const { res, get } = mockRes(); - const handled = await tryHandleTrajectoryReadRoutes({ - pathname: "/api/trajectories", - method: "GET", - url: url("/api/trajectories?resolve=1"), - runtime: runtimeWith(service, { - "room-1": { - id: "room-1", - name: "ruby-trivia", - type: "GROUP", - worldId: "world-1", - serverId: "guild-1", - }, - }), - res, - }); - expect(handled).toBe(true); - const rows = ( - get().body as { trajectories: Array> } - ).trajectories; - expect(rows[0].roomContext).toEqual({ - id: "room-1", - name: "ruby-trivia", - type: "GROUP", - worldId: "world-1", - serverId: "guild-1", - }); - }); + it("resolves room context only when requested", async () => { + const service = { + listTrajectories: async () => ({ + trajectories: [ + { + id: "t1", + status: "completed", + llmCallCount: 1, + metadata: { roomId: "room-1" }, + }, + ], + total: 1, + }), + }; + const { res, get } = mockRes(); + const handled = await tryHandleTrajectoryReadRoutes({ + pathname: "/api/trajectories", + method: "GET", + url: url("/api/trajectories?resolve=1"), + runtime: runtimeWith(service, { + "room-1": { + id: "room-1", + name: "ruby-trivia", + type: "GROUP", + worldId: "world-1", + serverId: "guild-1", + }, + }), + res, + }); + expect(handled).toBe(true); + const rows = ( + get().body as { trajectories: Array> } + ).trajectories; + expect(rows[0].roomContext).toEqual({ + id: "room-1", + name: "ruby-trivia", + type: "GROUP", + worldId: "world-1", + serverId: "guild-1", + }); + }); - it("404s an unknown detail id", async () => { - const service = { getTrajectoryDetail: async () => null }; - const { res, get } = mockRes(); - const handled = await tryHandleTrajectoryReadRoutes({ - pathname: "/api/trajectories/missing", - method: "GET", - url: url("/api/trajectories/missing"), - runtime: runtimeWith(service), - res, - }); - expect(handled).toBe(true); - expect(get().status).toBe(404); - }); + it("404s an unknown detail id", async () => { + const service = { getTrajectoryDetail: async () => null }; + const { res, get } = mockRes(); + const handled = await tryHandleTrajectoryReadRoutes({ + pathname: "/api/trajectories/missing", + method: "GET", + url: url("/api/trajectories/missing"), + runtime: runtimeWith(service), + res, + }); + expect(handled).toBe(true); + expect(get().status).toBe(404); + }); - it("returns an empty list (200, not 404) when the service is absent", async () => { - const { res, get } = mockRes(); - const handled = await tryHandleTrajectoryReadRoutes({ - pathname: "/api/trajectories", - method: "GET", - url: url("/api/trajectories"), - runtime: runtimeWith(null), - res, - }); - expect(handled).toBe(true); - expect(get().status).toBe(200); - expect((get().body as { trajectories: unknown[] }).trajectories).toEqual( - [], - ); - }); + it("returns an empty list (200, not 404) when the service is absent", async () => { + const { res, get } = mockRes(); + const handled = await tryHandleTrajectoryReadRoutes({ + pathname: "/api/trajectories", + method: "GET", + url: url("/api/trajectories"), + runtime: runtimeWith(null), + res, + }); + expect(handled).toBe(true); + expect(get().status).toBe(200); + expect((get().body as { trajectories: unknown[] }).trajectories).toEqual( + [], + ); + }); - it("does not treat /stats or /config as a detail id", async () => { - const service = { - getStats: async () => ({ totalTrajectories: 5 }), - getTrajectoryDetail: async () => { - throw new Error("should not be called for /stats"); - }, - }; - const { res, get } = mockRes(); - const handled = await tryHandleTrajectoryReadRoutes({ - pathname: "/api/trajectories/stats", - method: "GET", - url: url("/api/trajectories/stats"), - runtime: runtimeWith(service), - res, - }); - expect(handled).toBe(true); - expect(get().status).toBe(200); - expect(get().body).toMatchObject({ totalTrajectories: 5 }); - }); + it("does not treat /stats or /config as a detail id", async () => { + const service = { + getStats: async () => ({ totalTrajectories: 5 }), + getTrajectoryDetail: async () => { + throw new Error("should not be called for /stats"); + }, + }; + const { res, get } = mockRes(); + const handled = await tryHandleTrajectoryReadRoutes({ + pathname: "/api/trajectories/stats", + method: "GET", + url: url("/api/trajectories/stats"), + runtime: runtimeWith(service), + res, + }); + expect(handled).toBe(true); + expect(get().status).toBe(200); + expect(get().body).toMatchObject({ totalTrajectories: 5 }); + }); }); diff --git a/packages/core/src/features/trajectories/read-routes.ts b/packages/core/src/features/trajectories/read-routes.ts index ce860e54e5b3d..ff173ebb03316 100644 --- a/packages/core/src/features/trajectories/read-routes.ts +++ b/packages/core/src/features/trajectories/read-routes.ts @@ -22,253 +22,253 @@ import type { IAgentRuntime, UUID } from "../../types"; */ interface ServiceTrajectoryListItem { - id: string; - agentId?: string; - source?: string; - roomId?: string | null; - entityId?: string | null; - metadata?: Record; - status: "active" | "completed" | "error" | "timeout"; - startTime?: number; - endTime?: number | null; - durationMs?: number | null; - llmCallCount?: number; - createdAt?: string; - updatedAt?: string; + id: string; + agentId?: string; + source?: string; + roomId?: string | null; + entityId?: string | null; + metadata?: Record; + status: "active" | "completed" | "error" | "timeout"; + startTime?: number; + endTime?: number | null; + durationMs?: number | null; + llmCallCount?: number; + createdAt?: string; + updatedAt?: string; } interface ServiceLlmCall { - callId?: string; - model?: string; - provider?: string; - response?: string; - purpose?: string; - actionType?: string; - stepType?: string; + callId?: string; + model?: string; + provider?: string; + response?: string; + purpose?: string; + actionType?: string; + stepType?: string; } interface ServiceProviderAccess { - providerId?: string; - providerName?: string; - purpose?: string; + providerId?: string; + providerName?: string; + purpose?: string; } interface ServiceActionAttempt { - attemptId?: string; - actionType?: string; - actionName?: string; - success?: boolean; - error?: string; + attemptId?: string; + actionType?: string; + actionName?: string; + success?: boolean; + error?: string; } interface ServiceTrajectoryStep { - stepId?: string; - llmCalls?: ServiceLlmCall[]; - providerAccesses?: ServiceProviderAccess[]; - action?: ServiceActionAttempt; + stepId?: string; + llmCalls?: ServiceLlmCall[]; + providerAccesses?: ServiceProviderAccess[]; + action?: ServiceActionAttempt; } interface ServiceTrajectory { - trajectoryId: string; - agentId?: string; - startTime?: number; - endTime?: number; - steps?: ServiceTrajectoryStep[]; - metrics?: { finalStatus?: string }; - metadata?: Record; + trajectoryId: string; + agentId?: string; + startTime?: number; + endTime?: number; + steps?: ServiceTrajectoryStep[]; + metrics?: { finalStatus?: string }; + metadata?: Record; } interface ResolvedRoomContext { - id: string; - name?: string; - type?: string; - worldId?: string; - serverId?: string; + id: string; + name?: string; + type?: string; + worldId?: string; + serverId?: string; } interface TrajectoriesServiceLike { - listTrajectories?: (options: { - limit?: number; - offset?: number; - source?: string; - status?: string; - scenarioId?: string; - batchId?: string; - search?: string; - }) => Promise<{ trajectories: ServiceTrajectoryListItem[]; total: number }>; - getTrajectoryDetail?: (id: string) => Promise; - getStats?: () => Promise; + listTrajectories?: (options: { + limit?: number; + offset?: number; + source?: string; + status?: string; + scenarioId?: string; + batchId?: string; + search?: string; + }) => Promise<{ trajectories: ServiceTrajectoryListItem[]; total: number }>; + getTrajectoryDetail?: (id: string) => Promise; + getStats?: () => Promise; } function sendJson( - res: ServerResponse, - statusCode: number, - body: unknown, + res: ServerResponse, + statusCode: number, + body: unknown, ): void { - res.statusCode = statusCode; - res.setHeader("Content-Type", "application/json; charset=utf-8"); - res.end(JSON.stringify(body)); + res.statusCode = statusCode; + res.setHeader("Content-Type", "application/json; charset=utf-8"); + res.end(JSON.stringify(body)); } // timeout collapses to the viewer's tri-state "error". function normalizeStatus( - status: string | undefined, + status: string | undefined, ): "active" | "completed" | "error" { - if (status === "timeout" || status === "error" || status === "terminated") { - return "error"; - } - return status === "active" ? "active" : "completed"; + if (status === "timeout" || status === "error" || status === "terminated") { + return "error"; + } + return status === "active" ? "active" : "completed"; } function metadataRoomId( - metadata: Record | undefined, + metadata: Record | undefined, ): string | null { - return typeof metadata?.roomId === "string" ? metadata.roomId : null; + return typeof metadata?.roomId === "string" ? metadata.roomId : null; } function metadataEntityId( - metadata: Record | undefined, + metadata: Record | undefined, ): string | null { - return typeof metadata?.entityId === "string" ? metadata.entityId : null; + return typeof metadata?.entityId === "string" ? metadata.entityId : null; } function listItemToUi( - item: ServiceTrajectoryListItem, - roomContext?: ResolvedRoomContext | null, + item: ServiceTrajectoryListItem, + roomContext?: ResolvedRoomContext | null, ): Record { - const metadata = item.metadata ?? {}; - return { - id: item.id, - status: normalizeStatus(item.status), - llmCallCount: item.llmCallCount ?? 0, - agentId: item.agentId, - source: item.source ?? "chat", - roomId: item.roomId ?? metadataRoomId(metadata), - entityId: item.entityId ?? metadataEntityId(metadata), - metadata, - ...(roomContext ? { roomContext } : {}), - startTime: item.startTime, - endTime: item.endTime ?? null, - durationMs: item.durationMs ?? null, - createdAt: item.createdAt, - updatedAt: item.updatedAt ?? item.createdAt, - }; + const metadata = item.metadata ?? {}; + return { + id: item.id, + status: normalizeStatus(item.status), + llmCallCount: item.llmCallCount ?? 0, + agentId: item.agentId, + source: item.source ?? "chat", + roomId: item.roomId ?? metadataRoomId(metadata), + entityId: item.entityId ?? metadataEntityId(metadata), + metadata, + ...(roomContext ? { roomContext } : {}), + startTime: item.startTime, + endTime: item.endTime ?? null, + durationMs: item.durationMs ?? null, + createdAt: item.createdAt, + updatedAt: item.updatedAt ?? item.createdAt, + }; } // Flatten the recorded steps into the flat UI arrays the viewer's phase // classifier (`summarizePhases`) reads: llmCalls keyed by stepType/purpose drive // HANDLE/PLAN/EVALUATE; the per-step action drives the ACTION phase. function detailToUi( - traj: ServiceTrajectory, - roomContext?: ResolvedRoomContext | null, + traj: ServiceTrajectory, + roomContext?: ResolvedRoomContext | null, ): Record { - const id = String(traj.trajectoryId); - const metadata = traj.metadata ?? {}; - const llmCalls: Array> = []; - const providerAccesses: Array> = []; - const toolEvents: Array> = []; + const id = String(traj.trajectoryId); + const metadata = traj.metadata ?? {}; + const llmCalls: Array> = []; + const providerAccesses: Array> = []; + const toolEvents: Array> = []; - const steps = traj.steps ?? []; - for (let s = 0; s < steps.length; s++) { - const step = steps[s]; - const stepId = step.stepId ?? `step-${s}`; - const calls = step.llmCalls ?? []; - for (let i = 0; i < calls.length; i++) { - const c = calls[i]; - llmCalls.push({ - id: c.callId || `${stepId}-call-${i}`, - model: c.model || "unknown", - provider: c.provider || "", - response: c.response || "", - purpose: c.purpose || "", - actionType: c.actionType || "", - stepType: c.stepType || "", - }); - } - const accesses = step.providerAccesses ?? []; - for (let k = 0; k < accesses.length; k++) { - const p = accesses[k]; - providerAccesses.push({ - id: p.providerId || `${stepId}-provider-${k}`, - providerName: p.providerName || "unknown", - purpose: p.purpose || "", - }); - } - const action = step.action; - if (action && (action.actionName || action.actionType)) { - const failed = action.success === false || Boolean(action.error); - toolEvents.push({ - id: action.attemptId || `${stepId}-action`, - type: failed ? "tool_error" : "tool_result", - actionName: action.actionName || action.actionType || "action", - status: failed ? "failed" : "completed", - success: !failed, - ...(action.error ? { error: action.error } : {}), - }); - } - } + const steps = traj.steps ?? []; + for (let s = 0; s < steps.length; s++) { + const step = steps[s]; + const stepId = step.stepId ?? `step-${s}`; + const calls = step.llmCalls ?? []; + for (let i = 0; i < calls.length; i++) { + const c = calls[i]; + llmCalls.push({ + id: c.callId || `${stepId}-call-${i}`, + model: c.model || "unknown", + provider: c.provider || "", + response: c.response || "", + purpose: c.purpose || "", + actionType: c.actionType || "", + stepType: c.stepType || "", + }); + } + const accesses = step.providerAccesses ?? []; + for (let k = 0; k < accesses.length; k++) { + const p = accesses[k]; + providerAccesses.push({ + id: p.providerId || `${stepId}-provider-${k}`, + providerName: p.providerName || "unknown", + purpose: p.purpose || "", + }); + } + const action = step.action; + if (action && (action.actionName || action.actionType)) { + const failed = action.success === false || Boolean(action.error); + toolEvents.push({ + id: action.attemptId || `${stepId}-action`, + type: failed ? "tool_error" : "tool_result", + actionName: action.actionName || action.actionType || "action", + status: failed ? "failed" : "completed", + success: !failed, + ...(action.error ? { error: action.error } : {}), + }); + } + } - const finalStatus = traj.metrics?.finalStatus; - const status: "active" | "completed" | "error" = - finalStatus === "timeout" || - finalStatus === "terminated" || - finalStatus === "error" - ? "error" - : finalStatus === "completed" || - (typeof traj.endTime === "number" && traj.endTime > 0) - ? "completed" - : "active"; + const finalStatus = traj.metrics?.finalStatus; + const status: "active" | "completed" | "error" = + finalStatus === "timeout" || + finalStatus === "terminated" || + finalStatus === "error" + ? "error" + : finalStatus === "completed" || + (typeof traj.endTime === "number" && traj.endTime > 0) + ? "completed" + : "active"; - const startTime = typeof traj.startTime === "number" ? traj.startTime : 0; - const endTime = - typeof traj.endTime === "number" && traj.endTime > 0 ? traj.endTime : null; - const durationMs = - endTime !== null && startTime > 0 ? Math.max(0, endTime - startTime) : null; - return { - trajectory: { - id, - agentId: traj.agentId ?? "", - source: typeof metadata.source === "string" ? metadata.source : "chat", - roomId: metadataRoomId(metadata), - entityId: metadataEntityId(metadata), - metadata, - ...(roomContext ? { roomContext } : {}), - status, - startTime, - endTime, - durationMs, - llmCallCount: llmCalls.length, - providerAccessCount: providerAccesses.length, - createdAt: new Date(startTime > 0 ? startTime : 0).toISOString(), - }, - llmCalls, - providerAccesses, - toolEvents, - evaluationEvents: [], - }; + const startTime = typeof traj.startTime === "number" ? traj.startTime : 0; + const endTime = + typeof traj.endTime === "number" && traj.endTime > 0 ? traj.endTime : null; + const durationMs = + endTime !== null && startTime > 0 ? Math.max(0, endTime - startTime) : null; + return { + trajectory: { + id, + agentId: traj.agentId ?? "", + source: typeof metadata.source === "string" ? metadata.source : "chat", + roomId: metadataRoomId(metadata), + entityId: metadataEntityId(metadata), + metadata, + ...(roomContext ? { roomContext } : {}), + status, + startTime, + endTime, + durationMs, + llmCallCount: llmCalls.length, + providerAccessCount: providerAccesses.length, + createdAt: new Date(startTime > 0 ? startTime : 0).toISOString(), + }, + llmCalls, + providerAccesses, + toolEvents, + evaluationEvents: [], + }; } async function resolveRoomContext( - runtime: IAgentRuntime | null | undefined, - roomId: string | null | undefined, - cache: Map, + runtime: IAgentRuntime | null | undefined, + roomId: string | null | undefined, + cache: Map, ): Promise { - if (!roomId) return null; - if (cache.has(roomId)) return cache.get(roomId) ?? null; - const room = await runtime?.getRoom?.(roomId as UUID); - const context = room - ? { - id: String(room.id || roomId), - ...(typeof room.name === "string" ? { name: room.name } : {}), - ...(typeof room.type === "string" ? { type: room.type } : {}), - ...(typeof room.worldId === "string" ? { worldId: room.worldId } : {}), - ...(typeof room.serverId === "string" - ? { serverId: room.serverId } - : {}), - } - : null; - cache.set(roomId, context); - return context; + if (!roomId) return null; + if (cache.has(roomId)) return cache.get(roomId) ?? null; + const room = await runtime?.getRoom?.(roomId as UUID); + const context = room + ? { + id: String(room.id || roomId), + ...(typeof room.name === "string" ? { name: room.name } : {}), + ...(typeof room.type === "string" ? { type: room.type } : {}), + ...(typeof room.worldId === "string" ? { worldId: room.worldId } : {}), + ...(typeof room.serverId === "string" + ? { serverId: room.serverId } + : {}), + } + : null; + cache.set(roomId, context); + return context; } /** @@ -277,119 +277,119 @@ async function resolveRoomContext( * path/method does not belong to these read routes. */ export async function tryHandleTrajectoryReadRoutes(options: { - pathname: string; - method: string; - url: URL; - runtime: IAgentRuntime | null | undefined; - res: ServerResponse; + pathname: string; + method: string; + url: URL; + runtime: IAgentRuntime | null | undefined; + res: ServerResponse; }): Promise { - const { pathname, method, url, runtime, res } = options; - if (method !== "GET" || !pathname.startsWith("/api/trajectories")) { - return false; - } - // Only the READ routes the viewer needs. Mutations (DELETE, export, config) - // remain plugin-training's responsibility; let them 404 where it's absent. - const isList = pathname === "/api/trajectories"; - const isStats = pathname === "/api/trajectories/stats"; - const idMatch = pathname.match(/^\/api\/trajectories\/([^/]+)$/); - const detailId = - idMatch && idMatch[1] !== "stats" && idMatch[1] !== "config" - ? decodeURIComponent(idMatch[1]) - : null; - if (!isList && !isStats && !detailId) { - return false; - } + const { pathname, method, url, runtime, res } = options; + if (method !== "GET" || !pathname.startsWith("/api/trajectories")) { + return false; + } + // Only the READ routes the viewer needs. Mutations (DELETE, export, config) + // remain plugin-training's responsibility; let them 404 where it's absent. + const isList = pathname === "/api/trajectories"; + const isStats = pathname === "/api/trajectories/stats"; + const idMatch = pathname.match(/^\/api\/trajectories\/([^/]+)$/); + const detailId = + idMatch && idMatch[1] !== "stats" && idMatch[1] !== "config" + ? decodeURIComponent(idMatch[1]) + : null; + if (!isList && !isStats && !detailId) { + return false; + } - const service = runtime?.getService?.("trajectories") as - | TrajectoriesServiceLike - | null - | undefined; - // No service at all → empty (200) so the viewer reads "no trajectories yet" - // instead of the "unavailable" surface a 404/503 would trigger. - if (!service) { - if (isList) sendJson(res, 200, { trajectories: [], total: 0 }); - else if (isStats) sendJson(res, 200, { totalTrajectories: 0 }); - else sendJson(res, 404, { error: "Trajectory not found" }); - return true; - } + const service = runtime?.getService?.("trajectories") as + | TrajectoriesServiceLike + | null + | undefined; + // No service at all → empty (200) so the viewer reads "no trajectories yet" + // instead of the "unavailable" surface a 404/503 would trigger. + if (!service) { + if (isList) sendJson(res, 200, { trajectories: [], total: 0 }); + else if (isStats) sendJson(res, 200, { totalTrajectories: 0 }); + else sendJson(res, 404, { error: "Trajectory not found" }); + return true; + } - const shouldResolveRooms = url.searchParams.get("resolve") === "1"; - const roomCache = new Map(); + const shouldResolveRooms = url.searchParams.get("resolve") === "1"; + const roomCache = new Map(); - try { - if (isStats) { - const stats = (await service.getStats?.()) ?? { totalTrajectories: 0 }; - sendJson(res, 200, stats); - return true; - } - if (isList) { - const limit = Math.min( - 500, - Math.max(1, Number(url.searchParams.get("limit")) || 50), - ); - const offset = Math.max(0, Number(url.searchParams.get("offset")) || 0); - const result = (await service.listTrajectories?.({ - limit, - offset, - source: url.searchParams.get("source") || undefined, - status: url.searchParams.get("status") || undefined, - scenarioId: url.searchParams.get("scenarioId") || undefined, - batchId: url.searchParams.get("batchId") || undefined, - // The SQL reader filters + counts by `search` (id/scenario_id/ - // batch_id/metadata/steps_json LIKE). On mobile this owns - // /api/trajectories, so without forwarding `search` the viewer's - // search box returned the full unfiltered list. - search: url.searchParams.get("search") || undefined, - })) ?? { trajectories: [], total: 0 }; - const trajectories = shouldResolveRooms - ? await Promise.all( - result.trajectories.map(async (item) => - listItemToUi( - item, - await resolveRoomContext( - runtime, - item.roomId ?? metadataRoomId(item.metadata), - roomCache, - ), - ), - ), - ) - : result.trajectories.map((item) => listItemToUi(item)); - sendJson(res, 200, { - trajectories, - total: result.total, - offset, - limit, - }); - return true; - } - // detail - const traj = detailId - ? await service.getTrajectoryDetail?.(detailId) - : null; - if (!traj) { - sendJson(res, 404, { error: `Trajectory "${detailId}" not found` }); - return true; - } - sendJson( - res, - 200, - detailToUi( - traj, - shouldResolveRooms - ? await resolveRoomContext( - runtime, - metadataRoomId(traj.metadata), - roomCache, - ) - : null, - ), - ); - return true; - } catch (err) { - sendJson(res, 500, { - error: err instanceof Error ? err.message : "Trajectory read failed", - }); - return true; - } + try { + if (isStats) { + const stats = (await service.getStats?.()) ?? { totalTrajectories: 0 }; + sendJson(res, 200, stats); + return true; + } + if (isList) { + const limit = Math.min( + 500, + Math.max(1, Number(url.searchParams.get("limit")) || 50), + ); + const offset = Math.max(0, Number(url.searchParams.get("offset")) || 0); + const result = (await service.listTrajectories?.({ + limit, + offset, + source: url.searchParams.get("source") || undefined, + status: url.searchParams.get("status") || undefined, + scenarioId: url.searchParams.get("scenarioId") || undefined, + batchId: url.searchParams.get("batchId") || undefined, + // The SQL reader filters + counts by `search` (id/scenario_id/ + // batch_id/metadata/steps_json LIKE). On mobile this owns + // /api/trajectories, so without forwarding `search` the viewer's + // search box returned the full unfiltered list. + search: url.searchParams.get("search") || undefined, + })) ?? { trajectories: [], total: 0 }; + const trajectories = shouldResolveRooms + ? await Promise.all( + result.trajectories.map(async (item) => + listItemToUi( + item, + await resolveRoomContext( + runtime, + item.roomId ?? metadataRoomId(item.metadata), + roomCache, + ), + ), + ), + ) + : result.trajectories.map((item) => listItemToUi(item)); + sendJson(res, 200, { + trajectories, + total: result.total, + offset, + limit, + }); + return true; + } + // detail + const traj = detailId + ? await service.getTrajectoryDetail?.(detailId) + : null; + if (!traj) { + sendJson(res, 404, { error: `Trajectory "${detailId}" not found` }); + return true; + } + sendJson( + res, + 200, + detailToUi( + traj, + shouldResolveRooms + ? await resolveRoomContext( + runtime, + metadataRoomId(traj.metadata), + roomCache, + ) + : null, + ), + ); + return true; + } catch (err) { + sendJson(res, 500, { + error: err instanceof Error ? err.message : "Trajectory read failed", + }); + return true; + } } diff --git a/packages/core/src/services/trajectories.ts b/packages/core/src/services/trajectories.ts index 743755ec70d7f..bffef4d85435f 100644 --- a/packages/core/src/services/trajectories.ts +++ b/packages/core/src/services/trajectories.ts @@ -1,5 +1,5 @@ -export { TrajectoriesService } from "../features/trajectories/TrajectoriesService"; export { tryHandleTrajectoryReadRoutes } from "../features/trajectories/read-routes"; +export { TrajectoriesService } from "../features/trajectories/TrajectoriesService"; export * from "./trajectory-export"; export * from "./trajectory-types"; diff --git a/packages/ui/src/cloud/admin/admin-role.test.ts b/packages/ui/src/cloud/admin/admin-role.test.ts index ce87a0dadf7c0..70bf68948c840 100644 --- a/packages/ui/src/cloud/admin/admin-role.test.ts +++ b/packages/ui/src/cloud/admin/admin-role.test.ts @@ -36,9 +36,7 @@ describe("adminRoleRank", () => { expect(adminRoleRank("super_admin")).toBeGreaterThan( adminRoleRank("moderator"), ); - expect(adminRoleRank("moderator")).toBeGreaterThan( - adminRoleRank("viewer"), - ); + expect(adminRoleRank("moderator")).toBeGreaterThan(adminRoleRank("viewer")); }); it("ranks unknown/null below every real tier", () => { diff --git a/packages/ui/src/cloud/shell/cloud-route-gate.test.tsx b/packages/ui/src/cloud/shell/cloud-route-gate.test.tsx index 4cd7c2ab27b52..9a27dd83e9c7a 100644 --- a/packages/ui/src/cloud/shell/cloud-route-gate.test.tsx +++ b/packages/ui/src/cloud/shell/cloud-route-gate.test.tsx @@ -24,24 +24,20 @@ describe("applyRouteGate", () => {
{children}
), ); - render( - <>{applyRouteGate("test-gate",
)}, - ); + render(applyRouteGate("test-gate",
)); // The body carried no gate of its own, yet the shell applied one. expect(screen.getByTestId("gate-wrapper")).toBeTruthy(); expect(screen.getByTestId("ungated-body")).toBeTruthy(); }); it("fails closed when a declared gate has no registered implementation", () => { - render( - <>{applyRouteGate("no-such-gate",
)}, - ); + render(applyRouteGate("no-such-gate",
)); expect(screen.queryByTestId("secret-body")).toBeNull(); expect(screen.getByText("Access unavailable")).toBeTruthy(); }); it("renders the body ungated when no gate is declared", () => { - render(<>{applyRouteGate(undefined,
)}); + render(applyRouteGate(undefined,
)); expect(screen.getByTestId("public-body")).toBeTruthy(); }); }); diff --git a/packages/ui/src/components/apps/helpers-catalog-curation.test.ts b/packages/ui/src/components/apps/helpers-catalog-curation.test.ts index 3b7eaa3da894b..e50efffa1a883 100644 --- a/packages/ui/src/components/apps/helpers-catalog-curation.test.ts +++ b/packages/ui/src/components/apps/helpers-catalog-curation.test.ts @@ -74,7 +74,9 @@ describe("groupAppsForCatalog — declared curation", () => { category: "game", }), ]); - const byKey = new Map(sections.map((s) => [s.key, s.apps.map((a) => a.name)])); + const byKey = new Map( + sections.map((s) => [s.key, s.apps.map((a) => a.name)]), + ); expect(byKey.get("featured")).toEqual(["@elizaos/plugin-featured"]); expect(byKey.get("finance")).toEqual(["@elizaos/plugin-money"]); }); diff --git a/packages/ui/src/components/apps/helpers.ts b/packages/ui/src/components/apps/helpers.ts index a1495baa9d233..3dbe3b2c0b67e 100644 --- a/packages/ui/src/components/apps/helpers.ts +++ b/packages/ui/src/components/apps/helpers.ts @@ -178,7 +178,12 @@ export function isCuratedGameApp( export function shouldShowAppInAppsView( app: Pick< RegistryAppInfo, - "category" | "name" | "catalogSection" | "featured" | "defaultHidden" | "scope" + | "category" + | "name" + | "catalogSection" + | "featured" + | "defaultHidden" + | "scope" >, options: { isProd?: boolean; @@ -338,7 +343,12 @@ export function getDefaultAppsCatalogSelection( export function getAppCatalogSectionKey( app: Pick< RegistryAppInfo, - "name" | "displayName" | "description" | "category" | "catalogSection" | "featured" + | "name" + | "displayName" + | "description" + | "category" + | "catalogSection" + | "featured" >, ): AppCatalogSectionKey { if (isFeaturedApp(app)) { @@ -400,7 +410,12 @@ export function getAppCatalogSectionKey( export function getAppCatalogSectionLabel( app: Pick< RegistryAppInfo, - "name" | "displayName" | "description" | "category" | "catalogSection" | "featured" + | "name" + | "displayName" + | "description" + | "category" + | "catalogSection" + | "featured" >, ): string { return APP_CATALOG_SECTION_LABELS[getAppCatalogSectionKey(app)]; diff --git a/packages/ui/src/components/settings/ApiKeyConfig.tsx b/packages/ui/src/components/settings/ApiKeyConfig.tsx index bfc5d7bd2ee30..5cc1d5a03bc39 100644 --- a/packages/ui/src/components/settings/ApiKeyConfig.tsx +++ b/packages/ui/src/components/settings/ApiKeyConfig.tsx @@ -18,9 +18,9 @@ import { API_KEY_PREFIX_HINTS } from "../../config/api-key-prefix-hints"; import type { JsonSchemaObject } from "../../config/config-catalog"; import { useTimeout } from "../../hooks/useTimeout"; import { useAppSelector } from "../../state"; -import { OwnerOnlyNotice, RoleGate } from "../RoleGate"; import type { ConfigUiHint } from "../../types"; import { autoLabel } from "../../utils/labels"; +import { OwnerOnlyNotice, RoleGate } from "../RoleGate"; import { SettingsActionButton } from "./settings-agent-rows"; import { AdvancedSettingsDisclosure } from "./settings-control-primitives"; diff --git a/packages/ui/src/genui/genui-action-registry.test.ts b/packages/ui/src/genui/genui-action-registry.test.ts index baa11c30993e4..bf2dfe513aa0a 100644 --- a/packages/ui/src/genui/genui-action-registry.test.ts +++ b/packages/ui/src/genui/genui-action-registry.test.ts @@ -72,11 +72,9 @@ describe("routeElizaGenUiAction gate reads the registry", () => { return { ok: true }; }, ); - const result = await routeElizaGenUiAction( - act("regtest_plugin_e.go"), - {}, - [handler], - ); + const result = await routeElizaGenUiAction(act("regtest_plugin_e.go"), {}, [ + handler, + ]); expect(result).toEqual({ ok: true }); expect(seen).toEqual(["regtest_plugin_e.go"]); }); diff --git a/plugins/plugin-workflow/__tests__/unit/routes/workbench-todos.test.ts b/plugins/plugin-workflow/__tests__/unit/routes/workbench-todos.test.ts index b023f4d11134c..ed6e0951998c7 100644 --- a/plugins/plugin-workflow/__tests__/unit/routes/workbench-todos.test.ts +++ b/plugins/plugin-workflow/__tests__/unit/routes/workbench-todos.test.ts @@ -84,7 +84,7 @@ async function call( runtime: AgentRuntime, method: string, pathname: string, - body?: unknown, + body?: unknown ): Promise<{ handled: boolean; status: number; body: unknown }> { const { res, result } = createRes(); const handled = await handleWorkbenchTodosRoutes({ @@ -171,15 +171,9 @@ describe('workbench todos CRUD route', () => { const found = await call(store.runtime, 'GET', `/api/workbench/todos/${id}`); expect(found.status).toBe(200); - expect((found.body as { todo: { name: string } }).todo.name).toBe( - 'Read book', - ); - - const missing = await call( - store.runtime, - 'GET', - '/api/workbench/todos/does-not-exist', - ); + expect((found.body as { todo: { name: string } }).todo.name).toBe('Read book'); + + const missing = await call(store.runtime, 'GET', '/api/workbench/todos/does-not-exist'); expect(missing.status).toBe(404); expect((missing.body as { error: string }).error).toBe('Todo not found'); }); @@ -191,24 +185,18 @@ describe('workbench todos CRUD route', () => { }); const id = (created.body as { todo: { id: string } }).todo.id; - const updated = await call( - store.runtime, - 'PUT', - `/api/workbench/todos/${id}`, - { name: 'Renamed', priority: 5, isUrgent: true }, - ); + const updated = await call(store.runtime, 'PUT', `/api/workbench/todos/${id}`, { + name: 'Renamed', + priority: 5, + isUrgent: true, + }); expect(updated.status).toBe(200); const todo = (updated.body as { todo: Record }).todo; expect(todo.name).toBe('Renamed'); expect(todo.priority).toBe(5); expect(todo.isUrgent).toBe(true); - const blank = await call( - store.runtime, - 'PUT', - `/api/workbench/todos/${id}`, - { name: ' ' }, - ); + const blank = await call(store.runtime, 'PUT', `/api/workbench/todos/${id}`, { name: ' ' }); expect(blank.status).toBe(400); expect((blank.body as { error: string }).error).toBe('name cannot be empty'); }); @@ -219,26 +207,18 @@ describe('workbench todos CRUD route', () => { }); const id = (created.body as { todo: { id: string } }).todo.id; - const done = await call( - store.runtime, - 'POST', - `/api/workbench/todos/${id}/complete`, - { isCompleted: true }, - ); + const done = await call(store.runtime, 'POST', `/api/workbench/todos/${id}/complete`, { + isCompleted: true, + }); expect(done.status).toBe(200); expect((done.body as { ok: boolean }).ok).toBe(true); const after = await call(store.runtime, 'GET', `/api/workbench/todos/${id}`); - expect((after.body as { todo: { isCompleted: boolean } }).todo.isCompleted).toBe( - true, - ); - - const missing = await call( - store.runtime, - 'POST', - '/api/workbench/todos/nope/complete', - { isCompleted: true }, - ); + expect((after.body as { todo: { isCompleted: boolean } }).todo.isCompleted).toBe(true); + + const missing = await call(store.runtime, 'POST', '/api/workbench/todos/nope/complete', { + isCompleted: true, + }); expect(missing.status).toBe(404); }); @@ -248,20 +228,12 @@ describe('workbench todos CRUD route', () => { }); const id = (created.body as { todo: { id: string } }).todo.id; - const del = await call( - store.runtime, - 'DELETE', - `/api/workbench/todos/${id}`, - ); + const del = await call(store.runtime, 'DELETE', `/api/workbench/todos/${id}`); expect(del.status).toBe(200); expect((del.body as { ok: boolean }).ok).toBe(true); expect(store.tasks.has(id)).toBe(false); - const again = await call( - store.runtime, - 'DELETE', - `/api/workbench/todos/${id}`, - ); + const again = await call(store.runtime, 'DELETE', `/api/workbench/todos/${id}`); expect(again.status).toBe(404); }); diff --git a/plugins/plugin-workflow/src/routes/workbench-todos.ts b/plugins/plugin-workflow/src/routes/workbench-todos.ts index 2614ba240ae68..5f539e9c72274 100644 --- a/plugins/plugin-workflow/src/routes/workbench-todos.ts +++ b/plugins/plugin-workflow/src/routes/workbench-todos.ts @@ -13,13 +13,7 @@ */ import type http from 'node:http'; -import { - type AgentRuntime, - sendJson, - sendJsonError, - type Task, - type UUID, -} from '@elizaos/core'; +import { type AgentRuntime, sendJson, sendJsonError, type Task, type UUID } from '@elizaos/core'; import { PostWorkbenchTodoCompleteRequestSchema, PostWorkbenchTodoRequestSchema, @@ -73,7 +67,7 @@ function normalizeTags(value: unknown, required: string[] = []): string[] { function decodePathComponent( raw: string, res: http.ServerResponse, - fieldName: string, + fieldName: string ): string | null { try { return decodeURIComponent(raw); @@ -94,16 +88,12 @@ function readTodoMeta(task: Task): Record { export function toWorkbenchTodoView(task: Task): WorkbenchTodoView | null { if (!isWorkbenchTodoTask(task)) return null; - const id = - typeof task.id === 'string' && task.id.trim().length > 0 ? task.id : null; + const id = typeof task.id === 'string' && task.id.trim().length > 0 ? task.id : null; if (!id) return null; const todoMeta = readTodoMeta(task); return { id, - name: - typeof task.name === 'string' && task.name.trim().length > 0 - ? task.name - : 'Todo', + name: typeof task.name === 'string' && task.name.trim().length > 0 ? task.name : 'Todo', description: typeof todoMeta.description === 'string' ? todoMeta.description @@ -114,9 +104,7 @@ export function toWorkbenchTodoView(task: Task): WorkbenchTodoView | null { isUrgent: todoMeta.isUrgent === true, isCompleted: readTaskCompleted(task), type: - typeof todoMeta.type === 'string' && todoMeta.type.trim().length > 0 - ? todoMeta.type - : 'task', + typeof todoMeta.type === 'string' && todoMeta.type.trim().length > 0 ? todoMeta.type : 'task', }; } @@ -135,14 +123,11 @@ function readJsonObjectBody(req: http.IncomingMessage): Record * todos endpoint (and a response was written), `false` otherwise. */ export async function handleWorkbenchTodosRoutes( - ctx: WorkbenchTodosRouteContext, + ctx: WorkbenchTodosRouteContext ): Promise { const { req, res, method, pathname, runtime } = ctx; - if ( - pathname !== '/api/workbench/todos' && - !pathname.startsWith('/api/workbench/todos/') - ) { + if (pathname !== '/api/workbench/todos' && !pathname.startsWith('/api/workbench/todos/')) { return false; } @@ -167,15 +152,9 @@ export async function handleWorkbenchTodosRoutes( sendJsonError(res, 'Agent runtime is not available', 503); return true; } - const parsedTodo = PostWorkbenchTodoRequestSchema.safeParse( - readJsonObjectBody(req), - ); + const parsedTodo = PostWorkbenchTodoRequestSchema.safeParse(readJsonObjectBody(req)); if (!parsedTodo.success) { - sendJsonError( - res, - parsedTodo.error.issues[0]?.message ?? 'name is required', - 400, - ); + sendJsonError(res, parsedTodo.error.issues[0]?.message ?? 'name is required', 400); return true; } const body = parsedTodo.data; @@ -185,9 +164,7 @@ export async function handleWorkbenchTodosRoutes( const priority = parseNullableNumber(body.priority); const isUrgent = body.isUrgent === true; const type = - typeof body.type === 'string' && body.type.trim().length > 0 - ? body.type.trim() - : 'task'; + typeof body.type === 'string' && body.type.trim().length > 0 ? body.type.trim() : 'task'; const metadata = { isCompleted, @@ -216,29 +193,17 @@ export async function handleWorkbenchTodosRoutes( } // ── POST /api/workbench/todos/:id/complete ────────────────────────── - const todoCompleteMatch = /^\/api\/workbench\/todos\/([^/]+)\/complete$/.exec( - pathname, - ); + const todoCompleteMatch = /^\/api\/workbench\/todos\/([^/]+)\/complete$/.exec(pathname); if (method === 'POST' && todoCompleteMatch) { if (!runtime) { sendJsonError(res, 'Agent runtime is not available', 503); return true; } - const decodedTodoId = decodePathComponent( - todoCompleteMatch[1], - res, - 'todo id', - ); + const decodedTodoId = decodePathComponent(todoCompleteMatch[1], res, 'todo id'); if (!decodedTodoId) return true; - const parsedComp = PostWorkbenchTodoCompleteRequestSchema.safeParse( - readJsonObjectBody(req), - ); + const parsedComp = PostWorkbenchTodoCompleteRequestSchema.safeParse(readJsonObjectBody(req)); if (!parsedComp.success) { - sendJsonError( - res, - parsedComp.error.issues[0]?.message ?? 'Invalid request body', - 400, - ); + sendJsonError(res, parsedComp.error.issues[0]?.message ?? 'Invalid request body', 400); return true; } const isCompleted = parsedComp.data.isCompleted === true; @@ -296,15 +261,9 @@ export async function handleWorkbenchTodosRoutes( } // PUT - const parsedPut = PutWorkbenchTodoRequestSchema.safeParse( - readJsonObjectBody(req), - ); + const parsedPut = PutWorkbenchTodoRequestSchema.safeParse(readJsonObjectBody(req)); if (!parsedPut.success) { - sendJsonError( - res, - parsedPut.error.issues[0]?.message ?? 'Invalid request body', - 400, - ); + sendJsonError(res, parsedPut.error.issues[0]?.message ?? 'Invalid request body', 400); return true; } const body = parsedPut.data; From bdc5018394bf45282e6c7e7bc92b54c948ff627d Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 19:04:02 -0400 Subject: [PATCH 02/26] fix(cloud-api): align shared market shim --- packages/cloud/api/types/workspace-shims.d.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/cloud/api/types/workspace-shims.d.ts b/packages/cloud/api/types/workspace-shims.d.ts index d1b7722507dd1..ade0b150094d7 100644 --- a/packages/cloud/api/types/workspace-shims.d.ts +++ b/packages/cloud/api/types/workspace-shims.d.ts @@ -7,6 +7,18 @@ */ declare module "@elizaos/shared" { + export const COINGECKO_MARKET_PROVIDER: { + readonly providerId: "coingecko"; + readonly providerName: "CoinGecko"; + readonly providerUrl: "https://www.coingecko.com/"; + }; + + export const POLYMARKET_MARKET_PROVIDER: { + readonly providerId: "polymarket"; + readonly providerName: "Polymarket"; + readonly providerUrl: "https://polymarket.com/"; + }; + export interface CoinGeckoMarketRecord { id: string; symbol: string; @@ -73,29 +85,17 @@ declare module "@elizaos/shared" { predictions: WalletMarketPrediction[]; } - export const COINGECKO_MARKET_PROVIDER: { - providerId: "coingecko"; - providerName: "CoinGecko"; - providerUrl: "https://www.coingecko.com/"; - }; - - export const POLYMARKET_MARKET_PROVIDER: { - providerId: "polymarket"; - providerName: "Polymarket"; - providerUrl: "https://polymarket.com/"; - }; - export function buildCoinGeckoMarketsUrl(): URL; - export function buildMarketMovers( - markets: CoinGeckoMarketRecord[], - ): WalletMarketMover[]; + export function parseCoinGeckoMarkets( + payload: unknown, + ): CoinGeckoMarketRecord[]; export function buildMarketPriceSnapshots( markets: CoinGeckoMarketRecord[], ): WalletMarketPriceSnapshot[]; - export function parseCoinGeckoMarkets( - payload: unknown, - ): CoinGeckoMarketRecord[]; + export function buildMarketMovers( + markets: CoinGeckoMarketRecord[], + ): WalletMarketMover[]; } From c880fc87b45005b66fd3637c5d5ca173ef4971e7 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 19:17:29 -0400 Subject: [PATCH 03/26] chore(lint): fix rebased auth exports --- packages/agent/src/index.ts | 2 +- packages/app-core/src/services/account-pool.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index d290987ae02c8..f03bd48704512 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -58,6 +58,7 @@ export async function validateCloudBaseUrl( const { validateCloudBaseUrl } = await loadElizaCloudRoutes(); return validateCloudBaseUrl(value); } +export * from "@elizaos/auth"; export type { ElizaConfig, ReleaseChannel, RolesConfig } from "@elizaos/shared"; export { CONNECTOR_PLUGINS, @@ -178,7 +179,6 @@ export { export { getWalletAddresses, initStewardWalletCache } from "./api/wallet.ts"; export * from "./api/wallet-capability.ts"; export * from "./api/workbench-helpers.ts"; -export * from "@elizaos/auth"; export * from "./awareness/index.ts"; export { runBenchmark } from "./cli/benchmark.ts"; export { CharacterSchema } from "./config/character-schema.ts"; diff --git a/packages/app-core/src/services/account-pool.ts b/packages/app-core/src/services/account-pool.ts index a2dfc298f812a..ee2576020ae15 100644 --- a/packages/app-core/src/services/account-pool.ts +++ b/packages/app-core/src/services/account-pool.ts @@ -35,7 +35,6 @@ import { DIRECT_ACCOUNT_PROVIDER_IDS, type DirectAccountProvider, isSubscriptionProvider, - type SubscriptionProvider, } from "@elizaos/auth/types"; import { type AnthropicAccountPoolBridge, From b9393c4cb3c9354503d3c379e8c2249db81bee43 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 19:34:38 -0400 Subject: [PATCH 04/26] chore(build): ignore auth source declaration emit --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 9e8a7493b924c..e94c363d2edab 100644 --- a/.gitignore +++ b/.gitignore @@ -109,6 +109,8 @@ packages/cloud/sdk/src/**/*.js packages/cloud/sdk/src/**/*.js.map packages/cloud/sdk/src/**/*.d.ts packages/cloud/sdk/src/**/*.d.ts.map +packages/auth/src/**/*.d.ts +packages/auth/src/**/*.d.ts.map packages/plugin-remote-manifest/src/**/*.js packages/plugin-remote-manifest/src/**/*.js.map packages/plugin-remote-manifest/src/**/*.d.ts From a33fc6e9e74731257c40e0f5116515391fff2021 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 19:53:41 -0400 Subject: [PATCH 05/26] chore(lint): sort control transport test imports --- .../src/features/basic-capabilities/control-transport.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/features/basic-capabilities/control-transport.test.ts b/packages/core/src/features/basic-capabilities/control-transport.test.ts index a2676233de91f..d23e337972d7a 100644 --- a/packages/core/src/features/basic-capabilities/control-transport.test.ts +++ b/packages/core/src/features/basic-capabilities/control-transport.test.ts @@ -1,12 +1,12 @@ import { describe, expect, it, vi } from "vitest"; import { AgentRuntime } from "../../runtime.ts"; +import type { UUID } from "../../types/primitives.ts"; import type { IAgentRuntime } from "../../types/runtime.ts"; import { Service, ServiceType } from "../../types/service.ts"; import type { ControlTransportMessage, IControlTransportService, } from "../../types/service-interfaces.ts"; -import type { UUID } from "../../types/primitives.ts"; import { createBasicCapabilitiesPlugin } from "./index.ts"; describe("basic capabilities control transport", () => { From 037c143dfd10747ed7b87b325389e60d11116a30 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 20:13:49 -0400 Subject: [PATCH 06/26] fix(calendar): keep meeting helpers bundle-local --- .../components/calendar/CalendarView.test.tsx | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/plugins/plugin-calendar/src/components/calendar/CalendarView.test.tsx b/plugins/plugin-calendar/src/components/calendar/CalendarView.test.tsx index d386bebd10af8..9ca5f4537f475 100644 --- a/plugins/plugin-calendar/src/components/calendar/CalendarView.test.tsx +++ b/plugins/plugin-calendar/src/components/calendar/CalendarView.test.tsx @@ -16,7 +16,7 @@ import type { LifeOpsCalendarEvent } from "@elizaos/shared"; import { SpatialSurface } from "@elizaos/ui/spatial"; -import { cleanup, fireEvent, render } from "@testing-library/react"; +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { UseCalendarWeekResult } from "../../hooks/useCalendarWeek.js"; @@ -56,6 +56,13 @@ function agent(agentId: string): HTMLElement { return el as HTMLElement; } +function installPointerCaptureShim() { + HTMLElement.prototype.hasPointerCapture ??= () => false; + HTMLElement.prototype.setPointerCapture ??= () => undefined; + HTMLElement.prototype.releasePointerCapture ??= () => undefined; + Element.prototype.scrollIntoView ??= () => undefined; +} + function evt( over: Partial & { id: string }, ): LifeOpsCalendarEvent { @@ -106,6 +113,7 @@ function makeResult( describe("CalendarView (unified spatial wrapper)", () => { beforeEach(() => { + installPointerCaptureShim(); vi.clearAllMocks(); calendarState.current = makeResult(); }); @@ -160,11 +168,17 @@ describe("CalendarView (unified spatial wrapper)", () => { expect(goToToday).toHaveBeenCalledTimes(1); }); - it("changing the view-mode selector routes through to setViewMode", () => { + it("changing the view-mode selector routes through to setViewMode", async () => { render(); - fireEvent.change(agent("mode") as HTMLSelectElement, { - target: { value: "month" }, + fireEvent.pointerDown(agent("mode"), { + button: 0, + ctrlKey: false, + pageX: 1, + pageY: 1, + pointerId: 1, + pointerType: "mouse", }); + fireEvent.click(await screen.findByText("month")); expect(setViewMode).toHaveBeenCalledWith("month"); }); From 46ddbf51e08ef232a9ae87542bd89bac8d2ecf1d Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 20:13:53 -0400 Subject: [PATCH 07/26] chore(hyperliquid): refresh generated plugin entry --- plugins/plugin-hyperliquid/src/plugin.js | 1 + plugins/plugin-hyperliquid/src/plugin.js.map | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/plugin-hyperliquid/src/plugin.js b/plugins/plugin-hyperliquid/src/plugin.js index 24853447ecc8c..1883cf9aa9bcf 100644 --- a/plugins/plugin-hyperliquid/src/plugin.js +++ b/plugins/plugin-hyperliquid/src/plugin.js @@ -115,6 +115,7 @@ export const hyperliquidPlugin = { bundlePath: "dist/views/bundle.js", componentExport: "HyperliquidView", tags: ["trading", "perps", "hyperliquid", "crypto"], + relatedActions: ["PERPETUAL_MARKET"], // Reached as a sub-view of Wallet (WalletSectionNav), not a launcher tile. visibleInManager: false, desktopTabEnabled: false, diff --git a/plugins/plugin-hyperliquid/src/plugin.js.map b/plugins/plugin-hyperliquid/src/plugin.js.map index 77fcbb184662d..e6ff235d75abe 100644 --- a/plugins/plugin-hyperliquid/src/plugin.js.map +++ b/plugins/plugin-hyperliquid/src/plugin.js.map @@ -1 +1 @@ -{"version":3,"file":"plugin.js","sourceRoot":"","sources":["plugin.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAElD,SAAS,qBAAqB,CAAC,GAAiB;IAC9C,IACE,OAAO,GAAG,KAAK,QAAQ;QACvB,GAAG,KAAK,IAAI;QACZ,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;QAC9B,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAC/B,CAAC;QACD,MAAM,IAAI,SAAS,CAAC,gDAAgD,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,GAA2B,CAAC;AACrC,CAAC;AAED,SAAS,oBAAoB,CAAC,GAAkB;IAC9C,IACE,OAAO,GAAG,KAAK,QAAQ;QACvB,GAAG,KAAK,IAAI;QACZ,OAAO,GAAG,CAAC,GAAG,KAAK,UAAU;QAC7B,OAAO,GAAG,CAAC,SAAS,KAAK,UAAU,EACnC,CAAC;QACD,MAAM,IAAI,SAAS,CAAC,iDAAiD,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,GAAqC,CAAC;AAC/C,CAAC;AAED,SAAS,uBAAuB,CAC9B,QAAgB;IAEhB,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACxB,MAAM,OAAO,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;QACvD,MAAM,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACnE,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,iBAAiB,GAAY;IACjC;QACE,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,yBAAyB,CAAC;KAC5D;IACD;QACE,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,0BAA0B;QAChC,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,0BAA0B,CAAC;KAC7D;IACD;QACE,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,0BAA0B;QAChC,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,0BAA0B,CAAC;KAC7D;IACD;QACE,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,4BAA4B;QAClC,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,4BAA4B,CAAC;KAC/D;IACD;QACE,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,yBAAyB,CAAC;KAC5D;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,8BAA8B;QACpC,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,8BAA8B,CAAC;KACjE;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,+BAA+B;QACrC,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,+BAA+B,CAAC;KAClE;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,2BAA2B;QACjC,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,2BAA2B,CAAC;KAC9D;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,yBAAyB,CAAC;KAC5D;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,yBAAyB,CAAC;KAC5D;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,uBAAuB;QAC7B,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,uBAAuB,CAAC;KAC1D;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAW;IACvC,IAAI,EAAE,6BAA6B;IACnC,WAAW,EACT,gHAAgH;IAClH,OAAO,EAAE,kBAAkB;IAC3B,QAAQ,EAAE,CAAC,sBAAsB,CAAC;IAClC,MAAM,EAAE,iBAAiB;IACzB,KAAK,EAAE;QACL,8DAA8D;QAC9D,uEAAuE;QACvE,yEAAyE;QACzE,2CAA2C;QAC3C;YACE,EAAE,EAAE,aAAa;YACjB,KAAK,EAAE,aAAa;YACpB,WAAW,EACT,4EAA4E;YAC9E,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,cAAc;YACpB,UAAU,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC;YAChC,UAAU,EAAE,sBAAsB;YAClC,eAAe,EAAE,iBAAiB;YAClC,IAAI,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC;YACnD,2EAA2E;YAC3E,gBAAgB,EAAE,KAAK;YACvB,iBAAiB,EAAE,KAAK;SACzB;KACF;IACD,KAAK,CAAC,OAAO,CAAC,OAAsB;QAClC,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,CAC5B,6BAA6B,CAC9B,CAAC;QACF,MAAM,GAAG,EAAE,IAAI,EAAE,CAAC;IACpB,CAAC;CACF,CAAC"} \ No newline at end of file +{"version":3,"file":"plugin.js","sourceRoot":"","sources":["plugin.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,kBAAkB,EAClB,6BAA6B,EAC7B,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAElD,SAAS,qBAAqB,CAAC,GAAiB;IAC9C,IACE,OAAO,GAAG,KAAK,QAAQ;QACvB,GAAG,KAAK,IAAI;QACZ,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;QAC9B,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAC/B,CAAC;QACD,MAAM,IAAI,SAAS,CAAC,gDAAgD,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,GAA2B,CAAC;AACrC,CAAC;AAED,SAAS,oBAAoB,CAAC,GAAkB;IAC9C,IACE,OAAO,GAAG,KAAK,QAAQ;QACvB,GAAG,KAAK,IAAI;QACZ,OAAO,GAAG,CAAC,GAAG,KAAK,UAAU;QAC7B,OAAO,GAAG,CAAC,SAAS,KAAK,UAAU,EACnC,CAAC;QACD,MAAM,IAAI,SAAS,CAAC,iDAAiD,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,GAAqC,CAAC;AAC/C,CAAC;AAED,SAAS,uBAAuB,CAC9B,QAAgB;IAEhB,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACxB,MAAM,OAAO,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC3C,MAAM,OAAO,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;QACvD,MAAM,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACnE,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,iBAAiB,GAAY;IACjC;QACE,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,yBAAyB,CAAC;KAC5D;IACD;QACE,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,0BAA0B;QAChC,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,0BAA0B,CAAC;KAC7D;IACD;QACE,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,0BAA0B;QAChC,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,0BAA0B,CAAC;KAC7D;IACD;QACE,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,4BAA4B;QAClC,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,4BAA4B,CAAC;KAC/D;IACD;QACE,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,yBAAyB,CAAC;KAC5D;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,8BAA8B;QACpC,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,8BAA8B,CAAC;KACjE;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,+BAA+B;QACrC,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,+BAA+B,CAAC;KAClE;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,2BAA2B;QACjC,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,2BAA2B,CAAC;KAC9D;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,yBAAyB,CAAC;KAC5D;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,yBAAyB,CAAC;KAC5D;IACD;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,uBAAuB;QAC7B,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,uBAAuB,CAAC,uBAAuB,CAAC;KAC1D;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAW;IACvC,IAAI,EAAE,6BAA6B;IACnC,WAAW,EACT,gHAAgH;IAClH,OAAO,EAAE,kBAAkB;IAC3B,QAAQ,EAAE,CAAC,sBAAsB,CAAC;IAClC,MAAM,EAAE,iBAAiB;IACzB,KAAK,EAAE;QACL,8DAA8D;QAC9D,uEAAuE;QACvE,yEAAyE;QACzE,2CAA2C;QAC3C;YACE,EAAE,EAAE,aAAa;YACjB,KAAK,EAAE,aAAa;YACpB,WAAW,EACT,4EAA4E;YAC9E,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,cAAc;YACpB,UAAU,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC;YAChC,UAAU,EAAE,sBAAsB;YAClC,eAAe,EAAE,iBAAiB;YAClC,IAAI,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC;YACnD,cAAc,EAAE,CAAC,kBAAkB,CAAC;YACpC,2EAA2E;YAC3E,gBAAgB,EAAE,KAAK;YACvB,iBAAiB,EAAE,KAAK;SACzB;KACF;IACD,KAAK,CAAC,OAAO,CAAC,OAAsB;QAClC,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,CAC5B,6BAA6B,CAC9B,CAAC;QACF,MAAM,GAAG,EAAE,IAAI,EAAE,CAAC;IACpB,CAAC;CACF,CAAC"} \ No newline at end of file From 76a2f6a32130de90df8b9d6b39b765fa5a0bcf81 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 20:26:05 -0400 Subject: [PATCH 08/26] fix(app-core): skip recursive runtime package symlinks --- .../assert-required-bundled-packages.test.ts | 29 ++++++++++++++++++- .../scripts/copy-runtime-node-modules.ts | 4 ++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/app-core/scripts/assert-required-bundled-packages.test.ts b/packages/app-core/scripts/assert-required-bundled-packages.test.ts index b97e6656fd5f3..49407327d4f79 100644 --- a/packages/app-core/scripts/assert-required-bundled-packages.test.ts +++ b/packages/app-core/scripts/assert-required-bundled-packages.test.ts @@ -12,7 +12,7 @@ */ import { execFileSync } from "node:child_process"; -import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; +import { mkdirSync, mkdtempSync, symlinkSync, writeFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; @@ -317,6 +317,33 @@ describe("assertRequiredBundledPackagesLanded", () => { expect(shouldCopyPackageEntry(uiReadme, "@elizaos/ui", uiRoot)).toBe(false); }); + it("skips Bun package self-links before dereferenced desktop copies", () => { + const packageRoot = path.join( + tmpDir, + ".bun", + "lucide-react@1.18.0", + "node_modules", + "lucide-react", + ); + const rootPackageLink = path.join(nodeModulesDir, "lucide-react"); + const selfLink = path.join(packageRoot, "lucide-react"); + const externalAssets = path.join(tmpDir, "external-assets"); + const externalAssetsLink = path.join(packageRoot, "external-assets"); + + mkdirSync(packageRoot, { recursive: true }); + mkdirSync(externalAssets, { recursive: true }); + symlinkSync(packageRoot, rootPackageLink, "dir"); + symlinkSync(rootPackageLink, selfLink, "dir"); + symlinkSync(externalAssets, externalAssetsLink, "dir"); + + expect(shouldCopyPackageEntry(selfLink, "lucide-react", packageRoot)).toBe( + false, + ); + expect( + shouldCopyPackageEntry(externalAssetsLink, "lucide-react", packageRoot), + ).toBe(true); + }); + it("uses the top-level Octokit peer for git-workspace-service", () => { expect( shouldSkipPackagedDependency("git-workspace-service", "@octokit/rest"), diff --git a/packages/app-core/scripts/copy-runtime-node-modules.ts b/packages/app-core/scripts/copy-runtime-node-modules.ts index a19c29006e182..75b6d7516070e 100644 --- a/packages/app-core/scripts/copy-runtime-node-modules.ts +++ b/packages/app-core/scripts/copy-runtime-node-modules.ts @@ -1566,8 +1566,10 @@ function isRecursivePackageSymlinkTarget( resolvedTarget: string, ): boolean { let targetStats: fs.Stats; + let realTarget: string; try { targetStats = fs.statSync(resolvedTarget); + realTarget = fs.realpathSync.native(resolvedTarget); } catch { return true; } @@ -1576,7 +1578,7 @@ function isRecursivePackageSymlinkTarget( return false; } - const relative = path.relative(resolvedTarget, entry); + const relative = path.relative(realTarget, entry); return ( relative === "" || (Boolean(relative) && From c56402ee6a2e8f4973c3ded374b3c1e66ce2b2ad Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 20:29:42 -0400 Subject: [PATCH 09/26] chore(lint): sort rebased connector imports --- plugins/plugin-discord/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/plugin-discord/index.ts b/plugins/plugin-discord/index.ts index dcffbd4e019b7..659b9e785daff 100644 --- a/plugins/plugin-discord/index.ts +++ b/plugins/plugin-discord/index.ts @@ -20,10 +20,10 @@ import { registerDiscordTargetSource } from "./discord-target-source"; import { DiscordOwnerPairingServiceImpl } from "./owner-pairing-service"; import { getPermissionValues } from "./permissions"; import { registerDiscordDmSensitiveRequestAdapter } from "./sensitive-request-adapter"; -import { registerDiscordTriageAdapter } from "./triage-adapter"; import { DiscordService } from "./service"; import { discordSetupRoutes } from "./setup-routes"; import { DiscordTestSuite } from "./tests"; +import { registerDiscordTriageAdapter } from "./triage-adapter"; import { DiscordUserAccountScraperImpl } from "./user-account-scraper/service"; const discordPlugin: Plugin = { From b691d6a3bfd59c12d75b6c8384091906361b71dc Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 20:32:21 -0400 Subject: [PATCH 10/26] chore(lint): format rebased messaging adapters --- .../__tests__/adapter-registration.test.ts | 6 +-- plugins/plugin-imessage/src/triage-adapter.ts | 43 +++++++++---------- .../src/confidential/cove-quote-x509.test.ts | 2 +- .../src/confidential/cove-quote.test.ts | 2 +- .../src/confidential/dstack-tee-provider.ts | 2 +- plugins/plugin-tee/src/index.ts | 16 +++---- plugins/plugin-todos/test/single-home.test.ts | 14 +++--- 7 files changed, 41 insertions(+), 44 deletions(-) diff --git a/packages/core/src/features/messaging/triage/__tests__/adapter-registration.test.ts b/packages/core/src/features/messaging/triage/__tests__/adapter-registration.test.ts index 4f69c2abec4f5..4c54adcf5ce87 100644 --- a/packages/core/src/features/messaging/triage/__tests__/adapter-registration.test.ts +++ b/packages/core/src/features/messaging/triage/__tests__/adapter-registration.test.ts @@ -6,11 +6,7 @@ import { __resetDefaultTriageServiceForTests, getDefaultTriageService, } from "../triage-service.ts"; -import type { - ListOptions, - MessageRef, - MessageSource, -} from "../types.ts"; +import type { ListOptions, MessageRef, MessageSource } from "../types.ts"; import { createFakeRuntime } from "./fake-runtime.ts"; /** diff --git a/plugins/plugin-imessage/src/triage-adapter.ts b/plugins/plugin-imessage/src/triage-adapter.ts index 1985e3c31a276..294423b6d6f3c 100644 --- a/plugins/plugin-imessage/src/triage-adapter.ts +++ b/plugins/plugin-imessage/src/triage-adapter.ts @@ -1,9 +1,9 @@ import { - BaseMessageAdapter, - getDefaultTriageService, - type IAgentRuntime, - type MessageAdapterCapabilities, - type MessageSource, + BaseMessageAdapter, + getDefaultTriageService, + type IAgentRuntime, + type MessageAdapterCapabilities, + type MessageSource, } from "@elizaos/core"; /** @@ -14,27 +14,24 @@ import { * wires them up. */ export class IMessageMessageAdapter extends BaseMessageAdapter { - readonly source: MessageSource = "imessage"; + readonly source: MessageSource = "imessage"; - isAvailable(runtime: IAgentRuntime): boolean { - return ( - runtime.getService("imessage") != null || - runtime.getService("bluebubbles") != null - ); - } + isAvailable(runtime: IAgentRuntime): boolean { + return runtime.getService("imessage") != null || runtime.getService("bluebubbles") != null; + } - capabilities(): MessageAdapterCapabilities { - return { - list: false, - search: false, - manage: {}, - send: {}, - worlds: "single", - channels: "implicit", - }; - } + capabilities(): MessageAdapterCapabilities { + return { + list: false, + search: false, + manage: {}, + send: {}, + worlds: "single", + channels: "implicit", + }; + } } export function registerIMessageTriageAdapter(): void { - getDefaultTriageService().register(new IMessageMessageAdapter()); + getDefaultTriageService().register(new IMessageMessageAdapter()); } diff --git a/plugins/plugin-tee/src/confidential/cove-quote-x509.test.ts b/plugins/plugin-tee/src/confidential/cove-quote-x509.test.ts index ec978cccac2ce..0089b2690370b 100644 --- a/plugins/plugin-tee/src/confidential/cove-quote-x509.test.ts +++ b/plugins/plugin-tee/src/confidential/cove-quote-x509.test.ts @@ -10,6 +10,7 @@ import { generateKeyPairSync, type KeyObject, } from "node:crypto"; +import { evaluateTeeEvidencePolicy } from "@elizaos/agent/services/tee-policy"; import { describe, expect, it } from "vitest"; import { coveX509ToTeeEvidence, @@ -19,7 +20,6 @@ import { TCG_DICE_TCB_INFO_OID, verifyCoveX509Chain, } from "./cove-quote-x509.ts"; -import { evaluateTeeEvidencePolicy } from "@elizaos/agent/services/tee-policy"; /** * Real Salus CoVE evidence certificate, captured from the COVG diff --git a/plugins/plugin-tee/src/confidential/cove-quote.test.ts b/plugins/plugin-tee/src/confidential/cove-quote.test.ts index 0d2e2b8139ca7..80ccc9118d52c 100644 --- a/plugins/plugin-tee/src/confidential/cove-quote.test.ts +++ b/plugins/plugin-tee/src/confidential/cove-quote.test.ts @@ -12,6 +12,7 @@ import { hkdfSync, type KeyObject, } from "node:crypto"; +import { evaluateTeeEvidencePolicy } from "@elizaos/agent/services/tee-policy"; import { describe, expect, it } from "vitest"; import { type CoveClaims, @@ -25,7 +26,6 @@ import { expectedReportData, verifyCoveQuote, } from "./cove-quote.ts"; -import { evaluateTeeEvidencePolicy } from "@elizaos/agent/services/tee-policy"; /** * Local DICE key ceremony for tests. This is the reference vector the silicon diff --git a/plugins/plugin-tee/src/confidential/dstack-tee-provider.ts b/plugins/plugin-tee/src/confidential/dstack-tee-provider.ts index 79487744f366b..ad34b616fda65 100644 --- a/plugins/plugin-tee/src/confidential/dstack-tee-provider.ts +++ b/plugins/plugin-tee/src/confidential/dstack-tee-provider.ts @@ -8,11 +8,11 @@ // biome-ignore-all assist/source/organizeImports: preserve current import order in this comments-only header follow-up. import { timingSafeEqual } from "node:crypto"; import { readFile } from "node:fs/promises"; -import { coveQuoteToTeeEvidence, verifyCoveQuote } from "./cove-quote.ts"; import { normalizeTeeEvidence, type TeeEvidence, } from "@elizaos/agent/services/tee-evidence"; +import { coveQuoteToTeeEvidence, verifyCoveQuote } from "./cove-quote.ts"; const DEFAULT_DSTACK_EVIDENCE_PATHS = [ "/run/dstack/tee-evidence.json", diff --git a/plugins/plugin-tee/src/index.ts b/plugins/plugin-tee/src/index.ts index 564a7958440a2..46e7088685071 100644 --- a/plugins/plugin-tee/src/index.ts +++ b/plugins/plugin-tee/src/index.ts @@ -10,6 +10,14 @@ import { type IAgentRuntime, logger, type Plugin } from "@elizaos/core"; import { TEEService } from "./services/tee"; import { getVendor, TeeVendorNames } from "./vendors"; +// Confidential-VM (dstack/CoVE) TEE deployment surface. Registers the host +// boot-gate evidence provider through the `@elizaos/agent` seam. Kept isolated +// from the Phala vendor surface above; the two TEE providers do not tangle. +export { + createDstackTeeProvider, + dstackConfidentialTeePlugin, + registerDstackEvidenceProvider, +} from "./confidential"; export { DeriveKeyProvider, PhalaDeriveKeyProvider, @@ -19,14 +27,6 @@ export { RemoteAttestationProvider, } from "./providers"; export { TEEService } from "./services"; -// Confidential-VM (dstack/CoVE) TEE deployment surface. Registers the host -// boot-gate evidence provider through the `@elizaos/agent` seam. Kept isolated -// from the Phala vendor surface above; the two TEE providers do not tangle. -export { - createDstackTeeProvider, - dstackConfidentialTeePlugin, - registerDstackEvidenceProvider, -} from "./confidential"; export * from "./types"; export { calculateSHA256, diff --git a/plugins/plugin-todos/test/single-home.test.ts b/plugins/plugin-todos/test/single-home.test.ts index de8ba8b7a47f2..42742d357a5a3 100644 --- a/plugins/plugin-todos/test/single-home.test.ts +++ b/plugins/plugin-todos/test/single-home.test.ts @@ -41,14 +41,18 @@ describe("todos single-home: core no longer bakes todos into advanced-capabiliti }); it("advancedServices contains no todos service", () => { - expect(advancedServices.some((s) => isTodoName(String(s.serviceType)))).toBe( - false, - ); + expect( + advancedServices.some((s) => isTodoName(String(s.serviceType))), + ).toBe(false); }); it("the advancedCapabilities flag path registers no todos provider or service", () => { - const plugin = createBasicCapabilitiesPlugin({ advancedCapabilities: true }); - expect((plugin.providers ?? []).some((p) => isTodoName(p.name))).toBe(false); + const plugin = createBasicCapabilitiesPlugin({ + advancedCapabilities: true, + }); + expect((plugin.providers ?? []).some((p) => isTodoName(p.name))).toBe( + false, + ); expect( (plugin.services ?? []).some((s) => isTodoName(String(s.serviceType))), ).toBe(false); From 9181ef447143ae55de20bc3988014c882e103f04 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 20:52:15 -0400 Subject: [PATCH 11/26] chore(tee): fix confidential import specifiers --- plugins/plugin-tee/src/confidential/cove-quote.ts | 2 +- .../plugin-tee/src/confidential/dstack-tee-provider.ts | 2 +- plugins/plugin-tee/src/confidential/index.ts | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/plugin-tee/src/confidential/cove-quote.ts b/plugins/plugin-tee/src/confidential/cove-quote.ts index 89b794fd21213..c59285aa04b62 100644 --- a/plugins/plugin-tee/src/confidential/cove-quote.ts +++ b/plugins/plugin-tee/src/confidential/cove-quote.ts @@ -693,4 +693,4 @@ export { SHA384_OID, TCG_DICE_TCB_INFO_OID, verifyCoveX509Chain, -} from "./cove-quote-x509.ts"; +} from "./cove-quote-x509"; diff --git a/plugins/plugin-tee/src/confidential/dstack-tee-provider.ts b/plugins/plugin-tee/src/confidential/dstack-tee-provider.ts index ad34b616fda65..3715a47c6d7db 100644 --- a/plugins/plugin-tee/src/confidential/dstack-tee-provider.ts +++ b/plugins/plugin-tee/src/confidential/dstack-tee-provider.ts @@ -12,7 +12,7 @@ import { normalizeTeeEvidence, type TeeEvidence, } from "@elizaos/agent/services/tee-evidence"; -import { coveQuoteToTeeEvidence, verifyCoveQuote } from "./cove-quote.ts"; +import { coveQuoteToTeeEvidence, verifyCoveQuote } from "./cove-quote"; const DEFAULT_DSTACK_EVIDENCE_PATHS = [ "/run/dstack/tee-evidence.json", diff --git a/plugins/plugin-tee/src/confidential/index.ts b/plugins/plugin-tee/src/confidential/index.ts index 72814ebff436a..76608ddbba0ba 100644 --- a/plugins/plugin-tee/src/confidential/index.ts +++ b/plugins/plugin-tee/src/confidential/index.ts @@ -6,11 +6,11 @@ */ import { registerTeeEvidenceProviderFactory } from "@elizaos/agent/services/tee-evidence-provider"; import { logger, type Plugin } from "@elizaos/core"; -import { createDstackTeeProvider } from "./dstack-tee-provider.ts"; +import { createDstackTeeProvider } from "./dstack-tee-provider"; -export * from "./cove-quote.ts"; -export * from "./cove-quote-x509.ts"; -export * from "./dstack-tee-provider.ts"; +export * from "./cove-quote"; +export * from "./cove-quote-x509"; +export * from "./dstack-tee-provider"; /** * Register the dstack/CoVE evidence provider with the host boot-gate seam From ea716ff00b3e1d3363c79f3e2941490820324edb Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 20:55:33 -0400 Subject: [PATCH 12/26] fix(blocker): include platform in website permission state --- plugins/plugin-blocker/src/native.test.ts | 1 + .../src/services/website-blocker/engine.ts | 23 ++++++++++++++++++- .../services/website-blocker/permissions.ts | 5 ++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/plugins/plugin-blocker/src/native.test.ts b/plugins/plugin-blocker/src/native.test.ts index b5569621c4e40..bc2122bc34539 100644 --- a/plugins/plugin-blocker/src/native.test.ts +++ b/plugins/plugin-blocker/src/native.test.ts @@ -71,6 +71,7 @@ function makeWebsiteBackend( status: "granted", lastChecked: 0, canRequest: false, + platform: "ios", }; return { getStatus: async () => status, diff --git a/plugins/plugin-blocker/src/services/website-blocker/engine.ts b/plugins/plugin-blocker/src/services/website-blocker/engine.ts index 6c10d4e1addf4..8a860f66d3ead 100644 --- a/plugins/plugin-blocker/src/services/website-blocker/engine.ts +++ b/plugins/plugin-blocker/src/services/website-blocker/engine.ts @@ -7,7 +7,11 @@ import path from "node:path"; import { domainToASCII } from "node:url"; import { promisify } from "node:util"; import type { HandlerOptions } from "@elizaos/core"; -import type { PermissionState, PermissionStatus } from "./permissions.ts"; +import type { + PermissionPlatform, + PermissionState, + PermissionStatus, +} from "./permissions.ts"; const BLOCK_START_MARKER = "# >>> eliza-selfcontrol >>>"; const BLOCK_END_MARKER = "# <<< eliza-selfcontrol <<<"; @@ -126,6 +130,22 @@ export interface SelfControlBlockPolicy { matchMode: SelfControlBlockMatchMode; } +function normalizeSelfControlPermissionPlatform( + platform: NodeJS.Platform | string, +): PermissionPlatform { + switch (platform) { + case "darwin": + case "win32": + case "linux": + case "ios": + case "android": + case "web": + return platform; + default: + return "web"; + } +} + export interface SelfControlBlockMetadata { version: 1; startedAt: string; @@ -586,6 +606,7 @@ export async function getSelfControlPermissionState( status: permissionStatus, lastChecked: Date.now(), canRequest, + platform: normalizeSelfControlPermissionPlatform(status.platform), reason: buildSelfControlPermissionReason(status, { prompted: false, promptSucceeded: false, diff --git a/plugins/plugin-blocker/src/services/website-blocker/permissions.ts b/plugins/plugin-blocker/src/services/website-blocker/permissions.ts index 368e9f5d9f8b3..a596747ec3a2d 100644 --- a/plugins/plugin-blocker/src/services/website-blocker/permissions.ts +++ b/plugins/plugin-blocker/src/services/website-blocker/permissions.ts @@ -1,3 +1,5 @@ +import type { Platform } from "@elizaos/shared"; + export type PermissionStatus = | "granted" | "denied" @@ -5,10 +7,13 @@ export type PermissionStatus = | "restricted" | "not-applicable"; +export type PermissionPlatform = Platform; + export interface PermissionState { id: "website-blocking"; status: PermissionStatus; lastChecked: number; canRequest: boolean; + platform: PermissionPlatform; reason?: string; } From 81b96fe063b596ee2c0ac1b991e38944577dc009 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 21:00:05 -0400 Subject: [PATCH 13/26] fix(websiteblocker): report native permission platform --- .../src/backend.test.ts | 7 +++- .../src/backend.ts | 36 +++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/plugins/plugin-native-websiteblocker/src/backend.test.ts b/plugins/plugin-native-websiteblocker/src/backend.test.ts index d688d2ad93bec..b21c1f3890d2e 100644 --- a/plugins/plugin-native-websiteblocker/src/backend.test.ts +++ b/plugins/plugin-native-websiteblocker/src/backend.test.ts @@ -176,13 +176,18 @@ describe("createNativeWebsiteBlockerBackend", () => { }); it("maps permission checks into the engine permission-state shape", async () => { - const backend = createNativeWebsiteBlockerBackend(makePlugin()); + const backend = createNativeWebsiteBlockerBackend( + makePlugin({ + getStatus: vi.fn(async () => makeStatus({ platform: "android" })), + }), + ); const permission = await backend.getPermissionState(); expect(permission.id).toBe("website-blocking"); expect(permission.status).toBe("granted"); expect(permission.canRequest).toBe(false); + expect(permission.platform).toBe("android"); expect(typeof permission.lastChecked).toBe("number"); }); }); diff --git a/plugins/plugin-native-websiteblocker/src/backend.ts b/plugins/plugin-native-websiteblocker/src/backend.ts index 76c7b0b13db01..aa0973c26123a 100644 --- a/plugins/plugin-native-websiteblocker/src/backend.ts +++ b/plugins/plugin-native-websiteblocker/src/backend.ts @@ -24,6 +24,13 @@ import type { // Node-only modules); the shapes are validated against the real interface by // the consumer that calls `registerNativeWebsiteBlockerBackend(...)`. type SelfControlMatchMode = "exact" | "subdomain"; +type SelfControlPermissionPlatform = + | "darwin" + | "win32" + | "linux" + | "ios" + | "android" + | "web"; interface SelfControlStatus { available: boolean; @@ -59,6 +66,7 @@ interface SelfControlPermissionState { status: "granted" | "denied" | "not-determined" | "not-applicable"; lastChecked: number; canRequest: boolean; + platform: SelfControlPermissionPlatform; reason?: string; hostsFilePath?: string | null; supportsElevationPrompt?: boolean; @@ -126,17 +134,35 @@ function toElevationMethod( function toSelfControlPermissionState( permission: WebsiteBlockerPermissionResult, + platform: string, ): SelfControlPermissionState { return { id: "website-blocking", status: permission.status, lastChecked: Date.now(), canRequest: permission.canRequest, + platform: toSelfControlPermissionPlatform(platform), reason: permission.reason, supportsElevationPrompt: permission.canRequest, }; } +function toSelfControlPermissionPlatform( + platform: string, +): SelfControlPermissionPlatform { + switch (platform) { + case "darwin": + case "win32": + case "linux": + case "ios": + case "android": + case "web": + return platform; + default: + return "web"; + } +} + /** * Wrap a `WebsiteBlockerPlugin` (pass the registered `WebsiteBlocker` Capacitor * plugin) as a `NativeWebsiteBlockerBackend`. @@ -168,10 +194,16 @@ export function createNativeWebsiteBlockerBackend( return { success: false, error: result.error, status }; }, async getPermissionState() { - return toSelfControlPermissionState(await plugin.checkPermissions()); + const [permission, status] = await Promise.all([ + plugin.checkPermissions(), + plugin.getStatus(), + ]); + return toSelfControlPermissionState(permission, status.platform); }, async requestPermission() { - return toSelfControlPermissionState(await plugin.requestPermissions()); + const permission = await plugin.requestPermissions(); + const status = await plugin.getStatus(); + return toSelfControlPermissionState(permission, status.platform); }, }; } From 30f164399407ed4479eef67c259a0ef37b777c50 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 21:05:33 -0400 Subject: [PATCH 14/26] chore(lockfile): sync trajectory logger workspace dependency --- bun.lock | 92 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/bun.lock b/bun.lock index 873235d908ef8..9f62108122dc2 100644 --- a/bun.lock +++ b/bun.lock @@ -5647,70 +5647,70 @@ }, }, "trustedDependencies": [ - "nx", - "esbuild", + "@elizaos/plugin-starter", + "utf-8-validate", "@swc/core", - "keccak", "protobufjs", - "sharp", - "utf-8-validate", - "bigint-buffer", - "bufferutil", + "keccak", "secp256k1", - "@elizaos/plugin-starter", + "bufferutil", "workerd", - "electron", + "esbuild", + "sharp", "@biomejs/biome", + "electron", + "nx", + "bigint-buffer", ], "patchedDependencies": { - "@solana/rpc@5.5.1": "patches/@solana%2Frpc@5.5.1.patch", - "@solana/sysvars@5.5.1": "patches/@solana%2Fsysvars@5.5.1.patch", - "@solana/rpc-spec@5.5.1": "patches/@solana%2Frpc-spec@5.5.1.patch", - "@solana/rpc-subscriptions@5.5.1": "patches/@solana%2Frpc-subscriptions@5.5.1.patch", - "@farcaster/quick-auth@0.0.8": "patches/@farcaster%2Fquick-auth@0.0.8.patch", "@solana/rpc-subscriptions-spec@5.5.1": "patches/@solana%2Frpc-subscriptions-spec@5.5.1.patch", - "@solana/rpc-types@5.5.1": "patches/@solana%2Frpc-types@5.5.1.patch", - "@solana/instruction-plans@5.5.1": "patches/@solana%2Finstruction-plans@5.5.1.patch", - "@solana/codecs-strings@5.5.1": "patches/@solana%2Fcodecs-strings@5.5.1.patch", - "@solana/nominal-types@5.5.1": "patches/@solana%2Fnominal-types@5.5.1.patch", - "@solana/rpc-parsed-types@5.5.1": "patches/@solana%2Frpc-parsed-types@5.5.1.patch", - "@solana/fast-stable-stringify@5.5.1": "patches/@solana%2Ffast-stable-stringify@5.5.1.patch", - "@solana/rpc-spec-types@5.5.1": "patches/@solana%2Frpc-spec-types@5.5.1.patch", "@solana/rpc-transformers@5.5.1": "patches/@solana%2Frpc-transformers@5.5.1.patch", - "@solana/codecs-numbers@5.5.1": "patches/@solana%2Fcodecs-numbers@5.5.1.patch", - "tsup@8.5.1": "patches/tsup@8.5.1.patch", - "@vitest/mocker@4.1.5": "patches/@vitest%2Fmocker@4.1.5.patch", - "@solana/codecs-core@5.5.1": "patches/@solana%2Fcodecs-core@5.5.1.patch", - "@solana/errors@5.5.1": "patches/@solana%2Ferrors@5.5.1.patch", - "@solana/offchain-messages@5.5.1": "patches/@solana%2Foffchain-messages@5.5.1.patch", + "@solana/fast-stable-stringify@5.5.1": "patches/@solana%2Ffast-stable-stringify@5.5.1.patch", + "@solana/rpc-transport-http@5.5.1": "patches/@solana%2Frpc-transport-http@5.5.1.patch", + "@capacitor/barcode-scanner@3.0.2": "patches/@capacitor%2Fbarcode-scanner@3.0.2.patch", "electrobun@1.18.1": "patches/electrobun@1.18.1.patch", - "@capacitor/ios@8.4.1": "patches/@capacitor%2Fios@8.4.1.patch", - "@solana/codecs@5.5.1": "patches/@solana%2Fcodecs@5.5.1.patch", "@solana/transactions@5.5.1": "patches/@solana%2Ftransactions@5.5.1.patch", - "vitest@4.1.5": "patches/vitest@4.1.5.patch", - "@solana/plugin-core@5.5.1": "patches/@solana%2Fplugin-core@5.5.1.patch", - "@solana/rpc-transport-http@5.5.1": "patches/@solana%2Frpc-transport-http@5.5.1.patch", - "@solana/rpc-subscriptions-api@5.5.1": "patches/@solana%2Frpc-subscriptions-api@5.5.1.patch", + "@solana/rpc-spec@5.5.1": "patches/@solana%2Frpc-spec@5.5.1.patch", + "@solana/codecs-data-structures@5.5.1": "patches/@solana%2Fcodecs-data-structures@5.5.1.patch", + "tsup@8.5.1": "patches/tsup@8.5.1.patch", + "@solana/codecs-core@5.5.1": "patches/@solana%2Fcodecs-core@5.5.1.patch", + "@solana/rpc-spec-types@5.5.1": "patches/@solana%2Frpc-spec-types@5.5.1.patch", "@solana/functional@5.5.1": "patches/@solana%2Ffunctional@5.5.1.patch", + "@solana/codecs-numbers@5.5.1": "patches/@solana%2Fcodecs-numbers@5.5.1.patch", + "@solana/sysvars@5.5.1": "patches/@solana%2Fsysvars@5.5.1.patch", "bigint-buffer@1.1.5": "patches/bigint-buffer@1.1.5.patch", - "@solana/addresses@5.5.1": "patches/@solana%2Faddresses@5.5.1.patch", - "telegraf@4.16.3": "patches/telegraf@4.16.3.patch", - "@solana/kit@5.5.1": "patches/@solana%2Fkit@5.5.1.patch", - "@solana/assertions@5.5.1": "patches/@solana%2Fassertions@5.5.1.patch", - "@solana/codecs-data-structures@5.5.1": "patches/@solana%2Fcodecs-data-structures@5.5.1.patch", - "@solana/promises@5.5.1": "patches/@solana%2Fpromises@5.5.1.patch", + "@solana/offchain-messages@5.5.1": "patches/@solana%2Foffchain-messages@5.5.1.patch", + "@capacitor/ios@8.4.1": "patches/@capacitor%2Fios@8.4.1.patch", + "@solana/rpc-parsed-types@5.5.1": "patches/@solana%2Frpc-parsed-types@5.5.1.patch", + "@solana/rpc-subscriptions@5.5.1": "patches/@solana%2Frpc-subscriptions@5.5.1.patch", "@solana/keys@5.5.1": "patches/@solana%2Fkeys@5.5.1.patch", - "@solana/transaction-messages@5.5.1": "patches/@solana%2Ftransaction-messages@5.5.1.patch", - "@solana/rpc-subscriptions-channel-websocket@5.5.1": "patches/@solana%2Frpc-subscriptions-channel-websocket@5.5.1.patch", - "@solana/subscribable@5.5.1": "patches/@solana%2Fsubscribable@5.5.1.patch", - "@solana/rpc-api@5.5.1": "patches/@solana%2Frpc-api@5.5.1.patch", "@solana/transaction-confirmation@5.5.1": "patches/@solana%2Ftransaction-confirmation@5.5.1.patch", - "@solana/options@5.5.1": "patches/@solana%2Foptions@5.5.1.patch", + "@solana/addresses@5.5.1": "patches/@solana%2Faddresses@5.5.1.patch", + "@solana/transaction-messages@5.5.1": "patches/@solana%2Ftransaction-messages@5.5.1.patch", + "@solana/promises@5.5.1": "patches/@solana%2Fpromises@5.5.1.patch", + "@solana/errors@5.5.1": "patches/@solana%2Ferrors@5.5.1.patch", + "@solana/nominal-types@5.5.1": "patches/@solana%2Fnominal-types@5.5.1.patch", "@solana/instructions@5.5.1": "patches/@solana%2Finstructions@5.5.1.patch", + "@solana/kit@5.5.1": "patches/@solana%2Fkit@5.5.1.patch", + "@solana/rpc-api@5.5.1": "patches/@solana%2Frpc-api@5.5.1.patch", + "@solana/rpc-types@5.5.1": "patches/@solana%2Frpc-types@5.5.1.patch", "@solana/signers@5.5.1": "patches/@solana%2Fsigners@5.5.1.patch", - "@capacitor/barcode-scanner@3.0.2": "patches/@capacitor%2Fbarcode-scanner@3.0.2.patch", - "@solana/accounts@5.5.1": "patches/@solana%2Faccounts@5.5.1.patch", + "@solana/codecs-strings@5.5.1": "patches/@solana%2Fcodecs-strings@5.5.1.patch", + "vitest@4.1.5": "patches/vitest@4.1.5.patch", "@solana/programs@5.5.1": "patches/@solana%2Fprograms@5.5.1.patch", + "@solana/rpc@5.5.1": "patches/@solana%2Frpc@5.5.1.patch", + "@solana/assertions@5.5.1": "patches/@solana%2Fassertions@5.5.1.patch", + "@solana/options@5.5.1": "patches/@solana%2Foptions@5.5.1.patch", + "@farcaster/quick-auth@0.0.8": "patches/@farcaster%2Fquick-auth@0.0.8.patch", + "@solana/subscribable@5.5.1": "patches/@solana%2Fsubscribable@5.5.1.patch", + "@solana/accounts@5.5.1": "patches/@solana%2Faccounts@5.5.1.patch", + "@solana/instruction-plans@5.5.1": "patches/@solana%2Finstruction-plans@5.5.1.patch", + "telegraf@4.16.3": "patches/telegraf@4.16.3.patch", + "@vitest/mocker@4.1.5": "patches/@vitest%2Fmocker@4.1.5.patch", + "@solana/codecs@5.5.1": "patches/@solana%2Fcodecs@5.5.1.patch", + "@solana/plugin-core@5.5.1": "patches/@solana%2Fplugin-core@5.5.1.patch", + "@solana/rpc-subscriptions-api@5.5.1": "patches/@solana%2Frpc-subscriptions-api@5.5.1.patch", + "@solana/rpc-subscriptions-channel-websocket@5.5.1": "patches/@solana%2Frpc-subscriptions-channel-websocket@5.5.1.patch", }, "overrides": { "@ai-sdk/gateway": "3.0.109", From ce1d3d37c46588fc70ed5392b666838637d50c48 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 21:06:31 -0400 Subject: [PATCH 15/26] chore(lint): sort cloud ui approval imports --- packages/cloud-ui/src/approvals/ApprovalsRoute.tsx | 4 ++-- .../cloud-ui/src/approvals/components/approvals-tab.tsx | 4 ++-- .../cloud-ui/src/approvals/components/ballots-tab.tsx | 4 ++-- .../cloud-ui/src/approvals/components/sensitive-tab.tsx | 4 ++-- packages/cloud-ui/src/approvals/index.ts | 2 +- packages/cloud-ui/src/approvals/lib/approvals.ts | 2 +- packages/cloud-ui/src/index.ts | 8 +++----- 7 files changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/cloud-ui/src/approvals/ApprovalsRoute.tsx b/packages/cloud-ui/src/approvals/ApprovalsRoute.tsx index 1880d67685ce3..19a9993c50de7 100644 --- a/packages/cloud-ui/src/approvals/ApprovalsRoute.tsx +++ b/packages/cloud-ui/src/approvals/ApprovalsRoute.tsx @@ -17,7 +17,7 @@ * wrapper. */ -import { ShieldCheck } from "lucide-react"; +import { useRequireAuth } from "@elizaos/ui/cloud/lib/use-session-auth"; import { DashboardLoadingState } from "@elizaos/ui/cloud-ui/components/dashboard/route-placeholders"; import { Tabs, @@ -25,7 +25,7 @@ import { TabsList, TabsTrigger, } from "@elizaos/ui/components/primitives"; -import { useRequireAuth } from "@elizaos/ui/cloud/lib/use-session-auth"; +import { ShieldCheck } from "lucide-react"; import { ApprovalsTab } from "./components/approvals-tab"; import { BallotsTab } from "./components/ballots-tab"; import { SensitiveTab } from "./components/sensitive-tab"; diff --git a/packages/cloud-ui/src/approvals/components/approvals-tab.tsx b/packages/cloud-ui/src/approvals/components/approvals-tab.tsx index 85adee418de21..ea1af2716f798 100644 --- a/packages/cloud-ui/src/approvals/components/approvals-tab.tsx +++ b/packages/cloud-ui/src/approvals/components/approvals-tab.tsx @@ -11,8 +11,6 @@ * for the public approval page. */ -import { CheckCircle2, Loader2, ShieldCheck, Wallet } from "lucide-react"; -import { useCallback, useState } from "react"; import { Alert, AlertDescription, @@ -20,6 +18,8 @@ import { Button, Textarea, } from "@elizaos/ui/components/primitives"; +import { CheckCircle2, Loader2, ShieldCheck, Wallet } from "lucide-react"; +import { useCallback, useState } from "react"; import { type ApprovalRequest, formatApprovalTimestamp, diff --git a/packages/cloud-ui/src/approvals/components/ballots-tab.tsx b/packages/cloud-ui/src/approvals/components/ballots-tab.tsx index 7a883b43a553b..7842ee8bfe4ed 100644 --- a/packages/cloud-ui/src/approvals/components/ballots-tab.tsx +++ b/packages/cloud-ui/src/approvals/components/ballots-tab.tsx @@ -5,8 +5,6 @@ * session, so the owner can only vote with a real scoped token they were issued. */ -import { CheckCircle2, Loader2, Vote } from "lucide-react"; -import { useCallback, useState } from "react"; import { Alert, AlertDescription, @@ -15,6 +13,8 @@ import { Input, Textarea, } from "@elizaos/ui/components/primitives"; +import { CheckCircle2, Loader2, Vote } from "lucide-react"; +import { useCallback, useState } from "react"; import { type Ballot, formatApprovalTimestamp, diff --git a/packages/cloud-ui/src/approvals/components/sensitive-tab.tsx b/packages/cloud-ui/src/approvals/components/sensitive-tab.tsx index ee3a73eb0f76a..07a1b4d7e7f19 100644 --- a/packages/cloud-ui/src/approvals/components/sensitive-tab.tsx +++ b/packages/cloud-ui/src/approvals/components/sensitive-tab.tsx @@ -10,8 +10,6 @@ * work tracked as a follow-up. */ -import { Loader2, LockKeyhole, Search } from "lucide-react"; -import { type FormEvent, useCallback, useState } from "react"; import { Alert, AlertDescription, @@ -19,6 +17,8 @@ import { Button, Input, } from "@elizaos/ui/components/primitives"; +import { Loader2, LockKeyhole, Search } from "lucide-react"; +import { type FormEvent, useCallback, useState } from "react"; import { formatApprovalTimestamp, useCancelSensitiveRequest, diff --git a/packages/cloud-ui/src/approvals/index.ts b/packages/cloud-ui/src/approvals/index.ts index 5126605159c3f..34e0d0b18e950 100644 --- a/packages/cloud-ui/src/approvals/index.ts +++ b/packages/cloud-ui/src/approvals/index.ts @@ -16,11 +16,11 @@ * a custom path if needed. */ -import { lazy } from "react"; import { type CloudRouteDef, registerCloudRoute, } from "@elizaos/ui/cloud/shell/cloud-route-registry"; +import { lazy } from "react"; export { ApprovalsSurface, default as ApprovalsRoute } from "./ApprovalsRoute"; export { diff --git a/packages/cloud-ui/src/approvals/lib/approvals.ts b/packages/cloud-ui/src/approvals/lib/approvals.ts index 24cb574836acc..0ca110fd5fe32 100644 --- a/packages/cloud-ui/src/approvals/lib/approvals.ts +++ b/packages/cloud-ui/src/approvals/lib/approvals.ts @@ -24,12 +24,12 @@ * so every timestamp field below is typed `string`. */ -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { api } from "@elizaos/ui/cloud/lib/api-client"; import { authenticatedQueryKey, useAuthenticatedQueryGate, } from "@elizaos/ui/cloud/lib/auth-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; // ── Approval requests ────────────────────────────────────────────────────── diff --git a/packages/cloud-ui/src/index.ts b/packages/cloud-ui/src/index.ts index 45f85f175e5ed..fbdc255f3e872 100644 --- a/packages/cloud-ui/src/index.ts +++ b/packages/cloud-ui/src/index.ts @@ -23,16 +23,14 @@ // Side-effecting feature modules: importing them runs their top-level // `registerCloudRoute(...)` calls. -import { - registerApprovalsCloudRoute, -} from "./approvals"; +import { registerApprovalsCloudRoute } from "./approvals"; export { - ApprovalsRoute, - ApprovalsSurface, APPROVALS_ROUTE_PATH, APPROVALS_SECTION_ID, type ApprovalRequest, + ApprovalsRoute, + ApprovalsSurface, approvalsCloudRoute, type Ballot, registerApprovalsCloudRoute, From b9d10407407f1c83bbe938585d0c1f27cc049d88 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 21:08:14 -0400 Subject: [PATCH 16/26] chore(lint): sort agent autonomy imports --- packages/agent/src/actions/trigger.ts | 2 +- packages/agent/src/api/agent-lifecycle-routes.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/agent/src/actions/trigger.ts b/packages/agent/src/actions/trigger.ts index dc0c8a9d6b921..95405d6745cb7 100644 --- a/packages/agent/src/actions/trigger.ts +++ b/packages/agent/src/actions/trigger.ts @@ -20,8 +20,8 @@ import { type Action, type ActionExample, type ActionResult, - asUUID, AUTONOMY_SERVICE_TYPE, + asUUID, type HandlerCallback, type HandlerOptions, type IAgentRuntime, diff --git a/packages/agent/src/api/agent-lifecycle-routes.ts b/packages/agent/src/api/agent-lifecycle-routes.ts index 98cc74cb7c1a2..ac31ceb4c345f 100644 --- a/packages/agent/src/api/agent-lifecycle-routes.ts +++ b/packages/agent/src/api/agent-lifecycle-routes.ts @@ -1,6 +1,6 @@ import { - AUTONOMY_SERVICE_TYPE, type AgentRuntime, + AUTONOMY_SERVICE_TYPE, type RouteRequestMeta, } from "@elizaos/core"; import type { RouteHelpers } from "@elizaos/shared"; From 9c73a2ad33c151d638e68f6c4984b5f4ed4a335f Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 21:10:03 -0400 Subject: [PATCH 17/26] chore(lint): format rebased ui and plugin files --- .../message/direct-action-heuristics.test.ts | 8 ++++---- packages/ui/src/api/desktop-local-agent-transport.ts | 4 ++-- packages/ui/src/components/chat/TasksEventsPanel.tsx | 1 + .../ui/src/components/shell/TrayLauncher.stories.tsx | 4 +++- .../ui/src/components/training/injected.test.tsx | 5 ++++- packages/ui/src/state/AppContext.tsx | 6 +++++- packages/ui/src/state/setup-resume.ts | 4 +++- packages/ui/src/widgets/WidgetHost.tsx | 1 + .../src/lib/server-wallet-trade.test.ts | 12 +++++++++--- 9 files changed, 32 insertions(+), 13 deletions(-) diff --git a/packages/core/src/services/message/direct-action-heuristics.test.ts b/packages/core/src/services/message/direct-action-heuristics.test.ts index 723b6016e818f..051ec016374ea 100644 --- a/packages/core/src/services/message/direct-action-heuristics.test.ts +++ b/packages/core/src/services/message/direct-action-heuristics.test.ts @@ -89,10 +89,10 @@ describe("findCodingDelegationActionName", () => { describe("hasActionTags", () => { it("matches declared tags case-insensitively", () => { expect( - hasActionTags( - { tags: ["Domain:Coding", "Capability:Delegate"] }, - ["domain:coding", "capability:delegate"], - ), + hasActionTags({ tags: ["Domain:Coding", "Capability:Delegate"] }, [ + "domain:coding", + "capability:delegate", + ]), ).toBe(true); }); }); diff --git a/packages/ui/src/api/desktop-local-agent-transport.ts b/packages/ui/src/api/desktop-local-agent-transport.ts index fd29a5bdcffb1..0f48f3b9cd537 100644 --- a/packages/ui/src/api/desktop-local-agent-transport.ts +++ b/packages/ui/src/api/desktop-local-agent-transport.ts @@ -2,12 +2,12 @@ * AgentRequestTransport for the desktop-hosted local agent: dispatches requests * over the Electrobun renderer RPC to the in-process agent via its IPC base. */ +import { getElectrobunRendererRpc } from "../bridge/electrobun-rpc"; +import { isElectrobunRuntime } from "../bridge/electrobun-runtime"; import { isMobileLocalAgentIpcUrl, mobileLocalAgentPathFromUrl, } from "../first-run/mobile-runtime-mode"; -import { getElectrobunRendererRpc } from "../bridge/electrobun-rpc"; -import { isElectrobunRuntime } from "../bridge/electrobun-runtime"; import { type AgentRequestTransport, bodyToString, diff --git a/packages/ui/src/components/chat/TasksEventsPanel.tsx b/packages/ui/src/components/chat/TasksEventsPanel.tsx index 604d5a6cbe3ff..8f4ec20a0ea7f 100644 --- a/packages/ui/src/components/chat/TasksEventsPanel.tsx +++ b/packages/ui/src/components/chat/TasksEventsPanel.tsx @@ -156,6 +156,7 @@ export function TasksEventsPanel({ // Build the candidate list for the edit panel from the live registry. const editCandidates = useMemo(() => { + void registryVersion; const resolved = resolveWidgetsForSlot("chat-sidebar", plugins ?? []); const widgetCandidates: WidgetVisibilityCandidate[] = resolved.map( ({ declaration }) => ({ diff --git a/packages/ui/src/components/shell/TrayLauncher.stories.tsx b/packages/ui/src/components/shell/TrayLauncher.stories.tsx index 01aa14a30a9d7..fbbdbada967fc 100644 --- a/packages/ui/src/components/shell/TrayLauncher.stories.tsx +++ b/packages/ui/src/components/shell/TrayLauncher.stories.tsx @@ -48,7 +48,9 @@ export const FullCatalog: Story = {}; export const OpenElizaOnly: Story = { args: { - entries: [{ itemId: "tray-show-window", label: "Open Eliza", icon: "home" }], + entries: [ + { itemId: "tray-show-window", label: "Open Eliza", icon: "home" }, + ], }, }; diff --git a/packages/ui/src/components/training/injected.test.tsx b/packages/ui/src/components/training/injected.test.tsx index dcaf4d87b3137..ea6afd8b34c0c 100644 --- a/packages/ui/src/components/training/injected.test.tsx +++ b/packages/ui/src/components/training/injected.test.tsx @@ -20,7 +20,10 @@ describe("trunk FineTuningView wrapper", () => { } render( , diff --git a/packages/ui/src/state/AppContext.tsx b/packages/ui/src/state/AppContext.tsx index 002f93d83b380..f9f7801f2cffb 100644 --- a/packages/ui/src/state/AppContext.tsx +++ b/packages/ui/src/state/AppContext.tsx @@ -706,7 +706,11 @@ function AppProviderInner({ }, setFirstRunRemoteError: (value: string | null): void => { if (value) { - dispatch({ type: "SET_REMOTE_STATUS", status: "error", error: value }); + dispatch({ + type: "SET_REMOTE_STATUS", + status: "error", + error: value, + }); return; } if (remote.status === "error") { diff --git a/packages/ui/src/state/setup-resume.ts b/packages/ui/src/state/setup-resume.ts index 10fbdf965cbca..5a6a9f8313b4b 100644 --- a/packages/ui/src/state/setup-resume.ts +++ b/packages/ui/src/state/setup-resume.ts @@ -79,7 +79,9 @@ export function deriveFirstRunResumeFieldsFromConfig( // The provider resumes only when the routing unambiguously names one: the // Eliza Cloud proxy route, or an explicit non-cloud backend. const firstRunProvider = - llmText && llmText.transport === "cloud-proxy" && llmBackend === "elizacloud" + llmText && + llmText.transport === "cloud-proxy" && + llmBackend === "elizacloud" ? "elizacloud" : llmBackend && llmBackend !== "elizacloud" ? llmBackend diff --git a/packages/ui/src/widgets/WidgetHost.tsx b/packages/ui/src/widgets/WidgetHost.tsx index 48c1ab9ceb10f..189e578d4daac 100644 --- a/packages/ui/src/widgets/WidgetHost.tsx +++ b/packages/ui/src/widgets/WidgetHost.tsx @@ -241,6 +241,7 @@ export function WidgetHost({ }, [plugins]); const resolved = useMemo(() => { + void registryVersion; const all = resolveWidgetsForSlot(slot, plugins ?? [], serverDeclarations); const fullAppShellRoutesEnabled = supportsFullAppShellRoutes(currentBaseUrl); diff --git a/plugins/plugin-wallet/src/lib/server-wallet-trade.test.ts b/plugins/plugin-wallet/src/lib/server-wallet-trade.test.ts index 2026e06ad8666..38dd2ff273eae 100644 --- a/plugins/plugin-wallet/src/lib/server-wallet-trade.test.ts +++ b/plugins/plugin-wallet/src/lib/server-wallet-trade.test.ts @@ -127,17 +127,23 @@ describe("canUseLocalTradeExecution (local-key authorization)", () => { describe("resolveTradePermissionMode", () => { it("returns configured valid modes and defaults to user-sign-only", () => { expect( - resolveTradePermissionMode({ features: { tradePermissionMode: "agent-auto" } }), + resolveTradePermissionMode({ + features: { tradePermissionMode: "agent-auto" }, + }), ).toBe("agent-auto"); expect( resolveTradePermissionMode({ features: { tradePermissionMode: "manual-local-key" }, }), ).toBe("manual-local-key"); - expect(resolveTradePermissionMode({ features: { tradePermissionMode: "bogus" } })).toBe( + expect( + resolveTradePermissionMode({ + features: { tradePermissionMode: "bogus" }, + }), + ).toBe("user-sign-only"); + expect(resolveTradePermissionMode({ features: null })).toBe( "user-sign-only", ); - expect(resolveTradePermissionMode({ features: null })).toBe("user-sign-only"); }); }); From 112e130f1f7c67b3e9926a929c1ed9fc16c3cf49 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 21:11:31 -0400 Subject: [PATCH 18/26] chore(lint): format app and desktop shell files --- plugins/plugin-tunnel/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/plugin-tunnel/src/types.ts b/plugins/plugin-tunnel/src/types.ts index 3018632656f93..756ac10b779ff 100644 --- a/plugins/plugin-tunnel/src/types.ts +++ b/plugins/plugin-tunnel/src/types.ts @@ -12,8 +12,8 @@ export { getTunnelService, - tunnelSlotIsFree, type ITunnelService, type TunnelProvider, type TunnelStatus, + tunnelSlotIsFree, } from '@elizaos/core'; From d4c28492ff024861cdc59525ce7585d99454eada Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 21:23:56 -0400 Subject: [PATCH 19/26] fix(ui): expose shared events to dynamic views --- .gitignore | 2 ++ packages/ui/src/components/views/DynamicViewLoader.tsx | 1 + 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index e94c363d2edab..6764d6d7e53e2 100644 --- a/.gitignore +++ b/.gitignore @@ -445,6 +445,8 @@ packages/shared/src/**/*.js.map packages/shared/src/**/*.d.ts packages/shared/src/**/*.d.ts.map !packages/shared/src/elizaos-core-roles.d.ts +packages/ui/src/**/*.js +packages/ui/src/**/*.js.map packages/ui/src/**/*.d.ts packages/ui/src/**/*.d.ts.map # Hand-written ambient declarations for npm deps without their own types. diff --git a/packages/ui/src/components/views/DynamicViewLoader.tsx b/packages/ui/src/components/views/DynamicViewLoader.tsx index 7c9318734dc92..2eb41a5b653e8 100644 --- a/packages/ui/src/components/views/DynamicViewLoader.tsx +++ b/packages/ui/src/components/views/DynamicViewLoader.tsx @@ -407,6 +407,7 @@ const HOST_EXTERNAL_IMPORTERS: Record = { "@elizaos/capacitor-system": () => importHostExternal("@elizaos/capacitor-system"), "@elizaos/shared": () => importHostExternal("@elizaos/shared"), + "@elizaos/shared/events": () => importHostExternal("@elizaos/shared/events"), "@elizaos/ui": importUiRootCompat, "@elizaos/ui/agent-surface": async () => AgentSurfaceHost, "@elizaos/ui/app-navigate-view": () => import("../../app-navigate-view.ts"), From 03a4de70313fa880a9023ab83bf71714a2c12ad5 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 22:23:30 -0400 Subject: [PATCH 20/26] chore(lint): format final rebased files --- packages/agent/src/triggers/runtime.ts | 18 +-- .../src/triggers/workbench-migration.test.ts | 8 +- .../agent/src/triggers/workbench-migration.ts | 6 +- .../src/components/pages/AutomationsFeed.tsx | 4 +- .../ui/src/components/pages/TaskEditor.tsx | 7 +- .../ui/src/components/pages/TriggersView.tsx | 6 +- .../ContinuousChatOverlay.firstrun.test.tsx | 4 +- .../src/first-run/first-run-action-channel.ts | 4 +- .../src/first-run/use-first-run-conductor.ts | 3 +- .../first-run/use-model-status-conductor.ts | 2 +- packages/ui/src/index.ts | 2 +- packages/ui/src/state/AppContext.tsx | 2 +- .../ui/src/state/startup-phase-hydrate.ts | 2 +- plugins/plugin-app-control/src/index.ts | 14 +- .../integration/trigger-dispatch-e2e.test.ts | 141 ++++++++---------- .../unit/routes/trigger-routes.test.ts | 10 +- 16 files changed, 111 insertions(+), 122 deletions(-) diff --git a/packages/agent/src/triggers/runtime.ts b/packages/agent/src/triggers/runtime.ts index a98bbb123e955..24586ed06e119 100644 --- a/packages/agent/src/triggers/runtime.ts +++ b/packages/agent/src/triggers/runtime.ts @@ -1,11 +1,5 @@ import crypto from "node:crypto"; -import type { - IAgentRuntime, - Memory, - Service, - Task, - UUID, -} from "@elizaos/core"; +import type { IAgentRuntime, Memory, Service, Task, UUID } from "@elizaos/core"; import { ServiceType, stringToUuid } from "@elizaos/core"; import { buildTriggerMetadata, @@ -254,7 +248,9 @@ interface AutonomyRoomService { async function dispatchPrompt( runtime: IAgentRuntime, trigger: PromptTriggerConfig, -): Promise<{ ok: true; executionId?: undefined } | { ok: false; error: string }> { +): Promise< + { ok: true; executionId?: undefined } | { ok: false; error: string } +> { const instructions = trigger.instructions.trim(); if (!instructions) { return { ok: false, error: "prompt trigger missing instructions" }; @@ -265,8 +261,10 @@ async function dispatchPrompt( } const roomId = - (runtime.getService("AUTONOMY") as AutonomyRoomService | null) - ?.getAutonomousRoomId?.() ?? stringToUuid(`trigger-room:${runtime.agentId}`); + ( + runtime.getService("AUTONOMY") as AutonomyRoomService | null + )?.getAutonomousRoomId?.() ?? + stringToUuid(`trigger-room:${runtime.agentId}`); const entityId = stringToUuid(`trigger-entity:${trigger.triggerId}`); const message: Memory = { diff --git a/packages/agent/src/triggers/workbench-migration.test.ts b/packages/agent/src/triggers/workbench-migration.test.ts index 85eb5130cc2ab..ff2c2842c4c8e 100644 --- a/packages/agent/src/triggers/workbench-migration.test.ts +++ b/packages/agent/src/triggers/workbench-migration.test.ts @@ -58,7 +58,9 @@ function makeWorkbenchTask(overrides: Partial): Task { describe("decodeScheduleTag", () => { it("decodes a schedule: tag", () => { - expect(decodeScheduleTag(["workbench-task", "schedule:0 9 * * 1-5"])).toEqual({ + expect( + decodeScheduleTag(["workbench-task", "schedule:0 9 * * 1-5"]), + ).toEqual({ triggerType: "cron", cronExpression: "0 9 * * 1-5", }); @@ -92,7 +94,9 @@ describe("migrateWorkbenchScheduleTags", () => { const patch = updates[0].patch; expect(patch.name).toBe(TRIGGER_TASK_NAME); // Retagged to the trigger tags; the schedule tag and workbench-task tag are gone. - expect(patch.tags).toEqual(expect.arrayContaining(["queue", "repeat", "trigger"])); + expect(patch.tags).toEqual( + expect.arrayContaining(["queue", "repeat", "trigger"]), + ); expect(patch.tags).not.toContain("workbench-task"); expect(patch.tags?.some((t) => t.startsWith("schedule:"))).toBe(false); diff --git a/packages/agent/src/triggers/workbench-migration.ts b/packages/agent/src/triggers/workbench-migration.ts index 79e0c00473299..bd984a868dda3 100644 --- a/packages/agent/src/triggers/workbench-migration.ts +++ b/packages/agent/src/triggers/workbench-migration.ts @@ -18,7 +18,11 @@ import crypto from "node:crypto"; import type { IAgentRuntime, Task, TriggerType, UUID } from "@elizaos/core"; import { stringToUuid } from "@elizaos/core"; import { WORKBENCH_TASK_TAG } from "../api/workbench-helpers.ts"; -import { readTriggerConfig, TRIGGER_TASK_NAME, TRIGGER_TASK_TAGS } from "./runtime.ts"; +import { + readTriggerConfig, + TRIGGER_TASK_NAME, + TRIGGER_TASK_TAGS, +} from "./runtime.ts"; import { buildTriggerConfig, buildTriggerMetadata } from "./scheduling.ts"; import type { NormalizedTriggerDraft } from "./types.ts"; diff --git a/packages/ui/src/components/pages/AutomationsFeed.tsx b/packages/ui/src/components/pages/AutomationsFeed.tsx index c194c889fe23f..b88b241494d5d 100644 --- a/packages/ui/src/components/pages/AutomationsFeed.tsx +++ b/packages/ui/src/components/pages/AutomationsFeed.tsx @@ -578,9 +578,7 @@ export function AutomationsFeed({ setEditor({ kind: "task", taskId: - row.source.task?.id ?? - row.source.triggerId ?? - null, + row.source.task?.id ?? row.source.triggerId ?? null, }); } else { setEditor({ diff --git a/packages/ui/src/components/pages/TaskEditor.tsx b/packages/ui/src/components/pages/TaskEditor.tsx index 2ff363cf70857..258809339ce27 100644 --- a/packages/ui/src/components/pages/TaskEditor.tsx +++ b/packages/ui/src/components/pages/TaskEditor.tsx @@ -174,8 +174,11 @@ export function TaskEditor({ displayName: trimmedName, instructions: trimmedPrompt, triggerType: - scheduleKind === "recurring" ? ("cron" as const) : ("event" as const), - cronExpression: scheduleKind === "recurring" ? cron.trim() : undefined, + scheduleKind === "recurring" + ? ("cron" as const) + : ("event" as const), + cronExpression: + scheduleKind === "recurring" ? cron.trim() : undefined, eventKind: scheduleKind === "event" ? eventName.trim() : undefined, wakeMode: "inject_now" as const, enabled: true, diff --git a/packages/ui/src/components/pages/TriggersView.tsx b/packages/ui/src/components/pages/TriggersView.tsx index 7b75e4066776b..d76319baff51c 100644 --- a/packages/ui/src/components/pages/TriggersView.tsx +++ b/packages/ui/src/components/pages/TriggersView.tsx @@ -47,13 +47,13 @@ import { formFromTrigger, getTemplateInstructions, getTemplateName, - type TriggerTemplate, loadUserTemplates, localizedExecutionStatus, railMonogram, saveUserTemplates, scheduleLabel, type TriggerFormState, + type TriggerTemplate, toneForLastStatus, validateForm, } from "./trigger-form-utils"; @@ -439,9 +439,7 @@ function useTriggersViewController() { type TriggersViewController = ReturnType; -const TriggersViewContext = createContext( - null, -); +const TriggersViewContext = createContext(null); function useTriggersViewContext(): TriggersViewController { const context = useContext(TriggersViewContext); diff --git a/packages/ui/src/components/shell/ContinuousChatOverlay.firstrun.test.tsx b/packages/ui/src/components/shell/ContinuousChatOverlay.firstrun.test.tsx index 5fefd3318c1cc..5f41491432810 100644 --- a/packages/ui/src/components/shell/ContinuousChatOverlay.firstrun.test.tsx +++ b/packages/ui/src/components/shell/ContinuousChatOverlay.firstrun.test.tsx @@ -188,7 +188,9 @@ describe("ContinuousChatOverlay first-run gating", () => { }); it("paints an OPAQUE bg-bg backdrop while onboarding is open (no launcher/home shows through)", () => { - render(); + render( + , + ); const backdrop = screen.getByTestId("chat-first-run-backdrop"); expect(backdrop.getAttribute("data-first-run-opaque")).toBe("true"); expect(backdrop.className).toContain("bg-bg"); diff --git a/packages/ui/src/first-run/first-run-action-channel.ts b/packages/ui/src/first-run/first-run-action-channel.ts index 9afaecf04d8bc..f5a8fd56c973f 100644 --- a/packages/ui/src/first-run/first-run-action-channel.ts +++ b/packages/ui/src/first-run/first-run-action-channel.ts @@ -36,9 +36,7 @@ export function setFirstRunActionHandler( * the seam that lets the user type freely during onboarding and get a * deterministic in-chat reply WITHOUT the text ever reaching the server. */ -export function setFirstRunTextHandler( - next: FirstRunTextHandler | null, -): void { +export function setFirstRunTextHandler(next: FirstRunTextHandler | null): void { textHandler = next; } diff --git a/packages/ui/src/first-run/use-first-run-conductor.ts b/packages/ui/src/first-run/use-first-run-conductor.ts index 6ed508f8cc723..6e7f0055ec356 100644 --- a/packages/ui/src/first-run/use-first-run-conductor.ts +++ b/packages/ui/src/first-run/use-first-run-conductor.ts @@ -937,7 +937,8 @@ export function useFirstRunConductor(): void { : erroredRef.current ? FIRST_RUN_TEXT_REPLY.error : FIRST_RUN_TEXT_REPLY.choosing; - const seq = (textTurnSeqRef.current += 1); + textTurnSeqRef.current += 1; + const seq = textTurnSeqRef.current; seedTurn({ id: `first-run:user:${seq}`, role: "user", diff --git a/packages/ui/src/first-run/use-model-status-conductor.ts b/packages/ui/src/first-run/use-model-status-conductor.ts index cf4aa16e13a5f..20280f4282ecb 100644 --- a/packages/ui/src/first-run/use-model-status-conductor.ts +++ b/packages/ui/src/first-run/use-model-status-conductor.ts @@ -21,8 +21,8 @@ import type { ConversationMessage } from "../api"; import { client } from "../api"; import { useShellControllerContext } from "../components/shell/ShellControllerContext.hooks"; import type { HomeModelStatus } from "../services/local-inference/home-model-status"; -import { useConversationMessages } from "../state/ConversationMessagesContext.hooks"; import { TEXT_GENERATION_SLOTS } from "../services/local-inference/types"; +import { useConversationMessages } from "../state/ConversationMessagesContext.hooks"; import { MODEL_ACTION_PREFIX, setModelActionHandler, diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index f71f6f0b60c84..95bffc344b6a0 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -452,6 +452,7 @@ export * from "./first-run/first-run-config"; export * from "./first-run/mobile-runtime-mode"; export * from "./first-run/pre-seed-local-runtime"; export * from "./genui/index"; +export * from "./gestures"; export { DEFAULT_FRAME_BUDGET, FRAME_SAMPLER_INIT, @@ -462,7 +463,6 @@ export { shouldReportFrameBudget, summarizeFrameSamples, } from "./hooks/frame-budget"; -export * from "./gestures"; export * from "./hooks/index"; export type { ActivityEvent } from "./hooks/useActivityEvents"; export { useActivityEvents } from "./hooks/useActivityEvents"; diff --git a/packages/ui/src/state/AppContext.tsx b/packages/ui/src/state/AppContext.tsx index f9f7801f2cffb..d1057161bea05 100644 --- a/packages/ui/src/state/AppContext.tsx +++ b/packages/ui/src/state/AppContext.tsx @@ -23,11 +23,11 @@ import { tryHandleFirstRunAction, tryHandleFirstRunText, } from "../first-run/first-run-action-channel"; -import { tryHandleModelAction } from "../first-run/model-action-channel"; import { isMobileLocalAgentIpcBase, persistMobileRuntimeModeForServerTarget, } from "../first-run/mobile-runtime-mode"; +import { tryHandleModelAction } from "../first-run/model-action-channel"; import { activeServerKindToFirstRunRuntimeTarget, type FirstRunRuntimeTarget, diff --git a/packages/ui/src/state/startup-phase-hydrate.ts b/packages/ui/src/state/startup-phase-hydrate.ts index d126ca3f79672..6c5ac7879615f 100644 --- a/packages/ui/src/state/startup-phase-hydrate.ts +++ b/packages/ui/src/state/startup-phase-hydrate.ts @@ -43,7 +43,6 @@ import { loadAgentProfileRegistry, resolveAgentProfileByQuery, } from "./agent-profiles"; -import { switchRuntimeNonDestructive } from "./switch-runtime"; import { loadAvatarIndex, normalizeAvatarIndex, @@ -53,6 +52,7 @@ import { } from "./internal"; import { shouldStartAtCharacterSelectOnLaunch } from "./shell-routing"; import type { StartupEvent } from "./startup-coordinator"; +import { switchRuntimeNonDestructive } from "./switch-runtime"; export interface HydratingDeps { setStartupError: (v: null) => void; diff --git a/plugins/plugin-app-control/src/index.ts b/plugins/plugin-app-control/src/index.ts index db681e7ee153b..d3d1f536b2045 100644 --- a/plugins/plugin-app-control/src/index.ts +++ b/plugins/plugin-app-control/src/index.ts @@ -39,10 +39,10 @@ import { VerificationRoomBridgeService } from "./services/verification-room-brid import { viewNavigationShortcuts } from "./shortcuts.js"; export { - agentSwitchAction, type AgentSwitchActionDeps, type AgentSwitchFn, type AgentSwitchOutcome, + agentSwitchAction, createAgentSwitchAction, inferAgentSwitchProfile, } from "./actions/agent-switch.js"; @@ -56,21 +56,21 @@ export { createBackgroundAction, inferBackgroundPlan, } from "./actions/background.js"; -export { - __matcherData, - MATCHER_VIEW_IDS, - matchViewCommand, -} from "./actions/view-command-matcher.js"; export { createModelSwitchAction, inferModelSwitchRequest, type ModelSwitchActionDeps, type ModelSwitchFn, type ModelSwitchOutcome, - modelSwitchAction, type ModelSwitchTarget, + modelSwitchAction, sanctionedModelError, } from "./actions/model-switch.js"; +export { + __matcherData, + MATCHER_VIEW_IDS, + matchViewCommand, +} from "./actions/view-command-matcher.js"; export type { ViewsMode } from "./actions/views.js"; export { closeAllViewsAction, diff --git a/plugins/plugin-workflow/__tests__/integration/trigger-dispatch-e2e.test.ts b/plugins/plugin-workflow/__tests__/integration/trigger-dispatch-e2e.test.ts index 57305e75d7ce3..7999dc56b89c6 100644 --- a/plugins/plugin-workflow/__tests__/integration/trigger-dispatch-e2e.test.ts +++ b/plugins/plugin-workflow/__tests__/integration/trigger-dispatch-e2e.test.ts @@ -16,47 +16,39 @@ * is all real code with real persistence. */ -import { mkdtemp, rm } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; -import { PGlite } from "@electric-sql/pglite"; -import type { IAgentRuntime, Task, TaskWorker, UUID } from "@elizaos/core"; -import { stringToUuid } from "@elizaos/core"; -import { TaskService } from "../../../../packages/core/src/services/task.ts"; -import { drizzle } from "drizzle-orm/pglite"; -import { - afterEach, - beforeEach, - describe, - expect, - setDefaultTimeout, - test, -} from "bun:test"; - +import { afterEach, beforeEach, describe, expect, setDefaultTimeout, test } from 'bun:test'; +import { mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { PGlite } from '@electric-sql/pglite'; +import type { IAgentRuntime, Task, TaskWorker, UUID } from '@elizaos/core'; +import { stringToUuid } from '@elizaos/core'; +import { drizzle } from 'drizzle-orm/pglite'; import { executeTriggerTask, readTriggerRuns, registerTriggerTaskWorker, -} from "../../../../packages/agent/src/triggers/runtime.ts"; +} from '../../../../packages/agent/src/triggers/runtime.ts'; import { buildTriggerConfig, buildTriggerMetadata, -} from "../../../../packages/agent/src/triggers/scheduling.ts"; -import type { NormalizedTriggerDraft } from "../../../../packages/agent/src/triggers/types.ts"; -import * as dbSchema from "../../src/db/schema"; +} from '../../../../packages/agent/src/triggers/scheduling.ts'; +import type { NormalizedTriggerDraft } from '../../../../packages/agent/src/triggers/types.ts'; +import { TaskService } from '../../../../packages/core/src/services/task.ts'; +import * as dbSchema from '../../src/db/schema'; import { EMBEDDED_WORKFLOW_SERVICE_TYPE, EmbeddedWorkflowService, TRIGGER_TASK_NAME, -} from "../../src/services/embedded-workflow-service"; +} from '../../src/services/embedded-workflow-service'; import { registerWorkflowDispatchService, WORKFLOW_DISPATCH_SERVICE_TYPE, -} from "../../src/services/workflow-dispatch"; +} from '../../src/services/workflow-dispatch'; setDefaultTimeout(60_000); -const AGENT_ID = stringToUuid("wi6-trigger-dispatch-agent"); +const AGENT_ID = stringToUuid('wi6-trigger-dispatch-agent'); interface Harness { runtime: IAgentRuntime; @@ -72,8 +64,8 @@ interface Harness { * WORKFLOW_DISPATCH all run for real against it. */ async function makeHarness(): Promise { - const dir = await mkdtemp(join(tmpdir(), "wi6-trigger-")); - const client = new PGlite({ dataDir: join(dir, "pglite") }); + const dir = await mkdtemp(join(tmpdir(), 'wi6-trigger-')); + const client = new PGlite({ dataDir: join(dir, 'pglite') }); const db = drizzle(client, { schema: dbSchema }); const tasks = new Map(); @@ -110,12 +102,9 @@ async function makeHarness(): Promise { getTask: async (id: UUID) => tasks.get(id) ?? null, getTasks: async (params: { tags?: string[] }) => { const wanted = params?.tags ?? []; - return [...tasks.values()].filter((t) => - wanted.every((tag) => t.tags?.includes(tag)), - ); + return [...tasks.values()].filter((t) => wanted.every((tag) => t.tags?.includes(tag))); }, - getTasksByName: async (name: string) => - [...tasks.values()].filter((t) => t.name === name), + getTasksByName: async (name: string) => [...tasks.values()].filter((t) => t.name === name), updateTask: async (id: UUID, patch: Partial) => { const existing = tasks.get(id); if (existing) tasks.set(id, { ...existing, ...patch }); @@ -151,30 +140,30 @@ async function makeHarness(): Promise { async function createScheduledWorkflow( workflow: EmbeddedWorkflowService, name: string, - intervalMs: number, + intervalMs: number ): Promise { const created = await workflow.createWorkflow({ name, nodes: [ { - id: "sched", - name: "Schedule Trigger", - type: "workflows-nodes-base.scheduleTrigger", + id: 'sched', + name: 'Schedule Trigger', + type: 'workflows-nodes-base.scheduleTrigger', typeVersion: 1.2, position: [0, 0], parameters: { intervalMs }, }, { - id: "set", - name: "Set", - type: "workflows-nodes-base.set", + id: 'set', + name: 'Set', + type: 'workflows-nodes-base.set', typeVersion: 3.4, position: [200, 0], parameters: { assignments: { assignments: [] } }, }, ], connections: { - "Schedule Trigger": { main: [[{ node: "Set", type: "main", index: 0 }]] }, + 'Schedule Trigger': { main: [[{ node: 'Set', type: 'main', index: 0 }]] }, }, }); return created.id; @@ -191,7 +180,7 @@ function makeTaskDueNow(task: Task): void { if (trigger) trigger.nextRunAtMs = 0; } -describe("WI-6: workflow schedulable via the task/cron layer (real tick)", () => { +describe('WI-6: workflow schedulable via the task/cron layer (real tick)', () => { let h: Harness; beforeEach(async () => { @@ -202,13 +191,11 @@ describe("WI-6: workflow schedulable via the task/cron layer (real tick)", () => await h.close(); }); - test("(a) a scheduled workflow fires through the real TaskService tick and records an execution + a TriggerRunRecord", async () => { - const workflowId = await createScheduledWorkflow(h.workflow, "WI6 sched", 60_000); + test('(a) a scheduled workflow fires through the real TaskService tick and records an execution + a TriggerRunRecord', async () => { + const workflowId = await createScheduledWorkflow(h.workflow, 'WI6 sched', 60_000); await h.workflow.activateWorkflow(workflowId); - const triggerTasks = [...h.tasks.values()].filter( - (t) => t.name === TRIGGER_TASK_NAME, - ); + const triggerTasks = [...h.tasks.values()].filter((t) => t.name === TRIGGER_TASK_NAME); expect(triggerTasks).toHaveLength(1); makeTaskDueNow(triggerTasks[0]); @@ -221,22 +208,20 @@ describe("WI-6: workflow schedulable via the task/cron layer (real tick)", () => expect(executions.length).toBeGreaterThanOrEqual(1); // A TriggerRunRecord was appended to the task metadata. - const refreshed = triggerTasks[0].id - ? await h.runtime.getTask(triggerTasks[0].id) - : null; + const refreshed = triggerTasks[0].id ? await h.runtime.getTask(triggerTasks[0].id) : null; expect(refreshed).not.toBeNull(); const runs = refreshed ? readTriggerRuns(refreshed) : []; expect(runs.length).toBeGreaterThanOrEqual(1); - expect(runs[0].status).toBe("success"); + expect(runs[0].status).toBe('success'); }); - test("(b) the WORKFLOW_DISPATCH service runs a workflow by id (headless service call)", async () => { - const workflowId = await createScheduledWorkflow(h.workflow, "WI6 dispatch", 60_000); + test('(b) the WORKFLOW_DISPATCH service runs a workflow by id (headless service call)', async () => { + const workflowId = await createScheduledWorkflow(h.workflow, 'WI6 dispatch', 60_000); const dispatch = h.runtime.getService(WORKFLOW_DISPATCH_SERVICE_TYPE) as { execute: ( id: string, payload?: Record, - options?: { idempotencyKey?: string }, + options?: { idempotencyKey?: string } ) => Promise<{ ok: boolean; executionId?: string; error?: string }>; } | null; expect(dispatch).not.toBeNull(); @@ -248,13 +233,11 @@ describe("WI-6: workflow schedulable via the task/cron layer (real tick)", () => expect(executions.length).toBeGreaterThanOrEqual(1); }); - test("(c) one core TaskService clock drives both consumers — a trigger task and a LifeOps-scheduler task fire on the same tick", async () => { + test('(c) one core TaskService clock drives both consumers — a trigger task and a LifeOps-scheduler task fire on the same tick', async () => { // Consumer 1: a real scheduled-workflow TRIGGER_DISPATCH task. - const workflowId = await createScheduledWorkflow(h.workflow, "WI6 coexist", 60_000); + const workflowId = await createScheduledWorkflow(h.workflow, 'WI6 coexist', 60_000); await h.workflow.activateWorkflow(workflowId); - const triggerTask = [...h.tasks.values()].find( - (t) => t.name === TRIGGER_TASK_NAME, - ); + const triggerTask = [...h.tasks.values()].find((t) => t.name === TRIGGER_TASK_NAME); expect(triggerTask).toBeDefined(); if (triggerTask) makeTaskDueNow(triggerTask); @@ -265,16 +248,16 @@ describe("WI-6: workflow schedulable via the task/cron layer (real tick)", () => // clock, two consumers" architecture, verified structurally). let lifeopsFired = 0; h.runtime.registerTaskWorker({ - name: "LIFEOPS_SCHEDULER", + name: 'LIFEOPS_SCHEDULER', execute: async () => { lifeopsFired += 1; return undefined; }, }); await h.runtime.createTask({ - name: "LIFEOPS_SCHEDULER", - description: "LifeOps scheduler", - tags: ["queue", "repeat", "lifeops"], + name: 'LIFEOPS_SCHEDULER', + description: 'LifeOps scheduler', + tags: ['queue', 'repeat', 'lifeops'], metadata: { updatedAt: 0, updateInterval: 60_000 }, }); @@ -288,23 +271,23 @@ describe("WI-6: workflow schedulable via the task/cron layer (real tick)", () => expect(lifeopsFired).toBe(1); }); - test("(d) a disabled trigger does not fire; a re-enabled fire runs; maxRuns is respected", async () => { - const workflowId = await createScheduledWorkflow(h.workflow, "WI6 gated", 60_000); + test('(d) a disabled trigger does not fire; a re-enabled fire runs; maxRuns is respected', async () => { + const workflowId = await createScheduledWorkflow(h.workflow, 'WI6 gated', 60_000); // Build a maxRuns=1 workflow trigger task directly and drive executeTriggerTask. const draft: NormalizedTriggerDraft = { - displayName: "Gated", + displayName: 'Gated', instructions: `Run workflow ${workflowId}`, - triggerType: "interval", - wakeMode: "inject_now", + triggerType: 'interval', + wakeMode: 'inject_now', enabled: false, // disabled - createdBy: "wi6", + createdBy: 'wi6', intervalMs: 60_000, maxRuns: 1, - kind: "workflow", + kind: 'workflow', workflowId, }; - const triggerId = stringToUuid("wi6-gated"); + const triggerId = stringToUuid('wi6-gated'); const disabledTrigger = buildTriggerConfig({ draft, triggerId }); const metadata = buildTriggerMetadata({ trigger: disabledTrigger, nowMs: Date.now() }) ?? { trigger: disabledTrigger, @@ -312,15 +295,15 @@ describe("WI-6: workflow schedulable via the task/cron layer (real tick)", () => const taskId = await h.runtime.createTask({ name: TRIGGER_TASK_NAME, description: disabledTrigger.displayName, - tags: ["queue", "repeat", "trigger", "workflow"], - metadata: metadata as Task["metadata"], + tags: ['queue', 'repeat', 'trigger', 'workflow'], + metadata: metadata as Task['metadata'], }); const task = await h.runtime.getTask(taskId); - if (!task) throw new Error("task not created"); + if (!task) throw new Error('task not created'); // Disabled → skipped, no execution. - const skipped = await executeTriggerTask(h.runtime, task, { source: "scheduler" }); - expect(skipped.status).toBe("skipped"); + const skipped = await executeTriggerTask(h.runtime, task, { source: 'scheduler' }); + expect(skipped.status).toBe('skipped'); let executions = (await h.workflow.listExecutions({ workflowId })).data; expect(executions.length).toBe(0); @@ -328,12 +311,12 @@ describe("WI-6: workflow schedulable via the task/cron layer (real tick)", () => const enabledTrigger = { ...disabledTrigger, enabled: true }; const enabledMeta = buildTriggerMetadata({ trigger: enabledTrigger, nowMs: Date.now() }) ?? - ({ trigger: enabledTrigger } as Task["metadata"]); - await h.runtime.updateTask(taskId, { metadata: enabledMeta as Task["metadata"] }); + ({ trigger: enabledTrigger } as Task['metadata']); + await h.runtime.updateTask(taskId, { metadata: enabledMeta as Task['metadata'] }); const enabledTask = await h.runtime.getTask(taskId); - if (!enabledTask) throw new Error("task missing"); - const ran = await executeTriggerTask(h.runtime, enabledTask, { source: "scheduler" }); - expect(ran.status).toBe("success"); + if (!enabledTask) throw new Error('task missing'); + const ran = await executeTriggerTask(h.runtime, enabledTask, { source: 'scheduler' }); + expect(ran.status).toBe('success'); executions = (await h.workflow.listExecutions({ workflowId })).data; expect(executions.length).toBe(1); diff --git a/plugins/plugin-workflow/__tests__/unit/routes/trigger-routes.test.ts b/plugins/plugin-workflow/__tests__/unit/routes/trigger-routes.test.ts index ee56d785d484e..f81ba2cb07335 100644 --- a/plugins/plugin-workflow/__tests__/unit/routes/trigger-routes.test.ts +++ b/plugins/plugin-workflow/__tests__/unit/routes/trigger-routes.test.ts @@ -173,7 +173,7 @@ describe('POST /api/triggers — kind parsing (WI-3)', () => { }); }); -describe("PUT /api/triggers/:id — switching to prompt kind (WI-3 review fix #1)", () => { +describe('PUT /api/triggers/:id — switching to prompt kind (WI-3 review fix #1)', () => { // A stored workflow trigger whose instructions are the synthesized default. const workflowCurrent = { version: 1, @@ -192,7 +192,7 @@ describe("PUT /api/triggers/:id — switching to prompt kind (WI-3 review fix #1 function putCtx( body: Record, - current: Record = workflowCurrent, + current: Record = workflowCurrent ) { const built = makeCtx({ method: 'PUT', @@ -209,7 +209,7 @@ describe("PUT /api/triggers/:id — switching to prompt kind (WI-3 review fix #1 expect(handled).toBe(true); expect(captured.status).toBe(400); expect((captured.body as { error: string }).error).toContain( - "instructions is required when kind is 'prompt'", + "instructions is required when kind is 'prompt'" ); }); @@ -225,7 +225,7 @@ describe("PUT /api/triggers/:id — switching to prompt kind (WI-3 review fix #1 expect(handled).toBe(true); expect(captured.status).toBe(400); expect((captured.body as { error: string }).error).not.toContain( - "instructions is required when kind is 'prompt'", + "instructions is required when kind is 'prompt'" ); }); @@ -241,7 +241,7 @@ describe("PUT /api/triggers/:id — switching to prompt kind (WI-3 review fix #1 expect(handled).toBe(true); // Falls through to the generic invalid-update path, not the instructions 400. expect((captured.body as { error: string }).error).not.toContain( - "instructions is required when kind is 'prompt'", + "instructions is required when kind is 'prompt'" ); }); }); From ea4fcd81dc6430a05cb932218e18f8ec0b084aec Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 22:26:50 -0400 Subject: [PATCH 21/26] chore(lint): apply final formatter fixes --- .../voice-workbench-logic-baseline.json | 653 ++++++++---------- 1 file changed, 295 insertions(+), 358 deletions(-) diff --git a/plugins/plugin-local-inference/src/services/voice/__fixtures__/voice-workbench-logic-baseline.json b/plugins/plugin-local-inference/src/services/voice/__fixtures__/voice-workbench-logic-baseline.json index 370c00012cbc5..a901e981252c1 100644 --- a/plugins/plugin-local-inference/src/services/voice/__fixtures__/voice-workbench-logic-baseline.json +++ b/plugins/plugin-local-inference/src/services/voice/__fixtures__/voice-workbench-logic-baseline.json @@ -1,360 +1,297 @@ { - "schemaVersion": 1, - "overall": "pass", - "scenariosTotal": 24, - "scenariosRan": 24, - "scenariosSkipped": 0, - "scenarios": [ - { - "scenarioId": "multi-voice-greeting", - "classes": [ - "multi-voice", - "diarization" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 8, - "failedCaseKinds": [] - }, - { - "scenarioId": "respond-vs-bystander", - "classes": [ - "respond-no-respond", - "multi-speaker" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 9, - "failedCaseKinds": [] - }, - { - "scenarioId": "pauses-midutterance", - "classes": [ - "pauses", - "eot" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 7, - "failedCaseKinds": [] - }, - { - "scenarioId": "entity-from-speech", - "classes": [ - "entity-extraction", - "voice-recognition" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 7, - "failedCaseKinds": [] - }, - { - "scenarioId": "transcription-mode-dictation", - "classes": [ - "transcription-mode", - "long-form-monologue" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 5, - "failedCaseKinds": [] - }, - { - "scenarioId": "multi-agent-room-address", - "classes": [ - "multi-agent-room", - "respond-no-respond" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 8, - "failedCaseKinds": [] - }, - { - "scenarioId": "noisy-room-commands", - "classes": [ - "robustness", - "respond-no-respond" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 8, - "failedCaseKinds": [] - }, - { - "scenarioId": "music-background-commands", - "classes": [ - "robustness", - "respond-no-respond" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 8, - "failedCaseKinds": [] - }, - { - "scenarioId": "far-field-reverb", - "classes": [ - "robustness", - "respond-no-respond" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 6, - "failedCaseKinds": [] - }, - { - "scenarioId": "background-talkers", - "classes": [ - "robustness", - "overlapping-speech", - "multi-speaker" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 6, - "failedCaseKinds": [] - }, - { - "scenarioId": "echo-self-trigger", - "classes": [ - "echo-rejection", - "respond-no-respond" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 10, - "failedCaseKinds": [] - }, - { - "scenarioId": "multi-speaker-name-capture", - "classes": [ - "diarization", - "entity-extraction", - "multi-speaker", - "voice-recognition" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 13, - "failedCaseKinds": [] - }, - { - "scenarioId": "confusable-names-clean", - "classes": [ - "entity-extraction", - "name-disambiguation", - "multi-speaker", - "voice-recognition", - "diarization" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 11, - "failedCaseKinds": [] - }, - { - "scenarioId": "confusable-names-noisy", - "classes": [ - "entity-extraction", - "name-disambiguation", - "multi-speaker", - "voice-recognition", - "robustness" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 13, - "failedCaseKinds": [] - }, - { - "scenarioId": "confusable-name-garbled-transcript", - "classes": [ - "entity-extraction", - "name-disambiguation", - "multi-speaker", - "voice-recognition" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 11, - "failedCaseKinds": [] - }, - { - "scenarioId": "echo-mistranscribed", - "classes": [ - "echo-rejection" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 8, - "failedCaseKinds": [] - }, - { - "scenarioId": "owner-enrollment-inference", - "classes": [ - "owner-security", - "voice-recognition" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 15, - "failedCaseKinds": [] - }, - { - "scenarioId": "owner-vs-intruder", - "classes": [ - "owner-security", - "respond-no-respond", - "multi-speaker" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 10, - "failedCaseKinds": [] - }, - { - "scenarioId": "endpoint-latency", - "classes": [ - "endpoint-latency", - "eot" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 8, - "failedCaseKinds": [] - }, - { - "scenarioId": "tail-off-thinking", - "classes": [ - "tail-off", - "eot", - "pauses" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 7, - "failedCaseKinds": [] - }, - { - "scenarioId": "streaming-partials-monotonic", - "classes": [ - "streaming-partials", - "eot" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 6, - "failedCaseKinds": [] - }, - { - "scenarioId": "speaker-gated-barge-in", - "classes": [ - "speaker-gated-barge-in", - "echo-rejection" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 14, - "failedCaseKinds": [] - }, - { - "scenarioId": "desktop-aec-echo", - "classes": [ - "desktop-aec", - "echo-rejection" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 10, - "failedCaseKinds": [] - }, - { - "scenarioId": "long-turn-diarization", - "classes": [ - "long-turn-diarization", - "diarization", - "multi-speaker" - ], - "status": "ran", - "verdict": "pass", - "caseCount": 20, - "failedCaseKinds": [] - } - ], - "metrics": { - "wer": { - "count": 65, - "mean": 0, - "worst": 0 - }, - "eotFalseTriggerRate": { - "count": 24, - "mean": 0, - "worst": 0 - }, - "eotLatencyP50Ms": null, - "eotLatencyP95Ms": null, - "der": { - "count": 24, - "mean": 0.01, - "worst": 0.2394 - }, - "respondAccuracy": { - "count": 24, - "mean": 1, - "worst": 1 - }, - "entityF1": { - "count": 5, - "mean": 1, - "worst": 1 - }, - "voiceEntityMatchRate": { - "count": 24, - "mean": 1, - "worst": 1 - }, - "firstAudioMs": { - "count": 55, - "mean": 250, - "worst": 250 - }, - "echoRejectionRate": { - "count": 4, - "mean": 1, - "worst": 1 - }, - "ownerAccuracy": { - "count": 2, - "mean": 1, - "worst": 1 - }, - "impostorAcceptRate": { - "count": 2, - "mean": 0, - "worst": 0 - }, - "bargeInGatingAccuracy": { - "count": 1, - "mean": 1, - "worst": 1 - }, - "bargeInCancelMs": { - "count": 1, - "mean": 120, - "worst": 120 - }, - "erleDb": { - "count": 0, - "mean": null, - "worst": null - }, - "partialRetractions": { - "count": 0, - "mean": null, - "worst": null - } - } + "schemaVersion": 1, + "overall": "pass", + "scenariosTotal": 24, + "scenariosRan": 24, + "scenariosSkipped": 0, + "scenarios": [ + { + "scenarioId": "multi-voice-greeting", + "classes": ["multi-voice", "diarization"], + "status": "ran", + "verdict": "pass", + "caseCount": 8, + "failedCaseKinds": [] + }, + { + "scenarioId": "respond-vs-bystander", + "classes": ["respond-no-respond", "multi-speaker"], + "status": "ran", + "verdict": "pass", + "caseCount": 9, + "failedCaseKinds": [] + }, + { + "scenarioId": "pauses-midutterance", + "classes": ["pauses", "eot"], + "status": "ran", + "verdict": "pass", + "caseCount": 7, + "failedCaseKinds": [] + }, + { + "scenarioId": "entity-from-speech", + "classes": ["entity-extraction", "voice-recognition"], + "status": "ran", + "verdict": "pass", + "caseCount": 7, + "failedCaseKinds": [] + }, + { + "scenarioId": "transcription-mode-dictation", + "classes": ["transcription-mode", "long-form-monologue"], + "status": "ran", + "verdict": "pass", + "caseCount": 5, + "failedCaseKinds": [] + }, + { + "scenarioId": "multi-agent-room-address", + "classes": ["multi-agent-room", "respond-no-respond"], + "status": "ran", + "verdict": "pass", + "caseCount": 8, + "failedCaseKinds": [] + }, + { + "scenarioId": "noisy-room-commands", + "classes": ["robustness", "respond-no-respond"], + "status": "ran", + "verdict": "pass", + "caseCount": 8, + "failedCaseKinds": [] + }, + { + "scenarioId": "music-background-commands", + "classes": ["robustness", "respond-no-respond"], + "status": "ran", + "verdict": "pass", + "caseCount": 8, + "failedCaseKinds": [] + }, + { + "scenarioId": "far-field-reverb", + "classes": ["robustness", "respond-no-respond"], + "status": "ran", + "verdict": "pass", + "caseCount": 6, + "failedCaseKinds": [] + }, + { + "scenarioId": "background-talkers", + "classes": ["robustness", "overlapping-speech", "multi-speaker"], + "status": "ran", + "verdict": "pass", + "caseCount": 6, + "failedCaseKinds": [] + }, + { + "scenarioId": "echo-self-trigger", + "classes": ["echo-rejection", "respond-no-respond"], + "status": "ran", + "verdict": "pass", + "caseCount": 10, + "failedCaseKinds": [] + }, + { + "scenarioId": "multi-speaker-name-capture", + "classes": [ + "diarization", + "entity-extraction", + "multi-speaker", + "voice-recognition" + ], + "status": "ran", + "verdict": "pass", + "caseCount": 13, + "failedCaseKinds": [] + }, + { + "scenarioId": "confusable-names-clean", + "classes": [ + "entity-extraction", + "name-disambiguation", + "multi-speaker", + "voice-recognition", + "diarization" + ], + "status": "ran", + "verdict": "pass", + "caseCount": 11, + "failedCaseKinds": [] + }, + { + "scenarioId": "confusable-names-noisy", + "classes": [ + "entity-extraction", + "name-disambiguation", + "multi-speaker", + "voice-recognition", + "robustness" + ], + "status": "ran", + "verdict": "pass", + "caseCount": 13, + "failedCaseKinds": [] + }, + { + "scenarioId": "confusable-name-garbled-transcript", + "classes": [ + "entity-extraction", + "name-disambiguation", + "multi-speaker", + "voice-recognition" + ], + "status": "ran", + "verdict": "pass", + "caseCount": 11, + "failedCaseKinds": [] + }, + { + "scenarioId": "echo-mistranscribed", + "classes": ["echo-rejection"], + "status": "ran", + "verdict": "pass", + "caseCount": 8, + "failedCaseKinds": [] + }, + { + "scenarioId": "owner-enrollment-inference", + "classes": ["owner-security", "voice-recognition"], + "status": "ran", + "verdict": "pass", + "caseCount": 15, + "failedCaseKinds": [] + }, + { + "scenarioId": "owner-vs-intruder", + "classes": ["owner-security", "respond-no-respond", "multi-speaker"], + "status": "ran", + "verdict": "pass", + "caseCount": 10, + "failedCaseKinds": [] + }, + { + "scenarioId": "endpoint-latency", + "classes": ["endpoint-latency", "eot"], + "status": "ran", + "verdict": "pass", + "caseCount": 8, + "failedCaseKinds": [] + }, + { + "scenarioId": "tail-off-thinking", + "classes": ["tail-off", "eot", "pauses"], + "status": "ran", + "verdict": "pass", + "caseCount": 7, + "failedCaseKinds": [] + }, + { + "scenarioId": "streaming-partials-monotonic", + "classes": ["streaming-partials", "eot"], + "status": "ran", + "verdict": "pass", + "caseCount": 6, + "failedCaseKinds": [] + }, + { + "scenarioId": "speaker-gated-barge-in", + "classes": ["speaker-gated-barge-in", "echo-rejection"], + "status": "ran", + "verdict": "pass", + "caseCount": 14, + "failedCaseKinds": [] + }, + { + "scenarioId": "desktop-aec-echo", + "classes": ["desktop-aec", "echo-rejection"], + "status": "ran", + "verdict": "pass", + "caseCount": 10, + "failedCaseKinds": [] + }, + { + "scenarioId": "long-turn-diarization", + "classes": ["long-turn-diarization", "diarization", "multi-speaker"], + "status": "ran", + "verdict": "pass", + "caseCount": 20, + "failedCaseKinds": [] + } + ], + "metrics": { + "wer": { + "count": 65, + "mean": 0, + "worst": 0 + }, + "eotFalseTriggerRate": { + "count": 24, + "mean": 0, + "worst": 0 + }, + "eotLatencyP50Ms": null, + "eotLatencyP95Ms": null, + "der": { + "count": 24, + "mean": 0.01, + "worst": 0.2394 + }, + "respondAccuracy": { + "count": 24, + "mean": 1, + "worst": 1 + }, + "entityF1": { + "count": 5, + "mean": 1, + "worst": 1 + }, + "voiceEntityMatchRate": { + "count": 24, + "mean": 1, + "worst": 1 + }, + "firstAudioMs": { + "count": 55, + "mean": 250, + "worst": 250 + }, + "echoRejectionRate": { + "count": 4, + "mean": 1, + "worst": 1 + }, + "ownerAccuracy": { + "count": 2, + "mean": 1, + "worst": 1 + }, + "impostorAcceptRate": { + "count": 2, + "mean": 0, + "worst": 0 + }, + "bargeInGatingAccuracy": { + "count": 1, + "mean": 1, + "worst": 1 + }, + "bargeInCancelMs": { + "count": 1, + "mean": 120, + "worst": 120 + }, + "erleDb": { + "count": 0, + "mean": null, + "worst": null + }, + "partialRetractions": { + "count": 0, + "mean": null, + "worst": null + } + } } From ed4f93582efb310deae70cd29359020a6dbab8a4 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 22:49:19 -0400 Subject: [PATCH 22/26] fix(app): stub capacitor attachment plugins for web builds --- .../test/native-module-stub-plugin.test.ts | 27 ++++++++++++++++++- .../app/vite/native-module-stub-plugin.ts | 14 ++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/app/test/native-module-stub-plugin.test.ts b/packages/app/test/native-module-stub-plugin.test.ts index 9e9fb4946304b..efe302a572847 100644 --- a/packages/app/test/native-module-stub-plugin.test.ts +++ b/packages/app/test/native-module-stub-plugin.test.ts @@ -1,7 +1,10 @@ import { createRequire } from "node:module"; import { describe, expect, it } from "vitest"; -import { generateNodeBuiltinStub } from "../vite/native-module-stub-plugin"; +import { + generateNodeBuiltinStub, + nativeModuleStubPlugin, +} from "../vite/native-module-stub-plugin"; describe("native module stub plugin", () => { it("preserves proxy invariants for generated builtin stubs", () => { @@ -20,4 +23,26 @@ describe("native module stub plugin", () => { expect(source).not.toContain("ownKeys() { return []; }"); expect(source).not.toContain("p === 'prototype') return {}"); }); + + it("exports named Capacitor attachment plugin stubs for web builds", () => { + const plugin = nativeModuleStubPlugin({ + isCapacitorMobileBuild: false, + requireModule: createRequire(import.meta.url), + }) as unknown as { + resolveId: (id: string) => string | null; + load: (id: string) => string | null; + }; + + const filesystemId = plugin.resolveId("@capacitor/filesystem"); + const filesystemSource = plugin.load(filesystemId ?? ""); + expect(filesystemId).toBe("\0native-stub:@capacitor/filesystem"); + expect(filesystemSource).toContain("export { Filesystem };"); + expect(filesystemSource).not.toContain("writeFile"); + + const shareId = plugin.resolveId("@capacitor/share"); + const shareSource = plugin.load(shareId ?? ""); + expect(shareId).toBe("\0native-stub:@capacitor/share"); + expect(shareSource).toContain("export { Share };"); + expect(shareSource).not.toContain("share:"); + }); }); diff --git a/packages/app/vite/native-module-stub-plugin.ts b/packages/app/vite/native-module-stub-plugin.ts index 198b0effd06cd..f3990a90a24c1 100644 --- a/packages/app/vite/native-module-stub-plugin.ts +++ b/packages/app/vite/native-module-stub-plugin.ts @@ -825,6 +825,20 @@ export function nativeModuleStubPlugin( "export default noopObj;", ].join("\n"); } + if (capPkg === "@capacitor/filesystem") { + return [ + "const Filesystem = Object.freeze({});", + "export { Filesystem };", + "export default Filesystem;", + ].join("\n"); + } + if (capPkg === "@capacitor/share") { + return [ + "const Share = Object.freeze({});", + "export { Share };", + "export default Share;", + ].join("\n"); + } if (capPkg === "@capacitor/background-runner") { return [ "const asyncNoop = async () => {};", From d4e8b9ab5eb0b63563f5030abc26630d0dfb0169 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 23:21:53 -0400 Subject: [PATCH 23/26] fix(agent): type dispatch route onchunk test handlers --- .../src/api/dispatch-route-onchunk.test.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/agent/src/api/dispatch-route-onchunk.test.ts b/packages/agent/src/api/dispatch-route-onchunk.test.ts index 4fabb6edc12d4..c97a8a9027264 100644 --- a/packages/agent/src/api/dispatch-route-onchunk.test.ts +++ b/packages/agent/src/api/dispatch-route-onchunk.test.ts @@ -1,4 +1,4 @@ -import type { IAgentRuntime, Route } from "@elizaos/core"; +import type { IAgentRuntime, LegacyRouteHandler, Route } from "@elizaos/core"; import { describe, expect, it } from "vitest"; import { dispatchRoute } from "./dispatch-route.ts"; @@ -15,13 +15,17 @@ function runtimeWithRoutes(routes: Route[]): IAgentRuntime { return { routes } as unknown as IAgentRuntime; } +function legacyHandler(handler: LegacyRouteHandler): LegacyRouteHandler { + return handler; +} + describe("dispatchRoute onChunk sink (#12352)", () => { it("forwards each SSE fragment live and returns the buffered body", async () => { const seen: string[] = []; const route: Route = { type: "POST", path: "/api/stream", - handler: async (_req: unknown, res: unknown) => { + handler: legacyHandler(async (_req, res) => { const r = res as unknown as { setHeader: (k: string, v: string) => void; write: (c: string) => void; @@ -31,7 +35,7 @@ describe("dispatchRoute onChunk sink (#12352)", () => { r.write("data: one\n\n"); r.write("data: two\n\n"); r.end(); - }, + }), } as unknown as Route; const chunks: string[] = []; @@ -60,9 +64,9 @@ describe("dispatchRoute onChunk sink (#12352)", () => { const route: Route = { type: "GET", path: "/api/plain", - handler: async (_req: unknown, res: unknown) => { + handler: legacyHandler(async (_req, res) => { (res as unknown as { json: (b: unknown) => void }).json({ ok: true }); - }, + }), } as unknown as Route; let chunkCount = 0; @@ -87,7 +91,7 @@ describe("dispatchRoute onChunk sink (#12352)", () => { const route: Route = { type: "POST", path: "/api/stream", - handler: async (_req: unknown, res: unknown) => { + handler: legacyHandler(async (_req, res) => { const r = res as unknown as { write: (c: string) => void; end: () => void; @@ -95,7 +99,7 @@ describe("dispatchRoute onChunk sink (#12352)", () => { r.write("a"); r.write("b"); r.end(); - }, + }), } as unknown as Route; const result = await dispatchRoute({ From acd7535d07dafc7be0e0640731853fc6a04ac842 Mon Sep 17 00:00:00 2001 From: Shaw Date: Fri, 3 Jul 2026 23:38:28 -0400 Subject: [PATCH 24/26] fix(electrobun): normalize renderer proxy api base --- packages/app-core/platforms/electrobun/src/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/app-core/platforms/electrobun/src/index.ts b/packages/app-core/platforms/electrobun/src/index.ts index 7860d595a04c7..0f3c579fe99a2 100644 --- a/packages/app-core/platforms/electrobun/src/index.ts +++ b/packages/app-core/platforms/electrobun/src/index.ts @@ -844,7 +844,8 @@ async function startRendererServer(): Promise { // on same-origin /api fetches whether it's loaded via Vite (watch mode) // or this static server (non-watch dev:desktop). Without this, every // /api/* call returned SPA HTML and Settings sat on "Loading…" forever. - const apiBase = apiBaseOwner.getCurrent().base ?? initialApiBase; + const apiBase = + apiBaseOwner.getCurrent().base ?? initialApiBase ?? undefined; if (shouldProxyToApiBase(apiBase) && isRendererApiProxyPath(pathname)) { const target = new URL(pathname + url.search, apiBase); try { From 2891758fce8bc5f7dae6bc03df058a58f069a33f Mon Sep 17 00:00:00 2001 From: Shaw Date: Sat, 4 Jul 2026 00:25:36 -0400 Subject: [PATCH 25/26] fix(agent): declare ui navigation window helpers --- packages/agent/src/external-modules.d.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/agent/src/external-modules.d.ts b/packages/agent/src/external-modules.d.ts index 2e081607622b0..76df3de534f0d 100644 --- a/packages/agent/src/external-modules.d.ts +++ b/packages/agent/src/external-modules.d.ts @@ -1,10 +1,7 @@ declare module "@elizaos/plugin-agent-orchestrator"; declare module "@elizaos/plugin-capacitor-bridge" { import type { Server } from "node:http"; - import type { - AgentRuntime, - MobileDeviceBridgeStatus, - } from "@elizaos/core"; + import type { AgentRuntime, MobileDeviceBridgeStatus } from "@elizaos/core"; export type { MobileDeviceBridgeStatus }; @@ -853,7 +850,6 @@ declare module "@elizaos/ui" { export const App: ComponentType; export const AppProvider: ComponentType; export const AppWindowRenderer: ComponentType; - export const EmbeddedAppViewer: ComponentType; export const Button: ComponentType; export const CharacterEditor: ComponentType; export const COMMAND_PALETTE_EVENT: string; @@ -932,6 +928,8 @@ declare module "@elizaos/ui" { declare module "@elizaos/ui/navigation" { export const TAB_PATHS: Record; + export const getWindowNavigationPath: AnyFunction; + export const isAppWindowRoute: AnyFunction; } declare module "@elizaos/plugin-discord" { From 75c00a98d9a07ddcd93d08e303757d2d9cdfbe6c Mon Sep 17 00:00:00 2001 From: Shaw Date: Sat, 4 Jul 2026 01:07:59 -0400 Subject: [PATCH 26/26] chore(validation): fix rebased verify fallout --- bun.lock | 1 + packages/agent/src/api/media-runtime.test.ts | 14 ++++++-- packages/app/scripts/patch-ios-plist.mjs | 4 ++- packages/lifeops-bench/package.json | 2 +- .../scripts/typecheck-dist-path-consumers.mjs | 1 + .../src/cloud/shell/cloud-route-registry.ts | 6 +--- .../__e2e__/home-screen-fixture.views-stub.ts | 7 ---- .../ui/src/voice/ios-live-activity.test.ts | 36 +++++++++++++++---- packages/ui/src/voice/ios-live-activity.ts | 11 ++++-- .../src/services/downloader.ts | 4 +-- plugins/plugin-sql/src/stores/agent.store.ts | 5 +-- 11 files changed, 62 insertions(+), 29 deletions(-) diff --git a/bun.lock b/bun.lock index 9f62108122dc2..2aeda8ea41411 100644 --- a/bun.lock +++ b/bun.lock @@ -3290,6 +3290,7 @@ "version": "2.0.3-beta.7", "dependencies": { "@elizaos/core": "workspace:*", + "@elizaos/shared": "workspace:*", }, "devDependencies": { "@biomejs/biome": "2.5.2", diff --git a/packages/agent/src/api/media-runtime.test.ts b/packages/agent/src/api/media-runtime.test.ts index d5d8b705b186b..eec911d6769c2 100644 --- a/packages/agent/src/api/media-runtime.test.ts +++ b/packages/agent/src/api/media-runtime.test.ts @@ -292,7 +292,10 @@ describe("mediaFileRoute", () => { // this in-process route; a forwarded `Range` header is what lets an // `