Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 8 additions & 6 deletions .oxlintrc.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
},
"ignorePatterns": ["tools/oxlint/anti-slop/**"],
"jsPlugins": [
"./tools/oxlint/architecture/index.ts",
"./tools/oxlint/anti-slop/index.ts",
"./tools/oxlint/next/index.ts",
"eslint-plugin-turbo",
Expand All @@ -35,10 +36,11 @@
],
"settings": {
"tailwindcss": {
"entryPoint": "src/app/globals.css"
"entryPoint": "app/globals.css"
}
},
"rules": {
"local-architecture/no-forbidden-layer-imports": "error",
"anti-slop/no-chained-type-assertions": "error",
"anti-slop/no-conditional-empty-object-spread": "error",
"anti-slop/no-known-value-widening": "error",
Expand Down Expand Up @@ -273,7 +275,7 @@
"tailwindcss/no-deprecated-classes": "warn",
"tailwindcss/no-duplicate-classes": "error",
// The semantic typography utilities are hand-authored `@layer utilities`
// classes in src/app/styles/brand/typography.css.
// classes in app/styles/brand/typography.css.
"tailwindcss/no-unknown-classes": [
"warn",
{
Expand Down Expand Up @@ -407,7 +409,7 @@
{
// shadcn registry source uses behavior utilities that the Tailwind
// linter cannot resolve through package stylesheet imports.
"files": ["src/components/ui/**/*.tsx"],
"files": ["web/components/ui/**/*.tsx"],
"rules": {
"tailwindcss/no-unknown-classes": "off"
}
Expand All @@ -417,9 +419,9 @@
// package styles that the Tailwind linter cannot resolve from the app
// entrypoint. Keep this exception limited to the affected components.
"files": [
"src/components/ai-elements/message.tsx",
"src/components/ai-elements/reasoning.tsx",
"src/components/ai-elements/tool.tsx"
"web/components/ai-elements/message.tsx",
"web/components/ai-elements/reasoning.tsx",
"web/components/ai-elements/tool.tsx"
],
"rules": {
"tailwindcss/no-unknown-classes": "off"
Expand Down
12 changes: 6 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ Run the validation the task requests. When it does not establish the behavior yo

- The repository root owns the single Next.js application, Eve agent, and shared UI contract.
- The workspace manager lives on `/` and the agent chat on `/chat`; browser execution belongs only to the declared browser-agent subagent's flat tool surface under `agent/subagents/browser-agent/tools`.
- Keep each worker browser tool's schema and implementation together. Share the Kernel SDK client through `src/lib/kernel.ts`; do not add a Kernel extension or root browser connection.
- Keep each worker browser tool's schema and implementation together. Share the Kernel SDK client through `agent/subagents/browser-agent/lib/kernel.ts`; do not add a Kernel extension or root browser connection.
- `agent/subagents/browser-agent/lib` is for code genuinely shared by worker tools. Group a shared worker domain in a lower-case folder, such as `trace/domains.ts` or `autofill/provider.ts`; do not use it as a holding area for a tool's one-off logic.
- Validate runtime environment variables through `src/env.ts`. `KERNEL_API_KEY` is required by the worker browser tools.
- Validate runtime environment variables through `shared/environment/env.ts`. `KERNEL_API_KEY` is required by the worker browser tools.
- Run `pnpm check` and `pnpm build` before handing off changes.

## Code organization

- Treat `src/lib` as a small shared infrastructure and contract boundary, not a default destination for application code. A file belongs there only when it has real cross-feature ownership; put database access in `db/services`, agent behavior under `agent`, and route or section behavior with its route.
- Do not add a generic `src/modules` layer. Give code a concrete owner and colocate it there. A route section owns its section components, forms, and local parsing; split it only when the files have distinct responsibilities.
- Treat `shared` as a narrow contract boundary, not a default destination for application code. A file belongs there only when at least two of `agent`, `app`, `web`, and `db` consume it; put database access in `db/services`, agent behavior under `agent`, and route or section behavior with its route.
- Do not add a generic `modules` layer. Give code a concrete owner and colocate it there. A route section owns its section components, forms, and local parsing; split it only when the files have distinct responsibilities.
- Prefer one cohesive call-site file for code used once. Do not add production factories, dependency containers, server wrappers, or files solely to make a unit test easier to mock.
- Mock imported modules at their owning or external boundary in tests. Do not export mutable dependency bags, dependency setters, reset hooks, or other test-only seams from production modules; keep production exports limited to application behavior and real domain contracts.
- Use lower-case file and folder names. When several files share a domain prefix, make that prefix a folder and name files for their role, such as `trace/domains.ts` rather than `trace-domains.ts`. Do not introduce camel-case filenames.
Expand All @@ -76,8 +76,8 @@ Run the validation the task requests. When it does not establish the behavior yo

Before planning or changing product UI:

- Build from the primitives in `src/components/ui` and the semantic `type-*`
typography utilities defined in `src/app/styles/brand/typography.css`.
- Build from the primitives in `web/components/ui` and the semantic `type-*`
typography utilities defined in `app/styles/brand/typography.css`.
- Preserve the current `components.json` primitive base and local extensions;
add new primitives with the official shadcn CLI.

Expand Down
8 changes: 4 additions & 4 deletions agent/agent.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { defineAgent, defineDynamic } from "eve";
import { scheduledRunIdentity } from "@/agent/lib/schedules/identity";
import { isScheduledAgentRunLeaseActive } from "@/db/services/scheduled-agent-run-leases";
import { getGatewayModel } from "@/db/services/settings";
import { scopeFromPrincipal } from "@/agent/lib/principal-scope";
import { scheduledRunIdentity } from "@agent/lib/schedules/identity";
import { isScheduledAgentRunLeaseActive } from "@db/services/scheduled-agent-run-leases";
import { getGatewayModel } from "@db/services/settings";
import { scopeFromPrincipal } from "@agent/lib/principal-scope";

export default defineAgent({
experimental: {
Expand Down
13 changes: 8 additions & 5 deletions agent/channels/eve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@ import {
UnauthenticatedError,
} from "eve/channels/auth";
import { z } from "zod";
import { isSessionOwned } from "@/db/services/sessions";
import { accessScopeForUser, type AccessScope } from "@/lib/access-scope";
import { getAuthSession } from "@/auth/session";
import { sendMessageToolResultSchema } from "@/agent/lib/send-message";
import { isSessionOwned } from "@db/services/sessions";
import {
accessScopeForUser,
type AccessScope,
} from "@shared/identity/access-scope";
import { getAuthSession } from "@db/services/auth/session";
import { sendMessageToolResultSchema } from "@shared/chat/message-delivery";
import {
finalizeScheduledReportDelivery,
releaseScheduledReportDelivery,
scheduledReportFromSession,
} from "@/agent/lib/schedules/report-lifecycle";
} from "@agent/lib/schedules/report-lifecycle";

const authenticateLocalDev = localDev();

Expand Down
16 changes: 8 additions & 8 deletions agent/channels/linq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@ import {
} from "eve/channels/linq";
import { vercelOidc } from "eve/channels/auth";
import { z } from "zod";
import { getAuth } from "@/auth";
import { reactToMessageToolResultSchema } from "@/agent/lib/react-to-message";
import { sendMessageToolResultSchema } from "@/agent/lib/send-message";
import { normalizeAuthPhoneNumber } from "@/auth/phone-number";
import { scopeFromPrincipal } from "@/agent/lib/principal-scope";
import { accessScopeForUser } from "@/lib/access-scope";
import { getAuth } from "@db/services/auth";
import { reactToMessageToolResultSchema } from "@shared/chat/reaction";
import { sendMessageToolResultSchema } from "@shared/chat/message-delivery";
import { normalizeAuthPhoneNumber } from "@shared/identity/phone-number";
import { scopeFromPrincipal } from "@agent/lib/principal-scope";
import { accessScopeForUser } from "@shared/identity/access-scope";
import { prepareLinqImageArtifactDelivery } from "../lib/linq-image-artifact/delivery";
import {
extractImageArtifactMarkdownReferences,
stripImageArtifactMarkdownReferences,
} from "../lib/linq-image-artifact/markdown";
import { env } from "@/env";
import { env } from "@shared/environment";
import {
finalizeScheduledReportDelivery,
releaseScheduledReportDelivery,
scheduledReportFromSession,
} from "@/agent/lib/schedules/report-lifecycle";
} from "@agent/lib/schedules/report-lifecycle";

const verifiedPhoneUserSchema = z.object({
id: z.string().min(1),
Expand Down
4 changes: 2 additions & 2 deletions agent/channels/scheduled-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { defineChannel, POST } from "eve/channels";
import { localDev, routeAuth, vercelOidc } from "eve/channels/auth";
import { parseInputResponses, resolveTextToResponses } from "eve/client";
import { z } from "zod";
import { dispatchScheduledReport } from "@/agent/lib/schedules/report";
import { dispatchScheduledReport } from "@agent/lib/schedules/report";
import {
claimScheduledAgentRunInput,
finishScheduledAgentRunInput,
restoreScheduledAgentRunInput,
} from "@/db/services/scheduled-agent-jobs";
} from "@db/services/scheduled-agent-jobs";

const scheduledRunTargetSchema = z.strictObject({
restart: z.boolean().optional(),
Expand Down
6 changes: 3 additions & 3 deletions agent/hooks/scheduled-run-completion.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { defineHook } from "eve/hooks";
import { scheduledRunIdentity } from "@/agent/lib/schedules/identity";
import { scheduledRunOutcomeSchema } from "@/agent/lib/schedules/outcome";
import { scheduledRunIdentity } from "@agent/lib/schedules/identity";
import { scheduledRunOutcomeSchema } from "@shared/schedules/outcome";
import {
completeScheduledAgentRun,
deferScheduledAgentRunCompletion,
markScheduledAgentRunStarted,
releaseScheduledAgentRun,
waitForScheduledAgentRunInput,
} from "@/db/services/scheduled-agent-jobs";
} from "@db/services/scheduled-agent-jobs";

const workerRuntimeLimitMs = 6 * 60 * 60_000;

Expand Down
8 changes: 4 additions & 4 deletions agent/hooks/session-owner.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { defineHook, type HookContext } from "eve/hooks";
import { saveChat } from "@/db/services/chats";
import { ensureScope } from "@/db/services/scope";
import { claimSession } from "@/db/services/sessions";
import { scopeFromPrincipal } from "@/agent/lib/principal-scope";
import { saveChat } from "@db/services/chats";
import { ensureScope } from "@db/services/scope";
import { claimSession } from "@db/services/sessions";
import { scopeFromPrincipal } from "@agent/lib/principal-scope";

export default defineHook({
events: {
Expand Down
2 changes: 1 addition & 1 deletion agent/instructions/10-execution-safety.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineDynamic } from "eve/instructions";
import { resolveModeInstructions } from "@/agent/lib/mode";
import { resolveModeInstructions } from "@agent/lib/mode";
import executionSafety from "./content/execution-safety.md?raw";

export default defineDynamic({
Expand Down
2 changes: 1 addition & 1 deletion agent/instructions/20-role.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineDynamic } from "eve/instructions";
import { resolveModeInstructions } from "@/agent/lib/mode";
import { resolveModeInstructions } from "@agent/lib/mode";
import interactiveInstructions from "./content/role/interactive.md?raw";
import scheduledReportInstructions from "./content/role/scheduled-report.md?raw";
import scheduledWorkerInstructions from "./content/role/scheduled-worker.md?raw";
Expand Down
2 changes: 1 addition & 1 deletion agent/instructions/25-worker-coordination.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineDynamic } from "eve/instructions";
import { resolveModeInstructions } from "@/agent/lib/mode";
import { resolveModeInstructions } from "@agent/lib/mode";
import workerCoordination from "./content/worker-coordination.md?raw";

export default defineDynamic({
Expand Down
2 changes: 1 addition & 1 deletion agent/instructions/30-message-style.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineDynamic } from "eve/instructions";
import { resolveModeInstructions } from "@/agent/lib/mode";
import { resolveModeInstructions } from "@agent/lib/mode";
import messageStyle from "./content/message-style.md?raw";

export default defineDynamic({
Expand Down
4 changes: 2 additions & 2 deletions agent/lib/google-workspace/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { auth } from "@googleapis/gmail";
import { connect, type EveAuthorizationOptions } from "@vercel/connect/eve";
import type { ToolContext } from "eve/tools";
import { z } from "zod";
import { env } from "@/env";
import { env } from "@shared/environment";
import {
googleWorkspaceSubject,
googleWorkspaceScopes,
} from "@/lib/google-workspace";
} from "@shared/google-workspace/connection";

export const googleWorkspaceAuthOptions = {
connector: env.GOOGLE_CONNECTOR_UID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import * as GmailApi from "@googleapis/gmail";
import * as PeopleApi from "@googleapis/people";
import type { ToolContext } from "eve/tools";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createCalendarEvent } from "@/agent/lib/google-workspace/calendar";
import { withGoogleAuth } from "@/agent/lib/google-workspace/client";
import { searchGoogleContacts } from "@/agent/lib/google-workspace/contacts";
import { sendGmail } from "@/agent/lib/google-workspace/gmail";
import { createCalendarEvent } from "@agent/lib/google-workspace/calendar";
import { withGoogleAuth } from "@agent/lib/google-workspace/client";
import { searchGoogleContacts } from "@agent/lib/google-workspace/contacts";
import { sendGmail } from "@agent/lib/google-workspace/gmail";

interface RequestOptions {
signal: AbortSignal;
Expand Down
12 changes: 6 additions & 6 deletions agent/lib/google-workspace/tests/google-workspace.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { describe, expect, it } from "vitest";
import { parseCalendarAvailability } from "@/agent/lib/google-workspace/calendar";
import { googleWorkspaceAuthOptions } from "@/agent/lib/google-workspace/client";
import { gmailUpdateLabels } from "@/agent/lib/google-workspace/gmail";
import { calendarCreateEvent } from "@/agent/tools/calendar";
import { gmailSend, gmailUpdate } from "@/agent/tools/gmail";
import { parseCalendarAvailability } from "@agent/lib/google-workspace/calendar";
import { googleWorkspaceAuthOptions } from "@agent/lib/google-workspace/client";
import { gmailUpdateLabels } from "@agent/lib/google-workspace/gmail";
import { calendarCreateEvent } from "@agent/tools/calendar";
import { gmailSend, gmailUpdate } from "@agent/tools/gmail";
import {
googleWorkspaceScopes,
googleWorkspaceSubject,
googleWorkspaceTokenParams,
} from "@/lib/google-workspace";
} from "@shared/google-workspace/connection";

const userId = "better-auth:user-123";

Expand Down
4 changes: 2 additions & 2 deletions agent/lib/linq-image-artifact/delivery.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* oxlint-disable vitest/require-mock-type-parameters -- The Blob mock implements only the read operation exercised here. */
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { AccessScope } from "@/lib/access-scope";
import type { AccessScope } from "@shared/identity/access-scope";

const firstId = "0d01e667-d128-4bb7-a248-1ae21db72f4f";
const secondId = "206c3a7e-c0b8-4317-9e34-552cff646673";
const mocks = vi.hoisted(() => ({ getBlob: vi.fn(), readArtifact: vi.fn() }));

vi.mock("@/db/services/browser-images", () => ({
vi.mock("@db/services/browser-images", () => ({
readReadyBrowserImageArtifact: mocks.readArtifact,
}));
vi.mock("@vercel/blob", () => ({
Expand Down
10 changes: 5 additions & 5 deletions agent/lib/linq-image-artifact/delivery.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { createHash } from "node:crypto";
import { get } from "@vercel/blob";
import type { AccessScope } from "@/lib/access-scope";
import { readReadyBrowserImageArtifact } from "@/db/services/browser-images";
import { maximumBrowserImageBytes } from "@/lib/browser-artifact";
import { env } from "@/env";
import { maximumWorkerCompletionImages } from "@/lib/worker-completion";
import type { AccessScope } from "@shared/identity/access-scope";
import { readReadyBrowserImageArtifact } from "@db/services/browser-images";
import { maximumBrowserImageBytes } from "@shared/browser/artifact";
import { env } from "@shared/environment";
import { maximumWorkerCompletionImages } from "@agent/subagents/browser-agent/lib/completion";
import {
extractImageArtifactMarkdownReferences,
stripImageArtifactMarkdownReferences,
Expand Down
2 changes: 1 addition & 1 deletion agent/lib/linq-image-artifact/markdown.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isBrowserImageArtifactUrl } from "@/lib/browser-artifact";
import { isBrowserImageArtifactUrl } from "@shared/browser/artifact";

const imageArtifactMarkdownPattern =
/!\[((?:\\.|[^\]])*)\]\((\/artifacts\/([^\s)]+))\)/giu;
Expand Down
5 changes: 4 additions & 1 deletion agent/lib/principal-scope.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { ConnectionPrincipal } from "eve/connections";
import type { SessionAuthContext } from "eve/context";
import { z } from "zod";
import { accessScopeForUser, type AccessScope } from "@/lib/access-scope";
import {
accessScopeForUser,
type AccessScope,
} from "@shared/identity/access-scope";

const principalScopeSchema = z.object({
attributes: z.object({
Expand Down
4 changes: 2 additions & 2 deletions agent/lib/profile-memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
type MemoryScopeContext,
} from "eve/memory";
import { z } from "zod";
import type { env } from "@/env";
import { resolveModeValue } from "@/agent/lib/mode";
import type { env } from "@shared/environment";
import { resolveModeValue } from "@agent/lib/mode";

export function resolveProfileMemoryBackend(
environment: Pick<
Expand Down
4 changes: 2 additions & 2 deletions agent/lib/schedules/report-lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import type { SessionContext } from "eve/context";
import {
finalizeScheduledReport,
releaseScheduledReport,
} from "@/db/services/scheduled-agent-jobs";
import { scheduledReportIdentity } from "@/agent/lib/schedules/identity";
} from "@db/services/scheduled-agent-jobs";
import { scheduledReportIdentity } from "@agent/lib/schedules/identity";

export function scheduledReportFromSession(session: SessionContext) {
return scheduledReportIdentity(session.session.auth);
Expand Down
2 changes: 1 addition & 1 deletion agent/lib/schedules/report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
claimScheduledReport,
finalizeScheduledReport,
releaseScheduledReport,
} from "@/db/services/scheduled-agent-jobs";
} from "@db/services/scheduled-agent-jobs";
import linq from "../../channels/linq";

export async function dispatchScheduledReport(
Expand Down
4 changes: 2 additions & 2 deletions agent/lib/schedules/request.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { readFile } from "node:fs/promises";
import { getVercelOidcToken } from "@vercel/oidc";
import { z } from "zod";
import { env } from "@/env";
import { applicationOrigin } from "@/lib/application-origin";
import { env } from "@shared/environment";
import { applicationOrigin } from "@shared/environment/origin";

const eveDevServerSchema = z.object({
appRoot: z.string(),
Expand Down
4 changes: 2 additions & 2 deletions agent/lib/schedules/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { z } from "zod";
import type {
createScheduledAgentJob,
listScheduledAgentJobs,
} from "@/db/services/scheduled-agent-jobs";
import { scopeFromPrincipal } from "@/agent/lib/principal-scope";
} from "@db/services/scheduled-agent-jobs";
import { scopeFromPrincipal } from "@agent/lib/principal-scope";

export function scheduleOwner(context: ToolContext) {
const auth = context.session.auth.current;
Expand Down
4 changes: 2 additions & 2 deletions agent/lib/tests/principal-scope.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import { scopeFromPrincipal } from "@/agent/lib/principal-scope";
import { accessScopeForUser } from "@/lib/access-scope";
import { scopeFromPrincipal } from "@agent/lib/principal-scope";
import { accessScopeForUser } from "@shared/identity/access-scope";

describe("principal scope", () => {
it("accepts the workspace derived from the authenticated user", () => {
Expand Down
Loading