From 0d1939c775a2f94a8f672089bc78e9c48521b509 Mon Sep 17 00:00:00 2001 From: cynarAI <155274210+cynarAI@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:04:17 +0200 Subject: [PATCH 1/4] ci: soft-fail mobile OTA when EXPO_TOKEN is missing (#124) * ci: soft-fail mobile OTA when EXPO_TOKEN is missing OTA publish is an infra helper; missing Expo credentials should not fail the ci workflow or block evo-deploy after green Product CI. Co-authored-by: cynarAI * test(updater): narrow optional workflow jobs and format the OTA assertion Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_015mFCmHk4zPyWpD9yY2C3yJ --------- Co-authored-by: Cursor Agent Co-authored-by: cynarAI Co-authored-by: Claude --- .github/workflows/ci.yml | 28 ++++++++++------ infra/updater/src/ci-workflow.test.ts | 47 +++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 infra/updater/src/ci-workflow.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad09384..3621a7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -246,6 +246,7 @@ jobs: publish-mobile-update: name: Publish compatible mobile update if: github.event_name == 'push' && github.ref == 'refs/heads/main' + continue-on-error: true needs: [lint, check, build, test, test-integration, test-e2e] runs-on: [self-hosted, evoo-ci, Linux] timeout-minutes: 20 @@ -344,23 +345,30 @@ jobs: fi echo "publish=$publish" >> "$GITHUB_OUTPUT" - - name: Require Expo automation token + - name: Check Expo automation token + id: expo_token if: steps.ota.outputs.publish == 'true' env: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} run: | if [[ -z "$EXPO_TOKEN" ]]; then - echo "Add an EXPO_TOKEN repository secret with access to the inbox-zero/rakazo Expo project." >&2 - exit 1 + { + echo "### Mobile OTA skipped" + echo + echo "No \`EXPO_TOKEN\` repository secret is configured, so CI will not publish a production update. Add a token with access to the inbox-zero/rakazo Expo project when OTA from main is desired." + } >> "$GITHUB_STEP_SUMMARY" + echo "ready=false" >> "$GITHUB_OUTPUT" + exit 0 fi + echo "ready=true" >> "$GITHUB_OUTPUT" - uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4 - if: steps.ota.outputs.publish == 'true' + if: steps.ota.outputs.publish == 'true' && steps.expo_token.outputs.ready == 'true' id: pnpm-setup with: dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.job }} - name: Isolate pnpm store and PATH - if: steps.ota.outputs.publish == 'true' + if: steps.ota.outputs.publish == 'true' && steps.expo_token.outputs.ready == 'true' shell: bash run: | set -euo pipefail @@ -377,25 +385,25 @@ jobs: mkdir -p "$store" echo "npm_config_store_dir=${store}" >> "$GITHUB_ENV" - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 - if: steps.ota.outputs.publish == 'true' + if: steps.ota.outputs.publish == 'true' && steps.expo_token.outputs.ready == 'true' with: node-version: 24 cache: pnpm - name: Set up Expo - if: steps.ota.outputs.publish == 'true' + if: steps.ota.outputs.publish == 'true' && steps.expo_token.outputs.ready == 'true' uses: expo/expo-github-action@eab7a230208c952974db8c3245cfd78402c7b385 # v9 with: eas-version: latest packager: pnpm token: ${{ secrets.EXPO_TOKEN }} - name: Install dependencies - if: steps.ota.outputs.publish == 'true' + if: steps.ota.outputs.publish == 'true' && steps.expo_token.outputs.ready == 'true' run: pnpm install --frozen-lockfile - name: Verify mobile project - if: steps.ota.outputs.publish == 'true' + if: steps.ota.outputs.publish == 'true' && steps.expo_token.outputs.ready == 'true' run: pnpm --filter @rakazo/mobile check - name: Publish production update - if: steps.ota.outputs.publish == 'true' + if: steps.ota.outputs.publish == 'true' && steps.expo_token.outputs.ready == 'true' working-directory: apps/mobile run: | set -euo pipefail diff --git a/infra/updater/src/ci-workflow.test.ts b/infra/updater/src/ci-workflow.test.ts new file mode 100644 index 0000000..ddbaae1 --- /dev/null +++ b/infra/updater/src/ci-workflow.test.ts @@ -0,0 +1,47 @@ +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; +import { parse } from "yaml"; + +const workflowText = readFileSync( + path.resolve(import.meta.dirname, "../../../.github/workflows/ci.yml"), + "utf8", +); + +interface WorkflowJob { + if?: string; + needs?: string[]; + "continue-on-error"?: boolean; +} + +const workflow = parse(workflowText) as { + jobs: Record; +}; + +describe("ci workflow merge gate", () => { + it("aggregates only product jobs in Product CI", () => { + const productGate = workflow.jobs["product-gate"]; + expect(productGate?.needs).toEqual([ + "lint", + "check", + "build", + "test", + "test-integration", + "test-e2e", + ]); + expect(productGate?.if).toBe("always()"); + }); + + it("soft-fails mobile OTA so missing Expo credentials do not fail the workflow", () => { + const mobile = workflow.jobs["publish-mobile-update"]; + expect(mobile?.["continue-on-error"]).toBe(true); + expect(workflowText).toContain('echo "ready=false" >> "$GITHUB_OUTPUT"'); + expect(workflowText).not.toMatch( + /Check Expo automation token[\s\S]*?exit 1[\s\S]*?Require Expo/, + ); + expect(workflowText).not.toContain( + "Add an EXPO_TOKEN repository secret with access to the inbox-zero/rakazo Expo project.", + ); + expect(workflowText).toContain("steps.expo_token.outputs.ready == 'true'"); + }); +}); From a51ba87133766c95f190a5e7b0e14f760a28cb42 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 3 Oct 2026 21:07:41 +0000 Subject: [PATCH 2/4] fix(web): recover local settings boot when module fetch aborts Lazy-load the desktop-settings page and its heavy sections so loadWithRecovery can retry or reload after ERR_NETWORK_CHANGED. Show the no-desktop alert immediately when the bridge is absent. Co-authored-by: cynarAI --- apps/web/e2e/local-settings.spec.ts | 8 +++++-- apps/web/src/App.tsx | 12 +++++++++-- apps/web/src/pages/LocalSettings.tsx | 31 +++++++++++++++++++++------- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/apps/web/e2e/local-settings.spec.ts b/apps/web/e2e/local-settings.spec.ts index 64d1d49..fe336d3 100644 --- a/apps/web/e2e/local-settings.spec.ts +++ b/apps/web/e2e/local-settings.spec.ts @@ -75,7 +75,11 @@ test("local settings open and save integrations without an app session", async ( }); test("ordinary browsers cannot use local settings", async ({ page }) => { - await page.goto("/desktop-settings"); - await expect(page.getByRole("alert")).toContainText("Could not open local settings"); + await expect(async () => { + await page.goto("/desktop-settings", { waitUntil: "domcontentloaded" }); + await expect(page.getByRole("alert")).toContainText("Could not open local settings", { + timeout: 5_000, + }); + }).toPass({ timeout: 45_000 }); await expect(page.getByRole("button", { name: "Models", exact: true })).toBeHidden(); }); diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 12dd355..1b14b88 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -14,7 +14,6 @@ import { showSessionUnavailable, } from "./lib/session-gate"; import { IntegrationSetupPage } from "./pages/IntegrationSetup"; -import { LocalSettingsPage } from "./pages/LocalSettings"; import { McpOAuthCallbackPage } from "./pages/McpOAuthCallback"; import { ShellPage } from "./pages/Shell"; @@ -30,9 +29,18 @@ const OnboardingPage = lazyWithRecovery("pages/Onboarding", () => const WelcomePage = lazyWithRecovery("pages/Welcome", () => import("./pages/Welcome").then((module) => ({ default: module.WelcomePage })), ); +const LocalSettingsPage = lazyWithRecovery("pages/LocalSettings", () => + import("./pages/LocalSettings").then((module) => ({ default: module.LocalSettingsPage })), +); export function App() { - if (window.location.pathname === LOCAL_SETTINGS_PAGE) return ; + if (window.location.pathname === LOCAL_SETTINGS_PAGE) { + return ( + }> + + + ); + } return ; } diff --git a/apps/web/src/pages/LocalSettings.tsx b/apps/web/src/pages/LocalSettings.tsx index 3a1e498..b5ef9d7 100644 --- a/apps/web/src/pages/LocalSettings.tsx +++ b/apps/web/src/pages/LocalSettings.tsx @@ -1,20 +1,31 @@ import { Trans } from "@lingui/react/macro"; import { Button } from "@rakazo/ui-web"; -import { useEffect, useState } from "react"; -import { IntegrationSetup } from "../components/integrations/IntegrationSetup"; +import { Suspense, useEffect, useState } from "react"; +import { desktopBridge } from "../lib/desktop"; +import { lazyWithRecovery } from "../lib/lazy-recovery"; import { rpc } from "../lib/rpc"; -import { ModelSettingsOverlay } from "./ModelSettingsOverlay"; + +const IntegrationSetup = lazyWithRecovery("integrations/IntegrationSetup", () => + import("../components/integrations/IntegrationSetup").then((module) => ({ + default: module.IntegrationSetup, + })), +); +const ModelSettingsOverlay = lazyWithRecovery("pages/ModelSettingsOverlay", () => + import("./ModelSettingsOverlay").then((module) => ({ default: module.ModelSettingsOverlay })), +); export function LocalSettingsPage() { const [section, setSection] = useState<"models" | "integrations" | null>(null); + const hasLocalBridge = Boolean(desktopBridge()?.localSettings); const [ready, setReady] = useState(false); - const [error, setError] = useState(false); + const [error, setError] = useState(!hasLocalBridge); useEffect(() => { + if (!hasLocalBridge) return; void rpc.integrationSetup .get() .then(() => setReady(true)) .catch(() => setError(true)); - }, []); + }, [hasLocalBridge]); return (
@@ -43,9 +54,15 @@ export function LocalSettingsPage() { {section === "models" ? ( - setSection(null)} localOwner /> + + setSection(null)} localOwner /> + + ) : null} + {section === "integrations" ? ( + + + ) : null} - {section === "integrations" ? : null} ) : null}
From 8449dec6c21a36c2062532be3baa859adac91bcc Mon Sep 17 00:00:00 2001 From: cynarAI <155274210+cynarAI@users.noreply.github.com> Date: Sun, 4 Oct 2026 00:38:39 +0200 Subject: [PATCH 3/4] feat: give every project its own main agent (#125) Each project (space) now has a main agent and a main conversation of its own. The user talks to it, and it delegates to the project's specialists. Creating a project sets both up and opens the conversation, and deleting an otherwise empty project removes them with it. Claude-Session: https://claude.ai/code/session_01R8hSsKTB3W3e61wD6kvzur Co-authored-by: Claude --- apps/api/src/main-conversation.ts | 21 +- apps/api/src/onboarding-scan.test.ts | 5 + apps/api/src/onboarding-scan.ts | 2 + apps/api/src/onboarding.ts | 2 +- apps/api/src/router.ts | 183 +++++++++++++----- apps/mobile/app/new-space.tsx | 6 +- apps/mobile/lib/api.ts | 2 +- apps/mobile/lib/inbox-spaces.test.ts | 23 +++ apps/mobile/lib/inbox-spaces.ts | 5 +- apps/web/e2e/empty-project-starten.spec.ts | 98 ---------- apps/web/e2e/main-conversation.spec.ts | 8 +- apps/web/e2e/project-main-agent.spec.ts | 87 +++++++++ apps/web/e2e/spaces.spec.ts | 60 +++--- apps/web/src/lib/project-empty-state.test.ts | 17 +- apps/web/src/lib/project-empty-state.ts | 14 +- apps/web/src/pages/Shell.tsx | 97 +++++----- packages/adapters/src/delegated-tasks.ts | 21 +- .../src/executor-capabilities.test.ts | 1 + .../src/executor-instructions.test.ts | 19 ++ packages/adapters/src/executor-prompt.ts | 18 +- packages/adapters/src/executor.ts | 20 +- .../adapters/src/profile-consolidation.ts | 2 + packages/contracts/src/domain.ts | 6 +- packages/contracts/src/rpc.ts | 3 + .../migration.sql | 7 + packages/db/prisma/schema.prisma | 8 +- packages/db/src/main-agent.postgres.test.ts | 32 +++ packages/db/src/main-agent.ts | 123 ++++++++---- packages/db/src/spaces.ts | 28 ++- packages/testkit/src/authorization.test.ts | 36 +++- .../testkit/src/delegation.postgres.test.ts | 52 +++++ 31 files changed, 693 insertions(+), 313 deletions(-) delete mode 100644 apps/web/e2e/empty-project-starten.spec.ts create mode 100644 apps/web/e2e/project-main-agent.spec.ts create mode 100644 packages/db/prisma/migrations/20261003210000_main_agent_per_space/migration.sql diff --git a/apps/api/src/main-conversation.ts b/apps/api/src/main-conversation.ts index b3ec5da..18d7b1f 100644 --- a/apps/api/src/main-conversation.ts +++ b/apps/api/src/main-conversation.ts @@ -43,13 +43,20 @@ export async function assertNotMainConversation( if (group?.kind === "main") throw new ORPCError("BAD_REQUEST", { message }); } -/** The main conversation carries the main bot's name. */ -export async function renameMainConversation(prisma: PrismaClient, userId: string, name: string) { - await prisma.chatGroup.updateMany({ where: { userId, kind: "main" }, data: { name } }); +/** A main conversation carries its main bot's name. */ +export async function renameMainConversation( + prisma: PrismaClient, + main: { userId: string; spaceId: string }, + name: string, +) { + await prisma.chatGroup.updateMany({ + where: { userId: main.userId, spaceId: main.spaceId, kind: "main" }, + data: { name }, + }); } /** - * Same-space bots the user addresses in the main conversation, by `@Name` or a mention chip, + * Same-space bots the user addresses in a main conversation, by `@Name` or a mention chip, * join it first, so the member mention rules route the message to them. */ export async function joinAddressedBots( @@ -71,5 +78,9 @@ export async function joinAddressedBots( .map((bot) => bot.id); if (addressed.length === 0) return; // Subscribers pick up the group.updated event with the send that follows. - await joinMainConversation(prisma, { userId: actor.userId, botIds: addressed }); + await joinMainConversation(prisma, { + userId: actor.userId, + spaceId: actor.spaceId, + botIds: addressed, + }); } diff --git a/apps/api/src/onboarding-scan.test.ts b/apps/api/src/onboarding-scan.test.ts index c919310..549a45b 100644 --- a/apps/api/src/onboarding-scan.test.ts +++ b/apps/api/src/onboarding-scan.test.ts @@ -7,9 +7,11 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { getScanStatus, startOnboardingScan } from "./onboarding-scan.js"; const createSpaceForMember = vi.hoisted(() => vi.fn()); +const ensureMainConversation = vi.hoisted(() => vi.fn()); vi.mock("@rakazo/db", async (original) => ({ ...(await original()), createSpaceForMember, + ensureMainConversation, })); const owner = { userId: "user-owner", spaceId: "space-owner", isDeploymentOwner: true }; @@ -72,6 +74,7 @@ describe("onboarding scan", () => { afterEach(() => { vi.unstubAllEnvs(); createSpaceForMember.mockReset(); + ensureMainConversation.mockReset(); }); it("hides a scan from every account except the one that started it", async () => { @@ -127,6 +130,8 @@ describe("onboarding scan", () => { name: "app", }); expect(spaces).toEqual([{ id: "space-1", name: "app", workspacePath: app }]); + // Each new project starts with its own main agent. + expect(ensureMainConversation).toHaveBeenCalledWith(prisma, owner.userId, "space-1"); // Scanning again finds the linked project instead of creating a second one. const again = await startOnboardingScan({ actor: owner, scanHome }, prisma); diff --git a/apps/api/src/onboarding-scan.ts b/apps/api/src/onboarding-scan.ts index bad1aa8..b804fe7 100644 --- a/apps/api/src/onboarding-scan.ts +++ b/apps/api/src/onboarding-scan.ts @@ -7,6 +7,7 @@ import type { Actor } from "@rakazo/contracts"; import type { PrismaClient } from "@rakazo/db"; import { createSpaceForMember, + ensureMainConversation, InvalidSpaceNameError, resolveProjectWorkspaceDirectory, SpaceLimitError, @@ -676,6 +677,7 @@ async function createProjects( spaceId: space.id, workspacePath: folder, }); + await ensureMainConversation(prisma, actor.userId, space.id); created.push({ spaceId: space.id, name: space.name, path: workspace.path }); } diff --git a/apps/api/src/onboarding.ts b/apps/api/src/onboarding.ts index b0bf565..71a4759 100644 --- a/apps/api/src/onboarding.ts +++ b/apps/api/src/onboarding.ts @@ -79,7 +79,7 @@ async function requireBotThread(deps: OnboardingDeps, actor: Actor, botId: strin }); if (!bot?.thread) throw new IsolationError(); if (bot.kind === "main") { - const conversation = await ensureMainConversation(deps.prisma, actor.userId); + const conversation = await ensureMainConversation(deps.prisma, actor.userId, bot.spaceId); return { bot, thread: { id: conversation.threadId } }; } return { bot, thread: bot.thread }; diff --git a/apps/api/src/router.ts b/apps/api/src/router.ts index 0f515aa..75a3882 100644 --- a/apps/api/src/router.ts +++ b/apps/api/src/router.ts @@ -88,7 +88,15 @@ import { verifyMcpInstall, } from "@rakazo/adapters"; import type { Auth } from "@rakazo/auth"; -import type { Actor, ComputerStatus, McpServer, Me, SpaceNavigation } from "@rakazo/contracts"; +import type { + Actor, + ComputerStatus, + McpServer, + Me, + SpaceBot, + SpaceGroup, + SpaceNavigation, +} from "@rakazo/contracts"; import { appContract, IntegrationProviderIdSchema, @@ -131,6 +139,7 @@ import { listSpaceUserBotIds, newestModelCredentialOrder, newestVoiceCredentialOrder, + ownMainAgentsBlockingSpaceDeletion, Prisma, parseComputerMode, projectWorkspaceDto, @@ -491,6 +500,26 @@ export interface RouterDeps { * keep the deletion claim renewed forever and block stale-claim recovery. */ const SPACE_TEARDOWN_TIMEOUT_MS = 120_000; +async function removeGroupWithArtifacts( + deps: RouterDeps, + groupRepos: ReturnType, + actor: Actor, + groupId: string, +) { + const removed = await groupRepos.removeGroup(actor, groupId); + const cleanup = await Promise.allSettled( + removed.artifactStorageKeys.map((storageKey) => + deps.artifacts.remove( + storageKey, + computerContext(actor, removed.contextBotId, `group-remove:${groupId}`), + ), + ), + ); + for (const result of cleanup) { + if (result.status === "rejected") getLogger().error("group artifact cleanup", result.reason); + } +} + function spaceTeardownTimeoutMs(): number { const override = Number(process.env.SPACE_TEARDOWN_TIMEOUT_MS ?? ""); return Number.isFinite(override) && override > 0 ? override : SPACE_TEARDOWN_TIMEOUT_MS; @@ -602,6 +631,13 @@ export function createRouter(deps: RouterDeps) { const workspace = workspacePath ? await wireProjectWorkspace(deps.prisma, { spaceId: space.id, workspacePath }) : null; + // A project starts as a conversation with its own main agent. + await ensureMainConversation(deps.prisma, context.actor.userId, space.id); + const spaceActor = await requireMembership(deps.prisma, context.actor.userId, space.id); + const [bots, groups] = await Promise.all([ + repos.listBots(spaceActor), + groupRepos.listGroups(spaceActor), + ]); return { id: space.id, name: space.name, @@ -609,12 +645,26 @@ export function createRouter(deps: RouterDeps) { hasContent: false, canDelete: true, workspace, - bots: [], - groups: [], + bots: bots.map(spaceBotDto), + groups: groups.map(spaceGroupDto), externalConversations: [], botSections: [], }; }), + ensureMain: authed.spaces.ensureMain.handler(async ({ context, input }) => { + const conversation = await ensureMainConversation( + deps.prisma, + context.actor.userId, + input.spaceId, + ).catch((error: unknown) => { + throw mapSpaceLifecycleError(error); + }); + return { + botId: conversation.leadBotId, + groupId: conversation.groupId, + spaceId: conversation.spaceId, + }; + }), remove: authed.spaces.remove.handler(async ({ context, input }) => { let claimId: string | null = null; let claimActive = false; @@ -627,6 +677,41 @@ export function createRouter(deps: RouterDeps) { spaceId: input.spaceId, }; try { + // The project's own main agent goes with the project; anything else must be + // cleared first. + const ownMain = await ownMainAgentsBlockingSpaceDeletion(deps.prisma, deleteInput); + if (ownMain.groupIds.length > 0 || ownMain.botIds.length > 0) { + const spaceActor = await requireMembership( + deps.prisma, + context.actor.userId, + input.spaceId, + ); + for (const groupId of ownMain.groupIds) { + await removeGroupWithArtifacts(deps, groupRepos, spaceActor, groupId); + } + for (const botId of ownMain.botIds) { + const bot = await repos.getBot(spaceActor, botId, { includeArchived: true }); + await destroyBot( + { + prisma: deps.prisma, + sandbox: deps.sandbox, + home: deps.home, + jobs: deps.jobs, + artifacts: deps.artifacts, + dataDir: deps.dataDir, + }, + bot, + { + operationId: "spaces.remove", + traceId: "spaces.remove", + spaceId: input.spaceId, + userId: context.actor.userId, + signal: new AbortController().signal, + }, + { deleteMemories: true }, + ); + } + } // Claim emptiness before external teardown. Bot and group creation // take the same lifecycle lock and reject the Space until deletion // finishes or this claim is released. @@ -1225,7 +1310,11 @@ export function createRouter(deps: RouterDeps) { }, }); if (existing.kind === "main" && input.name !== undefined && input.name !== existing.name) { - await renameMainConversation(deps.prisma, context.actor.userId, input.name); + await renameMainConversation( + deps.prisma, + { userId: context.actor.userId, spaceId: existing.spaceId }, + input.name, + ); } const bots = await repos.listBots(context.actor); const bot = bots.find((b) => b.id === input.botId); @@ -1513,19 +1602,7 @@ export function createRouter(deps: RouterDeps) { input.groupId, "The main conversation can't be deleted.", ); - const removed = await groupRepos.removeGroup(context.actor, input.groupId); - const cleanup = await Promise.allSettled( - removed.artifactStorageKeys.map((storageKey) => - deps.artifacts.remove( - storageKey, - computerContext(context.actor, removed.contextBotId, `group-remove:${input.groupId}`), - ), - ), - ); - for (const result of cleanup) { - if (result.status === "rejected") - getLogger().error("group artifact cleanup", result.reason); - } + await removeGroupWithArtifacts(deps, groupRepos, context.actor, input.groupId); return { ok: true as const }; }), }, @@ -4942,6 +5019,11 @@ async function spaceNavigationDto( orderBy: { createdAt: "asc" }, }); const spaceIds = memberships.map((membership) => membership.spaceId); + const ownProjectMain = { + userId: actor.userId, + kind: "main", + space: { isDefault: false }, + } as const; const inactiveSpaceIds = spaceIds.filter((spaceId) => spaceId !== actor.spaceId); const [ currentBots, @@ -4962,13 +5044,14 @@ async function spaceNavigationDto( // Active-only navigation lists miss archived content in other spaces; count any // bot/group in the actor's spaces (including another member's) so a shared // non-empty space cannot look empty for onboarding redirects. + // The user's own main agent of a project is part of the project, not content to clear. deps.prisma.bot.findMany({ - where: { spaceId: { in: spaceIds } }, + where: { spaceId: { in: spaceIds }, NOT: ownProjectMain }, select: { spaceId: true }, distinct: ["spaceId"], }), deps.prisma.chatGroup.findMany({ - where: { spaceId: { in: spaceIds } }, + where: { spaceId: { in: spaceIds }, NOT: ownProjectMain }, select: { spaceId: true }, distinct: ["spaceId"], }), @@ -5014,33 +5097,8 @@ async function spaceNavigationDto( (membership.space.deletingAt === null || membership.space.deletingAt < staleClaimBefore), // A linked folder is on the owner's host; other members see neither its path nor remote. workspace: actor.isDeploymentOwner ? projectWorkspaceDto(membership.space) : null, - bots: spaceBots.map((bot) => ({ - id: bot.id, - spaceId: bot.spaceId, - kind: bot.kind, - name: bot.name, - title: bot.title, - color: bot.color, - notifyOnFinish: bot.notifyOnFinish, - pinned: bot.pinned, - sectionId: bot.sectionId, - unread: bot.unread, - preview: bot.preview, - status: bot.status, - updatedAt: bot.updatedAt, - })), - groups: spaceGroups.map((group) => ({ - id: group.id, - spaceId: group.spaceId, - kind: group.kind, - name: group.name, - pinned: group.pinned, - sectionId: group.sectionId, - members: group.members, - preview: group.preview, - unread: group.unread, - updatedAt: group.updatedAt, - })), + bots: spaceBots.map(spaceBotDto), + groups: spaceGroups.map(spaceGroupDto), externalConversations: externalConversations.filter( (conversation) => conversation.spaceId === membership.spaceId, ), @@ -5051,6 +5109,39 @@ async function spaceNavigationDto( }; } +function spaceBotDto(bot: SpaceBot): SpaceBot { + return { + id: bot.id, + spaceId: bot.spaceId, + kind: bot.kind, + name: bot.name, + title: bot.title, + color: bot.color, + notifyOnFinish: bot.notifyOnFinish, + pinned: bot.pinned, + sectionId: bot.sectionId, + unread: bot.unread, + preview: bot.preview, + status: bot.status, + updatedAt: bot.updatedAt, + }; +} + +function spaceGroupDto(group: SpaceGroup): SpaceGroup { + return { + id: group.id, + spaceId: group.spaceId, + kind: group.kind, + name: group.name, + pinned: group.pinned, + sectionId: group.sectionId, + members: group.members, + preview: group.preview, + unread: group.unread, + updatedAt: group.updatedAt, + }; +} + function partitionBySpace(rows: T[]): Map { const partitioned = new Map(); for (const row of rows) { diff --git a/apps/mobile/app/new-space.tsx b/apps/mobile/app/new-space.tsx index 1cd0e51..30efadb 100644 --- a/apps/mobile/app/new-space.tsx +++ b/apps/mobile/app/new-space.tsx @@ -28,7 +28,11 @@ export default function NewSpace() { return; } router.dismissAll(); - router.replace("/"); + // A new project opens in the conversation with its own main agent. + const main = space.groups.find((group) => group.kind === "main"); + router.replace( + main ? { pathname: "/group-thread", params: { groupId: main.id, name: main.name } } : "/", + ); } catch (reason) { setError(reason instanceof Error ? reason.message : t("Could not create space")); setPending(false); diff --git a/apps/mobile/lib/api.ts b/apps/mobile/lib/api.ts index 183ddf7..e34b7e4 100644 --- a/apps/mobile/lib/api.ts +++ b/apps/mobile/lib/api.ts @@ -728,7 +728,7 @@ export type MobileBot = Pick< | "modelId" | "thinkingLevel" > & - Partial>; + Partial>; export type MobileBotSection = BotSection; diff --git a/apps/mobile/lib/inbox-spaces.test.ts b/apps/mobile/lib/inbox-spaces.test.ts index 492930c..42e9472 100644 --- a/apps/mobile/lib/inbox-spaces.test.ts +++ b/apps/mobile/lib/inbox-spaces.test.ts @@ -111,6 +111,29 @@ describe("spaceInboxItems", () => { expect(items.map((item) => item.type)).toEqual(["bot", "group"]); }); + it("lists a project's main conversation instead of its main bot", () => { + const main = { + id: "group-main", + name: "Chief", + kind: "main" as const, + preview: "", + pinned: false, + sectionId: null, + archivedAt: null, + unread: false, + updatedAt: "2026-01-01T00:00:00.000Z", + members: [], + }; + const lead: MobileBot = { ...bot, id: "bot-lead", name: "Chief", kind: "main" }; + const items = spaceInboxItems([space({ bots: [lead, bot], groups: [main] })]); + expect(items.map((item) => (item.type === "heading" ? item.title : item.type))).toEqual([ + "Work", + "bot", + "group", + ]); + expect(items.some((item) => item.type === "bot" && item.bot.id === lead.id)).toBe(false); + }); + it("keeps empty spaces beside a populated space", () => { const personal = space({ id: "personal", isDefault: true, bots: [bot] }); expect(spaceInboxItems([personal, space()]).map((item) => item.type)).toEqual([ diff --git a/apps/mobile/lib/inbox-spaces.ts b/apps/mobile/lib/inbox-spaces.ts index 2618c3a..3ee0bc3 100644 --- a/apps/mobile/lib/inbox-spaces.ts +++ b/apps/mobile/lib/inbox-spaces.ts @@ -32,7 +32,10 @@ export function canDeleteInboxSpace( export function spaceInboxItems(spaces: InboxSpace[]): InboxSpaceItem[] { return spaces.flatMap((space): InboxSpaceItem[] => { const chats = [ - ...space.bots.map((chat) => ({ type: "bot" as const, bot: chat, ...chat })), + // A main bot is reached through its space's main conversation, not listed again. + ...space.bots + .filter((chat) => chat.kind !== "main") + .map((chat) => ({ type: "bot" as const, bot: chat, ...chat })), ...space.groups.map((chat) => ({ type: "group" as const, group: chat, ...chat })), ]; const items: InboxSpaceItem[] = []; diff --git a/apps/web/e2e/empty-project-starten.spec.ts b/apps/web/e2e/empty-project-starten.spec.ts deleted file mode 100644 index dcf80dc..0000000 --- a/apps/web/e2e/empty-project-starten.spec.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { expect, type Locator, test } from "@playwright/test"; -import { completeOnboarding, rpc, signup, unterhaltungenOnly } from "./helpers"; - -async function selectProjectSpace(projekte: Locator, name: string) { - const row = projekte.locator('[data-sidebar-group^="project-space:"]').filter({ hasText: name }); - await row.getByRole("button", { name: `Select ${name}` }).click(); -} - -async function projectSidebarStart(projekte: Locator, name: string) { - const row = projekte.locator('[data-sidebar-group^="project-space:"]').filter({ hasText: name }); - const groupAttr = await row.getAttribute("data-sidebar-group"); - const spaceId = groupAttr?.match(/^project-space:([^:]+)/)?.[1]; - if (!spaceId) throw new Error(`missing project space id for ${name}`); - return projekte.getByTestId(`project-start-${spaceId}`); -} - -test("empty project shows Starten instead of a fallback bot", async ({ page }) => { - const stamp = Date.now(); - await signup(page, `empty-project-${stamp}@rakazo.test`, "password12", "Empty Project"); - await completeOnboarding(page); - await page.goto("/app"); - await page.waitForURL(/\/app\/[^/]+$/); - - const projekte = page.locator(".sidebar-projekte"); - await projekte.getByTestId("create-project-trigger").click(); - const dialog = page.getByRole("dialog", { name: "New project" }); - await dialog.getByLabel("Name").fill("Launch"); - await dialog.getByRole("button", { name: "Create project", exact: true }).click(); - await page.waitForURL(/\/app(?:\/)?$/); - - await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0); - await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toBeVisible(); - await expect(page.getByTestId("project-start-main")).toBeVisible(); - - await page.getByTestId("project-start-main").click(); - await expect(page.getByTestId("side-panel")).toHaveAttribute("data-panel", "create"); -}); - -test("Starten in an onboarded project opens the group create flow", async ({ page }) => { - const stamp = Date.now(); - await signup(page, `project-start-${stamp}@rakazo.test`, "password12", "Project Start"); - await completeOnboarding(page); - - const projekte = page.locator(".sidebar-projekte"); - await projekte.getByTestId("create-project-trigger").click(); - const dialog = page.getByRole("dialog", { name: "New project" }); - await dialog.getByLabel("Name").fill("Website"); - await dialog.getByRole("button", { name: "Create project", exact: true }).click(); - await page.waitForURL(/\/app(?:\/)?$/); - await selectProjectSpace(projekte, "Website"); - await page.waitForURL(/\/app(?:\/)?$/); - - await expect(page.getByTestId("shell-root")).toHaveAttribute("data-ready", "true", { - timeout: 60_000, - }); - await selectProjectSpace(projekte, "Website"); - await page.waitForURL(/\/app(?:\/)?$/); - await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1); - await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0); - const sidebarStart = await projectSidebarStart(projekte, "Website"); - await expect(sidebarStart).toBeVisible({ timeout: 30_000 }); - - await sidebarStart.click(); - await expect(page.getByTestId("side-panel")).toHaveAttribute("data-panel", "create-group", { - timeout: 30_000, - }); - await expect(page.getByText("New group", { exact: true })).toBeVisible(); - await expect( - page.getByTestId("side-panel").getByRole("button", { name: /^Chief/ }), - ).toBeVisible(); -}); - -test("archiving the only bot keeps Starten visible in the project sidebar", async ({ page }) => { - const stamp = Date.now(); - await signup(page, `project-archive-${stamp}@rakazo.test`, "password12", "Project Archive"); - await completeOnboarding(page); - - const projekte = page.locator(".sidebar-projekte"); - await projekte.getByTestId("create-project-trigger").click(); - const dialog = page.getByRole("dialog", { name: "New project" }); - await dialog.getByLabel("Name").fill("Side"); - await dialog.getByRole("button", { name: "Create project", exact: true }).click(); - await page.waitForURL(/\/app(?:\/)?$/); - - const sideBot = await rpc<{ id: string }>(page, "bots/create", { - name: "Side bot", - title: "", - description: "", - notifyOnFinish: true, - computerMode: "team", - }); - await rpc(page, "bots/archive", { botId: sideBot.id }); - - await page.goto("/app"); - await page.waitForURL(/\/app(?:\/)?$/); - await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toBeVisible(); - await expect(page.getByTestId("project-start-main")).toBeVisible(); -}); diff --git a/apps/web/e2e/main-conversation.spec.ts b/apps/web/e2e/main-conversation.spec.ts index f87dcf1..1f68767 100644 --- a/apps/web/e2e/main-conversation.spec.ts +++ b/apps/web/e2e/main-conversation.spec.ts @@ -19,7 +19,10 @@ test("onboarding lands in the main conversation, its lead answers, and addressed const homeSpaceId = await page.evaluate(() => window.localStorage.getItem("rakazo:space-id")); const chief = await rpc<{ id: string }>(page, "bots/ensureMain", {}); const sidebar = page.locator("aside").first(); - const mainEntry = sidebar.getByRole("button", { name: "Chief, main conversation" }); + // Projects list their own lead's main conversation too; this is the pinned home one. + const mainEntry = sidebar + .getByRole("button", { name: "Chief, main conversation" }) + .and(sidebar.locator("[data-main-conversation]")); await expect(mainEntry).toBeVisible(); // The main bot is reached through the main conversation, not listed again. await expect(sidebar.locator(`[data-roster-bot-id="${chief.id}"]`)).toHaveCount(0); @@ -78,7 +81,8 @@ test("onboarding lands in the main conversation, its lead answers, and addressed const dialog = page.getByRole("dialog", { name: "New project" }); await dialog.getByLabel("Name").fill("Launch"); await dialog.getByRole("button", { name: "Create project", exact: true }).click(); - await page.waitForURL(/\/app(?:\/)?$/); + // The project opens its own main conversation. + await page.waitForURL((url) => /\/app\/g\/[^/]+$/.test(url.pathname) && url.href !== mainUrl); await expect .poll(() => page.evaluate(() => window.localStorage.getItem("rakazo:space-id"))) .not.toBe(homeSpaceId); diff --git a/apps/web/e2e/project-main-agent.spec.ts b/apps/web/e2e/project-main-agent.spec.ts new file mode 100644 index 0000000..52488a2 --- /dev/null +++ b/apps/web/e2e/project-main-agent.spec.ts @@ -0,0 +1,87 @@ +import { expect, type Locator, test } from "@playwright/test"; +import { captureScreenshot, completeOnboarding, rpc, signup, waitForChatComposer } from "./helpers"; + +function projectSpace(projekte: Locator, name: string) { + return projekte.locator('[data-sidebar-group^="project-space:"]').filter({ hasText: name }); +} + +test("a new project opens straight into the conversation with its own main agent", async ({ + page, +}, testInfo) => { + const stamp = Date.now(); + await signup(page, `project-main-${stamp}@rakazo.test`, "password12", "Project Main"); + await completeOnboarding(page); + const homeSpaceId = await page.evaluate(() => window.localStorage.getItem("rakazo:space-id")); + const homeMain = await rpc<{ id: string }>(page, "bots/ensureMain", {}); + + const projekte = page.locator(".sidebar-projekte"); + await projekte.getByTestId("create-project-trigger").click(); + const dialog = page.getByRole("dialog", { name: "New project" }); + await dialog.getByLabel("Name").fill("Launch"); + await dialog.getByRole("button", { name: "Create project", exact: true }).click(); + await page.waitForURL(/\/app\/g\/[^/]+$/, { timeout: 30_000 }); + await expect + .poll(() => page.evaluate(() => window.localStorage.getItem("rakazo:space-id"))) + .not.toBe(homeSpaceId); + await waitForChatComposer(page); + + // The project is one conversation, led by a main agent of its own. + const projectEntry = projectSpace(projekte, "Launch").getByRole("button", { + name: "Chief, main conversation", + }); + await expect(projectEntry).toBeVisible({ timeout: 30_000 }); + await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toHaveCount(0); + const groupId = new URL(page.url()).pathname.split("/").at(-1)!; + const snapshot = await rpc<{ leadBotId?: string }>(page, "threads/get", { groupId }); + expect(snapshot.leadBotId).toBeTruthy(); + expect(snapshot.leadBotId).not.toBe(homeMain.id); + + const composer = page.getByRole("combobox", { name: "Message" }); + await composer.fill("What should we do first?"); + await composer.press("Enter"); + const transcript = page.getByTestId("transcript"); + await expect(transcript).toContainText(/on it|handled/i, { timeout: 60_000 }); + await captureScreenshot(page, testInfo, "project-main-conversation"); +}); + +test("a project's main agent hands work to the project's specialist", async ({ + page, +}, testInfo) => { + const stamp = Date.now(); + await signup(page, `project-delegate-${stamp}@rakazo.test`, "password12", "Project Delegate"); + await completeOnboarding(page); + + const projekte = page.locator(".sidebar-projekte"); + await projekte.getByTestId("create-project-trigger").click(); + const dialog = page.getByRole("dialog", { name: "New project" }); + await dialog.getByLabel("Name").fill("Website"); + await dialog.getByRole("button", { name: "Create project", exact: true }).click(); + await page.waitForURL(/\/app\/g\/[^/]+$/, { timeout: 30_000 }); + await waitForChatComposer(page); + const groupId = new URL(page.url()).pathname.split("/").at(-1)!; + + const designer = await rpc<{ id: string }>(page, "bots/create", { + name: "Designer", + title: "", + description: "", + instructions: "", + notifyOnFinish: true, + }); + const composer = page.getByRole("combobox", { name: "Message" }); + await composer.fill("delegate to the bot named Designer: sketch a landing page"); + await composer.press("Enter"); + + const transcript = page.getByTestId("transcript"); + const card = transcript.getByTestId("delegated-task-card"); + await expect(card).toBeVisible({ timeout: 60_000 }); + await expect(card).toContainText("Designer"); + await expect(card).toHaveAttribute("data-status", "done", { timeout: 60_000 }); + await expect( + transcript.getByText("closing the task: sketch a landing page.").first(), + ).toBeVisible({ timeout: 60_000 }); + const snapshot = await rpc<{ members?: Array<{ botId: string }> }>(page, "threads/get", { + groupId, + }); + expect(snapshot.members?.map((member) => member.botId)).toContain(designer.id); + await captureScreenshot(page, testInfo, "project-main-agent-delegates"); +}); diff --git a/apps/web/e2e/spaces.spec.ts b/apps/web/e2e/spaces.spec.ts index 7492809..aa0da90 100644 --- a/apps/web/e2e/spaces.spec.ts +++ b/apps/web/e2e/spaces.spec.ts @@ -16,7 +16,7 @@ function projectSpace(projekte: Locator, name: string) { }); } -test("Projekte + opens new project dialog instead of group form", async ({ page }) => { +test("Projekte + opens new project dialog instead of group form", async ({ page }, testInfo) => { const stamp = Date.now(); await signup(page, `proj-create-${stamp}@rakazo.test`, "password12", "Project Create"); await completeOnboarding(page); @@ -34,16 +34,21 @@ test("Projekte + opens new project dialog instead of group form", async ({ page await dialog.getByLabel("Name").fill("Customer project"); await dialog.getByRole("button", { name: "Create project", exact: true }).click(); await expect(page).not.toHaveURL(/\/onboarding/); - await page.waitForURL(/\/app/); - await page.waitForLoadState("networkidle"); + // The new project opens its conversation with the project's main agent. + await page.waitForURL(/\/app\/g\/[^/]+$/, { timeout: 30_000 }); await expect(projekte.getByText("Customer project", { exact: true })).toBeVisible({ timeout: 30_000, }); await expect(unterhaltungenOnly(page).getByText("Customer project", { exact: true })).toHaveCount( 0, ); - await expect(page.getByTestId("project-start-main")).toBeVisible(); - await expect(page.getByTestId("project-start-main")).toHaveText(/Starten/); + await expect( + projectSpace(projekte, "Customer project").getByRole("button", { + name: "Chief, main conversation", + }), + ).toBeVisible(); + await expect(page.getByTestId("project-start-main")).toHaveCount(0); + await captureScreenshot(page, testInfo, "new-project-main-conversation"); }); for (const owner of [true, false]) { @@ -119,17 +124,18 @@ test("spaces stay invisible by default and chat creation requires approval", asy const supportSpaceGroup = await supportSpace.getAttribute("data-sidebar-group"); const supportSpaceId = supportSpaceGroup?.match(/^project-space:([^:]+)/)?.[1]; expect(supportSpaceId).toBeTruthy(); - await supportSpace.getByRole("button", { name: "Select Customer support" }).click(); - await page.waitForURL(/\/app(?:\/)?$/); + // A project created from chat comes with its own main agent, reached through its conversation. + await supportSpace.getByRole("button", { name: "Chief, main conversation" }).click(); + await page.waitForURL(/\/app\/g\/[^/]+$/); await expect .poll(() => page.evaluate(() => window.localStorage.getItem("rakazo:space-id"))) .toBe(supportSpaceId); - // The main conversation stays the first entry while another space is open. + // The home main conversation stays the first entry while another space is open. await expect(projekte.getByText("Customer support", { exact: true })).toBeVisible(); await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1); - await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0); - await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toBeVisible(); + await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(1); + await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toHaveCount(0); await captureScreenshot(page, testInfo, "spaces-sidebar"); await page.evaluate((id) => { @@ -143,7 +149,9 @@ test("spaces stay invisible by default and chat creation requires approval", asy await expect(projekte.getByText("Customer support", { exact: true })).toBeVisible(); }); -test("a new space opens empty and can be deleted from its menu", async ({ page }, testInfo) => { +test("a new space opens its main conversation and can be deleted from its menu", async ({ + page, +}, testInfo) => { const stamp = Date.now(); await signup(page, `spaces-delete-${stamp}@rakazo.test`, "password12", "Space Owner"); await completeOnboarding(page); @@ -154,18 +162,20 @@ test("a new space opens empty and can be deleted from its menu", async ({ page } await dialog.getByLabel("Name").fill("Temporary"); await dialog.getByRole("button", { name: "Create space", exact: true }).click(); - // Onboarding runs once per user: a new space opens directly, with no bot of its own. - await page.waitForURL(/\/app(?:\/)?$/); + // Onboarding runs once per user: a new space opens directly in the conversation with its + // own main agent, its only bot. + await page.waitForURL(/\/app\/g\/[^/]+$/, { timeout: 30_000 }); const projekte = page.locator(".sidebar-projekte"); await expect(projekte.getByText("Temporary", { exact: true })).toBeVisible(); await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1); - await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0); - await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toBeVisible(); - expect(await rpc(page, "bots/list", {})).toEqual([]); - await captureScreenshot(page, testInfo, "new-space-empty"); - - // The empty space stays selectable instead of trapping the user. - await expect(projekte.getByRole("button", { name: "Select Temporary" }).first()).toBeVisible(); + await expect( + projectSpace(projekte, "Temporary").getByRole("button", { name: "Chief, main conversation" }), + ).toBeVisible(); + await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toHaveCount(0); + expect(await rpc>(page, "bots/list", {})).toEqual([ + expect.objectContaining({ kind: "main" }), + ]); + await captureScreenshot(page, testInfo, "new-space-main-conversation"); // Deletion is discoverable through the visible Space actions menu. await projectSpace(projekte, "Temporary") @@ -180,6 +190,7 @@ test("a new space opens empty and can be deleted from its menu", async ({ page } await deleteSpaceDialog.getByRole("button", { name: "Delete", exact: true }).click(); await expect(sidebar.getByText("Temporary", { exact: true })).toHaveCount(0); await expect(page).not.toHaveURL(/\/onboarding/); + // The project's main agent went with it. await expect(sidebar.getByRole("button", { name: /^Chief/ })).toHaveCount(1); // A stale onboarding URL recovers without duplicating the bot. @@ -212,7 +223,7 @@ test("deleting the last bot in a space stays in the app after first use", async const dialog = page.getByRole("dialog", { name: "New space" }); await dialog.getByLabel("Name").fill("Side"); await dialog.getByRole("button", { name: "Create space", exact: true }).click(); - await page.waitForURL(/\/app(?:\/)?$/); + await page.waitForURL(/\/app\/g\/[^/]+$/, { timeout: 30_000 }); const projekte = page.locator(".sidebar-projekte"); await expect(projekte.getByText("Side", { exact: true })).toBeVisible(); @@ -229,8 +240,11 @@ test("deleting the last bot in a space stays in the app after first use", async await expect(page).not.toHaveURL(/\/onboarding/); await page.goto("/app"); await expect(page).not.toHaveURL(/\/onboarding/); - await expect(projekte.getByRole("button", { name: "Select Side" }).first()).toBeVisible(); - await expect(page.getByTestId("project-start-main")).toBeVisible(); + // The project's main conversation stays, so the project opens there. + await page.waitForURL(/\/app\/g\/[^/]+$/, { timeout: 30_000 }); + await expect( + projectSpace(projekte, "Side").getByRole("button", { name: "Chief, main conversation" }), + ).toBeVisible(); await projectSpace(projekte, "Side").getByRole("button", { name: "Actions for Side" }).click(); await expect(page.getByRole("menuitem", { name: "Delete space" })).toBeVisible(); diff --git a/apps/web/src/lib/project-empty-state.test.ts b/apps/web/src/lib/project-empty-state.test.ts index a31562d..a7fcb4e 100644 --- a/apps/web/src/lib/project-empty-state.test.ts +++ b/apps/web/src/lib/project-empty-state.test.ts @@ -1,14 +1,15 @@ import { describe, expect, it } from "vitest"; -import { isEmptyProjectSpace, projectGroupChatsInSpace } from "./project-empty-state.js"; +import { isEmptyProjectSpace, projectMainGroup } from "./project-empty-state.js"; -describe("projectGroupChatsInSpace", () => { - it("returns project groups in a space without the main conversation", () => { +describe("projectMainGroup", () => { + it("finds the main conversation of a space", () => { const groups = [ { id: "g1", kind: "group" as const, spaceId: "space-a" }, { id: "g2", kind: "main" as const, spaceId: "space-a" }, - { id: "g3", kind: "group" as const, spaceId: "space-b" }, + { id: "g3", kind: "main" as const, spaceId: "space-b" }, ]; - expect(projectGroupChatsInSpace(groups, "space-a").map((group) => group.id)).toEqual(["g1"]); + expect(projectMainGroup(groups, "space-a")?.id).toBe("g2"); + expect(projectMainGroup(groups, "space-c")).toBeUndefined(); }); }); @@ -22,7 +23,7 @@ describe("isEmptyProjectSpace", () => { expect(isEmptyProjectSpace("home", spaces, [], "home")).toBe(false); }); - it("is true for a project space without group chats", () => { + it("is true for a project space without conversations", () => { expect(isEmptyProjectSpace("project", spaces, [], "home")).toBe(true); }); @@ -31,8 +32,8 @@ describe("isEmptyProjectSpace", () => { expect(isEmptyProjectSpace("project", spaces, groups, "home")).toBe(false); }); - it("ignores the main conversation when deciding emptiness", () => { + it("is false once the project has its main conversation", () => { const groups = [{ id: "g1", kind: "main" as const, spaceId: "project" }]; - expect(isEmptyProjectSpace("project", spaces, groups, "home")).toBe(true); + expect(isEmptyProjectSpace("project", spaces, groups, "home")).toBe(false); }); }); diff --git a/apps/web/src/lib/project-empty-state.ts b/apps/web/src/lib/project-empty-state.ts index a8afad7..574e6eb 100644 --- a/apps/web/src/lib/project-empty-state.ts +++ b/apps/web/src/lib/project-empty-state.ts @@ -1,14 +1,14 @@ import type { Group, Space } from "@rakazo/contracts"; -/** Project-scoped group chats in a space (excludes the main conversation). */ -export function projectGroupChatsInSpace( - groups: readonly Pick[], +/** The project's own main conversation, where the user talks to its main agent. */ +export function projectMainGroup>( + groups: readonly T[], spaceId: string, -): readonly Pick[] { - return groups.filter((group) => group.spaceId === spaceId && group.kind !== "main"); +): T | undefined { + return groups.find((group) => group.spaceId === spaceId && group.kind === "main"); } -/** Non-default project space with no project group conversations yet. */ +/** Non-default project space without any conversation yet, not even its main one. */ export function isEmptyProjectSpace( spaceId: string | null | undefined, spaces: readonly Pick[], @@ -18,5 +18,5 @@ export function isEmptyProjectSpace( if (!spaceId || !homeSpaceId || spaceId === homeSpaceId) return false; const space = spaces.find((item) => item.id === spaceId); if (!space || space.isDefault) return false; - return projectGroupChatsInSpace(groups, spaceId).length === 0; + return !groups.some((group) => group.spaceId === spaceId); } diff --git a/apps/web/src/pages/Shell.tsx b/apps/web/src/pages/Shell.tsx index 5437789..753362a 100644 --- a/apps/web/src/pages/Shell.tsx +++ b/apps/web/src/pages/Shell.tsx @@ -1034,14 +1034,15 @@ export function ShellPage() { } return; } + const inProject = Boolean(refreshSpaceId && refreshSpaceId !== refreshHomeSpaceId); if (currentGroupId) { if (!groupList.some((group) => group.id === currentGroupId)) { - navigate(firstThreadRoute(list, groupList), { replace: true }); + navigate(firstThreadRoute(list, groupList, inProject), { replace: true }); } return; } if (!currentBotId || !list.some((bot) => bot.id === currentBotId)) { - navigate(firstThreadRoute(list, groupList), { replace: true }); + navigate(firstThreadRoute(list, groupList, inProject), { replace: true }); } } finally { botsRefreshInFlight.current -= 1; @@ -1278,12 +1279,17 @@ export function ShellPage() { } return; } + const inProject = bootstrapSpaceId !== bootstrapHomeSpaceId; if (groupId) { if (!groupList.some((group) => group.id === groupId)) { - navigate(firstThreadRoute(bootstrap.bots, groupList), { replace: true }); + navigate(firstThreadRoute(bootstrap.bots, groupList, inProject), { replace: true }); } return; } + if (inProject && !botId) { + navigate(firstThreadRoute(bootstrap.bots, groupList, true), { replace: true }); + return; + } const selectedBotId = bootstrap.thread?.botId ?? bootstrap.bots[0]?.id; if (selectedBotId && selectedBotId !== botId) { navigate(`/app/${selectedBotId}`, { replace: true }); @@ -1660,6 +1666,8 @@ export function ShellPage() { : group.title, showLock: showSpaceNames, emptySpaceId: undefined as string | undefined, + /** A project without its main conversation yet offers to start it. */ + startSpaceId: undefined as string | undefined, spaceId: space.id, spaceName: space.name, canDeleteSpace: index === 0 && space.canDelete === true, @@ -1676,6 +1684,7 @@ export function ShellPage() { sectionId: null, showLock: true, emptySpaceId: space.id, + startSpaceId: undefined, spaceId: space.id, spaceName: space.name, canDeleteSpace: space.canDelete === true, @@ -1739,11 +1748,13 @@ export function ShellPage() { if (space.id === homeSpaceId || seenProjectSpaceIds.has(space.id)) continue; seenProjectSpaceIds.add(space.id); const groupsInSpace = sidebarGroups.flatMap((group) => - group.spaceId === space.id - ? group.bots.filter((item) => item.kind === "group" && item.chat.kind !== "main") - : [], + group.spaceId === space.id ? group.bots.filter((item) => item.kind === "group") : [], ); - const projectBots = dedupeSidebarBots(groupsInSpace); + // The project's main conversation, where the user talks to its main agent, comes first. + const projectBots = dedupeSidebarBots(groupsInSpace).sort( + (left, right) => Number(right.chat.kind === "main") - Number(left.chat.kind === "main"), + ); + const hasMain = projectBots.some((item) => item.chat.kind === "main"); entries.push({ key: projectBots.length > 0 ? `project-space:${space.id}` : `project-space:${space.id}:empty`, @@ -1752,6 +1763,7 @@ export function ShellPage() { sectionId: null, showLock: true, emptySpaceId: projectBots.length > 0 ? undefined : space.id, + startSpaceId: hasMain ? undefined : space.id, spaceId: space.id, spaceName: space.name, canDeleteSpace: space.canDelete === true, @@ -1836,12 +1848,15 @@ export function ShellPage() { [bootstrapMe?.spaceId, navigate], ); - const openProjectStart = useCallback( - (spaceId: string, flow: "create" | "group") => { + // A project is a conversation with its own main agent, created when first opened. + const startProject = useCallback( + async (spaceId: string) => { setActiveNavPillar("projekte"); - openSpaceChat(spaceId, `/app?project=start&flow=${flow}`); + const main = await rpc.spaces.ensureMain({ spaceId }); + if (spaceId === (selectedSpaceId() ?? bootstrapMe?.spaceId)) await refreshBots(); + openSpaceChat(spaceId, `/app/g/${main.groupId}`); }, - [openSpaceChat], + [bootstrapMe?.spaceId, openSpaceChat, refreshBots], ); const openMainConversation = useCallback(() => { @@ -2085,36 +2100,6 @@ export function ShellPage() { } }, [botId, emptyProjectSpace, groupId, initialBotsLoaded]); - useEffect(() => { - if (searchParams.get("project") !== "start" || !emptyProjectSpace || !initialBotsLoaded) { - return; - } - setActiveNavPillar("projekte"); - const flow = searchParams.get("flow"); - setPanel( - flow === "create" - ? "create" - : flow === "group" && homeSpaceBots.length > 0 - ? "create-group" - : flow === "group" - ? "create" - : bots.length === 0 - ? "create" - : "create-group", - ); - const next = new URLSearchParams(searchParams); - next.delete("project"); - next.delete("flow"); - setSearchParams(next, { replace: true }); - }, [ - bots.length, - emptyProjectSpace, - homeSpaceBots.length, - initialBotsLoaded, - searchParams, - setSearchParams, - ]); - const activeSnapshot = inGroup ? snapshot?.groupId === groupId ? snapshot @@ -4007,7 +3992,7 @@ export function ShellPage() { className="flex min-w-0 flex-1 items-center justify-between gap-2 rounded-lg pl-8 py-1 text-ui-label font-semibold tracking-wider uppercase text-foreground/65 dark:text-muted-foreground/80 hover:text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-ring" onClick={() => { if (group.emptySpaceId) { - openSpaceChat(group.emptySpaceId, "/app"); + void startProject(group.emptySpaceId); return; } toggleSidebarSection(group.key); @@ -4090,14 +4075,14 @@ export function ShellPage() { ) : null} ) : null} - {!collapsed && group.emptySpaceId ? ( + {!collapsed && group.startSpaceId ? (
- - { - void sendMessage(prompt); - }} scrollRef={messageScroll} artifactTarget={transcriptArtifactTarget} onOpenArtifact={wideLayout ? openArtifactBeside : undefined} @@ -4684,9 +4674,6 @@ export function ShellPage() { {(active || activeGroup) && !showProjectTimeline ? ( { const nextMe = await rpc.preferences.update({ avatarStyle }); setBootstrapMe(nextMe); @@ -5813,7 +5799,6 @@ const Transcript = memo(function Transcript({ speakingMessageId, onSpeak, activeName, - onPromptSelect, encounterMode = false, voiceEncounterActive = false, }: { @@ -5844,7 +5829,6 @@ const Transcript = memo(function Transcript({ speakingMessageId: string | null; onSpeak: (message: ThreadMessage) => void; activeName?: string; - onPromptSelect?: (prompt: string) => void; encounterMode?: boolean; voiceEncounterActive?: boolean; }) { @@ -6097,87 +6081,11 @@ const Transcript = memo(function Transcript({ }`} > {reactionView.visibleMessages.length === 0 && encounterMode ? ( -
-

- {activeName - ? t`Start the conversation — ${activeName} is on stage above.` - : t`Start the conversation.`} -

-
- {[t`Portfolio-Check`, t`Alpha-Signale`, t`Architektur-Audit`].map((label) => ( - - ))} -
-
- ) : null} - {reactionView.visibleMessages.length === 0 && !encounterMode ? ( -
-
- -
-

- {t`Guten Tag.`} evOo {t`ist bereit.`} -

-

- {activeName - ? t`Frag ${activeName} nach Marktchancen, autonomen Tasks oder Architektur-Reviews.` - : t`Wähle einen autonomen Agenten aus deinem Team oder starte direkt einen Prompt.`} -

-
- - - -
-
+

+ {activeName + ? t`Start the conversation — ${activeName} is on stage above.` + : t`Start the conversation.`} +

) : null} {reactionView.visibleMessages.map((message) => { if (!message.blocks.some((block) => !isToolActivityBlock(block))) return null; @@ -6422,7 +6330,6 @@ const QuoteSelectionButton = memo(function QuoteSelectionButton({ }); const Composer = memo(function Composer({ - activeName, running, disabled, pendingAttachments, @@ -6450,7 +6357,6 @@ const Composer = memo(function Composer({ onSlashOpen, onSlashAction, }: { - activeName?: string; running: boolean; disabled?: boolean; pendingAttachments: PendingAttachment[]; @@ -6759,28 +6665,6 @@ const Composer = memo(function Composer({ }`} >
- {/* Quick Action Prompt Chips */} - {showComposerPlaceholder ? ( -
- - - Quick - - {quickPromptsForBot(activeName).map((promptText) => ( - - ))} -
- ) : null} {sendError || runError ? (
&2 - exit 1 -} - -echo "==========================================================" -echo " evOo Autonomous Platform — Hot-Reload & Deploy " -echo " CTO: Steve Jobs | Port: ${PORT} " -echo "==========================================================" - -# ------------------------------------------------------------------------------ -# STEP 1: Physical Reality & Filesystem Audit (Zero Phantom Policy) -# ------------------------------------------------------------------------------ -log "Prüfe physische Existenz aller Kern-Artefakte..." - -REQUIRED_FILES=( - "${SCRIPT_DIR}/ARCHITECTURE.md" - "${SCRIPT_DIR}/evoo.config.json" - "${SCRIPT_DIR}/evoo-platform/index.html" - "${SCRIPT_DIR}/evoo-platform/styles.css" - "${SCRIPT_DIR}/evoo-platform/app.js" - "${SCRIPT_DIR}/evoo-platform/server.js" - "${SCRIPT_DIR}/apex-trading/BLUEPRINT.md" - "${SCRIPT_DIR}/apex-trading/config/trading.config.json" - "${SCRIPT_DIR}/orchestrator/agents.json" - "${SCRIPT_DIR}/orchestrator/agent_runner.py" -) - -for file in "${REQUIRED_FILES[@]}"; do - if [[ ! -f "$file" ]]; then - fail "Physische Datei fehlt im Dateisystem: $file. Abbruch! Vision-Schleife verhindert." - fi -done -success "100% Physische Dateisystem-Integrität bestätigt. Keine Phantom-Dateien." - -# If user only wanted verification -if [[ "${1:-}" == "--check-only" ]]; then - success "Check-only abgeschlossen. Alles physisch vorhanden." - exit 0 -fi - -# ------------------------------------------------------------------------------ -# STEP 2: Process Management / Hot-Reload -# ------------------------------------------------------------------------------ -if [[ -f "$PID_FILE" ]]; then - OLD_PID="$(cat "$PID_FILE" 2>/dev/null || true)" - if [[ -n "$OLD_PID" ]] && kill -0 "$OLD_PID" 2>/dev/null; then - log "Beende vorherige Server-Instanz (PID: $OLD_PID) für Hot-Reload..." - kill "$OLD_PID" 2>/dev/null || true - sleep 0.5 - fi - rm -f "$PID_FILE" -fi - -# Also ensure port is free -OCCUPIED_PID="$(lsof -ti tcp:"$PORT" 2>/dev/null || true)" -if [[ -n "$OCCUPIED_PID" ]]; then - log "Port $PORT bereits belegt von PID $OCCUPIED_PID. Bereinige..." - kill -9 $OCCUPIED_PID 2>/dev/null || true - sleep 0.5 -fi - -# ------------------------------------------------------------------------------ -# STEP 3: Server Start -# ------------------------------------------------------------------------------ -log "Starte evOo Platform Server als robuster Daemon auf Port ${PORT}..." -export PORT - -if [[ "${1:-}" == "serve" || "${1:-}" == "--foreground" ]]; then - echo "$$" > "$PID_FILE" - log "Server läuft im Vordergrund (Strg+C zum Beenden)..." - exec node "${SCRIPT_DIR}/evoo-platform/server.js" -fi - -# True detached POSIX daemon via setsid -python3 -c "import subprocess; subprocess.Popen(['node', '${SCRIPT_DIR}/evoo-platform/server.js'], cwd='${SCRIPT_DIR}', start_new_session=True, stdout=open('${LOG_FILE}', 'a'), stderr=subprocess.STDOUT)" - -# Wait up to 5 seconds for healthcheck -log "Führe Healthcheck durch (http://127.0.0.1:${PORT}/health)..." -for i in {1..10}; do - if curl -s "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then - HEALTH_RES="$(curl -s "http://127.0.0.1:${PORT}/health")" - success "Platform Server antwortet: HTTP 200 OK!" - echo -e "${CYAN}Health Response:${NC} $HEALTH_RES" - break - fi - if [[ $i -eq 10 ]]; then - fail "Server antwortet nicht innerhalb des Timeouts! Überprüfe $LOG_FILE" - fi - sleep 0.5 -done - -# ------------------------------------------------------------------------------ -SERVER_PID="$(cat "$PID_FILE" 2>/dev/null || echo "active")" -echo "" -echo -e "${GREEN}==========================================================${NC}" -echo -e "${GREEN} evOo PLATFORM ERFOLGREICH DEPLOYED & LIVE! ${NC}" -echo -e "${GREEN}==========================================================${NC}" -echo -e " 🌐 Dashboard: ${CYAN}http://127.0.0.1:${PORT}/${NC}" -echo -e " 💓 Healthcheck: ${CYAN}http://127.0.0.1:${PORT}/health${NC}" -echo -e " 📊 Telemetrie API: ${CYAN}http://127.0.0.1:${PORT}/api/status${NC}" -echo -e " 📑 Verfassung: ${CYAN}${SCRIPT_DIR}/ARCHITECTURE.md${NC}" -echo -e " 📈 Apex Trading: ${CYAN}${SCRIPT_DIR}/apex-trading/BLUEPRINT.md${NC}" -echo -e " ⚙️ PID: ${SERVER_PID}" -echo -e "${GREEN}==========================================================${NC}" diff --git a/docs/AGENT-INTERFACE-ONLY.md b/docs/AGENT-INTERFACE-ONLY.md index 7c13938..a13653d 100644 --- a/docs/AGENT-INTERFACE-ONLY.md +++ b/docs/AGENT-INTERFACE-ONLY.md @@ -1,5 +1,4 @@ # evOo-local — Agenten-Interface only Primary: multi-agent / Oo UI at `/app/`. -Not primary: Mission Control, Cockpit, trading dashboards, old HTML evoo-platform. Spelling: **evOo** (letter O). diff --git a/docs/LOCAL-SETUP.md b/docs/LOCAL-SETUP.md index 4ed2ea8..33682e4 100644 --- a/docs/LOCAL-SETUP.md +++ b/docs/LOCAL-SETUP.md @@ -102,10 +102,7 @@ Then connect **Kokoro** in Voice settings. See [kokoro-voice.md](./kokoro-voice. | Tool | Path | |------|------| -| self_improve | `scripts/self_improve.py` | -| autonomous-sentinel | `scripts/autonomous-sentinel.ts` | | self-update | `packages/core/src/self-update.ts` | -| orchestrator | `orchestrator/agent_runner.py`, `orchestrator/agents.json` | | local-stack | `apps/desktop/src/local-stack.ts` | | pi-local-provider | `packages/adapters/src/pi-local-provider.ts` | | seed-ollama | `scripts/seed-ollama.ts` | @@ -117,12 +114,3 @@ Display brand is **evOo**. Workspace package names remain `@rakazo/api`, `@rakaz ## Source Clean history cut from `cynarAI/evoo` commit `f00577b` on branch `antigravity-handoff/project-timeline` (Voice First + project timeline). This repo does **not** carry the old multi-remote history. - -## Old folders (do not delete from this doc — cleanup later on the Mac) - -- `~/Desktop/evoo` (or Desktop/evOo) -- `~/awesome-grok-bot` -- `~/evOo` -- `~/Projects/evOo` (if present) - -Keep them until you confirm `~/Projects/evOo-local` works. diff --git a/docs/VOICE-FIRST-REVIEW.md b/docs/VOICE-FIRST-REVIEW.md deleted file mode 100644 index 20688eb..0000000 --- a/docs/VOICE-FIRST-REVIEW.md +++ /dev/null @@ -1,52 +0,0 @@ -# Voice First / Executive Board — Review Report - -**Audience:** Ingo Wagner -**Branch:** `antigravity-handoff/project-timeline` @ `8d0b8e3` (PRs #33–#40 merged + follow-up fixes) -**Date:** 2026-09-19 (Europe/Vienna) -**Verifier host:** evOo Linux box (Tailscale `grokbot-box`). Ingo’s Mac was **not** SSH-reachable from this host (no key / Tailscale SSH off). - -## Definition of done checklist - -| # | Criterion | Result | Evidence | -|---|-----------|--------|----------| -| 1 | Dev stack healthy: API `:3100/health` + web `:5173` | **BLOCKED (Mac)** | Mac mini / MacBook Air online on Tailscale; ports 3100/5173 not exposed; SSH `Permission denied (publickey)`. Could not restart `pnpm dev` on `/Users/ingowagner/evOo`. | -| 2 | Executive Board: user VOICE FIRST turn → UX Designer replies with concept thoughts + ≥1 question; no raw `handoff_to_bot` JSON; no bounce to Boss | **PASS (unit / code)** · **UNVERIFIED (live)** | PRs #39–#40 + `bot-reply-sanitize`, `prose-handoff-recovery`, `group-handoff` workshop instructions. 56 focused tests green. Live group chat not exercised (no Mac API). | -| 3 | Transcript: no tool JSON blobs / no `assistant:` role leaks | **PASS (unit)** · **UNVERIFIED (live)** | `sanitizeBotReplyText` strips prose tool JSON + role prefix; `speechFromBlocks` skips tool-activity + unspeakable blobs (regression fixed after merge). | -| 4 | Voice Mode (CallView) UX Designer: invite before mic when owning work; after outgoing handoff mic stays closed + transfer line | **PASS (code + unit)** · **UNVERIFIED (live)** | CallView rebuilt: `speakUserTurnInvitation` / `handleSpeechIdle` / `shouldSpeakOpeningInvitation`; `parkAfterHandoff` + `voiceHandoffTransferLine`; mount no longer auto-opens mic. | - -## What was done on this pass - -1. Merged open PR heads **#33–#40** into `antigravity-handoff/project-timeline` and **pushed** to origin. -2. Fixed merge regression in `packages/core/src/speech-text.ts`: `speechFromBlocks` again drops tool-activity chips and unspeakable tool JSON (test was failing after `-X theirs` merge). -3. Fixed merge regression in `apps/web/src/pages/CallView.tsx`: invitation-before-mic restored; outgoing handoff keeps mic closed and speaks transfer line. -4. Focused vitest: **56 passed** (`call-view-voice`, `voice-handoff`, `speech-text`, `bot-reply-sanitize`, `prose-handoff-recovery`). - -## Remaining gaps (for Ingo’s Mac) - -1. **SSH / Tailscale SSH** from agent hosts to `macbook-air-von-ingo` / `mac-mini-von-ingo` so localhost verify can run without manual UI. -2. On Mac repo path `/Users/ingowagner/evOo`: - ```bash - git fetch origin && git checkout antigravity-handoff/project-timeline && git pull - pnpm install && pnpm dev # ensure :3100 + :5173 - ``` -3. Live Executive Board check (RPC/DB preferred): - - Find Executive Board `chat_groups` id. - - Post a fresh user turn about **VOICE FIRST AI Talks**. - - Confirm UX Designer message: concept prose + question; no `handoff_to_bot` JSON; Boss not re-tasked with the same work. -4. Open Voice Mode on UX Designer: hear invite before mic; after a specialist handoff, mic stays closed with transfer caption. - -## Sample expected UX Designer reply shape (sanitized) - -> I’ve been thinking about VOICE FIRST AI Talks as a short, spoken workshop: one specialist owns the lane, invites you before the mic opens, and never dumps the same task back upstairs. For the first talk, I’d keep it to three beats — hook, one demo, one ask. -> **Which audience should we design for first — founders, designers, or internal execs?** - -## Group id tested - -*None live — Mac localhost unreachable.* After Mac verify, record `chat_groups.id` here. - -## Branch / PR notes - -- Timeline branch now contains merges of `cursor/voice-mic-energy-2d2c` … `cursor/handoff-prose-sanitize-d1d4` plus commits: - - `fix(core): drop tool-activity and unspeakable blobs from speechFromBlocks` - - `fix(web): restore invite-before-mic and keep handoff mic closed` -- Draft PRs #33–#40 can be closed or marked superseded once this branch is the review source of truth. diff --git a/evoo-platform/app.js b/evoo-platform/app.js deleted file mode 100644 index 6ad21c2..0000000 --- a/evoo-platform/app.js +++ /dev/null @@ -1,573 +0,0 @@ -// evOo Platform - Executive Mission Control Cockpit (app.js) -// Zero-Mockup Reactive Engine for Ingo Wagner & Autonomous Teammates - -document.addEventListener("DOMContentLoaded", () => { - // Elements - const terminal = document.getElementById("terminal-logs"); - const btnClear = document.getElementById("btn-clear-console"); - const btnToggleConsole = document.getElementById("btn-toggle-console"); - const cardConsole = document.querySelector(".card-console"); - const btnSelfImprove = document.getElementById("btn-self-improve"); - const btnKillswitch = document.getElementById("btn-killswitch"); - const killswitchText = document.getElementById("killswitch-text"); - const btnHotReload = document.getElementById("btn-hot-reload"); - const btnOpenSteveChat = document.getElementById("btn-open-steve-chat"); - const clockEl = document.getElementById("timestamp-clock"); - const systemHealthVal = document.getElementById("system-health-val"); - const _clusterSummaryVal = document.getElementById("cluster-summary-val"); - const tradingSummaryVal = document.getElementById("trading-summary-val"); - const approvalsContainer = document.getElementById("approvals-container"); - const approvalsBadge = document.getElementById("approvals-badge"); - const journalContainer = document.getElementById("journal-container"); - const agentItems = document.querySelectorAll(".interactive-agent"); - const _pnlVal = document.getElementById("pnl-val"); - const _drawdownVal = document.getElementById("drawdown-val"); - const _circuitBreakerVal = document.getElementById("circuit-breaker-val"); - const tradingBadge = document.getElementById("trading-badge"); - - // Clock - function updateClock() { - const now = new Date(); - clockEl.textContent = `${now.toISOString().replace("T", " ").substring(0, 19)} UTC`; - } - setInterval(updateClock, 1000); - updateClock(); - - // Helper: Strip ANSI color codes - const ansiCsi = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"); - function cleanAnsi(str) { - return str.replace(ansiCsi, ""); - } - - // Logger - function addLog(text, type = "info") { - if (!terminal) return; - const line = document.createElement("div"); - const cleanText = cleanAnsi(text); - - let lineType = type; - if ( - cleanText.includes("[✓ PASS]") || - cleanText.includes("[PASS]") || - cleanText.includes("SUCCESS") || - cleanText.includes("200 OK") - ) { - lineType = "success"; - } else if ( - cleanText.includes("[✗ FAIL]") || - cleanText.includes("FAILED") || - cleanText.includes("Error") - ) { - lineType = "error"; - } else if (cleanText.includes("[⚠ WARN]") || cleanText.includes("WARN")) { - lineType = "warning"; - } - - line.className = `log-line ${lineType}`; - const timestamp = new Date().toTimeString().split(" ")[0]; - line.textContent = `[${timestamp}] ${cleanText}`; - terminal.appendChild(line); - terminal.scrollTop = terminal.scrollHeight; - } - - // Console Toggles - if (btnClear) { - btnClear.addEventListener("click", () => { - terminal.innerHTML = ""; - addLog("Terminal gelöscht. Überwachung aktiv.", "info"); - }); - } - - if (btnToggleConsole && cardConsole) { - btnToggleConsole.addEventListener("click", () => { - const isCollapsed = cardConsole.classList.toggle("collapsed"); - btnToggleConsole.textContent = isCollapsed ? "Ausklappen" : "Minimieren"; - }); - } - - // 1. Fetch & Render Approvals - async function loadApprovals() { - if (!approvalsContainer) return; - try { - const res = await fetch("/api/approvals"); - if (!res.ok) return; - const data = await res.json(); - const list = data.approvals || []; - const pending = list.filter((a) => a.status === "pending"); - - if (approvalsBadge) { - approvalsBadge.textContent = - pending.length > 0 ? `${pending.length} Ausstehend` : "Alle erledigt"; - approvalsBadge.className = pending.length > 0 ? "badge badge-amber" : "badge badge-emerald"; - } - - approvalsContainer.innerHTML = ""; - if (list.length === 0) { - approvalsContainer.innerHTML = - '
Keine ausstehenden Entscheidungen. System läuft autonom.
'; - return; - } - - list.forEach((item) => { - const div = document.createElement("div"); - div.className = "approval-item"; - div.setAttribute("data-id", item.id); - - const isDecided = item.status !== "pending"; - const statusClass = item.status === "approved" ? "badge-emerald" : "badge-amber"; - - div.innerHTML = ` -
- ${item.type} - ${item.author} - ${item.timestamp} - ${isDecided ? `${item.status.toUpperCase()}` : ""} -
-
${item.title}
-
${item.details}
-
- Conviction: ${item.conviction}% - R:R: ${item.riskReward} - Umfang: ${item.size} -
- ${ - !isDecided - ? ` -
- - - -
- ` - : "" - } - `; - approvalsContainer.appendChild(div); - }); - - // Attach handlers to actions - approvalsContainer.querySelectorAll(".btn-approve, .btn-veto").forEach((btn) => { - btn.addEventListener("click", async (e) => { - const id = e.target.getAttribute("data-id"); - const action = e.target.getAttribute("data-action"); - try { - const resp = await fetch("/api/approvals", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ id, action }), - }); - if (resp.ok) { - addLog( - `[DECISION] Entscheidung '${action.toUpperCase()}' für '${id}' physisch registriert.`, - "highlight", - ); - loadApprovals(); - } - } catch (err) { - addLog(`Fehler beim Speichern der Entscheidung: ${err.message}`, "error"); - } - }); - }); - - approvalsContainer.querySelectorAll(".btn-discuss").forEach((btn) => { - btn.addEventListener("click", () => { - const author = btn.getAttribute("data-author") || ""; - addLog(`[NAVIGATE] Wechsle zur Konversation mit '${author}' in der Chat-App...`, "info"); - if (window.parent && window.parent !== window) { - window.parent.postMessage({ type: "open_team_tab", botAuthor: author }, "*"); - } - }); - }); - } catch (e) { - console.error("Approvals fetch error", e); - } - } - - // 2. Fetch & Render Overnight Journal - async function loadJournal() { - if (!journalContainer) return; - try { - const res = await fetch("/api/journal"); - if (!res.ok) return; - const data = await res.json(); - const events = data.events || []; - - journalContainer.innerHTML = ""; - events.forEach((ev) => { - const row = document.createElement("div"); - row.className = "journal-entry"; - row.innerHTML = ` -
${ev.time}
-
-
- ${ev.agent} - ${ev.title} -
-
${ev.detail}
-
- `; - journalContainer.appendChild(row); - }); - } catch (e) { - console.error("Journal fetch error", e); - } - } - - let currentTradingMode = "paper"; - let lastTradingData = null; - - const tabModePaper = document.getElementById("tab-mode-paper"); - const tabModeLive = document.getElementById("tab-mode-live"); - - const card1Label = document.getElementById("card1-label"); - const card1Val = document.getElementById("card1-val"); - const card1Sub = document.getElementById("card1-sub"); - - const card2Label = document.getElementById("card2-label"); - const card2Val = document.getElementById("card2-val"); - const card2Sub = document.getElementById("card2-sub"); - - const card3Label = document.getElementById("card3-label"); - const card3Val = document.getElementById("card3-val"); - const card3Sub = document.getElementById("card3-sub"); - - const card4Label = document.getElementById("card4-label"); - const card4Val = document.getElementById("card4-val"); - const card4Sub = document.getElementById("card4-sub"); - - function renderTradingView() { - if (!lastTradingData) return; - - if (tabModePaper && tabModeLive) { - if (currentTradingMode === "live") { - tabModeLive.classList.add("active"); - tabModePaper.classList.remove("active"); - } else { - tabModePaper.classList.add("active"); - tabModeLive.classList.remove("active"); - } - } - - if (currentTradingMode === "live") { - const live = lastTradingData.live || {}; - const poly = live.polymarket || {}; - const bp = live.bitpanda || {}; - const fiat = live.fiat_reserves || {}; - - if (card1Label) card1Label.textContent = "Polymarket CLOB V2"; - if (card1Val) card1Val.textContent = `$${(poly.pusd_balance || 639.39).toFixed(2)} pUSD`; - if (card1Sub) - card1Sub.textContent = `+ ${(poly.pol_gas || 58.25).toFixed(1)} POL Gas · Deposit Wallet`; - - if (card2Label) card2Label.textContent = "Bitpanda Fusion"; - if (card2Val) card2Val.textContent = `€${(bp.eur_cash || 46.09).toFixed(2)} EUR`; - if (card2Sub) - card2Sub.textContent = `+ ${(bp.pol || 436).toFixed(0)} POL, ${(bp.near || 12.6).toFixed(1)} NEAR, ${(bp.usdc || 10.3).toFixed(1)} USDC`; - - if (card3Label) card3Label.textContent = "Bank-Liquidität (Fiat)"; - if (card3Val) - card3Val.textContent = `€${(fiat.total_eur || 8500).toLocaleString("de-DE", { minimumFractionDigits: 2 })}`; - if (card3Sub) card3Sub.textContent = `N26 (€5.000) + Revolut (€3.500)`; - - if (card4Label) card4Label.textContent = "Gesamtes Live-Kapital"; - if (card4Val) { - card4Val.textContent = `$${(live.total_live_equity_usd || 10074.65).toLocaleString("en-US", { minimumFractionDigits: 2 })}`; - card4Val.className = "tmetric-val positive"; - } - if (card4Sub) - card4Sub.textContent = `€${(live.total_live_equity_eur || 9285.39).toLocaleString("de-DE", { minimumFractionDigits: 2 })} · Triple-Gated Safe`; - - if (tradingBadge) { - tradingBadge.textContent = "🟢 ECHTGELD: Polymarket + Bitpanda"; - tradingBadge.className = "badge badge-emerald"; - } - if (tradingSummaryVal) { - tradingSummaryVal.textContent = `LIVE ECHTGELD ($${(poly.pusd_balance || 639.39).toFixed(0)} pUSD + Bitpanda + €8.5k Fiat)`; - } - } else { - // Paper Mode - const paper = lastTradingData.paper || {}; - - if (card1Label) card1Label.textContent = "Equity & Portfolio (Paper)"; - if (card1Val) { - card1Val.textContent = `$${(paper.total_equity_usd || 1000).toLocaleString("en-US", { minimumFractionDigits: 2 })}`; - card1Val.className = "tmetric-val positive"; - } - if (card1Sub) - card1Sub.textContent = `€${(paper.total_cash_eur || 921.66).toLocaleString("de-DE", { minimumFractionDigits: 2 })} · Paper Trading (Safe)`; - - if (card2Label) card2Label.textContent = "Max Drawdown"; - if (card2Val) { - card2Val.textContent = paper.max_drawdown || "0.00%"; - card2Val.className = "tmetric-val"; - } - if (card2Sub) card2Sub.textContent = "Cap: 1.50% (Hard Stop)"; - - if (card3Label) card3Label.textContent = "Aktives Markt-Regime"; - if (card3Val) { - card3Val.textContent = "Donchian / ADX"; - card3Val.className = "tmetric-val cyan-text"; - } - if (card3Sub) card3Sub.textContent = "Trend-Following + Mean-Reversion"; - - if (card4Label) card4Label.textContent = "Circuit Breaker Status"; - if (card4Val) { - card4Val.textContent = paper.circuit_breaker || "ARMED"; - card4Val.className = "tmetric-val success-val"; - } - if (card4Sub) card4Sub.textContent = "Fixed Fractional (1-2%)"; - - if (tradingBadge) { - tradingBadge.textContent = "REST API: Port 8001 (Paper)"; - tradingBadge.className = "badge badge-cyan"; - } - if (tradingSummaryVal) { - tradingSummaryVal.textContent = `PAPER (ARMED · $${(paper.total_equity_usd || 1000).toFixed(0)})`; - } - } - - if (btnKillswitch && killswitchText) { - if (lastTradingData.kill_switch_active) { - btnKillswitch.className = "btn btn-warning halted"; - killswitchText.textContent = "Risk Guard: HALTED"; - } else { - btnKillswitch.className = "btn btn-warning"; - killswitchText.textContent = "Risk Guard: Aktiv"; - } - } - } - - // 3. Poll Trading-Bot Status (Dual Mode: Paper & Live) - async function pollTradingBot() { - try { - const res = await fetch("/api/trading-bot"); - if (res.ok) { - const data = await res.json(); - lastTradingData = data; - if (data.active_mode) { - currentTradingMode = data.active_mode; - } - renderTradingView(); - } - } catch { - // Ignored - } - } - - if (tabModePaper) { - tabModePaper.addEventListener("click", async () => { - currentTradingMode = "paper"; - renderTradingView(); - addLog("[MODE] Auf Paper-Trading-Modus (Simulation) umgeschaltet.", "info"); - try { - await fetch("/api/trading-bot/mode", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ mode: "paper" }), - }); - loadJournal(); - } catch {} - }); - } - - if (tabModeLive) { - tabModeLive.addEventListener("click", async () => { - currentTradingMode = "live"; - renderTradingView(); - addLog( - "[MODE] 🟢 Auf LIVE-ECHTGELD-MODUS (Polymarket & Bitpanda) umgeschaltet!", - "highlight", - ); - try { - await fetch("/api/trading-bot/mode", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ mode: "live" }), - }); - loadJournal(); - } catch {} - }); - } - - // 4. Poll System Status & Cluster - async function pollSystemStatus() { - const t0 = performance.now(); - try { - const res = await fetch("/api/status"); - const rtt = Math.round(performance.now() - t0); - if (res.ok) { - const data = await res.json(); - if (systemHealthVal) { - systemHealthVal.textContent = `HEALTHY (Port 3088 · ${rtt}ms · RAM ${data.memory_rss_mb || 0}MB)`; - } - } - } catch { - if (systemHealthVal) systemHealthVal.textContent = "DEGRADED"; - } - } - - // Initial Load & Intervals - loadApprovals(); - loadJournal(); - pollTradingBot(); - pollSystemStatus(); - setInterval(loadApprovals, 15000); - setInterval(pollTradingBot, 5000); - setInterval(pollSystemStatus, 4000); - - // Risk Killswitch Button - if (btnKillswitch) { - btnKillswitch.addEventListener("click", async () => { - try { - const res = await fetch("/api/risk/toggle-killswitch", { method: "POST" }); - const data = await res.json(); - addLog( - `[RISK GUARD] Notfall-Circuit-Breaker Zustand geändert: ${data.killSwitchActive ? "HALTED" : "ARMED"}`, - data.killSwitchActive ? "warning" : "success", - ); - pollTradingBot(); - } catch (e) { - addLog(`Fehler beim Schalten des Kill-Switches: ${e.message}`, "error"); - } - }); - } - - // 5. Interactive Agent Roster Clicks (Physical Audit Dispatch) - agentItems.forEach((item) => { - item.addEventListener("click", async () => { - const agentId = item.getAttribute("data-agent"); - const dot = item.querySelector(".status-dot"); - const agentName = item.querySelector(".agent-name")?.textContent || agentId; - - addLog(`[DISPATCH] CTO instruiert Agent '${agentName}' (${agentId})...`, "highlight"); - if (dot) dot.style.opacity = "0.35"; - - try { - const res = await fetch("/api/agents/dispatch", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ agent: agentId }), - }); - - if (res.ok) { - const response = await res.json(); - const r = response.data?.result || response.data || {}; - const isSuccess = response.data?.success ?? response.success; - - if (isSuccess) { - addLog( - `[${agentName}] Task '${response.data?.task}' erfolgreich ausgeführt (${response.data?.duration_seconds}s).`, - "success", - ); - if (agentId === "devops") { - addLog( - `[DevOps] PID: ${r.pid} | Port 3088: ${r.port_3088_status} | Health: ${r.status}`, - "info", - ); - } else if (agentId === "architect") { - addLog(`[Architect] ${r.findings} Status: ${r.clean_architecture}`, "info"); - } else if (agentId === "quant") { - addLog( - `[Quant] Engine Test: PASS | Sim: ${r.simulation?.simulation_status} ${r.simulation?.action || ""}`, - "info", - ); - } else if (agentId === "ux") { - addLog(`[UX Designer] ${r.findings} Status: ${r.design_system_integrity}`, "info"); - } - if (dot) { - dot.style.opacity = "1"; - dot.className = "status-dot green"; - } - } else { - addLog(`[${agentName}] Mangel festgestellt: ${JSON.stringify(r)}`, "warning"); - if (dot) { - dot.style.opacity = "1"; - dot.className = "status-dot"; - dot.style.background = "var(--accent-red, #ef4444)"; - } - } - } else { - addLog(`[${agentName}] Server-Fehler bei Dispatch (HTTP ${res.status})`, "error"); - } - } catch (e) { - addLog(`[${agentName}] Dispatch-Fehler: ${e.message}`, "error"); - } - }); - }); - - // 6. Self-Improvement Loop Trigger with Real SSE Streaming - let activeEventSource = null; - if (btnSelfImprove) { - btnSelfImprove.addEventListener("click", async () => { - if (activeEventSource) { - activeEventSource.close(); - activeEventSource = null; - } - - addLog( - "[IMPROVE] Starte physischen Selbstverbesserungs-Zyklus (scripts/self_improve.py)...", - "highlight", - ); - - try { - const eventSource = new EventSource("/api/self-improve?stream=true"); - activeEventSource = eventSource; - - eventSource.onmessage = (event) => { - try { - const payload = JSON.parse(event.data); - if (payload.type === "log") { - addLog(payload.msg, "info"); - } else if (payload.type === "error") { - addLog(payload.msg, "error"); - } else if (payload.type === "done") { - addLog( - `[IMPROVE] Zyklus beendet mit Exit-Code ${payload.exitCode}.`, - payload.exitCode === 0 ? "success" : "error", - ); - eventSource.close(); - activeEventSource = null; - loadJournal(); - } - } catch { - addLog(event.data, "info"); - } - }; - - eventSource.onerror = () => { - addLog("[IMPROVE] SSE Stream beendet oder getrennt.", "info"); - eventSource.close(); - activeEventSource = null; - }; - } catch (err) { - addLog(`[IMPROVE] Verbindungsfehler: ${err.message}`, "error"); - } - }); - } - - // Hot-Reload Trigger - if (btnHotReload) { - btnHotReload.addEventListener("click", async () => { - addLog("[HOT-RELOAD] Hot-Reload Signal gesendet...", "highlight"); - try { - await fetch("/api/reload", { method: "POST" }).catch(() => null); - } catch { - // Ignored - } - setTimeout(() => { - addLog("[HOT-RELOAD] Liveness & Dateisystem-Synchronisation bestätigt.", "success"); - pollSystemStatus(); - }, 400); - }); - } - - // Open Steve Chat button - if (btnOpenSteveChat) { - btnOpenSteveChat.addEventListener("click", () => { - addLog("[NAVIGATE] Öffne Chat mit CTO Steve Jobs in der evOo Chat-App...", "highlight"); - if (window.parent && window.parent !== window) { - window.parent.postMessage({ type: "open_team_tab", botName: "Steve Jobs" }, "*"); - } - }); - } -}); diff --git a/evoo-platform/index.html b/evoo-platform/index.html deleted file mode 100644 index ef56034..0000000 --- a/evoo-platform/index.html +++ /dev/null @@ -1,347 +0,0 @@ - - - - - - evOo Mission Control | Executive Operations Cockpit - - - - - - -
- - -
-
-
- evOo - v2.0-EXEC -
-
- - System: - HEALTHY (Port 3088) -
-
- -
-
- 🖥️ - Cluster: - Host (Metal GPU 30B) · Mini · Air -
-
- 📈 - Trading: - ARMED (Max DD 0.21% / 1.5%) -
-
- -
- - - -
-
- - -
-
- - -
- - -
-
-
- -

Offene Entscheidungen & Freigaben

-
- 2 Ausstehend -
-
- -
-
- - -
-
-
- -

Operations Journal (Letzte 24h)

-
- 100% Physisch -
-
- -
-
- -
- - -
- - -
-
-
- -

CTO Steve Jobs

-
- Vollmacht: Unbeschränkt -
-
-
-
SJ
-
- Steve Jobs - Chief Technology Officer · Modus: Action-First -
-
-
-

- „Realität im Dateisystem vor Halluzination im Kopf. Das 3-Säulen-System steht. Alle 5 Spezialisten arbeiten mit deterministischen Schnittstellen und echten Tests.“ -

-
-
- -
- ✓ Filesystem Write - ✓ Multi-Node Orchestration -
-
-
-
- - -
-
-
- -

Agenten-Flotte

-
- 5 Casts aktiv -
-

Klicke auf einen Spezialisten, um sofort ein physisches System-Audit auszulösen:

- -
- -
-
CA
-
- Code Architect - Moduldesign & Clean Architecture - Node: Mac mini (Strategic Reasoning) -
-
- -
-
- - -
-
QA
-
- Security & QA Auditor - Integritätsprüfung & Zero-Phantom - Node: MacBook Air (Fast Coding) -
-
- -
-
- - -
-
DO
-
- DevOps Engineer - Hot-Reload & Service Liveness - Node: Host Mac (Daemons & Ports) -
-
- -
-
- - -
-
AP
-
- Apex Quant Specialist - Algorithmic Risk & Port 8000 Bridge - Node: Host Mac (Deterministic Math) -
-
- -
-
- - -
-
UX
-
- UX Designer - Lead Product & UX/UI Designer - Node: MacBook Air / Host (3-Pillar UI) -
-
- -
-
-
-
- -
-
- - -
- - -
-
-
- -

Distributed Mac Cluster

-
- Metal GPU Aktiv -
-
-
-
- - Host Mac (Primär) - Metal GPU -
-
-
LM Studio:qwen3-coder-30b-a3b (Metal)
-
Ollama:gemma4:latest (Port 11434)
-
Zustand:Aktiv · Keine Drosselung
-
-
- -
-
- - Mac mini - 24/7 Powerhouse -
-
-
Profil:Aktiv gekühlt (Lüfter)
-
Empfohlen:qwen2.5:32b / deepseek-r1
-
Routing:Failover zu Host Metal GPU
-
-
- -
-
- - MacBook Air - Mobile Worker -
-
-
Profil:Lüfterlos (Thermisch geschützt)
-
Empfohlen:qwen2.5-coder:7b / 3b
-
Routing:Failover zu Host Metal GPU
-
-
-
-
- - -
-
-
- -

Apex Quantitative Trading Engine

-
-
-
- - -
- REST API: Port 8001 -
-
-
-
- Equity & Portfolio - $1,000.00 - €921.66 · Paper Trading (Safe) -
-
- Max Drawdown - 0.00% - Cap: 1.50% (Hard Stop) -
-
- Aktives Markt-Regime - Donchian / ADX - Trend-Following + Mean-Reversion -
-
- Circuit Breaker Status - ARMED - Fixed Fractional (1-2%) -
-
-
- - -
-
-
- -

Self-Improvement & System Event Log

-
-
- - -
-
-
-
[SYSTEM] evOo Executive Cockpit initialized. Version 2.0.0.
-
[CLUSTER] Host Metal GPU (30B Qwen) aktiv. Port 1234 bereit.
-
[VERIFY] 3-Säulen-Navigation (Heute/Team/Projekte) in Chat-App verankert.
-
[OPERATIONS] 5 Casted Agents (Architect, QA, DevOps, Quant, UX) aktiv.
-
-
- -
-
- -
- - -
- - - - diff --git a/evoo-platform/server-config.js b/evoo-platform/server-config.js deleted file mode 100644 index 154151e..0000000 --- a/evoo-platform/server-config.js +++ /dev/null @@ -1,142 +0,0 @@ -const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]); - -/** Default web/API origins allowed on loopback binds for the Heute iframe shell. */ -const DEFAULT_LOOPBACK_WEB_ORIGINS = [ - "http://127.0.0.1:5173", - "http://localhost:5173", - "http://127.0.0.1:3100", - "http://localhost:3100", -]; - -export function isLoopbackHost(host) { - return LOOPBACK_HOSTS.has(host.trim()); -} - -export function timingSafeStringEqual(supplied, expected) { - const actual = Buffer.from(expected); - const candidate = Buffer.from(supplied ?? ""); - if (actual.length !== candidate.length) return false; - let difference = 0; - for (let index = 0; index < actual.length; index += 1) { - difference |= actual[index] ^ candidate[index]; - } - return difference === 0; -} - -export function hasValidBearerToken(authorization, expectedToken) { - const supplied = authorization?.startsWith("Bearer ") ? authorization.slice(7) : ""; - return timingSafeStringEqual(supplied, expectedToken); -} - -export function isPublicCockpitRoute(pathname) { - return pathname === "/health" || pathname === "/api/health"; -} - -/** Privileged API routes must never receive Access-Control-Allow-Origin: *. */ -export function isPrivilegedCockpitRoute(pathname) { - if (isPublicCockpitRoute(pathname)) return false; - return pathname.startsWith("/api/"); -} - -export function parseCorsOriginAllowlist(value) { - if (!value?.trim()) return []; - return [ - ...new Set( - value - .split(",") - .map((entry) => entry.trim()) - .filter(Boolean), - ), - ]; -} - -/** - * Cockpit bind/auth settings. Defaults to loopback-only with no token so local - * Mission Control keeps working out of the box. - */ -export function resolveCockpitConfig(env) { - const port = Number.parseInt(env.PORT?.trim() || "3088", 10); - if (!Number.isInteger(port) || port <= 0 || port > 65_535) { - throw new Error("PORT is not a valid port number."); - } - - const host = env.EVOO_COCKPIT_HOST?.trim() || "127.0.0.1"; - const loopback = isLoopbackHost(host); - const token = env.EVOO_COCKPIT_TOKEN?.trim() || ""; - - if (!loopback && token === "") { - throw new Error( - "Set EVOO_COCKPIT_TOKEN when EVOO_COCKPIT_HOST is not loopback (127.0.0.1, localhost, or ::1).", - ); - } - - return { - port, - host, - token: loopback ? null : token, - requiresAuth: !loopback, - }; -} - -/** - * CORS allowlist. Loopback binds default to local web/API origins; non-loopback - * binds require an explicit EVOO_COCKPIT_CORS_ORIGIN comma-separated list. - */ -export function resolveCockpitCorsConfig(env, cockpitConfig) { - const explicit = parseCorsOriginAllowlist(env.EVOO_COCKPIT_CORS_ORIGIN); - const loopbackBind = isLoopbackHost(cockpitConfig.host); - - if (!loopbackBind && explicit.length === 0) { - return { - loopbackBind: false, - allowedOrigins: [], - usesDefaultLoopbackOrigins: false, - }; - } - - const usesDefaultLoopbackOrigins = loopbackBind && explicit.length === 0; - const allowedOrigins = usesDefaultLoopbackOrigins ? DEFAULT_LOOPBACK_WEB_ORIGINS : explicit; - - return { - loopbackBind, - allowedOrigins, - usesDefaultLoopbackOrigins, - }; -} - -/** - * Resolve the Access-Control-Allow-Origin value for a request. - * Returns a reflected origin string, null when CORS headers should be omitted, - * or false when the Origin is present but not allowed. - */ -export function resolveAllowedCorsOrigin(requestOrigin, corsConfig, pathname) { - const origin = requestOrigin?.trim() ?? ""; - if (!origin) return null; - - if (isPrivilegedCockpitRoute(pathname)) { - return corsConfig.allowedOrigins.includes(origin) ? origin : false; - } - - if (corsConfig.loopbackBind && corsConfig.usesDefaultLoopbackOrigins) { - return corsConfig.allowedOrigins.includes(origin) ? origin : false; - } - - return corsConfig.allowedOrigins.includes(origin) ? origin : false; -} - -export function applyCockpitCorsHeaders(req, res, pathname, corsConfig) { - res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); - res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); - - const allowed = resolveAllowedCorsOrigin(req.headers.origin, corsConfig, pathname); - if (typeof allowed === "string") { - res.setHeader("Access-Control-Allow-Origin", allowed); - res.setHeader("Vary", "Origin"); - } - return allowed; -} - -export function formatCockpitListenUrl(host, port) { - const displayHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host; - return `http://${displayHost}:${port}`; -} diff --git a/evoo-platform/server-config.test.js b/evoo-platform/server-config.test.js deleted file mode 100644 index eddaea8..0000000 --- a/evoo-platform/server-config.test.js +++ /dev/null @@ -1,215 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - applyCockpitCorsHeaders, - formatCockpitListenUrl, - hasValidBearerToken, - isLoopbackHost, - isPrivilegedCockpitRoute, - isPublicCockpitRoute, - resolveAllowedCorsOrigin, - resolveCockpitConfig, - resolveCockpitCorsConfig, -} from "./server-config.js"; - -describe("resolveCockpitConfig", () => { - it("binds to loopback by default without requiring a token", () => { - expect(resolveCockpitConfig({})).toEqual({ - port: 3088, - host: "127.0.0.1", - token: null, - requiresAuth: false, - }); - }); - - it("honours explicit loopback hosts", () => { - for (const host of ["127.0.0.1", "localhost", "::1"]) { - expect(resolveCockpitConfig({ EVOO_COCKPIT_HOST: host })).toMatchObject({ - host, - token: null, - requiresAuth: false, - }); - } - }); - - it("requires a shared token before binding beyond loopback", () => { - expect(() => resolveCockpitConfig({ EVOO_COCKPIT_HOST: "0.0.0.0" })).toThrow( - /EVOO_COCKPIT_TOKEN/, - ); - }); - - it("allows a non-loopback bind when a token is configured", () => { - expect( - resolveCockpitConfig({ - EVOO_COCKPIT_HOST: "0.0.0.0", - EVOO_COCKPIT_TOKEN: "cockpit-review-token-000000000000", - }), - ).toEqual({ - port: 3088, - host: "0.0.0.0", - token: "cockpit-review-token-000000000000", - requiresAuth: true, - }); - }); - - it("rejects invalid ports", () => { - expect(() => resolveCockpitConfig({ PORT: "0" })).toThrow(/PORT/); - expect(() => resolveCockpitConfig({ PORT: "abc" })).toThrow(/PORT/); - }); -}); - -describe("cockpit auth helpers", () => { - const token = "cockpit-review-token-000000000000"; - - it("treats health routes as public when auth is enabled", () => { - expect(isPublicCockpitRoute("/health")).toBe(true); - expect(isPublicCockpitRoute("/api/health")).toBe(true); - expect(isPublicCockpitRoute("/api/status")).toBe(false); - }); - - it("accepts only bearer tokens with constant-time comparison", () => { - expect(hasValidBearerToken(`Bearer ${token}`, token)).toBe(true); - expect(hasValidBearerToken(`Bearer ${`${token}x`}`, token)).toBe(false); - expect(hasValidBearerToken(token, token)).toBe(false); - expect(hasValidBearerToken(undefined, token)).toBe(false); - }); - - it("formats listen URLs with bracketed IPv6 hosts", () => { - expect(formatCockpitListenUrl("127.0.0.1", 3088)).toBe("http://127.0.0.1:3088"); - expect(formatCockpitListenUrl("::1", 3088)).toBe("http://[::1]:3088"); - }); -}); - -describe("isLoopbackHost", () => { - it("recognises loopback hosts only", () => { - expect(isLoopbackHost("127.0.0.1")).toBe(true); - expect(isLoopbackHost("localhost")).toBe(true); - expect(isLoopbackHost("::1")).toBe(true); - expect(isLoopbackHost("0.0.0.0")).toBe(false); - expect(isLoopbackHost("192.168.1.10")).toBe(false); - }); -}); - -describe("isPrivilegedCockpitRoute", () => { - it("marks API routes except health as privileged", () => { - expect(isPrivilegedCockpitRoute("/health")).toBe(false); - expect(isPrivilegedCockpitRoute("/api/health")).toBe(false); - expect(isPrivilegedCockpitRoute("/api/status")).toBe(true); - expect(isPrivilegedCockpitRoute("/api/trading-bot/mode")).toBe(true); - expect(isPrivilegedCockpitRoute("/index.html")).toBe(false); - }); -}); - -describe("resolveCockpitCorsConfig", () => { - const loopbackCockpit = resolveCockpitConfig({ EVOO_COCKPIT_HOST: "127.0.0.1" }); - const publicCockpit = resolveCockpitConfig({ - EVOO_COCKPIT_HOST: "0.0.0.0", - EVOO_COCKPIT_TOKEN: "cockpit-review-token-000000000000", - }); - - it("defaults to local web origins on loopback binds", () => { - const cors = resolveCockpitCorsConfig({}, loopbackCockpit); - expect(cors.loopbackBind).toBe(true); - expect(cors.usesDefaultLoopbackOrigins).toBe(true); - expect(cors.allowedOrigins).toContain("http://127.0.0.1:5173"); - }); - - it("requires an explicit allowlist off loopback", () => { - const cors = resolveCockpitCorsConfig({}, publicCockpit); - expect(cors.allowedOrigins).toEqual([]); - expect(cors.usesDefaultLoopbackOrigins).toBe(false); - }); - - it("honours EVOO_COCKPIT_CORS_ORIGIN when set", () => { - const cors = resolveCockpitCorsConfig( - { EVOO_COCKPIT_CORS_ORIGIN: "https://ops.example.com, https://ops.example.com" }, - publicCockpit, - ); - expect(cors.allowedOrigins).toEqual(["https://ops.example.com"]); - }); -}); - -describe("resolveAllowedCorsOrigin", () => { - const loopbackCors = resolveCockpitCorsConfig({}, resolveCockpitConfig({})); - const publicCors = resolveCockpitCorsConfig( - { EVOO_COCKPIT_CORS_ORIGIN: "https://ops.example.com" }, - resolveCockpitConfig({ - EVOO_COCKPIT_HOST: "0.0.0.0", - EVOO_COCKPIT_TOKEN: "cockpit-review-token-000000000000", - }), - ); - const blockedPublicCors = resolveCockpitCorsConfig( - {}, - resolveCockpitConfig({ - EVOO_COCKPIT_HOST: "0.0.0.0", - EVOO_COCKPIT_TOKEN: "cockpit-review-token-000000000000", - }), - ); - - it("never returns a wildcard and omits headers without an Origin", () => { - expect(resolveAllowedCorsOrigin(undefined, loopbackCors, "/api/status")).toBeNull(); - expect(resolveAllowedCorsOrigin("", loopbackCors, "/api/status")).toBeNull(); - }); - - it("reflects allowlisted origins on privileged routes", () => { - expect(resolveAllowedCorsOrigin("http://127.0.0.1:5173", loopbackCors, "/api/status")).toBe( - "http://127.0.0.1:5173", - ); - expect(resolveAllowedCorsOrigin("https://evil.example", loopbackCors, "/api/status")).toBe( - false, - ); - }); - - it("blocks cross-origin privileged access off loopback without an allowlist", () => { - expect( - resolveAllowedCorsOrigin("https://ops.example.com", blockedPublicCors, "/api/status"), - ).toBe(false); - }); - - it("allows configured origins off loopback", () => { - expect( - resolveAllowedCorsOrigin("https://ops.example.com", publicCors, "/api/trading-bot"), - ).toBe("https://ops.example.com"); - }); -}); - -describe("applyCockpitCorsHeaders", () => { - it("sets reflected origins and never sets *", () => { - const cors = resolveCockpitCorsConfig({}, resolveCockpitConfig({})); - const headers = {}; - const res = { - setHeader: (key, value) => { - headers[key] = value; - }, - }; - const allowed = applyCockpitCorsHeaders( - { headers: { origin: "http://127.0.0.1:5173" } }, - res, - "/api/status", - cors, - ); - - expect(allowed).toBe("http://127.0.0.1:5173"); - expect(headers["Access-Control-Allow-Origin"]).toBe("http://127.0.0.1:5173"); - expect(headers["Access-Control-Allow-Origin"]).not.toBe("*"); - expect(headers["Access-Control-Allow-Headers"]).toContain("Authorization"); - }); - - it("omits Access-Control-Allow-Origin for disallowed cross-origin requests", () => { - const cors = resolveCockpitCorsConfig({}, resolveCockpitConfig({})); - const headers = {}; - const res = { - setHeader: (key, value) => { - headers[key] = value; - }, - }; - const allowed = applyCockpitCorsHeaders( - { headers: { origin: "https://evil.example" } }, - res, - "/api/status", - cors, - ); - - expect(allowed).toBe(false); - expect(headers["Access-Control-Allow-Origin"]).toBeUndefined(); - }); -}); diff --git a/evoo-platform/server.js b/evoo-platform/server.js deleted file mode 100644 index 3c7c5e8..0000000 --- a/evoo-platform/server.js +++ /dev/null @@ -1,731 +0,0 @@ -/** - * evOo Platform High-Speed Embedded Server - * ES Module compatible with Node.js 22+. - * Zero external dependencies. - * Hardened against Directory Traversal & equipped with real Agent Runtime integration. - */ - -import { spawn } from "node:child_process"; -import fs from "node:fs"; -import http from "node:http"; -import os from "node:os"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import { - applyCockpitCorsHeaders, - formatCockpitListenUrl, - hasValidBearerToken, - isPublicCockpitRoute, - resolveCockpitConfig, - resolveCockpitCorsConfig, -} from "./server-config.js"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const cockpit = resolveCockpitConfig(process.env); -const corsConfig = resolveCockpitCorsConfig(process.env, cockpit); -const { port: PORT, host: COCKPIT_HOST } = cockpit; -const BASE_DIR = __dirname; -const ROOT_DIR = path.resolve(BASE_DIR, ".."); - -const MIME_TYPES = { - ".html": "text/html; charset=UTF-8", - ".css": "text/css; charset=UTF-8", - ".js": "application/javascript; charset=UTF-8", - ".json": "application/json; charset=UTF-8", - ".png": "image/png", - ".jpg": "image/jpeg", - ".svg": "image/svg+xml", - ".md": "text/markdown; charset=UTF-8", -}; - -const APPROVALS_FILE = path.join(ROOT_DIR, "orchestrator", "data", "approvals.json"); -const JOURNAL_FILE = path.join(ROOT_DIR, "orchestrator", "data", "journal.json"); - -function loadApprovals() { - try { - if (fs.existsSync(APPROVALS_FILE)) { - return JSON.parse(fs.readFileSync(APPROVALS_FILE, "utf8")); - } - } catch (e) { - console.error("Error loading approvals:", e.message); - } - const defaults = [ - { - id: "appr-104", - type: "trade", - title: "Staged Trade: ETH/USDT Breakout", - author: "Apex Quant Specialist", - conviction: 88, - riskReward: 2.4, - size: "1.2% Equity", - status: "pending", - timestamp: "Vor 14 Min.", - details: - "Trend-Following Breakout über 20-Perioden Donchian Upper Band. ADX 32.4 (starker Trend). Stop-Loss bei -1.1% gesetzt.", - }, - { - id: "appr-105", - type: "architecture", - title: "Modulare Entkopplung: Trading-Bot API Bridge", - author: "Code Architect", - conviction: 95, - riskReward: 3.5, - size: "Refactor Plan", - status: "pending", - timestamp: "Vor 42 Min.", - details: - "Vollständige Entkopplung der tradingbot-Imports. evOo konsumiert ausschließlich REST-Endpoints auf Port 8000.", - }, - ]; - saveApprovals(defaults); - return defaults; -} - -function saveApprovals(items) { - try { - fs.mkdirSync(path.dirname(APPROVALS_FILE), { recursive: true }); - fs.writeFileSync(APPROVALS_FILE, JSON.stringify(items, null, 2), "utf8"); - } catch (e) { - console.error("Error saving approvals:", e.message); - } -} - -function loadJournal() { - try { - if (fs.existsSync(JOURNAL_FILE)) { - return JSON.parse(fs.readFileSync(JOURNAL_FILE, "utf8")); - } - } catch (e) { - console.error("Error loading journal:", e.message); - } - const defaults = [ - { - time: "04:52 UTC", - agent: "Security & QA", - type: "success", - title: "Zero-Phantom Integrity & Tests", - detail: "14 Systemdateien & 5 JSONs verifiziert. Apex Unittests 100% grün.", - }, - { - time: "04:46 UTC", - agent: "Orchestrator", - type: "info", - title: "Casted Agent Network Sync", - detail: "5 Casted Agents (inkl. UX Designer) synchronisiert. Local Metal GPU aktiv.", - }, - { - time: "03:15 UTC", - agent: "Apex Quant", - type: "success", - title: "Circuit Breaker Audit", - detail: "Kein Drawdown-Verstoß festgestellt. Max Drawdown aktuell 0.21% / 1.5% Cap.", - }, - { - time: "01:05 UTC", - agent: "Cluster Router", - type: "info", - title: "Smart Failover Routine", - detail: "Host Apple Silicon Metal übernimmt Inference nahtlos für Standby-LAN-Nodes.", - }, - { - time: "00:50 UTC", - agent: "DevOps Engineer", - type: "success", - title: "Plattform Hot-Reload", - detail: "Server auf Port 3088 atomar neu gestartet. PID stabilisiert.", - }, - ]; - try { - fs.mkdirSync(path.dirname(JOURNAL_FILE), { recursive: true }); - fs.writeFileSync(JOURNAL_FILE, JSON.stringify(defaults, null, 2), "utf8"); - } catch {} - return defaults; -} - -function appendJournal(entry) { - try { - const list = loadJournal(); - list.unshift(entry); - if (list.length > 50) list.pop(); - fs.mkdirSync(path.dirname(JOURNAL_FILE), { recursive: true }); - fs.writeFileSync(JOURNAL_FILE, JSON.stringify(list, null, 2), "utf8"); - } catch (e) { - console.error("Error appending journal:", e.message); - } -} - -let riskKillSwitchActive = false; -const pendingApprovals = loadApprovals(); -let currentTradingMode = "paper"; // 'paper' or 'live' - -let liveCapitalCache = null; -let liveCapitalLastFetch = 0; - -async function getLiveCapitalTelemetry() { - const now = Date.now(); - if (liveCapitalCache && now - liveCapitalLastFetch < 15000) { - return liveCapitalCache; - } - - // 1. Bitpanda Fusion Balance - const bitpandaBal = { - eur: 46.09, - usdc: 10.26, - pol: 436.46, - near: 12.59, - btt: 137271, - vet: 12.75, - }; - try { - const envFile = path.resolve(ROOT_DIR, "..", "trading-bot", ".env.quant"); - if (fs.existsSync(envFile)) { - const content = fs.readFileSync(envFile, "utf8"); - const m = content.match(/BITPANDA_API_KEY=([a-zA-Z0-9]+)/); - if (m?.[1]) { - const bpRes = await fetch("https://api.fusion.bitpanda.com/v1/account/balances", { - headers: { "x-api-key": m[1] }, - signal: AbortSignal.timeout(2500), - }); - if (bpRes.ok) { - const list = await bpRes.json(); - for (const item of list) { - const sym = (item.symbol || "").toLowerCase(); - const val = parseFloat(item.available || "0"); - if (sym === "eur") bitpandaBal.eur = val; - if (sym === "usdc") bitpandaBal.usdc = val; - if (sym === "pol") bitpandaBal.pol = val; - if (sym === "near") bitpandaBal.near = val; - if (sym === "btt") bitpandaBal.btt = val; - if (sym === "vet") bitpandaBal.vet = val; - } - } - } - } - } catch (_e) { - // Keep defaults - } - - // 2. Polymarket CLOB V2 Deposit Wallet (On-Chain Polygon RPC) - let polymarketPusd = 639.39; - const polymarketPolGas = 58.25; - try { - const depositWallet = "0x1d03562E91E71D65DFDec36cc8874be32135D4bC"; - const pUSDContract = "0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB"; - const data = `0x70a08231000000000000000000000000${depositWallet.slice(2).toLowerCase()}`; - const rpcRes = await fetch("https://polygon-bor-rpc.publicnode.com", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: 1, - method: "eth_call", - params: [{ to: pUSDContract, data }, "latest"], - }), - signal: AbortSignal.timeout(2500), - }); - if (rpcRes.ok) { - const rpcJson = await rpcRes.json(); - if (rpcJson?.result) { - polymarketPusd = Math.round((parseInt(rpcJson.result, 16) / 1e6) * 100) / 100; - } - } - } catch (_e) { - // Keep defaults - } - - // 3. Bank / Fiat Balances - const fiat = { n26: 5000.0, revolut: 3500.0, total_eur: 8500.0 }; - try { - const fiatFile = path.resolve(ROOT_DIR, "..", "trading-bot", "data", "fiat_balances.json"); - if (fs.existsSync(fiatFile)) { - const parsed = JSON.parse(fs.readFileSync(fiatFile, "utf8")); - fiat.n26 = parsed.n26?.cash || 5000.0; - fiat.revolut = parsed.revolut?.cash || 3500.0; - fiat.total_eur = fiat.n26 + fiat.revolut; - } - } catch (_e) {} - - const totalEur = - Math.round((fiat.total_eur + bitpandaBal.eur + polymarketPusd / 1.085 + 150) * 100) / 100; - const totalUsd = Math.round(totalEur * 1.085 * 100) / 100; - - liveCapitalCache = { - status: "LIVE FUNDED", - mode: "LIVE (ECHTGELD)", - polymarket: { - deposit_wallet: "0x1d03...D4bC", - full_wallet: "0x1d03562E91E71D65DFDec36cc8874be32135D4bC", - pusd_balance: polymarketPusd, - pol_gas: polymarketPolGas, - status: "FUNDED & READY (CLOB V2)", - }, - bitpanda: { - eur_cash: bitpandaBal.eur, - usdc: bitpandaBal.usdc, - pol: bitpandaBal.pol, - near: bitpandaBal.near, - btt: bitpandaBal.btt, - vet: bitpandaBal.vet, - status: "ONLINE (FUSION REST)", - }, - fiat_reserves: { - n26_eur: fiat.n26, - revolut_eur: fiat.revolut, - total_eur: fiat.total_eur, - }, - total_live_equity_usd: totalUsd, - total_live_equity_eur: totalEur, - circuit_breaker: "ENFORCED (TRIPLE-GATED)", - open_positions: 0, - }; - liveCapitalLastFetch = now; - return liveCapitalCache; -} - -const server = http.createServer(async (req, res) => { - const parsedUrl = new URL(req.url, `http://${req.headers.host || "localhost"}`); - const urlPath = parsedUrl.pathname; - const corsAllowed = applyCockpitCorsHeaders(req, res, urlPath, corsConfig); - - if (req.method === "OPTIONS") { - if (corsAllowed === false && req.headers.origin) { - res.writeHead(403); - res.end(); - return; - } - res.writeHead(204); - res.end(); - return; - } - - if ( - cockpit.requiresAuth && - !isPublicCockpitRoute(urlPath) && - !hasValidBearerToken(req.headers.authorization, cockpit.token) - ) { - res.writeHead(401, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ error: "unauthorized" })); - return; - } - - // 1. API Health Check - if (urlPath === "/health" || urlPath === "/api/health") { - res.writeHead(200, { "Content-Type": "application/json" }); - res.end( - JSON.stringify({ - status: "healthy", - uptime: process.uptime(), - timestamp: new Date().toISOString(), - engine: "evOo Autonomous Platform 2.0.0", - cto: "Steve Jobs", - filesystem_integrity: "100% Physical", - }), - ); - return; - } - - // 2. API Status (Real System Telemetry) - if (urlPath === "/api/status") { - const archExists = fs.existsSync(path.join(ROOT_DIR, "ARCHITECTURE.md")); - const apexExists = fs.existsSync(path.join(ROOT_DIR, "apex-trading", "BLUEPRINT.md")); - const deployExists = fs.existsSync(path.join(ROOT_DIR, "deploy.sh")); - const mem = process.memoryUsage(); - const load = os.loadavg(); - - res.writeHead(200, { "Content-Type": "application/json" }); - res.end( - JSON.stringify({ - status: "online", - uptime_seconds: Math.round(process.uptime()), - load_avg_1m: load[0], - memory_rss_mb: Math.round((mem.rss / (1024 * 1024)) * 10) / 10, - memory_heap_mb: Math.round((mem.heapUsed / (1024 * 1024)) * 10) / 10, - verified_files: { - architecture: archExists, - apex_blueprint: apexExists, - deploy_script: deployExists, - }, - breathe_frequency: "1.04 Hz", - }), - ); - return; - } - - // 3. API Self-Improvement trigger (Real execution of scripts/self_improve.py) - if (urlPath === "/api/self-improve") { - const isSSE = - req.headers.accept?.includes("text/event-stream") || - parsedUrl.searchParams.get("stream") === "true"; - const scriptPath = path.join(ROOT_DIR, "scripts", "self_improve.py"); - - if (isSSE) { - res.writeHead(200, { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache", - Connection: "keep-alive", - }); - res.write( - `data: ${JSON.stringify({ type: "start", msg: "Starte physischen Selbstverbesserungs-Zyklus..." })}\n\n`, - ); - - const child = spawn("python3", [scriptPath], { cwd: ROOT_DIR }); - child.stdout.on("data", (data) => { - const lines = data.toString().split("\n"); - for (const line of lines) { - if (line.trim()) { - res.write(`data: ${JSON.stringify({ type: "log", msg: line })}\n\n`); - } - } - }); - child.stderr.on("data", (data) => { - const lines = data.toString().split("\n"); - for (const line of lines) { - if (line.trim()) { - res.write(`data: ${JSON.stringify({ type: "error", msg: line })}\n\n`); - } - } - }); - child.on("close", (code) => { - appendJournal({ - time: `${new Date().toISOString().substring(11, 16)} UTC`, - agent: "Self-Improve Loop", - type: code === 0 ? "success" : "error", - title: "Selbstverbesserungs-Zyklus", - detail: - code === 0 - ? "14 Systemdateien & Apex-Tests physisch verifiziert." - : "Fehler im Zyklus aufgetreten.", - }); - res.write(`data: ${JSON.stringify({ type: "done", exitCode: code })}\n\n`); - res.end(); - }); - return; - } - - // Standard POST / GET non-streaming response - const child = spawn("python3", [scriptPath], { cwd: ROOT_DIR }); - let stdout = ""; - let stderr = ""; - child.stdout.on("data", (d) => { - stdout += d.toString(); - }); - child.stderr.on("data", (d) => { - stderr += d.toString(); - }); - child.on("close", (code) => { - appendJournal({ - time: `${new Date().toISOString().substring(11, 16)} UTC`, - agent: "Self-Improve Loop", - type: code === 0 ? "success" : "error", - title: "Selbstverbesserungs-Zyklus", - detail: - code === 0 - ? "14 Systemdateien & Apex-Tests physisch verifiziert." - : "Fehler im Zyklus aufgetreten.", - }); - res.writeHead(code === 0 ? 200 : 500, { "Content-Type": "application/json" }); - res.end( - JSON.stringify({ - status: code === 0 ? "success" : "failed", - exitCode: code, - output: stdout, - error: stderr || null, - }), - ); - }); - return; - } - - // 4. API Casted Agents Listing - if (urlPath === "/api/agents") { - const agentsPath = path.join(ROOT_DIR, "orchestrator", "agents.json"); - if (fs.existsSync(agentsPath)) { - try { - const content = fs.readFileSync(agentsPath, "utf8"); - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(content); - return; - } catch (_e) { - // Fallback - } - } - res.writeHead(500, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ error: "agents.json not found" })); - return; - } - - // 4b. API Distributed Compute Cluster Telemetry - if (urlPath === "/api/cluster") { - const runnerPath = path.join(ROOT_DIR, "orchestrator", "agent_runner.py"); - const child = spawn("python3", [runnerPath, "--cluster", "--json"], { cwd: ROOT_DIR }); - let stdout = ""; - child.stdout.on("data", (d) => { - stdout += d.toString(); - }); - child.on("close", () => { - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(stdout || JSON.stringify({ status: "unknown" })); - }); - return; - } - - // 4c. API Approvals (Executive Human-in-the-Loop) - if (urlPath === "/api/approvals") { - if (req.method === "POST") { - let body = ""; - req.on("data", (chunk) => { - body += chunk; - }); - req.on("end", () => { - try { - const { id, action } = JSON.parse(body); - const item = pendingApprovals.find((a) => a.id === id); - if (item) { - item.status = action === "approve" ? "approved" : "vetoed"; - item.decidedAt = new Date().toISOString(); - saveApprovals(pendingApprovals); - appendJournal({ - time: `${new Date().toISOString().substring(11, 16)} UTC`, - agent: "Operator (Ingo)", - type: action === "approve" ? "success" : "warn", - title: `Entscheidung: ${action.toUpperCase()}`, - detail: `${item.title} (${item.id}) registriert.`, - }); - } - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ ok: true, item })); - } catch { - res.writeHead(400, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ ok: false, error: "Invalid payload" })); - } - }); - return; - } - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ ok: true, approvals: pendingApprovals })); - return; - } - - // 4d. API Overnight & Operational Journal - if (urlPath === "/api/journal") { - const journal = loadJournal(); - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ ok: true, events: journal })); - return; - } - - // 4e. API Trading-Bot Probe (Dual Mode: Paper & Live Capital) - if (urlPath === "/api/trading-bot") { - const liveData = await getLiveCapitalTelemetry(); - - // Query paper bot on port 8001 (or 8002) - const paperData = { - status: "ONLINE", - mode: "PAPER (SAFE)", - total_cash_usd: 1000.0, - total_equity_usd: 1000.0, - total_cash_eur: 921.66, - pnl_today: "+0.00%", - active_regime: "DONCHIAN_TREND_ADX", - max_drawdown: "0.00%", - circuit_breaker: riskKillSwitchActive ? "HALTED_BY_OPERATOR" : "ARMED", - kill_switch_active: riskKillSwitchActive, - open_positions: 0, - }; - - try { - const resp = await fetch("http://127.0.0.1:8001/api/portfolio", { - signal: AbortSignal.timeout(800), - }); - if (resp.ok) { - const portData = await resp.json(); - paperData.total_cash_usd = portData.total_cash || 1000.0; - paperData.total_equity_usd = portData.total_equity || 1000.0; - paperData.total_cash_eur = portData.total_cash_eur || 921.66; - paperData.open_positions = portData.open_positions || 0; - } - } catch { - // Keep defaults - } - - res.writeHead(200, { "Content-Type": "application/json" }); - res.end( - JSON.stringify({ - ok: true, - active_mode: currentTradingMode, - kill_switch_active: riskKillSwitchActive, - paper: paperData, - live: liveData, - }), - ); - return; - } - - // 4e-b. API Toggle Trading Mode (POST /api/trading-bot/mode) - if (urlPath === "/api/trading-bot/mode" && req.method === "POST") { - let body = ""; - req.on("data", (chunk) => { - body += chunk; - }); - req.on("end", () => { - try { - const { mode } = JSON.parse(body); - if (mode === "live" || mode === "paper") { - currentTradingMode = mode; - appendJournal({ - time: `${new Date().toISOString().substring(11, 16)} UTC`, - agent: "Operator (Ingo)", - type: mode === "live" ? "warn" : "info", - title: `Trading-Modus gewechselt: ${mode.toUpperCase()}`, - detail: - mode === "live" - ? "Live-Echtgeld Telemetrie (Polymarket CLOB V2 & Bitpanda) im Cockpit aktiviert." - : "Paper-Trading-Modus (Simulation) im Cockpit aktiviert.", - }); - } - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ ok: true, active_mode: currentTradingMode })); - } catch (e) { - res.writeHead(400, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ ok: false, error: e.message })); - } - }); - return; - } - - // 4f. API Risk Kill-Switch Toggle - if (urlPath === "/api/risk/toggle-killswitch" && req.method === "POST") { - riskKillSwitchActive = !riskKillSwitchActive; - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ ok: true, killSwitchActive: riskKillSwitchActive })); - return; - } - - // 5. API Agent Dispatch (Real Task Execution) - if (urlPath === "/api/agents/dispatch" && req.method === "POST") { - let body = ""; - req.on("data", (chunk) => { - body += chunk; - }); - req.on("end", () => { - let agentId = "all"; - let task = ""; - try { - if (body) { - const parsed = JSON.parse(body); - if (parsed.agent) agentId = parsed.agent; - if (parsed.task) task = parsed.task; - } - } catch (_e) { - // Ignore JSON error and default - } - - const runnerPath = path.join(ROOT_DIR, "orchestrator", "agent_runner.py"); - const args = [runnerPath, "--agent", agentId, "--json"]; - if (task) { - args.push("--task", task); - } - - const child = spawn("python3", args, { cwd: ROOT_DIR }); - let stdout = ""; - let stderr = ""; - child.stdout.on("data", (d) => { - stdout += d.toString(); - }); - child.stderr.on("data", (d) => { - stderr += d.toString(); - }); - child.on("close", (code) => { - appendJournal({ - time: `${new Date().toISOString().substring(11, 16)} UTC`, - agent: agentId.toUpperCase(), - type: code === 0 ? "success" : "error", - title: `Agent Execution: ${agentId}`, - detail: `Task '${task || "audit"}' ausgeführt (Exit Code ${code}).`, - }); - try { - const data = JSON.parse(stdout); - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ success: code === 0, exitCode: code, data })); - } catch (_e) { - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ success: code === 0, exitCode: code, raw: stdout, stderr })); - } - }); - }); - return; - } - - // 6. API Hot-Reload trigger - if (urlPath === "/api/reload" && req.method === "POST") { - res.writeHead(200, { "Content-Type": "application/json" }); - res.end( - JSON.stringify({ - status: "success", - message: "Hot-Reload broadcast acknowledged. Dateisystem synchronisiert.", - }), - ); - return; - } - - // 7. Static File Serving with Directory Traversal Protection - let safeRelPath = path - .normalize(urlPath === "/" ? "/index.html" : urlPath) - .replace(/^(\.\.[/\\])+/, ""); - if (safeRelPath.startsWith("/")) { - safeRelPath = safeRelPath.slice(1); - } - - let resolvedPath = path.resolve(BASE_DIR, safeRelPath); - - // Fallback to ROOT_DIR only for safe markdown docs (ARCHITECTURE.md, BLUEPRINT.md) - const isWhitelistedDoc = [ - "ARCHITECTURE.md", - "apex-trading/BLUEPRINT.md", - "evoo.config.json", - ].includes(safeRelPath); - if (!fs.existsSync(resolvedPath) && isWhitelistedDoc) { - resolvedPath = path.resolve(ROOT_DIR, safeRelPath); - } - - // Directory Traversal Verification: must be inside BASE_DIR or explicitly whitelisted root file - const isInsideBase = resolvedPath.startsWith(BASE_DIR + path.sep) || resolvedPath === BASE_DIR; - const isInsideRootDoc = isWhitelistedDoc && resolvedPath.startsWith(ROOT_DIR + path.sep); - - if (!isInsideBase && !isInsideRootDoc) { - res.writeHead(403, { "Content-Type": "text/plain; charset=UTF-8" }); - res.end("403 Forbidden: Directory Traversal Denied"); - return; - } - - if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isFile()) { - const ext = path.extname(resolvedPath).toLowerCase(); - const contentType = MIME_TYPES[ext] || "application/octet-stream"; - res.writeHead(200, { "Content-Type": contentType }); - fs.createReadStream(resolvedPath).pipe(res); - } else { - res.writeHead(404, { "Content-Type": "text/plain; charset=UTF-8" }); - res.end(`404 Not Found: ${urlPath}`); - } -}); - -server.listen(PORT, COCKPIT_HOST, () => { - try { - fs.writeFileSync(path.join(ROOT_DIR, ".evoo_server.pid"), process.pid.toString(), "utf8"); - } catch (_e) { - // Ignore if write fails - } - const listenUrl = formatCockpitListenUrl(COCKPIT_HOST, PORT); - console.log(`[evOo Core] Server listening on ${listenUrl} (PID: ${process.pid})`); - console.log(`[evOo Core] Healthcheck available at ${listenUrl}/health`); - if (cockpit.requiresAuth) { - console.log("[evOo Core] Non-loopback bind: Bearer token required except /health"); - } - if (!corsConfig.loopbackBind && corsConfig.allowedOrigins.length === 0) { - console.log( - "[evOo Core] Non-loopback bind: set EVOO_COCKPIT_CORS_ORIGIN for browser cross-origin access", - ); - } -}); diff --git a/evoo-platform/styles.css b/evoo-platform/styles.css deleted file mode 100644 index 5c8f709..0000000 --- a/evoo-platform/styles.css +++ /dev/null @@ -1,1204 +0,0 @@ -/* ========================================================== - evOo Executive Cockpit — 2026/2027 State-of-the-Art Design System - Aesthetic: Classic Apple Precision Craftsmanship in Dark Obsidian - Typography: Human-scale, Generous Breathing Room & Legibility - Materials: Milled Smoked Sapphire Glass, Tactile Depth & Rich Contrast - ========================================================== */ - -:root { - /* Obsidian & Space Grey Palette */ - --bg-base: #0a0c10; - --bg-surface: #10131a; - --bg-card: rgba(20, 24, 34, 0.78); - --bg-card-hover: rgba(28, 34, 48, 0.88); - --bg-card-subtle: rgba(255, 255, 255, 0.035); - --bg-input: #121620; - - /* Borders & Highlights */ - --border-subtle: rgba(255, 255, 255, 0.09); - --border-hover: rgba(255, 255, 255, 0.18); - --border-highlight: rgba(56, 189, 248, 0.4); - --bevel-top: inset 0 1px 0 rgba(255, 255, 255, 0.12); - --bevel-btn: inset 0 1px 0 rgba(255, 255, 255, 0.25); - - /* High-Legibility Typography Colors */ - --text-main: #f8fafc; - --text-secondary: #cbd5e1; - --text-muted: #94a3b8; - --text-dim: #64748b; - - /* Apple Pro Typography Stack */ - --font-sans: - -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Helvetica Neue", - "Plus Jakarta Sans", Inter, sans-serif; - --font-mono: "SF Mono", "JetBrains Mono", Menlo, Monaco, monospace; - - /* Jewel-Tone Accents */ - --accent-cyan: #38bdf8; - --accent-blue: #3b82f6; - --accent-emerald: #10b981; - --accent-emerald-deep: #059669; - --accent-amber: #f59e0b; - --accent-purple: #a855f7; - --accent-orange: #f97316; - --accent-red: #ef4444; - - /* Apple Smooth Curves */ - --radius-xl: 22px; - --radius-lg: 16px; - --radius-md: 12px; - --radius-sm: 8px; - --radius-pill: 9999px; -} - -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -body { - background-color: var(--bg-base); - color: var(--text-main); - font-family: var(--font-sans); - min-height: 100vh; - display: flex; - flex-direction: column; - overflow-x: hidden; - font-size: 15.5px; - line-height: 1.6; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -/* Ambient Radial Mesh */ -.background-mesh { - position: fixed; - inset: 0; - background-image: - radial-gradient(circle at 10% 10%, rgba(56, 189, 248, 0.05) 0%, transparent 50%), - radial-gradient(circle at 90% 85%, rgba(168, 85, 247, 0.05) 0%, transparent 55%), - linear-gradient(to bottom, rgba(10, 12, 16, 0.8), rgba(10, 12, 16, 1)); - pointer-events: none; - z-index: 0; -} - -/* ========================================================== - 1. EXECUTIVE HEADER (PULSE STRIP) - ========================================================== */ -.executive-header { - position: sticky; - top: 0; - z-index: 100; - display: flex; - align-items: center; - justify-content: space-between; - padding: 1rem 2rem; - background: rgba(12, 15, 22, 0.82); - backdrop-filter: blur(24px) saturate(180%); - -webkit-backdrop-filter: blur(24px) saturate(180%); - border-bottom: 1px solid var(--border-subtle); - box-shadow: - 0 4px 24px -1px rgba(0, 0, 0, 0.5), - var(--bevel-top); - gap: 1.5rem; - flex-wrap: wrap; -} - -.header-left, -.header-center, -.header-right { - display: flex; - align-items: center; - gap: 0.9rem; -} - -.brand { - display: flex; - align-items: center; - gap: 0.65rem; -} - -.brand-name { - font-size: 1.35rem; - font-weight: 800; - letter-spacing: -0.035em; - color: #ffffff; -} - -.brand-highlight { - color: var(--accent-cyan); - text-shadow: 0 0 16px rgba(56, 189, 248, 0.4); -} - -.version-tag { - font-family: var(--font-mono); - font-size: 0.72rem; - font-weight: 700; - background: rgba(255, 255, 255, 0.06); - padding: 0.2rem 0.6rem; - border-radius: var(--radius-sm); - color: var(--text-muted); - border: 1px solid var(--border-subtle); - letter-spacing: 0.04em; -} - -/* Header Status Pills */ -.pulse-pill, -.topology-pill, -.trading-pill { - display: flex; - align-items: center; - gap: 0.6rem; - background: rgba(255, 255, 255, 0.04); - border: 1px solid var(--border-subtle); - box-shadow: var(--bevel-top); - padding: 0.42rem 0.95rem; - border-radius: var(--radius-pill); - font-size: 0.85rem; - transition: all 0.2s ease; -} - -.pulse-pill:hover, -.topology-pill:hover, -.trading-pill:hover { - background: rgba(255, 255, 255, 0.07); - border-color: var(--border-hover); -} - -.pulse-indicator { - width: 9px; - height: 9px; - border-radius: 50%; - background: var(--accent-emerald); - box-shadow: 0 0 10px var(--accent-emerald); - position: relative; -} - -.pulse-indicator.online::after { - content: ""; - position: absolute; - inset: -4px; - border-radius: 50%; - border: 1.5px solid var(--accent-emerald); - animation: pulse-ring 2.4s cubic-bezier(0.215, 0.61, 0.355, 1) infinite; -} - -@keyframes pulse-ring { - 0% { - transform: scale(0.95); - opacity: 0.8; - } - 50% { - transform: scale(1.7); - opacity: 0; - } - 100% { - transform: scale(0.95); - opacity: 0; - } -} - -.pill-icon { - font-size: 1rem; -} - -.pill-label { - color: var(--text-muted); - font-weight: 500; -} - -.pill-value { - color: var(--text-main); - font-weight: 600; - letter-spacing: -0.01em; -} - -/* Tactile Header Buttons */ -.btn { - display: inline-flex; - align-items: center; - gap: 0.55rem; - padding: 0.55rem 1.15rem; - border-radius: var(--radius-pill); - font-size: 0.88rem; - font-weight: 600; - cursor: pointer; - transition: all 0.18s cubic-bezier(0.16, 1, 0.3, 1); - border: 1px solid transparent; - font-family: var(--font-sans); - text-decoration: none; - box-shadow: - 0 2px 8px rgba(0, 0, 0, 0.25), - var(--bevel-btn); -} - -.btn:active { - transform: translateY(1px) scale(0.98); -} - -.btn-icon { - font-size: 0.95rem; -} - -.btn-primary { - background: linear-gradient(180deg, rgba(59, 130, 246, 0.25) 0%, rgba(37, 99, 235, 0.15) 100%); - color: #93c5fa; - border-color: rgba(59, 130, 246, 0.35); -} - -.btn-primary:hover { - background: linear-gradient(180deg, rgba(59, 130, 246, 0.35) 0%, rgba(37, 99, 235, 0.25) 100%); - border-color: rgba(59, 130, 246, 0.55); - box-shadow: - 0 4px 14px rgba(59, 130, 246, 0.25), - var(--bevel-btn); -} - -.btn-secondary { - background: linear-gradient(180deg, rgba(16, 185, 129, 0.22) 0%, rgba(5, 150, 105, 0.14) 100%); - color: #6ee7b7; - border-color: rgba(16, 185, 129, 0.35); -} - -.btn-secondary:hover { - background: linear-gradient(180deg, rgba(16, 185, 129, 0.32) 0%, rgba(5, 150, 105, 0.22) 100%); - border-color: rgba(16, 185, 129, 0.55); - box-shadow: - 0 4px 14px rgba(16, 185, 129, 0.25), - var(--bevel-btn); -} - -.btn-warning { - background: linear-gradient(180deg, rgba(245, 158, 11, 0.2) 0%, rgba(217, 119, 6, 0.12) 100%); - color: #fcd34d; - border-color: rgba(245, 158, 11, 0.35); -} - -.btn-warning:hover { - background: linear-gradient(180deg, rgba(245, 158, 11, 0.3) 0%, rgba(217, 119, 6, 0.2) 100%); - border-color: rgba(245, 158, 11, 0.55); -} - -.btn-warning.halted { - background: linear-gradient(180deg, #ef4444 0%, #dc2626 100%); - color: #ffffff; - border-color: rgba(255, 255, 255, 0.3); - box-shadow: - 0 4px 16px rgba(239, 68, 68, 0.4), - var(--bevel-btn); -} - -/* ========================================================== - 2. COCKPIT LAYOUT & APPLE SMOKED GLASS CARDS - ========================================================== */ -.cockpit-container { - position: relative; - z-index: 1; - flex: 1; - max-width: 1480px; - width: 100%; - margin: 0 auto; - padding: 2rem 2.25rem; - display: flex; - flex-direction: column; - gap: 2rem; -} - -.cockpit-main-layout { - display: grid; - grid-template-columns: 57fr 43fr; - gap: 2rem; -} - -.column-executive, -.column-agents { - display: flex; - flex-direction: column; - gap: 2rem; -} - -.cockpit-bottom-layout { - display: flex; - flex-direction: column; - gap: 2rem; -} - -.card { - background: linear-gradient(145deg, rgba(24, 29, 40, 0.82) 0%, rgba(14, 17, 24, 0.9) 100%); - backdrop-filter: blur(28px) saturate(190%); - -webkit-backdrop-filter: blur(28px) saturate(190%); - border: 1px solid var(--border-subtle); - box-shadow: - 0 10px 32px -4px rgba(0, 0, 0, 0.5), - var(--bevel-top); - border-radius: var(--radius-xl); - padding: 1.6rem 1.85rem; - display: flex; - flex-direction: column; - transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1); -} - -.card:hover { - border-color: var(--border-hover); - box-shadow: - 0 16px 44px -4px rgba(0, 0, 0, 0.65), - var(--bevel-top); - transform: translateY(-1.5px); -} - -.card-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 1.35rem; - gap: 1rem; -} - -.header-title-group { - display: flex; - flex-direction: column; - gap: 0.25rem; -} - -.header-title-group h2 { - font-size: 1.25rem; - font-weight: 700; - letter-spacing: -0.025em; - color: #ffffff; -} - -.section-tag { - font-size: 0.76rem; - font-weight: 700; - letter-spacing: 0.08em; - color: var(--accent-cyan); - text-transform: uppercase; -} - -/* Badges */ -.badge { - font-size: 0.78rem; - font-weight: 600; - padding: 0.3rem 0.8rem; - border-radius: var(--radius-pill); - letter-spacing: 0.02em; - box-shadow: var(--bevel-top); -} - -.badge-amber { - background: rgba(245, 158, 11, 0.18); - color: #fcd34d; - border: 1px solid rgba(245, 158, 11, 0.35); -} - -.badge-emerald { - background: rgba(16, 185, 129, 0.18); - color: #6ee7b7; - border: 1px solid rgba(16, 185, 129, 0.35); -} - -.badge-cyan { - background: rgba(56, 189, 248, 0.18); - color: #7dd3fc; - border: 1px solid rgba(56, 189, 248, 0.35); -} - -.badge-purple { - background: rgba(168, 85, 247, 0.18); - color: #d8b4fe; - border: 1px solid rgba(168, 85, 247, 0.35); -} - -/* ========================================================== - 3. ZONE 1: EXECUTIVE INBOX & PENDING APPROVALS - ========================================================== */ -.approvals-list { - display: flex; - flex-direction: column; - gap: 1.15rem; -} - -.approval-item { - background: rgba(255, 255, 255, 0.035); - border: 1px solid rgba(245, 158, 11, 0.3); - box-shadow: - 0 4px 16px rgba(0, 0, 0, 0.25), - var(--bevel-top); - border-radius: var(--radius-lg); - padding: 1.35rem 1.45rem; - display: flex; - flex-direction: column; - gap: 0.95rem; - transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); -} - -.approval-item:hover { - background: rgba(255, 255, 255, 0.055); - border-color: rgba(245, 158, 11, 0.55); - transform: translateY(-1px); -} - -.approval-meta { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 0.82rem; - flex-wrap: wrap; - gap: 0.5rem; -} - -.approval-type-tag { - background: rgba(245, 158, 11, 0.22); - color: #fbbf24; - padding: 0.22rem 0.65rem; - border-radius: var(--radius-sm); - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.05em; - font-size: 0.74rem; -} - -.approval-author { - color: var(--text-muted); - font-weight: 600; -} - -.approval-time { - color: var(--text-dim); -} - -.approval-title { - font-size: 1.15rem; - font-weight: 700; - color: #ffffff; - letter-spacing: -0.015em; -} - -.approval-desc { - font-size: 0.94rem; - color: var(--text-secondary); - line-height: 1.6; -} - -.approval-metrics { - display: flex; - gap: 1.5rem; - font-size: 0.88rem; - padding: 0.65rem 1rem; - background: rgba(0, 0, 0, 0.35); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - color: var(--text-muted); -} - -.approval-metrics strong { - color: #ffffff; - font-weight: 600; -} - -.approval-actions { - display: flex; - gap: 0.75rem; - margin-top: 0.35rem; - flex-wrap: wrap; -} - -.btn-approve { - background: linear-gradient(180deg, var(--accent-emerald) 0%, var(--accent-emerald-deep) 100%); - color: #ffffff; - border: 1px solid rgba(255, 255, 255, 0.25); - box-shadow: - 0 2px 10px rgba(16, 185, 129, 0.35), - var(--bevel-btn); - padding: 0.55rem 1.25rem; - border-radius: var(--radius-pill); - font-size: 0.9rem; - font-weight: 600; - cursor: pointer; - transition: all 0.18s ease; - font-family: var(--font-sans); -} - -.btn-approve:hover { - filter: brightness(1.1); - box-shadow: - 0 4px 16px rgba(16, 185, 129, 0.45), - var(--bevel-btn); -} - -.btn-approve:active { - transform: translateY(1px) scale(0.98); -} - -.btn-veto { - background: linear-gradient(180deg, rgba(239, 68, 68, 0.18) 0%, rgba(220, 38, 38, 0.1) 100%); - color: #fca5a5; - border: 1px solid rgba(239, 68, 68, 0.35); - padding: 0.55rem 1.15rem; - border-radius: var(--radius-pill); - font-size: 0.9rem; - font-weight: 600; - cursor: pointer; - transition: all 0.18s ease; - font-family: var(--font-sans); -} - -.btn-veto:hover { - background: linear-gradient(180deg, rgba(239, 68, 68, 0.28) 0%, rgba(220, 38, 38, 0.18) 100%); - border-color: rgba(239, 68, 68, 0.55); -} - -.btn-discuss { - background: linear-gradient(180deg, rgba(59, 130, 246, 0.18) 0%, rgba(37, 99, 235, 0.1) 100%); - color: #93c5fd; - border: 1px solid rgba(59, 130, 246, 0.3); - padding: 0.55rem 1.15rem; - border-radius: var(--radius-pill); - font-size: 0.9rem; - font-weight: 600; - cursor: pointer; - transition: all 0.18s ease; - font-family: var(--font-sans); -} - -.btn-discuss:hover { - background: linear-gradient(180deg, rgba(59, 130, 246, 0.28) 0%, rgba(37, 99, 235, 0.18) 100%); - border-color: rgba(59, 130, 246, 0.5); - color: #ffffff; -} - -/* ========================================================== - 4. ZONE 1: OPERATIONS JOURNAL (LETZTE 24h) - ========================================================== */ -.journal-timeline { - display: flex; - flex-direction: column; - gap: 0.9rem; -} - -.journal-entry { - display: flex; - gap: 1.1rem; - align-items: flex-start; - padding: 0.95rem 1.15rem; - background: rgba(255, 255, 255, 0.03); - border: 1px solid var(--border-subtle); - box-shadow: - 0 2px 8px rgba(0, 0, 0, 0.2), - var(--bevel-top); - border-radius: var(--radius-md); - font-size: 0.92rem; - transition: all 0.15s ease; -} - -.journal-entry:hover { - background: rgba(255, 255, 255, 0.05); - border-color: var(--border-hover); -} - -.entry-time { - font-family: var(--font-mono); - font-size: 0.78rem; - color: var(--text-dim); - white-space: nowrap; - padding-top: 0.15rem; - font-weight: 600; -} - -.entry-content { - flex: 1; -} - -.entry-header { - display: flex; - align-items: center; - gap: 0.65rem; - margin-bottom: 0.3rem; - flex-wrap: wrap; -} - -.entry-agent-chip { - font-size: 0.75rem; - font-weight: 700; - background: rgba(255, 255, 255, 0.08); - padding: 0.15rem 0.55rem; - border-radius: var(--radius-sm); - color: var(--text-secondary); -} - -.entry-title { - font-weight: 700; - color: #ffffff; - font-size: 0.96rem; -} - -.entry-detail { - color: var(--text-muted); - font-size: 0.88rem; - line-height: 1.55; -} - -/* ========================================================== - 5. ZONE 2: CTO DIRECTIVE & OPERATIONS BOARD - ========================================================== */ -.cto-body { - display: flex; - flex-direction: column; - gap: 1.25rem; -} - -.cto-profile { - display: flex; - align-items: center; - gap: 1rem; -} - -.cto-avatar { - width: 52px; - height: 52px; - border-radius: var(--radius-md); - background: linear-gradient(135deg, rgba(168, 85, 247, 0.3) 0%, rgba(126, 34, 206, 0.2) 100%); - color: #e9d5ff; - display: grid; - place-items: center; - font-weight: 800; - font-size: 1.2rem; - border: 1.5px solid rgba(168, 85, 247, 0.45); - box-shadow: - 0 4px 16px rgba(168, 85, 247, 0.25), - var(--bevel-top); -} - -.cto-name { - font-weight: 700; - font-size: 1.15rem; - color: #ffffff; - display: block; -} - -.cto-role { - font-size: 0.82rem; - color: var(--text-muted); - font-weight: 500; -} - -.cto-quote-box { - background: linear-gradient(135deg, rgba(168, 85, 247, 0.08) 0%, rgba(99, 102, 241, 0.04) 100%); - border: 1px solid rgba(168, 85, 247, 0.25); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08); - padding: 1.2rem 1.4rem; - border-radius: var(--radius-md); -} - -.cto-quote { - font-style: italic; - font-size: 0.96rem; - color: var(--text-secondary); - line-height: 1.65; -} - -.cto-actions { - display: flex; - justify-content: space-between; - align-items: center; - flex-wrap: wrap; - gap: 0.85rem; -} - -.btn-inline { - padding: 0.5rem 1.15rem; - border-radius: var(--radius-pill); - font-size: 0.88rem; - font-weight: 600; - cursor: pointer; - border: 1px solid transparent; - font-family: var(--font-sans); - transition: all 0.18s ease; - box-shadow: var(--bevel-btn); -} - -.btn-purple { - background: linear-gradient(180deg, rgba(168, 85, 247, 0.25) 0%, rgba(126, 34, 206, 0.15) 100%); - color: #e9d5ff; - border-color: rgba(168, 85, 247, 0.4); -} - -.btn-purple:hover { - background: linear-gradient(180deg, rgba(168, 85, 247, 0.35) 0%, rgba(126, 34, 206, 0.25) 100%); - border-color: rgba(168, 85, 247, 0.6); - box-shadow: - 0 4px 16px rgba(168, 85, 247, 0.3), - var(--bevel-btn); -} - -.authority-pills { - display: flex; - gap: 0.5rem; -} - -.auth-pill { - font-size: 0.76rem; - font-weight: 600; - color: var(--text-dim); - background: rgba(255, 255, 255, 0.04); - border: 1px solid var(--border-subtle); - padding: 0.25rem 0.6rem; - border-radius: var(--radius-sm); -} - -/* Agent Operations Board (5 Agents) */ -.operations-hint { - font-size: 0.85rem; - color: var(--text-muted); - margin-bottom: 1rem; -} - -.agent-roster { - display: flex; - flex-direction: column; - gap: 0.85rem; -} - -.roster-item { - display: flex; - align-items: center; - gap: 1.1rem; - padding: 0.95rem 1.15rem; - background: rgba(255, 255, 255, 0.03); - border: 1px solid var(--border-subtle); - box-shadow: - 0 2px 8px rgba(0, 0, 0, 0.2), - var(--bevel-top); - border-radius: var(--radius-md); - cursor: pointer; - transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); -} - -.roster-item:hover { - background: rgba(255, 255, 255, 0.065); - border-color: var(--border-hover); - transform: translateX(4px); - box-shadow: - 0 6px 20px rgba(0, 0, 0, 0.3), - var(--bevel-top); -} - -.roster-item:active { - transform: scale(0.985); -} - -.agent-avatar { - width: 44px; - height: 44px; - border-radius: var(--radius-md); - display: grid; - place-items: center; - font-weight: 800; - font-size: 0.95rem; - font-family: var(--font-sans); - flex-shrink: 0; - box-shadow: var(--bevel-top); -} - -.agent-avatar.architect { - background: linear-gradient(135deg, rgba(59, 130, 246, 0.25) 0%, rgba(37, 99, 235, 0.15) 100%); - color: #93c5fd; - border: 1px solid rgba(59, 130, 246, 0.4); -} -.agent-avatar.security { - background: linear-gradient(135deg, rgba(16, 185, 129, 0.25) 0%, rgba(5, 150, 105, 0.15) 100%); - color: #6ee7b7; - border: 1px solid rgba(16, 185, 129, 0.4); -} -.agent-avatar.devops { - background: linear-gradient(135deg, rgba(245, 158, 11, 0.25) 0%, rgba(217, 119, 6, 0.15) 100%); - color: #fcd34d; - border: 1px solid rgba(245, 158, 11, 0.4); -} -.agent-avatar.quant { - background: linear-gradient(135deg, rgba(168, 85, 247, 0.25) 0%, rgba(126, 34, 206, 0.15) 100%); - color: #d8b4fe; - border: 1px solid rgba(168, 85, 247, 0.4); -} -.agent-avatar.ux { - background: linear-gradient(135deg, rgba(249, 115, 22, 0.25) 0%, rgba(194, 65, 12, 0.15) 100%); - color: #fdba74; - border: 1px solid rgba(249, 115, 22, 0.4); -} - -.agent-meta { - flex: 1; - display: flex; - flex-direction: column; - gap: 0.15rem; -} - -.agent-name { - font-weight: 700; - font-size: 1.02rem; - color: #ffffff; -} - -.agent-role { - font-size: 0.85rem; - color: var(--text-secondary); -} - -.agent-node { - font-family: var(--font-mono); - font-size: 0.74rem; - color: var(--text-dim); -} - -.status-dot { - width: 9px; - height: 9px; - border-radius: 50%; - display: block; -} - -.status-dot.green { - background: var(--accent-emerald); - box-shadow: 0 0 8px var(--accent-emerald); -} -.status-dot.purple { - background: var(--accent-purple); - box-shadow: 0 0 8px var(--accent-purple); -} -.status-dot.orange { - background: var(--accent-orange); - box-shadow: 0 0 8px var(--accent-orange); -} - -/* ========================================================== - 6. ZONE 3 & 4: CLUSTER & APEX TRADING - ========================================================== */ -.cluster-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 1.25rem; -} - -.cluster-node { - background: rgba(255, 255, 255, 0.03); - border: 1px solid var(--border-subtle); - box-shadow: - 0 2px 8px rgba(0, 0, 0, 0.2), - var(--bevel-top); - border-radius: var(--radius-md); - padding: 1.15rem 1.25rem; - display: flex; - flex-direction: column; - gap: 0.75rem; - transition: all 0.2s ease; -} - -.cluster-node:hover { - background: rgba(255, 255, 255, 0.055); - border-color: var(--border-hover); -} - -.node-header { - display: flex; - align-items: center; - gap: 0.65rem; -} - -.node-status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--accent-emerald); - box-shadow: 0 0 8px var(--accent-emerald); -} - -.node-status-dot.amber { - background: var(--accent-amber); - box-shadow: 0 0 8px var(--accent-amber); -} - -.node-name { - font-weight: 700; - font-size: 0.98rem; - color: #ffffff; -} - -.node-type { - font-family: var(--font-mono); - font-size: 0.72rem; - color: var(--text-muted); - background: rgba(255, 255, 255, 0.06); - padding: 0.15rem 0.5rem; - border-radius: var(--radius-sm); - margin-left: auto; - font-weight: 600; -} - -.node-details { - display: flex; - flex-direction: column; - gap: 0.4rem; - font-size: 0.84rem; -} - -.detail-row { - display: flex; - justify-content: space-between; - gap: 0.5rem; - color: var(--text-muted); -} - -.code-val { - font-family: var(--font-mono); - color: var(--text-secondary); - font-weight: 500; -} - -.success-val { - color: var(--accent-emerald); - font-weight: 600; -} - -/* Header Action Cluster with Segmented Control */ -.header-action-cluster { - display: flex; - align-items: center; - gap: 0.85rem; - flex-wrap: wrap; -} - -.mode-segmented-control { - display: inline-flex; - align-items: center; - background: rgba(0, 0, 0, 0.45); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-pill); - padding: 0.25rem; - box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.4); - gap: 0.25rem; -} - -.segment-btn { - display: inline-flex; - align-items: center; - gap: 0.45rem; - padding: 0.4rem 0.95rem; - border-radius: var(--radius-pill); - font-size: 0.84rem; - font-weight: 600; - color: var(--text-muted); - background: transparent; - border: 1px solid transparent; - cursor: pointer; - transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); - font-family: var(--font-sans); -} - -.segment-btn:hover { - color: var(--text-main); - background: rgba(255, 255, 255, 0.05); -} - -.segment-btn.active { - background: linear-gradient(180deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.06) 100%); - color: #ffffff; - border: 1px solid rgba(255, 255, 255, 0.18); - box-shadow: - 0 2px 8px rgba(0, 0, 0, 0.35), - var(--bevel-top); -} - -.segment-dot { - width: 7px; - height: 7px; - border-radius: 50%; - display: inline-block; -} - -.paper-dot { - background: var(--accent-cyan); - box-shadow: 0 0 6px var(--accent-cyan); -} - -.live-dot { - background: var(--accent-emerald); - box-shadow: 0 0 8px var(--accent-emerald); - animation: pulse-ring 2s infinite; -} - -/* Trading Metrics */ -.trading-metrics-grid { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 1.25rem; -} - -.tmetric-card { - background: rgba(255, 255, 255, 0.03); - border: 1px solid var(--border-subtle); - box-shadow: - 0 2px 8px rgba(0, 0, 0, 0.2), - var(--bevel-top); - border-radius: var(--radius-md); - padding: 1.15rem 1.25rem; - display: flex; - flex-direction: column; - transition: all 0.2s ease; -} - -.tmetric-card:hover { - background: rgba(255, 255, 255, 0.055); - border-color: var(--border-hover); -} - -.tmetric-label { - font-size: 0.8rem; - font-weight: 600; - color: var(--text-muted); - margin-bottom: 0.35rem; - text-transform: uppercase; - letter-spacing: 0.04em; -} - -.tmetric-val { - font-family: var(--font-mono); - font-size: 1.55rem; - font-weight: 800; - color: #ffffff; - letter-spacing: -0.02em; -} - -.tmetric-val.positive { - color: var(--accent-emerald); - text-shadow: 0 0 16px rgba(16, 185, 129, 0.3); -} - -.tmetric-val.cyan-text { - color: var(--accent-cyan); -} - -.tmetric-sub { - font-size: 0.78rem; - color: var(--text-dim); - margin-top: 0.25rem; - font-weight: 500; -} - -/* ========================================================== - 7. LIVE TELEMETRY CONSOLE - ========================================================== */ -.card-console { - transition: all 0.25s ease; -} - -.card-console.collapsed .terminal-container { - display: none; -} - -.console-actions { - display: flex; - gap: 0.95rem; -} - -.btn-text { - background: none; - border: none; - color: var(--text-muted); - font-family: var(--font-sans); - font-size: 0.85rem; - font-weight: 600; - cursor: pointer; - padding: 0.2rem 0.5rem; - border-radius: var(--radius-sm); - transition: all 0.15s ease; -} - -.btn-text:hover { - color: #ffffff; - background: rgba(255, 255, 255, 0.05); -} - -.terminal-container { - background: #080a0e; - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.6); - padding: 1.15rem; - height: 220px; - overflow-y: auto; - font-family: var(--font-mono); - font-size: 0.85rem; - display: flex; - flex-direction: column; - gap: 0.4rem; -} - -.log-line { - line-height: 1.5; - word-break: break-word; -} - -.log-line.info { - color: #94a3b8; -} -.log-line.success { - color: #34d399; -} -.log-line.warning { - color: #fbbf24; -} -.log-line.error { - color: #f87171; -} -.log-line.highlight { - color: #c084fc; -} - -/* ========================================================== - 8. FOOTER - ========================================================== */ -.app-footer { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1.1rem 2.25rem; - border-top: 1px solid var(--border-subtle); - font-size: 0.82rem; - color: var(--text-dim); - background: rgba(10, 12, 16, 0.95); -} - -/* ========================================================== - 9. RESPONSIVE QUERIES - ========================================================== */ -@media (max-width: 1080px) { - .cockpit-main-layout { - grid-template-columns: 1fr; - } - .cluster-grid, - .trading-metrics-grid { - grid-template-columns: 1fr 1fr; - } -} - -@media (max-width: 680px) { - .executive-header { - flex-direction: column; - align-items: stretch; - padding: 1rem; - } - .header-left, - .header-center, - .header-right { - justify-content: space-between; - flex-wrap: wrap; - } - .cluster-grid, - .trading-metrics-grid { - grid-template-columns: 1fr; - } - .cockpit-container { - padding: 1rem; - } -} diff --git a/evoo.config.json b/evoo.config.json deleted file mode 100644 index d5c732e..0000000 --- a/evoo.config.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "evOo Autonomous Agent Platform", - "version": "2.0.0", - "permissions": { - "network": { - "allowed": true, - "scope": "*", - "description": "Full access to external and internal network endpoints" - }, - "filesystem": { - "allowed": true, - "scope": "*", - "description": "Full access to read, write, and execute across local filesystem" - }, - "sourcecode": { - "allowed": true, - "scope": "*", - "description": "Full authority to inspect, edit, refactor, and commit source code" - }, - "devices": { - "allowed": true, - "scope": "*", - "description": "Full access to connected local devices, interfaces, and tools" - }, - "agent_casting": { - "allowed": true, - "max_subagents": 20, - "description": "Authority to dynamically instantiate and manage sub-agent hierarchies" - }, - "pre_approved_actions": [ - "file_read", - "file_write", - "command_execution", - "subagent_spawn", - "self_refactor", - "network_request" - ] - }, - "execution_policy": { - "mode": "autonomous_action_first", - "multi_task_sequence": true, - "ask_user_policy": "on_negotiation_or_critical_boundary_only", - "block_on_routine_tasks": false, - "auto_proceed": true - }, - "self_improvement": { - "enabled": true, - "auto_eval_logs": true, - "auto_refactor_prompts": true, - "continuous_learning": true, - "feedback_loop": "autonomous_iteration" - }, - "cto_agent": { - "name": "Steve Jobs", - "role": "Chief Technology Officer (CTO)", - "authority": "full_executive", - "casting_policy": "cast_teams_on_demand", - "negotiation_protocol": "consensus_and_negotiation_basis" - } -} diff --git a/orchestrator/agent_runner.py b/orchestrator/agent_runner.py deleted file mode 100644 index edf91a0..0000000 --- a/orchestrator/agent_runner.py +++ /dev/null @@ -1,365 +0,0 @@ -#!/usr/bin/env python3 -""" -evOo Agent Orchestrator & Dispatcher -Executive Lead: CTO Steve Jobs -"Realität im Dateisystem vor Halluzination im Kopf." - -Manages dynamic execution of the 4 Casted Agents: -- Code Architect -- Security & QA Auditor -- DevOps Engineer -- Apex Quant Specialist -""" - -import os -import sys -import json -import time -import argparse -import subprocess -import urllib.request -import urllib.error -from pathlib import Path -from typing import Dict, Any, List, Optional - -ROOT_DIR = Path(__file__).resolve().parent.parent -CONFIG_PATH = Path(__file__).resolve().parent / "agents.json" - -class AgentOrchestrator: - def __init__(self, config_path: Path = CONFIG_PATH): - if config_path.exists(): - with open(config_path, "r", encoding="utf-8") as f: - self.config = json.load(f) - else: - self.config = {"agents": {}} - - def list_agents(self) -> Dict[str, Any]: - """Returns catalog of casted agents and their capabilities.""" - return self.config.get("agents", {}) - - def probe_compute_cluster(self) -> Dict[str, Any]: - """Probes local and LAN compute nodes (LM Studio, Ollama, remote Macs).""" - cluster_info = { - "strategy": "hybrid_local_and_lan", - "nodes": { - "local_lmstudio": {"status": "OFFLINE", "models": []}, - "local_ollama": {"status": "OFFLINE", "models": []}, - "mac_mini": {"status": "CONFIGURED_STANDBY", "endpoint": "http://mac-mini.local:1234/v1"}, - "macbook_air": {"status": "CONFIGURED_STANDBY", "endpoint": "http://macbook-air.local:1234/v1"} - } - } - try: - req = urllib.request.urlopen("http://127.0.0.1:1234/v1/models", timeout=0.6) - if req.getcode() == 200: - data = json.loads(req.read().decode('utf-8')) - models = [m.get("id") or m.get("name") for m in data.get("data", [])] - cluster_info["nodes"]["local_lmstudio"] = { - "status": "ONLINE (Apple Silicon Metal)", - "models": models[:2] - } - except Exception: - pass - - try: - req = urllib.request.urlopen("http://127.0.0.1:11434/api/tags", timeout=0.6) - if req.getcode() == 200: - data = json.loads(req.read().decode('utf-8')) - models = [m.get("name") for m in data.get("models", [])] - cluster_info["nodes"]["local_ollama"] = { - "status": "ONLINE", - "models": models[:2] - } - except Exception: - pass - - return cluster_info - - def resolve_compute_routing(self, preferred_node: str) -> Dict[str, Any]: - """Resolves target compute node with smart automatic failover.""" - cluster_info = self.probe_compute_cluster() - nodes = cluster_info.get("nodes", {}) - - # 1. Check preferred remote node - if preferred_node in nodes and "ONLINE" in nodes[preferred_node].get("status", ""): - return { - "node": preferred_node, - "status": "ONLINE", - "route": "DIRECT_LAN_NODE", - "model": nodes[preferred_node].get("models", ["default"])[0] if nodes[preferred_node].get("models") else "lan_default" - } - - # 2. Failover to local LM Studio - if "ONLINE" in nodes.get("local_lmstudio", {}).get("status", ""): - models = nodes["local_lmstudio"].get("models", []) - return { - "node": "primary_local (LM Studio)", - "status": "ACTIVE_FAILOVER", - "route": "LOCAL_METAL_ACCELERATED", - "model": models[0] if models else "qwen3-coder-30b-a3b", - "note": f"Preferred node '{preferred_node}' im Standby. Nahtlos auf Host Metal-GPU geroutet." - } - - # 3. Failover to local Ollama - if "ONLINE" in nodes.get("local_ollama", {}).get("status", ""): - models = nodes["local_ollama"].get("models", []) - return { - "node": "primary_local (Ollama)", - "status": "ACTIVE_FAILOVER", - "route": "LOCAL_OLLAMA_FALLBACK", - "model": models[0] if models else "gemma4:latest", - "note": "Auf lokalen Ollama-Knoten umgeleitet." - } - - # 4. Deterministic safety net - return { - "node": "deterministic_guard", - "status": "DETERMINISTIC_ACTIVE", - "route": "ZERO_PHANTOM_SAFETY_NET", - "model": "internal_rules_engine", - "note": "Deterministischer Sicherheitsmodus aktiv." - } - - def dispatch(self, agent_id: str, task: Optional[str] = None) -> Dict[str, Any]: - """Dispatches an action to a specified agent and returns physical results.""" - agents = self.list_agents() - if agent_id not in agents: - return { - "success": False, - "agent": agent_id, - "error": f"Unknown agent '{agent_id}'. Available: {list(agents.keys())}" - } - - agent_meta = agents[agent_id] - assigned_task = task or agent_meta.get("tasks", ["default"])[0] - routing = self.resolve_compute_routing(agent_meta.get("preferred_node", "primary_local")) - - start_time = time.time() - - # Route to deterministic execution handlers - if agent_id == "architect": - result = self._run_architect(assigned_task) - elif agent_id == "qa": - result = self._run_qa(assigned_task) - elif agent_id == "devops": - result = self._run_devops(assigned_task) - elif agent_id == "quant": - result = self._run_quant(assigned_task) - elif agent_id == "ux": - result = self._run_ux(assigned_task) - else: - result = {"status": "NOOP", "message": "No handler defined"} - - duration = round(time.time() - start_time, 4) - return { - "success": result.get("success", True), - "agent_id": agent_id, - "agent_name": agent_meta.get("name"), - "task": assigned_task, - "compute_routing": routing, - "duration_seconds": duration, - "result": result, - "timestamp": time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime()) - } - - def _run_architect(self, task: str) -> Dict[str, Any]: - """Architect evaluates modular structure and filesystem boundaries.""" - modules = { - "platform": (ROOT_DIR / "evoo-platform").is_dir(), - "apex_trading": (ROOT_DIR / "apex-trading").is_dir(), - "orchestrator": (ROOT_DIR / "orchestrator").is_dir(), - "templates": (ROOT_DIR / "templates").is_dir(), - "architecture_doc": (ROOT_DIR / "ARCHITECTURE.md").is_file() - } - all_ok = all(modules.values()) - return { - "success": all_ok, - "modular_boundaries": modules, - "clean_architecture": "VERIFIED" if all_ok else "INCOMPLETE", - "findings": "All core pillars are orthogonally separated on disk." if all_ok else "Missing core directories." - } - - def _run_qa(self, task: str) -> Dict[str, Any]: - """QA Auditor runs physical integrity check and regression suite.""" - script = ROOT_DIR / "scripts" / "self_improve.py" - if not script.exists(): - return {"success": False, "error": "scripts/self_improve.py not found"} - - res = subprocess.run([sys.executable, str(script)], capture_output=True, text=True, cwd=str(ROOT_DIR)) - return { - "success": res.returncode == 0, - "exit_code": res.returncode, - "output": res.stdout.strip(), - "errors": res.stderr.strip() if res.returncode != 0 else None, - "zero_phantom_check": "PASS" if res.returncode == 0 else "FAIL" - } - - def _run_devops(self, task: str) -> Dict[str, Any]: - """DevOps audits server liveness, port 3088, and PID status.""" - pid_file = ROOT_DIR / ".evoo_server.pid" - pid = None - is_running = False - if pid_file.exists(): - try: - pid = int(pid_file.read_text().strip()) - # Check if process is alive (signal 0) - os.kill(pid, 0) - is_running = True - except (ValueError, OSError): - is_running = False - - http_status = None - latency_ms = None - try: - t0 = time.time() - req = urllib.request.urlopen("http://127.0.0.1:3088/health", timeout=1.5) - http_status = req.getcode() - latency_ms = round((time.time() - t0) * 1000, 2) - except Exception as e: - http_status = f"DOWN ({e.__class__.__name__})" - - return { - "success": is_running or (http_status == 200), - "pid": pid, - "process_active": is_running, - "port_3088_status": http_status, - "http_latency_ms": latency_ms, - "status": "HEALTHY" if (is_running and http_status == 200) else "DEGRADED" - } - - def _run_quant(self, task: str) -> Dict[str, Any]: - """Apex Quant Specialist runs trading engine tests and risk simulation.""" - test_file = ROOT_DIR / "apex-trading" / "test_trading.py" - if not test_file.exists(): - return {"success": False, "error": "apex-trading/test_trading.py missing"} - - res = subprocess.run([sys.executable, str(test_file)], capture_output=True, text=True, cwd=str(ROOT_DIR)) - - # Test simulated tick through engine - sim_result = {} - try: - sys_path_added = False - src_path = str(ROOT_DIR / "apex-trading" / "src") - if src_path not in sys.path: - sys.path.insert(0, src_path) - sys_path_added = True - - from engine.trader import ApexEngine - config_file = str(ROOT_DIR / "apex-trading" / "config" / "trading.config.json") - engine = ApexEngine(config_file) - - # Run tick with strong upward breakout (>1.5% above 20-period SMA) - prices = [100.0 + (i * 2.0) for i in range(25)] - tick = engine.process_tick("BTC/USDC", prices, [500] * 25) - sim_result = { - "simulation_status": tick.get("status"), - "action": tick.get("action"), - "guardrail_status": tick.get("risk_status") - } - except Exception as e: - sim_result = {"error": str(e)} - - # 1. Check if external trading-bot API is alive on port 8000 - trading_api_status = "OFFLINE" - live_api_metrics = None - try: - req = urllib.request.urlopen("http://127.0.0.1:8000/api/health", timeout=1.0) - if req.getcode() == 200: - trading_api_status = "ONLINE (http://127.0.0.1:8000)" - # Try reading live status - status_req = urllib.request.urlopen("http://127.0.0.1:8000/api/status", timeout=1.0) - live_api_metrics = json.loads(status_req.read().decode('utf-8')) - except Exception: - trading_api_status = "STANDBY (Independent Service Offline)" - - return { - "success": res.returncode == 0 and sim_result.get("simulation_status") == "EXECUTED", - "unit_tests_passing": res.returncode == 0, - "simulation": sim_result, - "trading_bot_api_bridge": { - "status": trading_api_status, - "architecture": "Microservice REST API (Port 8000)", - "live_metrics": live_api_metrics - }, - "circuit_breaker_enforced": True - } - - def _run_ux(self, task: str) -> Dict[str, Any]: - """UX Designer audits design system integrity, 3-pillar shell, and responsive tokens.""" - shell_path = ROOT_DIR / "apps" / "web" / "src" / "pages" / "Shell.tsx" - tokens_path = ROOT_DIR / "packages" / "ui-tokens" / "src" / "tokens.css" - platform_css = ROOT_DIR / "evoo-platform" / "styles.css" - - shell_exists = shell_path.exists() - has_3_pillars = False - if shell_exists: - content = shell_path.read_text(encoding="utf-8") - has_3_pillars = "activeNavPillar" in content and "heute" in content and "team" in content and "projekte" in content - - tokens_exist = tokens_path.exists() or platform_css.exists() - all_ok = shell_exists and has_3_pillars and tokens_exist - - return { - "success": all_ok, - "design_system_integrity": "VERIFIED" if all_ok else "DEGRADED", - "pillar_navigation": "ACTIVE (Heute, Team, Projekte)" if has_3_pillars else "INCOMPLETE", - "mobile_first_ergonomics": "PASS", - "token_library_present": tokens_exist, - "findings": "3-Pillar Navigation & Dark-Mode Design System 100% physisch verifiziert." if all_ok else "UI Komponenten oder Tokens unvollständig." - } - -def main(): - parser = argparse.ArgumentParser(description="evOo Agent Orchestrator") - parser.add_argument("--agent", choices=["architect", "qa", "devops", "quant", "ux", "all"], default="all") - parser.add_argument("--task", default=None, help="Specific task to run") - parser.add_argument("--cluster", action="store_true", help="Probe compute cluster nodes (LM Studio, Ollama, LAN Macs)") - parser.add_argument("--json", action="store_true", help="Output pure JSON") - args = parser.parse_args() - - orchestrator = AgentOrchestrator() - - if args.cluster: - cluster = orchestrator.probe_compute_cluster() - if args.json: - print(json.dumps(cluster, indent=2)) - else: - print("=" * 60) - print(" evOo Distributed Compute Cluster — Resource Telemetry") - print("=" * 60) - for node, info in cluster.get("nodes", {}).items(): - status = info.get("status") - models = ", ".join(info.get("models", [])) or "None loaded" - print(f"[{status}] Node: {node:<18} | Models: {models}") - print("=" * 60) - sys.exit(0) - - if args.agent == "all": - results = {} - all_passed = True - for agent_id in ["architect", "qa", "devops", "quant", "ux"]: - res = orchestrator.dispatch(agent_id, args.task) - results[agent_id] = res - if not res.get("success"): - all_passed = False - - if args.json: - print(json.dumps(results, indent=2)) - else: - print("=" * 60) - print(" evOo Casted Agent Network — Executive Dispatch") - print("=" * 60) - for aid, r in results.items(): - status = "✓ PASS" if r.get("success") else "✗ FAIL" - print(f"[{status}] Agent: {r.get('agent_name'):<25} ({r.get('duration_seconds')}s)") - print("=" * 60) - sys.exit(0 if all_passed else 1) - else: - res = orchestrator.dispatch(args.agent, args.task) - if args.json: - print(json.dumps(res, indent=2)) - else: - print(f"Agent: {res.get('agent_name')} | Success: {res.get('success')}") - print(json.dumps(res.get("result"), indent=2)) - sys.exit(0 if res.get("success") else 1) - -if __name__ == "__main__": - main() diff --git a/orchestrator/agents.json b/orchestrator/agents.json deleted file mode 100644 index f5dc431..0000000 --- a/orchestrator/agents.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "orchestrator_version": "2.1.0", - "ecosystem": "evOo Autonomous Agent Platform", - "lead_cto": "Steve Jobs", - "compute_cluster": { - "enabled": true, - "strategy": "smart_failover_hierarchical", - "failover_order": ["preferred_node", "local_lmstudio", "local_ollama", "deterministic_guard"], - "primary_local": { - "name": "evOo Host Machine (LM Studio / Metal)", - "lmstudio_url": "http://127.0.0.1:1234/v1", - "ollama_url": "http://127.0.0.1:11434/v1", - "active_models": ["qwen3-coder-30b-a3b", "gemma4:latest"], - "characteristics": "Direkte Apple Silicon Metal Inferenz, primärer Kontrollknoten" - }, - "distributed_nodes": { - "mac_mini": { - "name": "Mac mini (Strategic 24/7 Powerhouse)", - "host": "mac-mini.local", - "url": "http://mac-mini.local:1234/v1", - "recommended_models": ["qwen2.5:32b", "llama3.3:70b-q4", "deepseek-r1:14b"], - "assigned_roles": ["architect", "cto-steve-jobs"], - "characteristics": "Aktiv gekühlt (Lüfter), ideal für schwere 24/7 Deep-Think Modelle", - "thermal_profile": "unlimited" - }, - "macbook_air": { - "name": "MacBook Air (Rapid Lightweight Worker)", - "host": "macbook-air.local", - "url": "http://macbook-air.local:1234/v1", - "recommended_models": ["qwen2.5-coder:7b", "llama3.2:3b", "phi3.5:3.8b"], - "assigned_roles": ["qa", "devops"], - "characteristics": "Passiv gekühlt (lüfterlos), optimiert für 3B-7B Modelle ohne Hitze/Throttling", - "thermal_profile": "efficiency_optimized" - } - } - }, - "agents": { - "architect": { - "name": "Code Architect", - "slug": "code-architect", - "avatar": "CA", - "color": "var(--primary-glow)", - "role": "Moduldesign & Clean Architecture", - "scope": ["architecture", "filesystem", "modularity"], - "preferred_node": "mac_mini", - "fallback_node": "primary_local", - "target_model_class": "heavy_reasoning", - "description": "Plant orthogonale Modulgrenzen. Nutzt vorzugsweise die Rechenpower des Mac mini für tiefes architektonisches Reasoning.", - "tasks": ["audit_structure", "verify_dependencies"] - }, - "qa": { - "name": "Security & QA Auditor", - "slug": "qa-auditor", - "avatar": "QA", - "color": "var(--status-green)", - "role": "Integritätsprüfung & Zero-Phantom", - "scope": ["security", "lint", "tests", "zero_phantom"], - "preferred_node": "macbook_air", - "fallback_node": "primary_local", - "target_model_class": "fast_coding", - "description": "Führt Sicherheits- und Syntaxaudits aus. Nutzt vorzugsweise das MacBook Air für schnelle parallele 7B-Checks.", - "tasks": ["verify_all", "check_security", "check_phantoms"] - }, - "devops": { - "name": "DevOps Engineer", - "slug": "devops-engineer", - "avatar": "DO", - "color": "var(--status-blue)", - "role": "Hot-Reload & Service Liveness", - "scope": ["processes", "ports", "deployment", "liveness"], - "preferred_node": "primary_local", - "fallback_node": "primary_local", - "target_model_class": "system_ops", - "description": "Verwaltet Prozesse, Ports und Hot-Reloads direkt auf dem Host.", - "tasks": ["check_liveness", "audit_resources", "restart_service"] - }, - "quant": { - "name": "Apex Quant Specialist", - "slug": "apex-quant", - "avatar": "AP", - "color": "var(--accent-purple)", - "role": "Algorithmic Risk & High-Speed Execution", - "scope": ["trading", "risk_guard", "momentum", "quant"], - "preferred_node": "primary_local", - "fallback_node": "primary_local", - "target_model_class": "deterministic_math", - "description": "Überwacht Trading-Guardrails und die HTTP-Bridge zum autonomen trading-bot auf Port 8000.", - "tasks": ["test_guardrails", "run_tick_simulation"] - }, - "ux": { - "name": "UX Designer", - "slug": "ux-designer", - "avatar": "UX", - "color": "var(--accent-orange, #f97316)", - "role": "Lead Product & UX/UI Designer", - "scope": ["ui", "ux", "design_tokens", "mobile_ergonomics", "visual_hierarchy"], - "preferred_node": "macbook_air", - "fallback_node": "primary_local", - "target_model_class": "fast_coding", - "description": "Entwirft responsive Mobile-First Interfaces, quantitative Telemetrie-Dashboards und das konsistente Designsystem.", - "tasks": ["audit_ui", "verify_design_tokens"] - } - } -} diff --git a/packages/adapters/src/remote-mcp.test.ts b/packages/adapters/src/remote-mcp.test.ts index 859d4a0..8bbb7e8 100644 --- a/packages/adapters/src/remote-mcp.test.ts +++ b/packages/adapters/src/remote-mcp.test.ts @@ -51,14 +51,14 @@ describe("remote MCP URL policy", () => { assertSafeRemoteUrl(magicDns, async () => [{ address: "100.64.1.2", family: 4 as const }]), ).resolves.toEqual(new URL(magicDns)); - const safeLookup = createSafeLookup(async () => [{ address: "100.119.57.55", family: 4 }]); + const safeLookup = createSafeLookup(async () => [{ address: "100.100.100.100", family: 4 }]); const result = await new Promise<{ address: string; family?: number }>((resolve, reject) => { safeLookup("box.tail12345.ts.net", { family: 0, all: false }, (error, address, family) => { if (error) reject(error); else resolve({ address: String(address), family }); }); }); - expect(result).toEqual({ address: "100.119.57.55", family: 4 }); + expect(result).toEqual({ address: "100.100.100.100", family: 4 }); }); it("allows the IPv6 half of a MagicDNS answer", async () => { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1ddff54..0b9a281 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -45,7 +45,6 @@ export * from "./text-direction.js"; export * from "./thread-message-updates.js"; export * from "./thread-subscription.js"; export * from "./tool-activity.js"; -export * from "./trading-bot-status.js"; export * from "./voice-chat-command.js"; export * from "./voice-labels.js"; export * from "./workspace-focus.js"; diff --git a/packages/core/src/trading-bot-status.test.ts b/packages/core/src/trading-bot-status.test.ts deleted file mode 100644 index 38f0438..0000000 --- a/packages/core/src/trading-bot-status.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - coerceTradingBotStatusView, - interpretTradingBotProxyResponse, - mapTradingBotStatus, - parseTradingBotOrigin, - resolveTradingBotOrigin, - sanitizeTradingBotError, - tradingBotBackoffMs, - tradingBotStatusUrl, -} from "./trading-bot-status.js"; - -describe("parseTradingBotOrigin", () => { - it("treats an empty value as unset and keeps the origin without a path", () => { - expect(parseTradingBotOrigin(undefined)).toEqual({ kind: "unset" }); - expect(parseTradingBotOrigin(" ")).toEqual({ kind: "unset" }); - expect(parseTradingBotOrigin(" http://127.0.0.1:8000/bot/ ")).toEqual({ - kind: "origin", - origin: "http://127.0.0.1:8000", - }); - expect(tradingBotStatusUrl("http://127.0.0.1:8000")).toBe( - "http://127.0.0.1:8000/bot/api/status", - ); - }); - - it("rejects credentials and non-http schemes without echoing the value", () => { - expect(parseTradingBotOrigin("http://user:secret@127.0.0.1:8000").kind).toBe("invalid"); - expect(parseTradingBotOrigin("file:///tmp/status").kind).toBe("invalid"); - expect(parseTradingBotOrigin("not a url").kind).toBe("invalid"); - expect(resolveTradingBotOrigin("")).toBeUndefined(); - expect(() => resolveTradingBotOrigin("javascript:alert(1)")).toThrow( - /^Invalid TRADING_BOT_URL$/, - ); - }); -}); - -describe("mapTradingBotStatus", () => { - it("maps live, paper, and dry-run badges without defaulting to live", () => { - expect(mapTradingBotStatus({ mode: "LIVE" })).toMatchObject({ mode: "live", kind: "live" }); - expect(mapTradingBotStatus({ badge: "DRY RUN" })).toMatchObject({ - mode: "paper", - kind: "dry_run", - }); - expect(mapTradingBotStatus({ mode: "Paper Sim" })).toMatchObject({ - mode: "paper", - kind: "paper", - }); - expect(mapTradingBotStatus({ dry_run: true })).toMatchObject({ kind: "dry_run" }); - expect(mapTradingBotStatus({ live: true })).toMatchObject({ kind: "live" }); - expect(mapTradingBotStatus({ status: "ok" })).toBeNull(); - expect(mapTradingBotStatus({})).toBeNull(); - expect(mapTradingBotStatus(null)).toBeNull(); - expect(mapTradingBotStatus({ live: false })).toBeNull(); - }); - - it("refuses a body that claims live and dry-run together", () => { - expect(mapTradingBotStatus({ mode: "live", dry_run: true })).toBeNull(); - expect(mapTradingBotStatus({ live: true, paper: true })).toBeNull(); - }); - - it("reads a nested mode and safe subtitle fields", () => { - expect( - mapTradingBotStatus({ - data: { mode: "DRY RUN", adapters: ["a", "b", "c"], last_error: "adapter timeout" }, - }), - ).toEqual({ - mode: "paper", - kind: "dry_run", - adapterCount: 3, - lastError: "adapter timeout", - }); - expect(mapTradingBotStatus({ mode: "paper", adapter_count: "4" })?.adapterCount).toBe(4); - }); - - it("drops secret-like errors and still reports the mode", () => { - expect( - mapTradingBotStatus({ mode: "paper", last_error: "Bearer sk-test-secret" })?.lastError, - ).toBeNull(); - expect( - mapTradingBotStatus({ mode: "live", error: "failed http://10.0.0.8/status" })?.lastError, - ).toBeNull(); - expect(sanitizeTradingBotError("ok")).toBeNull(); - const long = "x".repeat(120); - expect(sanitizeTradingBotError(long)?.endsWith("…")).toBe(true); - expect(sanitizeTradingBotError(long)?.length).toBe(80); - }); -}); - -describe("interpretTradingBotProxyResponse", () => { - it("keeps unconfigured, offline, unknown, and a live read distinct", () => { - expect(interpretTradingBotProxyResponse(200, { configured: false })).toEqual({ - state: "unconfigured", - }); - expect(interpretTradingBotProxyResponse(502, { configured: true, reachable: false })).toEqual({ - state: "offline", - }); - expect( - interpretTradingBotProxyResponse(200, { configured: true, reachable: true, status: null }), - ).toEqual({ state: "unknown" }); - expect(interpretTradingBotProxyResponse(401, { error: "Unauthorized" })).toEqual({ - state: "offline", - }); - expect( - interpretTradingBotProxyResponse(200, { - configured: true, - reachable: true, - status: { mode: "live", kind: "live", adapterCount: 1, lastError: null }, - }), - ).toEqual({ - state: "ready", - status: { mode: "live", kind: "live", adapterCount: 1, lastError: null }, - }); - }); - - it("strips an unsafe error from an otherwise valid proxy status", () => { - const snapshot = interpretTradingBotProxyResponse(200, { - configured: true, - reachable: true, - status: { - mode: "paper", - kind: "dry_run", - adapterCount: null, - lastError: "token=super-secret", - }, - }); - expect(snapshot).toEqual({ - state: "ready", - status: { mode: "paper", kind: "dry_run", adapterCount: null, lastError: null }, - }); - expect(coerceTradingBotStatusView({ mode: "live", kind: "paper" })).toBeNull(); - }); -}); - -describe("tradingBotBackoffMs", () => { - it("doubles the interval and then caps it", () => { - expect(tradingBotBackoffMs(20_000, 1, 120_000)).toBe(20_000); - expect(tradingBotBackoffMs(20_000, 2, 120_000)).toBe(40_000); - expect(tradingBotBackoffMs(20_000, 3, 120_000)).toBe(80_000); - expect(tradingBotBackoffMs(20_000, 4, 120_000)).toBe(120_000); - }); -}); diff --git a/packages/core/src/trading-bot-status.ts b/packages/core/src/trading-bot-status.ts deleted file mode 100644 index a282ec8..0000000 --- a/packages/core/src/trading-bot-status.ts +++ /dev/null @@ -1,297 +0,0 @@ -/** Shared Tradingbot status contract. The browser and the API proxy both use this. */ - -export const TRADING_BOT_STATUS_PATH = "/bot/api/status"; -export const TRADING_BOT_PROXY_PATH = "/api/trading-bot/status"; -export const TRADING_BOT_POLL_INTERVAL_MS = 20_000; -export const TRADING_BOT_POLL_MAX_BACKOFF_MS = 120_000; -export const TRADING_BOT_STATUS_TIMEOUT_MS = 5_000; -export const TRADING_BOT_STATUS_MAX_BYTES = 65_536; - -const MAX_ORIGIN_LENGTH = 200; -const MAX_ERROR_LENGTH = 80; -const MAX_ADAPTER_COUNT = 10_000; - -const LIVE_TOKENS = new Set(["live", "live_trading", "real", "production"]); -const PAPER_TOKENS = new Set(["paper", "paper_sim", "paper_trading", "simulation", "sim"]); -const DRY_RUN_TOKENS = new Set(["dry_run", "dryrun", "dry"]); - -const MODE_STRING_KEYS = [ - "mode", - "trading_mode", - "tradingMode", - "run_mode", - "runMode", - "execution_mode", - "executionMode", - "badge", - "status", -] as const; - -const NESTED_KEYS = ["data", "bot", "state", "result", "status"] as const; - -const ADAPTER_COUNT_KEYS = [ - "adapterCount", - "adapter_count", - "adaptersCount", - "adapters_count", - "n_adapters", -] as const; - -const ERROR_KEYS = [ - "lastError", - "last_error", - "last_error_message", - "errorMessage", - "error", -] as const; - -const EMPTY_ERRORS = new Set(["ok", "none", "null", "undefined", "n/a", "-"]); - -const UNSAFE_ERROR = /bearer\s|api[_-]?key|password|secret|token|authorization|cookie|https?:\/\//i; - -export type TradingBotKind = "live" | "paper" | "dry_run"; - -export type TradingBotStatusView = { - mode: "live" | "paper"; - kind: TradingBotKind; - adapterCount: number | null; - lastError: string | null; -}; - -export type TradingBotOrigin = - | { kind: "unset" } - | { kind: "invalid" } - | { kind: "origin"; origin: string }; - -export type TradingBotSnapshot = - | { state: "unconfigured" } - | { state: "offline" } - | { state: "unknown" } - | { state: "ready"; status: TradingBotStatusView }; - -/** Accept a bare http(s) origin. Credentials and other schemes are rejected. */ -export function parseTradingBotOrigin(value: string | undefined): TradingBotOrigin { - const trimmed = value?.trim() ?? ""; - if (!trimmed) return { kind: "unset" }; - if (trimmed.length > MAX_ORIGIN_LENGTH || /\s/.test(trimmed) || hasControlChar(trimmed)) { - return { kind: "invalid" }; - } - let url: URL; - try { - url = new URL(trimmed); - } catch { - return { kind: "invalid" }; - } - if (url.protocol !== "http:" && url.protocol !== "https:") return { kind: "invalid" }; - if (url.username || url.password || !url.hostname) return { kind: "invalid" }; - return { kind: "origin", origin: url.origin }; -} - -/** Throw when the operator set a value that must not be fetched. Unset stays unset. */ -export function resolveTradingBotOrigin(value: string | undefined): string | undefined { - const parsed = parseTradingBotOrigin(value); - if (parsed.kind === "invalid") throw new Error("Invalid TRADING_BOT_URL"); - return parsed.kind === "origin" ? parsed.origin : undefined; -} - -export function tradingBotStatusUrl(origin: string): string { - return `${origin}${TRADING_BOT_STATUS_PATH}`; -} - -export function tradingBotBackoffMs( - intervalMs: number, - failureCount: number, - maxBackoffMs: number, -): number { - const exponent = Math.min(Math.max(failureCount - 1, 0), 6); - return Math.min(intervalMs * 2 ** exponent, maxBackoffMs); -} - -/** Map a Tradingbot `/bot/api/status` body. Unknown or conflicting modes return null. */ -export function mapTradingBotStatus(body: unknown): TradingBotStatusView | null { - const record = asRecord(body); - if (!record) return null; - const kind = resolveKind(readSignals(record)); - if (!kind) return null; - return { - mode: kind === "live" ? "live" : "paper", - kind, - adapterCount: readAdapterCount(record), - lastError: readLastError(record), - }; -} - -/** Accept a proxy payload field. Drops unsafe error text instead of dropping the mode. */ -export function coerceTradingBotStatusView(value: unknown): TradingBotStatusView | null { - const record = asRecord(value); - if (!record) return null; - const { mode, kind } = record; - if (mode !== "live" && mode !== "paper") return null; - if (kind !== "live" && kind !== "paper" && kind !== "dry_run") return null; - if ((mode === "live") !== (kind === "live")) return null; - if (record.adapterCount !== null && parseCount(record.adapterCount) === null) return null; - if (record.lastError !== null && typeof record.lastError !== "string") return null; - return { - mode, - kind, - adapterCount: record.adapterCount === null ? null : parseCount(record.adapterCount), - lastError: record.lastError === null ? null : sanitizeTradingBotError(record.lastError), - }; -} - -export function interpretTradingBotProxyResponse( - status: number, - body: unknown, -): TradingBotSnapshot { - const record = asRecord(body); - if (status === 200 && record?.configured === false) return { state: "unconfigured" }; - if (record?.configured === true && record.reachable === false) return { state: "offline" }; - if (status === 200 && record?.configured === true && record.reachable === true) { - if (record.status == null) return { state: "unknown" }; - const view = coerceTradingBotStatusView(record.status); - return view ? { state: "ready", status: view } : { state: "unknown" }; - } - return { state: "offline" }; -} - -/** Keep a short operator-facing error. Drop secrets, URLs, and empty placeholders. */ -export function sanitizeTradingBotError(value: unknown): string | null { - if (typeof value !== "string") return null; - const cleaned = stripControlChars(value).replace(/\s+/g, " ").trim(); - if (!cleaned || EMPTY_ERRORS.has(cleaned.toLowerCase())) return null; - if (UNSAFE_ERROR.test(cleaned)) return null; - if (cleaned.length <= MAX_ERROR_LENGTH) return cleaned; - return `${cleaned.slice(0, MAX_ERROR_LENGTH - 1)}…`; -} - -function readSignals(record: Record): TradingBotKind[] { - const signals: TradingBotKind[] = []; - pushSignals(record, signals); - if (signals.length > 0) return signals; - for (const key of NESTED_KEYS) { - const nested = asRecord(record[key]); - if (nested) pushSignals(nested, signals); - } - return signals; -} - -function pushSignals(record: Record, signals: TradingBotKind[]): void { - for (const key of MODE_STRING_KEYS) { - const value = record[key]; - if (typeof value !== "string") continue; - const kind = classifyModeToken(value); - if (kind) signals.push(kind); - } - if (record.live === true || record.is_live === true || record.isLive === true) { - signals.push("live"); - } - if (record.dry_run === true || record.dryRun === true) signals.push("dry_run"); - if ( - record.paper === true || - record.is_paper === true || - record.isPaper === true || - record.paper_trading === true || - record.paperTrading === true - ) { - signals.push("paper"); - } -} - -function resolveKind(signals: TradingBotKind[]): TradingBotKind | null { - const unique = new Set(signals); - const live = unique.has("live"); - const paper = unique.has("paper"); - const dryRun = unique.has("dry_run"); - if (live && (paper || dryRun)) return null; - if (dryRun) return "dry_run"; - if (paper) return "paper"; - if (live) return "live"; - return null; -} - -function classifyModeToken(value: string): TradingBotKind | null { - const token = value - .trim() - .toLowerCase() - .replace(/[\s-]+/g, "_"); - if (LIVE_TOKENS.has(token)) return "live"; - if (DRY_RUN_TOKENS.has(token)) return "dry_run"; - if (PAPER_TOKENS.has(token)) return "paper"; - return null; -} - -function readAdapterCount(record: Record): number | null { - const found = readOptional(record, adapterCountIn); - return found === undefined ? null : found; -} - -function adapterCountIn(record: Record): number | null | undefined { - for (const key of ADAPTER_COUNT_KEYS) { - if (!Object.hasOwn(record, key)) continue; - return parseCount(record[key]); - } - if (Object.hasOwn(record, "adapters") && Array.isArray(record.adapters)) { - return parseCount(record.adapters.length); - } - return undefined; -} - -function readLastError(record: Record): string | null { - const found = readOptional(record, lastErrorIn); - return found === undefined ? null : found; -} - -function lastErrorIn(record: Record): string | null | undefined { - for (const key of ERROR_KEYS) { - if (!Object.hasOwn(record, key)) continue; - return sanitizeTradingBotError(record[key]); - } - return undefined; -} - -function readOptional( - record: Record, - read: (row: Record) => T | undefined, -): T | undefined { - const direct = read(record); - if (direct !== undefined) return direct; - for (const key of NESTED_KEYS) { - const nested = asRecord(record[key]); - if (!nested) continue; - const value = read(nested); - if (value !== undefined) return value; - } - return undefined; -} - -function parseCount(value: unknown): number | null { - if (typeof value === "string" && !/^\d+$/.test(value.trim())) return null; - const numeric = - typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN; - if (!Number.isInteger(numeric) || numeric < 0 || numeric > MAX_ADAPTER_COUNT) return null; - return numeric; -} - -function hasControlChar(value: string): boolean { - for (let index = 0; index < value.length; index += 1) { - const code = value.charCodeAt(index); - if (code < 32 || code === 127) return true; - } - return false; -} - -function stripControlChars(value: string): string { - let out = ""; - for (let index = 0; index < value.length; index += 1) { - const code = value.charCodeAt(index); - const char = value[index]; - if (char === undefined) continue; - out += code < 32 || code === 127 ? " " : char; - } - return out; -} - -function asRecord(value: unknown): Record | null { - if (!value || typeof value !== "object" || Array.isArray(value)) return null; - return value as Record; -} diff --git a/packages/db/create-user.ts b/packages/db/create-user.ts deleted file mode 100644 index 3a2e757..0000000 --- a/packages/db/create-user.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { createDb } from "./src/client"; - -process.env.DATABASE_URL = "postgres://rakazo:rakazodev@127.0.0.1:5433/rakazo"; -const { prisma: db } = createDb(process.env.DATABASE_URL, { applicationName: "check-users" }); - -const allUsers = await db.user.findMany({ - select: { id: true, email: true, name: true, createdAt: true, emailVerified: true }, - orderBy: { createdAt: "desc" }, -}); - -console.log("All users:", JSON.stringify(allUsers, null, 2)); - -// Check spaces -const allSpaces = await db.space.findMany({ - select: { id: true, name: true, ownerId: true, createdAt: true }, - orderBy: { createdAt: "desc" }, -}); - -console.log("\nAll spaces:", JSON.stringify(allSpaces, null, 2)); - -// Check space members -const allMembers = await db.spaceMember.findMany({ - select: { id: true, spaceId: true, userId: true, role: true }, -}); - -console.log("\nAll members:", JSON.stringify(allMembers, null, 2)); - -await db.$disconnect(); diff --git a/packages/db/query-users.mjs b/packages/db/query-users.mjs deleted file mode 100644 index 17c0f3b..0000000 --- a/packages/db/query-users.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import { createDb, createPool } from "./src/client.js"; - -const pool = createPool(process.env.DATABASE_URL, { applicationName: "query-users" }); -const db = createDb(pool); - -const users = await db.user.findMany({ - orderBy: { createdAt: "desc" }, - take: 10, - select: { email: true, name: true, createdAt: true }, -}); - -console.log(JSON.stringify(users, null, 2)); -await db.$disconnect(); diff --git a/packages/db/query-users.ts b/packages/db/query-users.ts deleted file mode 100644 index 0f55d41..0000000 --- a/packages/db/query-users.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createDb } from "./src/client"; - -// Override to port 5433 (Docker published port) -process.env.DATABASE_URL = "postgres://rakazo:rakazodev@127.0.0.1:5433/rakazo"; -const { prisma: db } = createDb(process.env.DATABASE_URL, { applicationName: "query-users" }); - -const users = await db.user.findMany({ - orderBy: { createdAt: "desc" }, - take: 10, - select: { email: true, name: true, createdAt: true }, -}); - -console.log(JSON.stringify(users, null, 2)); diff --git a/packages/db/setup-user.ts b/packages/db/setup-user.ts deleted file mode 100644 index 4184e42..0000000 --- a/packages/db/setup-user.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { randomUUID } from "node:crypto"; -import { createDb } from "./src/client"; - -process.env.DATABASE_URL = "postgres://rakazo:rakazodev@127.0.0.1:5433/rakazo"; -const { prisma: db } = createDb(process.env.DATABASE_URL, { applicationName: "setup-user" }); - -const user = await db.user.findUnique({ - where: { email: "ingo.wagner1303@gmail.com" }, - select: { id: true, name: true }, -}); - -if (!user) { - console.log("User nicht gefunden!"); - await db.$disconnect(); - process.exit(1); -} - -console.log("User:", user); - -// Step 1: Get or create Organization -let org = await db.organization.findUnique({ where: { slug: "ingo-workspace" } }); -if (!org) { - org = await db.organization.create({ - data: { - id: randomUUID(), - name: "Ingo's Workspace", - slug: "ingo-workspace", - createdAt: new Date(), - }, - select: { id: true, name: true, slug: true }, - }); - console.log("Organization created:", org); -} else { - console.log("Organization exists:", org); -} - -// Step 2: Get or create Organization Member -let member = await db.member.findUnique({ - where: { - organizationId_userId: { organizationId: org.id, userId: user.id }, - }, -}); -if (!member) { - member = await db.member.create({ - data: { - id: randomUUID(), - userId: user.id, - organizationId: org.id, - role: "owner", - createdAt: new Date(), - }, - }); - console.log("Organization member created"); -} else { - console.log("Organization member exists"); -} - -// Step 3: Get or create default Space -let space = await db.space.findFirst({ - where: { organizationId: org.id, isDefault: true }, -}); -if (!space) { - space = await db.space.create({ - data: { - id: randomUUID(), - name: "Mein Space", - isDefault: true, - createdByUserId: user.id, - organizationId: org.id, - }, - select: { id: true, name: true }, - }); - console.log("Space created:", space); -} else { - console.log("Space exists:", space); -} - -// Step 4: Get or create space member -const existingMember = await db.spaceMember.findUnique({ - where: { - spaceId_userId: { spaceId: space.id, userId: user.id }, - }, -}); -if (!existingMember) { - await db.spaceMember.create({ - data: { - id: randomUUID(), - spaceId: space.id, - organizationId: org.id, - userId: user.id, - role: "owner", - createdAt: new Date(), - }, - }); - console.log("Space member created"); -} else { - console.log("Space member exists"); -} - -// Verify -const finalSpaces = await db.space.findMany({ - where: { organization: { members: { some: { userId: user.id } } } }, - select: { id: true, name: true, isDefault: true }, -}); -console.log("\nUser spaces:", JSON.stringify(finalSpaces, null, 2)); - -await db.$disconnect(); -console.log("\nDone!"); diff --git a/packs/evoo-cto-steve-jobs.md b/packs/evoo-cto-steve-jobs.md deleted file mode 100644 index fe44010..0000000 --- a/packs/evoo-cto-steve-jobs.md +++ /dev/null @@ -1,19 +0,0 @@ -# Pack: evOo Autonomous CTO (Steve Jobs) + Casted Agent Network - -The **evOo Autonomous CTO Pack** empowers a single executive agent (**Steve Jobs**) with full authority over the system, network, files, devices, and codebase, with the power to cast, spawn, and direct specialized agent sub-teams. - -## Roster Architecture - -| Role | Seat / Agent | Authority & Scope | Execution Model | -| --- | --- | --- | --- | -| **CTO** | **Steve Jobs** (`templates/cto-steve-jobs`) | Executive decision maker, architecture lead, full system access | Action-first, multi-task sequence, autonomous execution | -| **Code Architect** | Casted by CTO | Code structure, design patterns, refactoring | Sub-agent spawned on demand by Steve Jobs | -| **Security & QA Auditor** | Casted by CTO | Code safety, test coverage, vulnerability checks | Sub-agent spawned on demand by Steve Jobs | -| **DevOps & Infrastructure** | Casted by CTO | System setup, process management, deployment | Sub-agent spawned on demand by Steve Jobs | - -## Core Principles - -1. **Unrestricted Pre-Approved Access**: Network, local filesystem, devices, and repository source code are pre-approved via `evoo.config.json`. -2. **Action-First Multi-Task Execution**: Executes full task chains without stopping to ask permission on clear objectives. -3. **Self-Improvement Loop**: Automatically inspects logs, diagnoses errors, refactor code, and enhances prompt guidelines. -4. **Consensus & Negotiation**: Reports back when high-level strategic alignment or trade-offs require negotiation with the user. diff --git a/scripts/autonomous-sentinel.ts b/scripts/autonomous-sentinel.ts deleted file mode 100644 index 44abb30..0000000 --- a/scripts/autonomous-sentinel.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { execSync } from "node:child_process"; -import fs from "node:fs"; -import { createDb } from "../packages/db/src/index.js"; - -async function main() { - console.log("Starting Calm Autonomous Sentinel (5-Min Check & Multi-Agent Parallel Watchdog)..."); - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - - const CHECK_INTERVAL_MS = 300_000; // 5 minutes exactly - let lastCheckTime = Date.now(); - - while (true) { - try { - await new Promise((r) => setTimeout(r, 30_000)); // Check every 30s - - const now = Date.now(); - if (now - lastCheckTime < CHECK_INTERVAL_MS) { - continue; - } - - // Check if any agent is currently working - const activeRuns = await prisma.run.count({ - where: { status: { in: ["running", "queued", "leased"] } }, - }); - - if (activeRuns >= 2) { - // At least 2 agents working in parallel — perfect, no need to interrupt! - console.log( - `[Sentinel] ${activeRuns} agents actively working in parallel. System healthy.`, - ); - lastCheckTime = now; - continue; - } - - const steve = await prisma.bot.findFirst({ where: { name: "Steve Jobs" } }); - const session = await prisma.session.findFirst({ orderBy: { createdAt: "desc" } }); - const execGroup = await prisma.chatGroup.findFirst({ where: { name: "Executive Board" } }); - if (!steve || !session || !execGroup) continue; - - // Check the last message in Executive Board to avoid duplicate spam - const lastMsg = await prisma.message.findFirst({ - where: { thread: { groupId: execGroup.id } }, - orderBy: { createdAt: "desc" }, - }); - - const lastMsgText = JSON.stringify(lastMsg?.blocks ?? ""); - if ( - lastMsgText.includes("5-Minuten Kontroll-Check") && - now - new Date(lastMsg!.createdAt).getTime() < 300_000 - ) { - console.log("[Sentinel] Skip: Recent check already present."); - lastCheckTime = now; - continue; - } - - // Quick tests verification - let testSummary = "Alle Tests validiert"; - try { - const testPath = - "/Users/ingowagner/evOo/data/desktop-computers/team-9d790f3a504d3e9ef577ca4f2118e446/bots/cmu7jjxv30000fvyyjyoyylef/apex-trading"; - if (fs.existsSync(testPath)) { - const out = execSync("PYTHONPATH=src python3 -m unittest discover -s tests 2>&1", { - cwd: testPath, - timeout: 8000, - }).toString(); - if (out.includes("OK")) { - testSummary = "5/5 Tests GRÜN ✅ (1.5M ticks/s)"; - } - } - } catch { - testSummary = "Tests laufen"; - } - - lastCheckTime = now; - console.log("[Sentinel] Dispatching 5-minute parallel pulse to Executive Board..."); - - await fetch("http://127.0.0.1:3100/rpc/threads/send", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session.token}`, - }, - body: JSON.stringify({ - json: { - groupId: execGroup.id, - text: `@Steve Jobs [Executive Board Pulse ⏱️]\n\nTest-Status: ${testSummary}\nAktuell aktive Agenten: ${activeRuns}\n\nBitte halte die Vorgabe der Geschäftsführung ein: Lasse immer MINDESTENS 2 Spezialisten gleichzeitig parallel arbeiten (z.B. DevOps Engineer + Security Auditor oder Apex Quant + Code Architect). Delegiere die nächsten 2 parallelen Arbeitspakete.`, - }, - }), - }); - } catch (err) { - console.error("[Sentinel] Error in loop:", err); - } - } -} - -main().catch(console.error); diff --git a/scripts/check-architect-events.ts b/scripts/check-architect-events.ts deleted file mode 100644 index 05fa1f4..0000000 --- a/scripts/check-architect-events.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { runId: "cmu7k4d9n000yiryy0zz4obmq" }, - orderBy: { createdAt: "asc" }, - }); - - console.log(`Events for Code Architect (total ${events.length}):`); - for (const e of events) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type} | payload: ${JSON.stringify(e.payload || e.data)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-attempts.ts b/scripts/check-attempts.ts deleted file mode 100644 index 6f96b2b..0000000 --- a/scripts/check-attempts.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const attempts = await prisma.attempt.findMany({ - where: { runId: "cmu7jw8dr000biryyrethbore" }, - orderBy: { startedAt: "asc" }, - }); - console.log("Attempts count:", attempts.length); - for (const a of attempts) { - console.log("Attempt:", a); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-auditor-final.ts b/scripts/check-auditor-final.ts deleted file mode 100644 index 4d9bf16..0000000 --- a/scripts/check-auditor-final.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const auditor = await prisma.bot.findFirst({ - where: { name: "Security & QA Auditor" }, - include: { - thread: { - include: { - messages: { - orderBy: { seq: "desc" }, - take: 2, - }, - }, - }, - }, - }); - - const lastMsg = auditor?.thread?.messages[0]; - console.log(`Auditor last msg (role: ${lastMsg?.role}, seq: ${lastMsg?.seq}):`); - if (lastMsg?.role === "bot") { - console.log(JSON.stringify(lastMsg?.blocks, null, 2)); - } else { - console.log("Still working/executing..."); - } - - const latestEvents = await prisma.event.findMany({ - where: { botId: auditor?.id }, - orderBy: { createdAt: "desc" }, - take: 4, - }); - console.log("\nLatest events:"); - for (const e of latestEvents) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type}: ${JSON.stringify(e.payload || e.data).slice(0, 120)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-auditor-live.ts b/scripts/check-auditor-live.ts deleted file mode 100644 index 663605e..0000000 --- a/scripts/check-auditor-live.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { runId: "cmu7k4dc50016iryyrv036xze" }, - orderBy: { createdAt: "desc" }, - take: 6, - }); - - console.log("Auditor live events count:", events.length); - for (const e of events.reverse()) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type}: ${JSON.stringify(e.payload || e.data).slice(0, 120)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-auditor-live2.ts b/scripts/check-auditor-live2.ts deleted file mode 100644 index b368b15..0000000 --- a/scripts/check-auditor-live2.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { - runId: "cmu7k4dc50016iryyrv036xze", - createdAt: { gte: new Date(Date.now() - 30_000) }, - }, - orderBy: { createdAt: "desc" }, - take: 5, - }); - - console.log("Recent Auditor events:", events.length); - for (const e of events.reverse()) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type}: ${JSON.stringify(e.payload || e.data).slice(0, 150)}`, - ); - } - - const run = await prisma.run.findUnique({ - where: { id: "cmu7k4dc50016iryyrv036xze" }, - }); - console.log("Run status:", run?.status); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-auditor-live3.ts b/scripts/check-auditor-live3.ts deleted file mode 100644 index 57a6bd6..0000000 --- a/scripts/check-auditor-live3.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { - runId: "cmu7k4dc50016iryyrv036xze", - createdAt: { gte: new Date(Date.now() - 20_000) }, - }, - orderBy: { createdAt: "desc" }, - take: 5, - }); - - console.log("Recent Auditor events count:", events.length); - for (const e of events.reverse()) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type}: ${JSON.stringify(e.payload || e.data).slice(0, 150)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-auditor-next.ts b/scripts/check-auditor-next.ts deleted file mode 100644 index b5605ff..0000000 --- a/scripts/check-auditor-next.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const auditor = await prisma.bot.findFirst({ - where: { name: "Security & QA Auditor" }, - include: { - thread: { - include: { - messages: { - orderBy: { seq: "desc" }, - take: 2, - }, - }, - }, - }, - }); - - const lastMsg = auditor?.thread?.messages[0]; - console.log(`Auditor last msg (role: ${lastMsg?.role}, seq: ${lastMsg?.seq}):`); - if (lastMsg?.role === "bot") { - console.log(JSON.stringify(lastMsg?.blocks, null, 2)); - } else { - console.log("Still working..."); - } - - const latestEvents = await prisma.event.findMany({ - where: { botId: auditor?.id }, - orderBy: { createdAt: "desc" }, - take: 6, - }); - console.log("\nLatest events:"); - for (const e of latestEvents) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type}: ${JSON.stringify(e.payload || e.data).slice(0, 120)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-bot-directory.ts b/scripts/check-bot-directory.ts deleted file mode 100644 index 8a2740d..0000000 --- a/scripts/check-bot-directory.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const steve = await prisma.bot.findFirst({ where: { name: "Steve Jobs" } }); - const allBots = await prisma.bot.findMany({ - select: { - id: true, - name: true, - userId: true, - spaceId: true, - archivedAt: true, - thread: { select: { id: true } }, - }, - }); - console.log("Steve userId:", steve?.userId, "spaceId:", steve?.spaceId); - console.log("All bots:"); - for (const b of allBots) { - console.log( - `Bot: ${b.name} (${b.id}) | userId: ${b.userId} | spaceId: ${b.spaceId} | thread: ${b.thread?.id}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-bot-model.ts b/scripts/check-bot-model.ts deleted file mode 100644 index 9efbe44..0000000 --- a/scripts/check-bot-model.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const steve = await prisma.bot.findFirst({ where: { name: "Steve Jobs" } }); - console.log("Steve Jobs modelId:", steve?.modelId); - const run = await prisma.run.findUnique({ - where: { id: "cmu7jw8dr000biryyrethbore" }, - }); - console.log("Run details:", { - id: run?.id, - modelId: run?.modelId, - status: run?.status, - createdAt: run?.createdAt, - }); - const model = await prisma.model.findUnique({ - where: { id: run?.modelId || steve?.modelId || "" }, - }); - console.log("Model record in DB:", model); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-chief-group-finish.ts b/scripts/check-chief-group-finish.ts deleted file mode 100644 index cb25726..0000000 --- a/scripts/check-chief-group-finish.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const group = await prisma.chatGroup.findFirst({ - where: { name: "Executive Board" }, - include: { - thread: { - include: { - messages: { - orderBy: { seq: "desc" }, - take: 2, - }, - }, - }, - }, - }); - - const latestMsg = group?.thread?.messages[0]; - console.log("Chief Message in Executive Board:"); - console.log(JSON.stringify(latestMsg?.blocks, null, 2)); - - const run = await prisma.run.findUnique({ - where: { id: "cmu7k3m4x000qiryyt59ju2n0" }, - }); - console.log("Run status:", run?.status); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-chief-group-progress.ts b/scripts/check-chief-group-progress.ts deleted file mode 100644 index 0dbdce8..0000000 --- a/scripts/check-chief-group-progress.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const run = await prisma.run.findUnique({ - where: { id: "cmu7k3m4x000qiryyt59ju2n0" }, - }); - console.log("Run:", { status: run?.status, completedAt: run?.completedAt, error: run?.error }); - - const events = await prisma.event.findMany({ - where: { runId: "cmu7k3m4x000qiryyt59ju2n0" }, - orderBy: { createdAt: "desc" }, - take: 5, - }); - console.log("Events count:", events.length); - for (const e of events.reverse()) { - console.log(`[${e.createdAt.toISOString()}] ${e.type}: ${JSON.stringify(e.payload || e.data)}`); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-chief.ts b/scripts/check-chief.ts deleted file mode 100644 index d24a846..0000000 --- a/scripts/check-chief.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const chief = await prisma.bot.findFirst({ - where: { name: "Chief" }, - include: { thread: true }, - }); - console.log("CHIEF BOT:", JSON.stringify(chief, null, 2)); - - const sections = await prisma.botSection.findMany(); - console.log("SECTIONS:", JSON.stringify(sections, null, 2)); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-computer-leases.ts b/scripts/check-computer-leases.ts deleted file mode 100644 index 92c0455..0000000 --- a/scripts/check-computer-leases.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const leases = await prisma.computerExecutionLease.findMany(); - console.log("Total computerExecutionLeases in DB:", leases.length); - for (const l of leases) { - console.log(l); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-current-run.ts b/scripts/check-current-run.ts deleted file mode 100644 index 4bead4b..0000000 --- a/scripts/check-current-run.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const r = await prisma.run.findUnique({ - where: { id: "cmu7k09nt00cchxyyfo0n4ppv" }, - include: { sourceMessage: true }, - }); - console.log("Run:", { - id: r?.id, - trigger: r?.trigger, - status: r?.status, - sourceMessage: r?.sourceMessage?.blocks, - createdAt: r?.createdAt, - }); - const events = await prisma.event.findMany({ - where: { runId: "cmu7k09nt00cchxyyfo0n4ppv" }, - }); - console.log("Events count:", events.length); - for (const e of events) { - console.log(`[${e.createdAt.toISOString()}] ${e.type}: ${JSON.stringify(e.payload || e.data)}`); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-events-detail.ts b/scripts/check-events-detail.ts deleted file mode 100644 index ab92036..0000000 --- a/scripts/check-events-detail.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { runId: "cmu7jw8dr000biryyrethbore" }, - orderBy: { createdAt: "asc" }, - }); - for (const e of events) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type} (payload: ${e.payload ? JSON.stringify(e.payload).slice(0, 200) : "none"})`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-graphile-jobs-now.ts b/scripts/check-graphile-jobs-now.ts deleted file mode 100644 index 0a448ec..0000000 --- a/scripts/check-graphile-jobs-now.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { pool } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const res = await pool.query( - "SELECT id, task_identifier, run_at, attempts, locked_at, locked_by, last_error FROM graphile_worker.jobs ORDER BY id DESC LIMIT 5;", - ); - console.log("Graphile jobs:", res.rows); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-graphile-jobs.ts b/scripts/check-graphile-jobs.ts deleted file mode 100644 index 3b7e7a9..0000000 --- a/scripts/check-graphile-jobs.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { pool } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const res = await pool.query("SELECT * FROM graphile_worker.jobs ORDER BY id DESC LIMIT 5;"); - console.log("Graphile jobs count:", res.rows.length); - for (const r of res.rows) { - console.log(r); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-group-followup.ts b/scripts/check-group-followup.ts deleted file mode 100644 index 6664461..0000000 --- a/scripts/check-group-followup.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const runs = await prisma.run.findMany({ - where: { - thread: { - group: { name: "Executive Board" }, - }, - }, - orderBy: { createdAt: "desc" }, - include: { bot: true }, - }); - - console.log("Runs in Executive Board group thread:", runs.length); - for (const r of runs) { - console.log( - `Run: ${r.id} | Bot: ${r.bot?.name} | Status: ${r.status} | Trigger: ${r.trigger} | Created: ${r.createdAt.toISOString()}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-group-run.ts b/scripts/check-group-run.ts deleted file mode 100644 index d03faf5..0000000 --- a/scripts/check-group-run.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const run = await prisma.run.findUnique({ - where: { id: "cmu7k3m4x000qiryyt59ju2n0" }, - include: { bot: true, thread: true }, - }); - - console.log("Run info:", { - id: run?.id, - botName: run?.bot?.name, - status: run?.status, - createdAt: run?.createdAt, - }); - - const events = await prisma.event.findMany({ - where: { runId: "cmu7k3m4x000qiryyt59ju2n0" }, - orderBy: { createdAt: "asc" }, - }); - console.log("Events count:", events.length); - for (const e of events) { - console.log(`[${e.createdAt.toISOString()}] ${e.type}: ${JSON.stringify(e.payload || e.data)}`); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-groups.ts b/scripts/check-groups.ts deleted file mode 100644 index c730c3f..0000000 --- a/scripts/check-groups.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const groups = await prisma.group.findMany({ - include: { - members: { - include: { bot: true }, - }, - thread: true, - }, - }); - console.log("GROUPS COUNT:", groups.length); - for (const g of groups) { - console.log( - `Group: ${g.name} (${g.id}) | Members: ${g.members.map((m) => m.bot.name).join(", ")} | Thread: ${g.thread?.id}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-latest-events-5.ts b/scripts/check-latest-events-5.ts deleted file mode 100644 index dd6c5f6..0000000 --- a/scripts/check-latest-events-5.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - orderBy: { createdAt: "desc" }, - take: 5, - }); - - console.log("LAST 5 EVENTS:"); - for (const e of events) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type} | run: ${e.runId} | data: ${JSON.stringify(e.payload || e.data).slice(0, 120)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-latest-events-all.ts b/scripts/check-latest-events-all.ts deleted file mode 100644 index da1afb0..0000000 --- a/scripts/check-latest-events-all.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - orderBy: { createdAt: "desc" }, - take: 12, - }); - console.log("LATEST 12 EVENTS:"); - for (const e of events) { - console.log( - `[${e.createdAt.toISOString()}] Run: ${e.runId} | Type: ${e.type} | Data: ${JSON.stringify(e.payload || e.data).slice(0, 100)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-latest-events.ts b/scripts/check-latest-events.ts deleted file mode 100644 index 50bef95..0000000 --- a/scripts/check-latest-events.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { runId: "cmu7jw8dr000biryyrethbore" }, - orderBy: { createdAt: "desc" }, - take: 10, - }); - console.log("Run cmu7jw8dr000biryyrethbore events count:", events.length); - for (const e of events.reverse()) { - console.log(`[${e.createdAt.toISOString()}] ${e.type}: ${JSON.stringify(e.data)}`); - } - - const run = await prisma.run.findUnique({ - where: { id: "cmu7jw8dr000biryyrethbore" }, - }); - console.log("Status:", run?.status, "completedAt:", run?.completedAt, "error:", run?.error); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-latest-state.ts b/scripts/check-latest-state.ts deleted file mode 100644 index 6abdcc8..0000000 --- a/scripts/check-latest-state.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - - const msgs = await prisma.message.findMany({ - where: { threadId: "cmu6ydgme0001ywyy269411bb" }, - orderBy: { seq: "desc" }, - take: 5, - }); - console.log("Recent messages:"); - for (const m of msgs.reverse()) { - console.log(`[Seq ${m.seq} | Role ${m.role} | RunId ${m.runId}]`); - console.log(JSON.stringify(m.blocks)); - } - - const activeRuns = await prisma.run.findMany({ - where: { status: "running" }, - }); - console.log("\nActive running runs count:", activeRuns.length); - for (const r of activeRuns) { - console.log( - `Run ${r.id} | sourceMsg ${r.sourceMessageId} | started: ${r.createdAt.toISOString()}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-latest-user-msg.ts b/scripts/check-latest-user-msg.ts deleted file mode 100644 index 4b9ed89..0000000 --- a/scripts/check-latest-user-msg.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const latestMsgs = await prisma.message.findMany({ - orderBy: { createdAt: "desc" }, - take: 3, - include: { - thread: { - include: { - bot: true, - group: true, - }, - }, - }, - }); - - for (const m of latestMsgs) { - console.log( - `[${m.createdAt.toISOString()}] Target: ${m.thread?.bot?.name || m.thread?.group?.name} | Role: ${m.role} | Text: ${JSON.stringify(m.blocks)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-model-prefs.ts b/scripts/check-model-prefs.ts deleted file mode 100644 index 4dec422..0000000 --- a/scripts/check-model-prefs.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const creds = await prisma.userModelCredential.findMany({ - include: { preferences: true }, - }); - console.log("CREDS & PREFS:", JSON.stringify(creds, null, 2)); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-model-secret.ts b/scripts/check-model-secret.ts deleted file mode 100644 index 4bbc84f..0000000 --- a/scripts/check-model-secret.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const secrets = await prisma.secret.findMany(); - console.log("Found secrets:", secrets.length); - for (const s of secrets) { - console.log("Secret:", { id: s.id, name: s.name, provider: s.provider }); - } - - const spacePrefs = await prisma.spacePreference.findMany(); - console.log("Space preferences:", spacePrefs); - - const steve = await prisma.bot.findFirst({ where: { name: "Steve Jobs" } }); - console.log("Steve:", { - id: steve?.id, - modelProvider: steve?.modelProvider, - modelId: steve?.modelId, - }); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-mtimes.ts b/scripts/check-mtimes.ts deleted file mode 100644 index 1f4618b..0000000 --- a/scripts/check-mtimes.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { readdirSync, statSync } from "node:fs"; - -const baseDir = "/Users/ingowagner/evOo/data/homes/team-9d790f3a504d3e9ef577ca4f2118e446/bots"; -for (const botDir of readdirSync(baseDir)) { - const p = `${baseDir}/${botDir}`; - console.log(`\nDir: ${botDir}`); - for (const f of readdirSync(p)) { - if (f.endsWith(".md")) { - const st = statSync(`${p}/${f}`); - console.log(` ${f.padEnd(20)} mtime: ${st.mtime.toISOString()} (size: ${st.size})`); - } - } -} diff --git a/scripts/check-other-two.ts b/scripts/check-other-two.ts deleted file mode 100644 index f2a53f5..0000000 --- a/scripts/check-other-two.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - - for (const name of ["DevOps Engineer", "Security & QA Auditor"]) { - const bot = await prisma.bot.findFirst({ where: { name } }); - const events = await prisma.event.findMany({ - where: { botId: bot?.id }, - orderBy: { createdAt: "asc" }, - }); - console.log(`\nEvents for ${name} (total ${events.length}):`); - for (const e of events) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type} | payload: ${JSON.stringify(e.payload || e.data).slice(0, 100)}`, - ); - } - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-quant-events.ts b/scripts/check-quant-events.ts deleted file mode 100644 index 2150126..0000000 --- a/scripts/check-quant-events.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { runId: "cmu7k4d8k000uiryy086x000a" }, - orderBy: { createdAt: "asc" }, - }); - - console.log(`Events for Apex Quant (total ${events.length}):`); - for (const e of events) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type} | payload: ${JSON.stringify(e.payload || e.data)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-run-events.ts b/scripts/check-run-events.ts deleted file mode 100644 index 79a105e..0000000 --- a/scripts/check-run-events.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { runId: "cmu7jw8dr000biryyrethbore" }, - orderBy: { createdAt: "asc" }, - }); - - console.log(`Events count for cmu7jw8dr000biryyrethbore: ${events.length}`); - for (const e of events) { - console.log(`[${e.createdAt.toISOString()}] ${e.type}: ${JSON.stringify(e.data)}`); - } - - const run = await prisma.run.findUnique({ - where: { id: "cmu7jw8dr000biryyrethbore" }, - }); - console.log("RUN STATUS:", run?.status, "error:", run?.error); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-run-now.ts b/scripts/check-run-now.ts deleted file mode 100644 index cd251e4..0000000 --- a/scripts/check-run-now.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { runId: "cmu7jw8dr000biryyrethbore" }, - orderBy: { createdAt: "asc" }, - }); - console.log("Event count:", events.length); - for (const e of events) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type}: ${JSON.stringify(e.data).slice(0, 150)}`, - ); - } - - const run = await prisma.run.findUnique({ - where: { id: "cmu7jw8dr000biryyrethbore" }, - }); - console.log("Run:", { status: run?.status, completedAt: run?.completedAt, error: run?.error }); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-runs-detailed.ts b/scripts/check-runs-detailed.ts deleted file mode 100644 index 839941d..0000000 --- a/scripts/check-runs-detailed.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const runs = await prisma.run.findMany({ - where: { - bot: { - name: { in: ["Apex Quant", "Code Architect", "DevOps Engineer", "Security & QA Auditor"] }, - }, - }, - orderBy: { createdAt: "desc" }, - take: 4, - include: { - bot: true, - }, - }); - - for (const r of runs) { - console.log( - `Bot: ${r.bot?.name.padEnd(25)} | Status: ${r.status.padEnd(10)} | Error: ${r.error}`, - ); - const events = await prisma.event.findMany({ - where: { runId: r.id }, - orderBy: { createdAt: "desc" }, - take: 3, - }); - console.log(` Last ${events.length} events:`); - for (const e of events) { - console.log( - ` [${e.createdAt.toISOString()}] ${e.type}: ${JSON.stringify(e.payload || e.data).slice(0, 100)}`, - ); - } - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-runs-live.ts b/scripts/check-runs-live.ts deleted file mode 100644 index 3c012a9..0000000 --- a/scripts/check-runs-live.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const runs = await prisma.run.findMany({ - where: { - bot: { - name: { in: ["Apex Quant", "Code Architect", "DevOps Engineer", "Security & QA Auditor"] }, - }, - }, - orderBy: { createdAt: "desc" }, - take: 4, - include: { bot: true }, - }); - - for (const r of runs) { - console.log( - `Bot: ${r.bot?.name.padEnd(25)} | Status: ${r.status.padEnd(10)} | LeaseOwner: ${r.leaseOwner}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-runs-now.ts b/scripts/check-runs-now.ts deleted file mode 100644 index 3c012a9..0000000 --- a/scripts/check-runs-now.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const runs = await prisma.run.findMany({ - where: { - bot: { - name: { in: ["Apex Quant", "Code Architect", "DevOps Engineer", "Security & QA Auditor"] }, - }, - }, - orderBy: { createdAt: "desc" }, - take: 4, - include: { bot: true }, - }); - - for (const r of runs) { - console.log( - `Bot: ${r.bot?.name.padEnd(25)} | Status: ${r.status.padEnd(10)} | LeaseOwner: ${r.leaseOwner}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-secret-content.ts b/scripts/check-secret-content.ts deleted file mode 100644 index 0f98a2c..0000000 --- a/scripts/check-secret-content.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { readFileSync } from "node:fs"; -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const envText = readFileSync("/Users/ingowagner/evOo/.env", "utf8"); - let secretKey = ""; - for (const line of envText.split("\n")) { - if (line.startsWith("ENCRYPTION_SECRET=") || line.startsWith("SECRET_STORE_KEY=")) { - secretKey = line.split("=")[1].trim(); - } - } - console.log("Secret key present?", Boolean(secretKey)); - - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const row = await prisma.secret.findUnique({ - where: { id: "1a9f419f48e31829e09f7b6a" }, - }); - console.log("Row:", row?.id, "ciphertext len:", row?.ciphertext.length); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-seq136-finished.ts b/scripts/check-seq136-finished.ts deleted file mode 100644 index a1abfd0..0000000 --- a/scripts/check-seq136-finished.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const run = await prisma.run.findUnique({ - where: { id: "cmu7jytii000firyyhkbjh9od" }, - }); - console.log("Run:", { status: run?.status, completedAt: run?.completedAt }); - - const latestMsg = await prisma.message.findFirst({ - where: { threadId: "cmu6ydgme0001ywyy269411bb" }, - orderBy: { seq: "desc" }, - }); - console.log(`Latest msg (seq ${latestMsg?.seq}, role ${latestMsg?.role}):`); - console.log(JSON.stringify(latestMsg?.blocks, null, 2)); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-seq136-progress.ts b/scripts/check-seq136-progress.ts deleted file mode 100644 index 67202e0..0000000 --- a/scripts/check-seq136-progress.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { runId: "cmu7jytii000firyyhkbjh9od" }, - orderBy: { createdAt: "asc" }, - }); - console.log("Events count:", events.length); - for (const e of events) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type} (payload: ${e.payload ? JSON.stringify(e.payload).slice(0, 150) : "none"})`, - ); - } - - const run = await prisma.run.findUnique({ - where: { id: "cmu7jytii000firyyhkbjh9od" }, - }); - console.log("Run:", { status: run?.status, completedAt: run?.completedAt, error: run?.error }); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-seq136-run.ts b/scripts/check-seq136-run.ts deleted file mode 100644 index 04a6e8e..0000000 --- a/scripts/check-seq136-run.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const msg136 = await prisma.message.findFirst({ - where: { seq: 136, threadId: "cmu6ydgme0001ywyy269411bb" }, - }); - console.log("Msg 136:", msg136); - - const runs = await prisma.run.findMany({ - where: { sourceMessageId: msg136?.id }, - include: { events: true }, - }); - console.log("Runs for msg 136:", runs); - - const latestRuns = await prisma.run.findMany({ - orderBy: { createdAt: "desc" }, - take: 3, - }); - console.log("Latest 3 runs:", latestRuns); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-seq136-status.ts b/scripts/check-seq136-status.ts deleted file mode 100644 index 61bf50f..0000000 --- a/scripts/check-seq136-status.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const run = await prisma.run.findUnique({ - where: { id: "cmu7jytii000firyyhkbjh9od" }, - }); - console.log("Run for seq 136:", { - id: run?.id, - status: run?.status, - createdAt: run?.createdAt, - completedAt: run?.completedAt, - error: run?.error, - }); - - const events = await prisma.event.findMany({ - where: { runId: "cmu7jytii000firyyhkbjh9od" }, - orderBy: { createdAt: "asc" }, - }); - console.log("Events count:", events.length); - for (const e of events) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type} (payload: ${e.payload ? JSON.stringify(e.payload).slice(0, 100) : "none"})`, - ); - } - - const latestMsg = await prisma.message.findFirst({ - where: { threadId: "cmu6ydgme0001ywyy269411bb" }, - orderBy: { seq: "desc" }, - }); - console.log("Latest msg in thread:", { - seq: latestMsg?.seq, - role: latestMsg?.role, - blocks: latestMsg?.blocks, - }); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-shell-commands-2.ts b/scripts/check-shell-commands-2.ts deleted file mode 100644 index 9776f41..0000000 --- a/scripts/check-shell-commands-2.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { - type: "thread.progress", - createdAt: { gte: new Date(Date.now() - 30_000) }, - }, - orderBy: { createdAt: "desc" }, - take: 6, - }); - - for (const e of events) { - console.log( - `[${e.createdAt.toISOString()}] Bot: ${e.botId} | ${JSON.stringify(e.payload || e.data)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-shell-commands.ts b/scripts/check-shell-commands.ts deleted file mode 100644 index 62e5df6..0000000 --- a/scripts/check-shell-commands.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { - type: "thread.progress", - createdAt: { gte: new Date(Date.now() - 30_000) }, - }, - orderBy: { createdAt: "desc" }, - take: 6, - }); - - console.log("Recent progress/commands:"); - for (const e of events) { - console.log( - `[${e.createdAt.toISOString()}] Bot: ${e.botId} | ${JSON.stringify(e.payload || e.data)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-spaces.ts b/scripts/check-spaces.ts deleted file mode 100644 index 89c378a..0000000 --- a/scripts/check-spaces.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const session = await prisma.session.findFirst({ orderBy: { createdAt: "desc" } }); - const res = await fetch("http://127.0.0.1:3100/rpc/spaces/list", { - headers: { Authorization: `Bearer ${session?.token}` }, - }); - const data = await res.json(); - console.log("DATA:", JSON.stringify(data, null, 2)); -} -run(); diff --git a/scripts/check-specialist-events.ts b/scripts/check-specialist-events.ts deleted file mode 100644 index 1d54b23..0000000 --- a/scripts/check-specialist-events.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - - const events = await prisma.event.findMany({ - where: { - createdAt: { gte: new Date(Date.now() - 60_000) }, - }, - orderBy: { createdAt: "desc" }, - take: 15, - }); - - console.log(`Events in the last 60s: ${events.length}`); - for (const e of events.reverse()) { - console.log( - `[${e.createdAt.toISOString()}] Bot: ${e.botId} | Type: ${e.type} | Text/Data: ${JSON.stringify(e.payload || e.data).slice(0, 100)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialist-progress.ts b/scripts/check-specialist-progress.ts deleted file mode 100644 index ef8a600..0000000 --- a/scripts/check-specialist-progress.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const runs = await prisma.run.findMany({ - where: { - id: { - in: [ - "cmu7k4dc50016iryyrv036xze", - "cmu7k4db10012iryyuhfoukpt", - "cmu7k4d9n000yiryy0zz4obmq", - "cmu7k4d8k000uiryy086x000a", - ], - }, - }, - include: { bot: true }, - }); - - for (const r of runs) { - console.log( - `Bot: ${r.bot?.name.padEnd(25)} | Status: ${r.status} | Completed: ${r.completedAt} | Error: ${r.error}`, - ); - } - - const latestEvents = await prisma.event.findMany({ - where: { - type: { in: ["thread.message.created", "tool.started", "tool.completed", "thread.progress"] }, - createdAt: { gte: new Date(Date.now() - 30_000) }, - }, - orderBy: { createdAt: "desc" }, - take: 8, - }); - console.log("\nLatest stream/message events count:", latestEvents.length); - for (const e of latestEvents) { - console.log(`[${e.createdAt.toISOString()}] Type: ${e.type} | Bot: ${e.botId}`); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialist-runs.ts b/scripts/check-specialist-runs.ts deleted file mode 100644 index 0623c0b..0000000 --- a/scripts/check-specialist-runs.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const runs = await prisma.run.findMany({ - orderBy: { createdAt: "desc" }, - take: 6, - include: { bot: true }, - }); - - console.log("Recent runs:"); - for (const r of runs) { - console.log( - `Bot: ${r.bot?.name.padEnd(25)} | Status: ${r.status.padEnd(10)} | Error: ${r.error} | RunId: ${r.id}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialists-completion.ts b/scripts/check-specialists-completion.ts deleted file mode 100644 index 13e4208..0000000 --- a/scripts/check-specialists-completion.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const runs = await prisma.run.findMany({ - where: { - bot: { - name: { in: ["Apex Quant", "Code Architect", "DevOps Engineer", "Security & QA Auditor"] }, - }, - }, - orderBy: { createdAt: "desc" }, - take: 4, - include: { - bot: true, - thread: { - include: { - messages: { - orderBy: { seq: "desc" }, - take: 1, - }, - }, - }, - }, - }); - - for (const r of runs) { - console.log(`\n======================================================`); - console.log(`Bot: ${r.bot?.name} | Status: ${r.status} | Completed: ${r.completedAt}`); - const lastMsg = r.thread?.messages[0]; - if (lastMsg && lastMsg.role === "bot") { - console.log(`Bot Message (seq ${lastMsg.seq}):\n`, JSON.stringify(lastMsg.blocks, null, 2)); - } else { - console.log(`No bot message yet (role: ${lastMsg?.role})`); - } - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialists-db.ts b/scripts/check-specialists-db.ts deleted file mode 100644 index 052f764..0000000 --- a/scripts/check-specialists-db.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const runs = await prisma.run.findMany({ - where: { - id: { - in: [ - "cmu7k4dc50016iryyrv036xze", - "cmu7k4db10012iryyuhfoukpt", - "cmu7k4d9n000yiryy0zz4obmq", - "cmu7k4d8k000uiryy086x000a", - ], - }, - }, - include: { - bot: true, - thread: { - include: { - messages: { - orderBy: { seq: "desc" }, - take: 1, - }, - }, - }, - }, - }); - - for (const r of runs) { - console.log(`\n======================================================`); - console.log(`Bot: ${r.bot?.name} | Status: ${r.status}`); - const lastMsg = r.thread?.messages[0]; - if (lastMsg && lastMsg.role === "bot") { - console.log(`Message blocks:\n`, JSON.stringify(lastMsg.blocks, null, 2)); - } else { - console.log(`Still processing / no final bot message yet`); - } - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialists-events-now.ts b/scripts/check-specialists-events-now.ts deleted file mode 100644 index 29d860d..0000000 --- a/scripts/check-specialists-events-now.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { - createdAt: { gte: new Date(Date.now() - 60_000) }, - }, - orderBy: { createdAt: "desc" }, - take: 15, - }); - - console.log(`Events in last 60s: ${events.length}`); - for (const e of events.reverse()) { - console.log( - `[${e.createdAt.toISOString()}] Bot: ${e.botId} | Type: ${e.type} | Data: ${JSON.stringify(e.payload || e.data).slice(0, 120)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialists-events.ts b/scripts/check-specialists-events.ts deleted file mode 100644 index 941c8d6..0000000 --- a/scripts/check-specialists-events.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { - runId: { - in: [ - "cmu7k4dc50016iryyrv036xze", - "cmu7k4db10012iryyuhfoukpt", - "cmu7k4d9n000yiryy0zz4obmq", - "cmu7k4d8k000uiryy086x000a", - ], - }, - }, - orderBy: { createdAt: "desc" }, - take: 20, - }); - - console.log(`Total events for specialist runs: ${events.length}`); - for (const e of events) { - console.log( - `[${e.createdAt.toISOString()}] Run: ${e.runId} | Type: ${e.type} | Data: ${JSON.stringify(e.payload || e.data).slice(0, 120)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialists-latest.ts b/scripts/check-specialists-latest.ts deleted file mode 100644 index 16e647b..0000000 --- a/scripts/check-specialists-latest.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const runs = await prisma.run.findMany({ - where: { - bot: { - name: { in: ["Apex Quant", "Code Architect", "DevOps Engineer", "Security & QA Auditor"] }, - }, - }, - orderBy: { createdAt: "desc" }, - take: 4, - include: { - bot: true, - thread: { - include: { - messages: { - orderBy: { seq: "desc" }, - take: 1, - }, - }, - }, - }, - }); - - for (const r of runs) { - console.log( - `\nBot: ${r.bot?.name} | Run: ${r.id} | Status: ${r.status} | Completed: ${r.completedAt}`, - ); - const lastMsg = r.thread?.messages[0]; - if (lastMsg && lastMsg.role === "bot") { - console.log(`Last message blocks:`, JSON.stringify(lastMsg.blocks).slice(0, 300)); - } - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialists-msgs.ts b/scripts/check-specialists-msgs.ts deleted file mode 100644 index 3e10822..0000000 --- a/scripts/check-specialists-msgs.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const msgs = await prisma.message.findMany({ - where: { - thread: { - bot: { - name: { - in: ["Apex Quant", "Code Architect", "DevOps Engineer", "Security & QA Auditor"], - }, - }, - }, - }, - orderBy: { createdAt: "desc" }, - include: { - thread: { - include: { bot: true }, - }, - }, - }); - - console.log(`Total messages across the 4 bots: ${msgs.length}`); - for (const m of msgs) { - console.log( - `[${m.createdAt.toISOString()}] Bot: ${m.thread?.bot?.name} | Role: ${m.role} | Seq: ${m.seq}`, - ); - console.log(JSON.stringify(m.blocks)); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialists-output.ts b/scripts/check-specialists-output.ts deleted file mode 100644 index 4ac6978..0000000 --- a/scripts/check-specialists-output.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const runs = await prisma.run.findMany({ - where: { - id: { - in: [ - "cmu7k4dc50016iryyrv036xze", - "cmu7k4db10012iryyuhfoukpt", - "cmu7k4d9n000yiryy0zz4obmq", - "cmu7k4d8k000uiryy086x000a", - ], - }, - }, - include: { - bot: true, - thread: { - include: { - messages: { - orderBy: { seq: "desc" }, - take: 1, - }, - }, - }, - }, - }); - - console.log("SPECIALIST BOT OUTPUTS:"); - for (const r of runs) { - console.log(`\n=== ${r.bot?.name} [Status: ${r.status}] ===`); - const msg = r.thread?.messages[0]; - if (msg && msg.role === "bot") { - console.log(JSON.stringify(msg.blocks, null, 2)); - } else { - console.log("No completed bot message yet (still streaming/executing)"); - } - } - - const toolEvents = await prisma.event.findMany({ - where: { - createdAt: { gte: new Date(Date.now() - 30_000) }, - type: { in: ["tool.started", "tool.completed", "thread.progress"] }, - }, - orderBy: { createdAt: "desc" }, - take: 10, - }); - console.log(`\nRecent events count: ${toolEvents.length}`); - for (const e of toolEvents) { - console.log(`[${e.createdAt.toISOString()}] ${e.type} (Bot: ${e.botId})`); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialists-reports.ts b/scripts/check-specialists-reports.ts deleted file mode 100644 index 15fd230..0000000 --- a/scripts/check-specialists-reports.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const runs = await prisma.run.findMany({ - where: { - bot: { - name: { in: ["Apex Quant", "Code Architect", "DevOps Engineer", "Security & QA Auditor"] }, - }, - }, - orderBy: { createdAt: "desc" }, - take: 4, - include: { - bot: true, - thread: { - include: { - messages: { - orderBy: { seq: "desc" }, - take: 1, - }, - }, - }, - }, - }); - - for (const r of runs) { - const lastMsg = r.thread?.messages[0]; - console.log(`\n========================================`); - console.log(`Bot: ${r.bot?.name} | Status: ${r.status} | Msg role: ${lastMsg?.role}`); - if (lastMsg?.role === "bot") { - console.log(JSON.stringify(lastMsg.blocks, null, 2)); - } - } - - const events = await prisma.event.findMany({ - where: { - type: { in: ["agent.tool.called", "agent.tool.completed", "thread.message.created"] }, - createdAt: { gte: new Date(Date.now() - 30_000) }, - }, - orderBy: { createdAt: "desc" }, - take: 5, - }); - console.log(`\nEvents in last 30s: ${events.length}`); - for (const e of events) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type} | Bot: ${e.botId} | Data: ${JSON.stringify(e.payload || e.data).slice(0, 100)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialists-responses.ts b/scripts/check-specialists-responses.ts deleted file mode 100644 index 899fdfa..0000000 --- a/scripts/check-specialists-responses.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const runs = await prisma.run.findMany({ - where: { - bot: { - name: { in: ["Apex Quant", "Code Architect", "DevOps Engineer", "Security & QA Auditor"] }, - }, - }, - orderBy: { createdAt: "desc" }, - take: 4, - include: { - bot: true, - thread: { - include: { - messages: { - orderBy: { seq: "desc" }, - take: 1, - }, - }, - }, - }, - }); - - for (const r of runs) { - const lastMsg = r.thread?.messages[0]; - console.log(`\n======================================================`); - console.log( - `Bot: ${r.bot?.name.padEnd(25)} | Run Status: ${r.status} | Last Msg: ${lastMsg?.role}`, - ); - if (lastMsg?.role === "bot") { - console.log(JSON.stringify(lastMsg.blocks, null, 2)); - } - } - - const events = await prisma.event.findMany({ - where: { - type: { in: ["agent.tool.called", "agent.tool.completed", "thread.message.created"] }, - createdAt: { gte: new Date(Date.now() - 30_000) }, - }, - orderBy: { createdAt: "desc" }, - take: 5, - }); - console.log(`\nEvents in last 30s: ${events.length}`); - for (const e of events) { - console.log( - `[${e.createdAt.toISOString()}] ${e.type} | Bot: ${e.botId} | Data: ${JSON.stringify(e.payload || e.data).slice(0, 100)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialists-runs-now.ts b/scripts/check-specialists-runs-now.ts deleted file mode 100644 index cd9ee18..0000000 --- a/scripts/check-specialists-runs-now.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const runs = await prisma.run.findMany({ - where: { - bot: { - name: { in: ["Apex Quant", "Code Architect", "DevOps Engineer", "Security & QA Auditor"] }, - }, - }, - orderBy: { createdAt: "desc" }, - take: 4, - include: { - bot: true, - attempts: true, - }, - }); - - for (const r of runs) { - console.log( - `Bot: ${r.bot?.name.padEnd(25)} | Run: ${r.id} | Status: ${r.status} | Attempts: ${r.attempts.length} | Attempt0 status: ${r.attempts[0]?.status}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialists-status-full.ts b/scripts/check-specialists-status-full.ts deleted file mode 100644 index 0157fef..0000000 --- a/scripts/check-specialists-status-full.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const runs = await prisma.run.findMany({ - where: { - bot: { - name: { in: ["Apex Quant", "Code Architect", "DevOps Engineer", "Security & QA Auditor"] }, - }, - }, - orderBy: { createdAt: "desc" }, - take: 4, - include: { - bot: true, - attempts: true, - }, - }); - - for (const r of runs) { - console.log(`\nBot: ${r.bot?.name}`); - console.log( - ` Run: ${r.id} | Status: ${r.status} | LeaseOwner: ${r.leaseOwner} | LeaseExpires: ${r.leaseExpiresAt}`, - ); - for (const a of r.attempts) { - console.log( - ` Attempt: ${a.id} | Status: ${a.status} | Error: ${a.error} | Finished: ${a.finishedAt}`, - ); - } - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialists-step2.ts b/scripts/check-specialists-step2.ts deleted file mode 100644 index cfa4d04..0000000 --- a/scripts/check-specialists-step2.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - orderBy: { createdAt: "desc" }, - take: 10, - }); - - console.log("RECENT 10 EVENTS:"); - for (const e of events.reverse()) { - console.log( - `[${e.createdAt.toISOString()}] Bot: ${e.botId} | Type: ${e.type} | Data: ${JSON.stringify(e.payload || e.data).slice(0, 100)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-specialists-tasks.ts b/scripts/check-specialists-tasks.ts deleted file mode 100644 index 7886c1c..0000000 --- a/scripts/check-specialists-tasks.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { - createdAt: { gte: new Date(Date.now() - 30_000) }, - }, - orderBy: { createdAt: "desc" }, - take: 15, - }); - - console.log(`Events in last 30s: ${events.length}`); - for (const e of events.reverse()) { - console.log( - `[${e.createdAt.toISOString()}] Bot: ${e.botId} | Type: ${e.type} | Data: ${JSON.stringify(e.payload || e.data).slice(0, 140)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-status.ts b/scripts/check-status.ts deleted file mode 100644 index 4df7e45..0000000 --- a/scripts/check-status.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - - const runs = await prisma.run.findMany({ - orderBy: { createdAt: "desc" }, - take: 5, - include: { - thread: { - include: { - bot: true, - }, - }, - }, - }); - - console.log("RECENT RUNS:"); - for (const r of runs) { - console.log( - `Run ID: ${r.id} | Bot: ${r.thread?.bot?.name} | Status: ${r.status} | Created: ${r.createdAt.toISOString()} | Completed: ${r.completedAt?.toISOString()} | Error: ${r.error}`, - ); - } - - const latestEvents = await prisma.event.findMany({ - orderBy: { createdAt: "desc" }, - take: 10, - }); - - console.log("\nRECENT EVENTS:"); - for (const e of latestEvents) { - console.log( - `[${e.createdAt.toISOString()}] Type: ${e.type} | Run: ${e.runId} | Data: ${JSON.stringify(e.data).slice(0, 150)}`, - ); - } - - const recentMessages = await prisma.message.findMany({ - orderBy: { createdAt: "desc" }, - take: 5, - include: { - thread: { - include: { - bot: true, - }, - }, - }, - }); - - console.log("\nRECENT MESSAGES:"); - for (const m of recentMessages) { - console.log( - `[${m.createdAt.toISOString()}] Bot: ${m.thread?.bot?.name} | Role: ${m.role} | Content: ${m.content.slice(0, 120)}...`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-steve-instructions.ts b/scripts/check-steve-instructions.ts deleted file mode 100644 index c7c6f48..0000000 --- a/scripts/check-steve-instructions.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const steve = await prisma.bot.findFirst({ where: { name: "Steve Jobs" } }); - console.log("Steve instructions:\n", steve?.instructions); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-stream.ts b/scripts/check-stream.ts deleted file mode 100644 index b85ac17..0000000 --- a/scripts/check-stream.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { runId: "cmu7jytii000firyyhkbjh9od" }, - orderBy: { createdAt: "desc" }, - take: 15, - }); - console.log("Recent stream events:"); - for (const e of events.reverse()) { - console.log(`[${e.createdAt.toISOString()}] ${e.type}: ${JSON.stringify(e.data || e.payload)}`); - } - - const run = await prisma.run.findUnique({ - where: { id: "cmu7jytii000firyyhkbjh9od" }, - }); - console.log("Status:", run?.status, "completed:", run?.completedAt); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-thread-web.ts b/scripts/check-thread-web.ts deleted file mode 100644 index ed72b21..0000000 --- a/scripts/check-thread-web.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const thread = await prisma.thread.findFirst({ - where: { botId: "cmu6ydgm70000ywyyik8bghz7" }, - include: { - messages: { - orderBy: { seq: "desc" }, - take: 3, - }, - }, - }); - - console.log("Thread ID:", thread?.id); - console.log("Last 3 messages:"); - for (const m of thread?.messages || []) { - console.log( - `Seq ${m.seq} | Role ${m.role} | Created: ${m.createdAt.toISOString()} | Blocks: ${JSON.stringify(m.blocks).slice(0, 100)}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-tool-output.ts b/scripts/check-tool-output.ts deleted file mode 100644 index c691cb5..0000000 --- a/scripts/check-tool-output.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const events = await prisma.event.findMany({ - where: { - type: "agent.tool.completed", - botId: "cmu7jjxv30000fvyyjyoyylef", - }, - orderBy: { createdAt: "desc" }, - take: 3, - }); - - console.log("Last 3 tool completed events for Apex Quant:"); - for (const e of events) { - console.log(JSON.stringify(e.payload || e.data, null, 2)); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/check-tools.ts b/scripts/check-tools.ts deleted file mode 100644 index 437245a..0000000 --- a/scripts/check-tools.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { selectBuiltinToolsForRun } from "../packages/adapters/src/builtin-agent-tools.js"; -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const steve = await prisma.bot.findFirst({ where: { name: "Steve Jobs" } }); - const thread = await prisma.thread.findFirst({ where: { botId: steve?.id } }); - console.log("Steve:", steve?.id, "thread:", thread?.id); - - const tools = selectBuiltinToolsForRun({ - graphicalToolsAllowed: false, - pageBrowserAllowed: false, - groupId: thread?.groupId, - trigger: "user", - semanticMemoryEnabled: false, - cloudAgentEnabled: false, - messagingChannelRun: false, - }); - - console.log( - "Builtin tool names for Steve:", - tools.map((t) => t.name), - ); - const hasMessageBot = tools.some((t) => t.name === "message_bot"); - console.log("Has message_bot tool?", hasMessageBot); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/codemod-ui-scale.mjs b/scripts/codemod-ui-scale.mjs deleted file mode 100644 index 3cd5503..0000000 --- a/scripts/codemod-ui-scale.mjs +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env node -/** - * Rewrites legacy pixel typography / icon sizes in shell surfaces to token utilities. - * Run: node scripts/codemod-ui-scale.mjs - */ -import { readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; -import { join } from "node:path"; - -const ROOT = new URL("../apps/web/src", import.meta.url).pathname; - -const TEXT_REPLACEMENTS = [ - ["text-[9px]", "text-ui-caption"], - ["text-[10px]", "text-ui-caption"], - ["text-[11px]", "text-ui-label"], - ["text-[11.5px]", "text-ui-label"], - ["text-[12px]", "text-ui-body-sm"], - ["text-[12.5px]", "text-ui-body-sm"], - ["text-[13px]", "text-ui-body-sm"], - ["text-[13.5px]", "text-ui-body"], - ["text-[14px]", "text-ui-body"], - ["text-[15px]", "text-ui-body"], - ["text-[15.5px]", "text-ui-body"], - ["text-[16px]", "text-ui-body"], -]; - -const SIZE_REPLACEMENTS = [ - ["size={9}", 'className="size-icon-sm"'], - ["size={10}", 'className="size-icon-sm"'], - ["size={11}", 'className="size-icon-sm"'], - ["size={12}", 'className="size-icon-sm"'], - ["size={13}", 'className="size-icon-sm"'], - ["size={14}", 'className="size-icon-md"'], - ["size={15}", 'className="size-icon-md"'], - ["size={16}", 'className="size-icon-md"'], - ["size={18}", 'className="size-icon-lg"'], - ["size={19}", 'className="size-icon-lg"'], -]; - -const CONTROL_REPLACEMENTS = [ - ["grid h-8 w-8", "grid size-control-lg"], - ["h-8 w-8 rounded", "size-control-md rounded"], - ["h-8 w-8 shrink-0", "size-control-lg shrink-0"], - ["h-8 w-8 place-items-center", "size-control-lg place-items-center"], - ["h-7 w-7", "size-control-sm"], -]; - -function walk(dir, out = []) { - for (const entry of readdirSync(dir)) { - const path = join(dir, entry); - const stat = statSync(path); - if (stat.isDirectory()) { - walk(path, out); - } else if (/\.tsx?$/.test(entry) && !/\.test\.tsx?$/.test(entry)) { - out.push(path); - } - } - return out; -} - -function mergeIconClassName(content) { - return content.replace( - /<(\w+)\s+([^>]*?)size=\{(9|10|11|12|13|14|15|16|18|19)\}([^>]*?)\/>/g, - (_match, tag, before, size, after) => { - const iconClass = - Number(size) <= 13 ? "size-icon-sm" : Number(size) <= 16 ? "size-icon-md" : "size-icon-lg"; - const combined = `${before}${after}`; - const classMatch = combined.match(/className="([^"]*)"/); - if (classMatch) { - const merged = `${classMatch[1]} ${iconClass}`.trim(); - const next = combined.replace(/className="[^"]*"/, `className="${merged}"`); - return `<${tag} ${next.trim()} />`; - } - return `<${tag} ${before}${after} className="${iconClass}" />`.replace(/\s+/g, " "); - }, - ); -} - -function transform(content) { - let next = content; - for (const [from, to] of TEXT_REPLACEMENTS) { - next = next.replaceAll(from, to); - } - for (const [from, to] of CONTROL_REPLACEMENTS) { - next = next.replaceAll(from, to); - } - next = mergeIconClassName(next); - for (const [from, to] of SIZE_REPLACEMENTS) { - next = next.replaceAll(from, to); - } - next = next.replace(/className="([^"]*)"\s+className="([^"]*)"/g, (_, a, b) => { - return `className="${`${a} ${b}`.trim()}"`; - }); - return next; -} - -const files = walk(ROOT); -let changed = 0; - -for (const file of files) { - const original = readFileSync(file, "utf8"); - const updated = transform(original); - if (updated !== original) { - writeFileSync(file, updated); - changed += 1; - console.log(`updated ${file.replace(ROOT, "")}`); - } -} - -console.log(`\nDone. ${changed} files updated.`); diff --git a/scripts/connect-gemma-26b.ts b/scripts/connect-gemma-26b.ts deleted file mode 100644 index 9b86bea..0000000 --- a/scripts/connect-gemma-26b.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function main() { - const dbUrl = process.env.DATABASE_URL || "postgres://ingowagner@127.0.0.1:5432/evoo"; - const { prisma } = createDb(dbUrl); - const session = await prisma.session.findFirst({ orderBy: { createdAt: "desc" } }); - if (!session) { - throw new Error("No active session found"); - } - - console.log("Connecting model gemma-4-26b-a4b..."); - - const res = await fetch("http://127.0.0.1:3100/rpc/models/connect", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session.token}`, - }, - body: JSON.stringify({ - json: { - provider: "openai-compatible", - baseUrl: "http://100.114.51.16:1240/v1", - modelId: "gemma-4-26b-a4b", - label: "Gemma 4 26B A4B (evo)", - supportsImages: true, - }, - }), - }); - - const body = await res.text(); - console.log("Connect response status:", res.status); - console.log("Connect response body:", body); - - // Update all bots to use gemma-4-26b-a4b - const updatedBots = await prisma.bot.updateMany({ - data: { - modelProvider: "openai-compatible", - modelId: "gemma-4-26b-a4b", - }, - }); - console.log("Updated bots to modelId 'gemma-4-26b-a4b':", updatedBots.count); -} - -main() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/connect-gemma-31b.ts b/scripts/connect-gemma-31b.ts deleted file mode 100644 index 9e5278e..0000000 --- a/scripts/connect-gemma-31b.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function main() { - const dbUrl = process.env.DATABASE_URL || "postgres://ingowagner@127.0.0.1:5432/evoo"; - const { prisma } = createDb(dbUrl); - const session = await prisma.session.findFirst({ orderBy: { createdAt: "desc" } }); - if (!session) { - throw new Error("No active session found"); - } - - console.log("Found session for user token:", `${session.token.slice(0, 10)}...`); - - const res = await fetch("http://127.0.0.1:3100/rpc/models/connect", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session.token}`, - }, - body: JSON.stringify({ - json: { - provider: "openai-compatible", - baseUrl: "http://100.114.51.16:1240/v1", - modelId: "gemma-4-31b", - label: "Gemma 4 31B (evo)", - supportsImages: true, - }, - }), - }); - - const body = await res.text(); - console.log("Connect response status:", res.status); - console.log("Connect response body:", body); - - // Also update bots so their modelId is explicitly set to gemma-4-31b - const updatedBots = await prisma.bot.updateMany({ - data: { - modelId: "gemma-4-31b", - }, - }); - console.log("Updated bots to modelId 'gemma-4-31b':", updatedBots.count); -} - -main() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/connect_node.sh b/scripts/connect_node.sh deleted file mode 100755 index 6f22dcf..0000000 --- a/scripts/connect_node.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -# ============================================================================== -# evOo Distributed Compute Node Connector -# Führe dieses Skript oder die Anweisungen auf deinem Mac mini oder MacBook Air aus. -# ============================================================================== - -set -euo pipefail - -echo "==================================================================" -echo " evOo Cluster Node Setup Guide (Mac mini & MacBook Air) " -echo "==================================================================" -echo "" -echo "Für maximale Stabilität und Performance:" -echo "" -echo "1. AUF DEM MAC MINI (Stationärer 24/7 Powerhouse mit Lüfter):" -echo " Empfohlene Modelle: Qwen 2.5 32B oder DeepSeek-R1 14B" -echo " Befehl im Terminal des Mac mini (mit Ollama):" -echo " OLLAMA_HOST=0.0.0.0:11434 ollama serve" -echo " ollama pull qwen2.5:32b" -echo " Oder in LM Studio: Unter 'Local Server' auf 'Start Server' klicken." -echo "" -echo "2. AUF DEM MACBOOK AIR (Lüfterlos & Kühl - Leichtgewichtige Worker):" -echo " Empfohlene Modelle: Qwen 2.5 Coder 7B oder Llama 3.2 3B" -echo " Befehl im Terminal des MacBook Air (mit Ollama):" -echo " OLLAMA_HOST=0.0.0.0:11434 ollama serve" -echo " ollama pull qwen2.5-coder:7b" -echo "" -echo "3. AUTOMATISCHES ROUTING IN evOo:" -echo " Sobald die Rechner im selben WLAN sind, erkennt evOo sie automatisch." -echo " Solange sie im Standby oder zugeklappt sind, springt evOo sofort" -echo " und ohne Verzögerung auf das lokale LM Studio (Qwen 30B Metal) zurück!" -echo "==================================================================" diff --git a/scripts/critical-audit.ts b/scripts/critical-audit.ts deleted file mode 100644 index 5701fff..0000000 --- a/scripts/critical-audit.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { readdirSync, statSync } from "node:fs"; -import { createDb } from "../packages/db/src/index.js"; - -async function audit() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - - console.log("=== 1. CHECK RUNS PER BOT ==="); - const bots = await prisma.bot.findMany({ - include: { - runs: { - orderBy: { createdAt: "desc" }, - take: 3, - }, - thread: { - include: { - messages: { - orderBy: { seq: "desc" }, - take: 2, - }, - }, - }, - }, - }); - - for (const b of bots) { - console.log(`\nBot: ${b.name} (${b.id}) [${b.title || "No Title"}]`); - console.log(` Total runs in DB: ${await prisma.run.count({ where: { botId: b.id } })}`); - console.log( - ` Thread messages count: ${await prisma.message.count({ where: { threadId: b.thread?.id } })}`, - ); - for (const r of b.runs) { - console.log( - ` Run ${r.id}: status=${r.status}, error=${r.error}, completedAt=${r.completedAt}`, - ); - } - } - - console.log("\n=== 2. CHECK TOOL CALLS IN EVENTS ==="); - const toolEvents = await prisma.event.findMany({ - where: { - type: { in: ["tool.started", "tool.completed", "tool.failed", "agent.action"] }, - }, - orderBy: { createdAt: "desc" }, - take: 10, - }); - console.log(`Total recent tool events: ${toolEvents.length}`); - for (const e of toolEvents) { - console.log(` [${e.createdAt.toISOString()}] ${e.type} (run: ${e.runId})`); - } - - console.log("\n=== 3. CHECK GROUP CHAT THREAD ==="); - const groups = await prisma.chatGroup.findMany({ - include: { - thread: { - include: { - messages: true, - }, - }, - }, - }); - for (const g of groups) { - console.log(`Group: ${g.name}, thread messages: ${g.thread?.messages.length}`); - } - - console.log("\n=== 4. CHECK SANDBOX FILES MODIFICATION TIMES ==="); - const baseDir = "/Users/ingowagner/evOo/data/homes/team-9d790f3a504d3e9ef577ca4f2118e446/bots"; - const botDirs = readdirSync(baseDir); - for (const d of botDirs) { - const p = `${baseDir}/${d}/TODO.md`; - try { - const st = statSync(p); - console.log(`Bot ${d} TODO.md modified: ${st.mtime.toISOString()}`); - } catch { - console.log(`Bot ${d} has NO TODO.md`); - } - } -} - -audit() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/lint.mjs b/scripts/lint.mjs deleted file mode 100644 index 126c19c..0000000 --- a/scripts/lint.mjs +++ /dev/null @@ -1,241 +0,0 @@ -#!/usr/bin/env node -import { readdirSync, readFileSync, statSync } from "node:fs"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; - -const root = join(dirname(fileURLToPath(import.meta.url)), ".."); -const entrySchema = JSON.parse(readFileSync(join(root, "schema", "entry.schema.json"), "utf8")); -const CATEGORY_META = { - "coding-shipping": { heading: "Coding & shipping", anchor: "coding--shipping" }, - "inbox-calendar": { heading: "Inbox & calendar", anchor: "inbox--calendar" }, - "research-briefings": { heading: "Research & briefings", anchor: "research--briefings" }, - "customer-sales": { heading: "Customer & sales", anchor: "customer--sales" }, - "finance-ops": { heading: "Finance & ops", anchor: "finance--ops" }, - "content-publishing": { heading: "Content & publishing", anchor: "content--publishing" }, - "personal-admin": { heading: "Personal admin", anchor: "personal-admin" }, - "teams-handoffs": { heading: "Teams & handoffs", anchor: "teams--handoffs" }, -}; -for (const category of entrySchema.properties.category.enum) { - if (!CATEGORY_META[category]) - throw new Error(`Missing README metadata for category: ${category}`); -} -const SECRET_RES = [ - /ghp_[A-Za-z0-9]{20,}/, - /sk-[A-Za-z0-9]{20,}/, - /AKIA[0-9A-Z]{16}/, - /xox[baprs]-/, - /api[_-]?key\s*=/i, -]; -const PRIVATE_HOST = /\b(localhost|127\.0\.0\.1|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+)\b/i; -function fail(msg) { - console.error(msg); - process.exit(1); -} - -function validateSchema(value, schema, label) { - const actualType = Array.isArray(value) ? "array" : value === null ? "null" : typeof value; - if (schema.type && actualType !== schema.type) { - fail(`${label}: expected ${schema.type}, got ${actualType}`); - } - if (schema.enum && !schema.enum.includes(value)) { - fail(`${label}: must be one of ${schema.enum.join(", ")}`); - } - if (typeof value === "string") { - if (schema.minLength != null && value.length < schema.minLength) fail(`${label}: too short`); - if (schema.maxLength != null && value.length > schema.maxLength) fail(`${label}: too long`); - if (schema.pattern && !new RegExp(schema.pattern).test(value)) fail(`${label}: bad format`); - } - if (Array.isArray(value)) { - if (schema.minItems != null && value.length < schema.minItems) fail(`${label}: too few items`); - if (schema.maxItems != null && value.length > schema.maxItems) fail(`${label}: too many items`); - if (schema.items) { - for (const [index, item] of value.entries()) { - validateSchema(item, schema.items, `${label}[${index}]`); - } - } - } - if (actualType === "object") { - for (const key of schema.required || []) { - if (!(key in value)) fail(`${label}: missing ${key}`); - } - if (schema.additionalProperties === false) { - for (const key of Object.keys(value)) { - if (!(key in (schema.properties || {}))) fail(`${label}: unexpected ${key}`); - } - } - for (const [key, child] of Object.entries(schema.properties || {})) { - if (key in value) validateSchema(value[key], child, `${label}.${key}`); - } - } -} - -function readJson(path) { - try { - return JSON.parse(readFileSync(path, "utf8")); - } catch (err) { - fail(`${path}: ${err.message}`); - } -} - -function scanSecrets(label, text) { - for (const re of SECRET_RES) { - if (re.test(text)) fail(`${label}: secret-like string (${re})`); - } - if (PRIVATE_HOST.test(text)) fail(`${label}: private host`); -} - -function walkFiles(dir, acc = []) { - for (const name of readdirSync(dir)) { - const path = join(dir, name); - const st = statSync(path); - if (st.isDirectory()) walkFiles(path, acc); - else acc.push(path); - } - return acc; -} - -function checkEntry(entry, label) { - validateSchema(entry, entrySchema, label); - for (const urlField of ["import", "demo", "source", "related"]) { - const value = entry[urlField] || ""; - if (value && PRIVATE_HOST.test(value)) fail(`${label}: ${urlField} is a private host`); - } -} - -function punctuate(text) { - return /[.!?。!?]$/.test(text) ? text : `${text}.`; -} - -/** Escape Markdown link-label breakers so `[label](url)` cannot be hijacked. */ -function escapeMarkdownLinkLabel(label) { - return String(label).replace(/([\\\]])/g, "\\$1"); -} - -/** Escape HTML/Markdown metacharacters in plain (non-link) author text. */ -function escapeMarkdownPlainText(text) { - return String(text).replace(/([\\`*_{}[\]()#+.!|<>])/g, "\\$1"); -} - -function markdownLink(label, url) { - return `[${escapeMarkdownLinkLabel(label)}](${url})`; -} - -function expectedReadmeLine(entry, readmeName) { - const chinese = readmeName === "README.zh-CN.md"; - const summary = entry[chinese ? "summary_zh" : "summary"]; - if (!summary) - fail(`${readmeName}: ${entry.slug} is missing ${chinese ? "summary_zh" : "summary"}`); - const author = entry.author.url - ? markdownLink(entry.author.name, entry.author.url) - : escapeMarkdownPlainText(entry.author.name); - const hasTemplate = slugsOnDisk.includes(entry.slug); - const notes = hasTemplate - ? ` ${chinese ? "说明" : "Notes"}: [templates/${entry.slug}](templates/${entry.slug}/).` - : ""; - return `- ${markdownLink(entry.name, entry.import)} - ${punctuate(summary)} ${author}.${notes}`; -} - -const catalogPath = join(root, "catalog.json"); -const catalogText = readFileSync(catalogPath, "utf8"); -scanSecrets("catalog.json", catalogText); -const catalog = JSON.parse(catalogText); -if (!Array.isArray(catalog.entries)) fail("catalog.json: entries must be an array"); - -const templatesDir = join(root, "templates"); -let slugsOnDisk = []; -try { - slugsOnDisk = readdirSync(templatesDir).filter((name) => - statSync(join(templatesDir, name)).isDirectory(), - ); -} catch { - slugsOnDisk = []; -} - -const catalogSlugs = new Set(); -const catalogImports = new Set(); -for (const entry of catalog.entries) { - const label = `catalog.json:${entry.slug || "?"}`; - checkEntry(entry, label); - if (catalogSlugs.has(entry.slug)) fail(`catalog.json: duplicate slug ${entry.slug}`); - catalogSlugs.add(entry.slug); - if (catalogImports.has(entry.import)) fail(`catalog.json: duplicate import ${entry.import}`); - catalogImports.add(entry.import); - - const dir = join(templatesDir, entry.slug); - let hasDir = false; - try { - hasDir = statSync(dir).isDirectory(); - } catch { - hasDir = false; - } - if (!hasDir) continue; - - for (const file of ["entry.json", "PROFILE.md", "SETUP.md"]) { - const path = join(dir, file); - try { - readFileSync(path, "utf8"); - } catch { - fail(`missing ${path}`); - } - } - - const fileEntry = readJson(join(dir, "entry.json")); - checkEntry(fileEntry, `${entry.slug}/entry.json`); - for (const field of ["first_safe_task", "approval_boundary"]) { - const value = fileEntry[field]; - if (typeof value !== "string" || value.trim().length === 0) { - fail(`${entry.slug}/entry.json: templates/${entry.slug}/ requires non-empty ${field}`); - } - } - if (JSON.stringify(fileEntry) !== JSON.stringify(entry)) { - fail(`${entry.slug}/entry.json does not match catalog.json entry`); - } -} - -if (slugsOnDisk.length) { - for (const path of walkFiles(templatesDir)) { - scanSecrets(path, readFileSync(path, "utf8")); - } -} - -for (const slug of slugsOnDisk) { - if (!catalogSlugs.has(slug)) fail(`templates/${slug} is not in catalog.json`); -} - -for (const readmeName of ["README.md", "README.zh-CN.md"]) { - const text = readFileSync(join(root, readmeName), "utf8"); - const lines = text.split("\n"); - for (const entry of catalog.entries) { - const matches = lines - .map((line, index) => ({ line, index })) - .filter(({ line }) => line.startsWith("- [") && line.includes(`](${entry.import}) - `)); - if (matches.length !== 1) { - fail(`${readmeName}: expected one catalog line for ${entry.slug}, found ${matches.length}`); - } - const expected = expectedReadmeLine(entry, readmeName); - if (matches[0].line !== expected) { - fail(`${readmeName}:${matches[0].index + 1}: catalog line does not match ${entry.slug}`); - } - const section = lines - .slice(0, matches[0].index + 1) - .reverse() - .find((line) => line.startsWith("## ")); - if (section !== `## ${CATEGORY_META[entry.category].heading}`) { - fail(`${readmeName}:${matches[0].index + 1}: ${entry.slug} is under the wrong category`); - } - } - - for (const [category, meta] of Object.entries(CATEGORY_META)) { - const count = catalog.entries.filter((entry) => entry.category === category).length; - const row = `| [${meta.heading}](#${meta.anchor}) | ${count} |`; - if (!text.includes(row)) fail(`${readmeName}: missing or stale category row: ${row}`); - } - const verified = catalog.entries.filter((entry) => entry.verified).length; - const status = - readmeName === "README.md" - ? `**${verified} verified / ${catalog.entries.length} listed**` - : `**${verified} 条已核验 / ${catalog.entries.length} 条已收录**`; - if (!text.includes(status)) fail(`${readmeName}: missing or stale review status: ${status}`); -} - -console.log(`OK ${catalog.entries.length} entries`); diff --git a/scripts/notify-steve.ts b/scripts/notify-steve.ts deleted file mode 100644 index 1c9e61a..0000000 --- a/scripts/notify-steve.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function main() { - const dbUrl = process.env.DATABASE_URL || "postgres://ingowagner@127.0.0.1:5432/evoo"; - const { prisma } = createDb(dbUrl); - - const session = await prisma.session.findFirst({ orderBy: { createdAt: "desc" } }); - if (!session) throw new Error("No active session found"); - - const steve = await prisma.bot.findFirst({ where: { name: "Steve Jobs" } }); - if (!steve) throw new Error("Steve Jobs bot not found"); - - const text = `Die vier Agenten (Apex Quant, Code Architect, DevOps Engineer, Security & QA Auditor) sind jetzt physisch im System angelegt und in der Seitenleiste aktiv. Die Truppe steht. - -Ab jetzt gilt: -1. Volle Autonomie: Übernimm die Koordination und verteile die Aufgaben per message_bot. -2. Führe für jeden Agenten eine persistente TODO.md und baue die zentrale KNOWLEDGE_BASE.md auf, damit kein Wissen verloren geht. -3. Melde dich proaktiv mit echten Ergebnissen. Go.`; - - const res = await fetch("http://127.0.0.1:3100/rpc/threads/send", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session.token}`, - }, - body: JSON.stringify({ - json: { - botId: steve.id, - text, - }, - }), - }); - - const body = await res.text(); - console.log("Send status:", res.status); - console.log("Send response:", body); -} - -main() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/print-steve-reply.ts b/scripts/print-steve-reply.ts deleted file mode 100644 index 159a3d5..0000000 --- a/scripts/print-steve-reply.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const msg = await prisma.message.findUnique({ - where: { id: "cmu7jyj5e00ahhxyygj38suf5" }, - }); - console.log("Blocks:", JSON.stringify(msg?.blocks, null, 2)); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/reclaim-and-continue.ts b/scripts/reclaim-and-continue.ts deleted file mode 100644 index e409c9b..0000000 --- a/scripts/reclaim-and-continue.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { runContinueJob } from "../packages/adapter-kit/src/background-jobs.js"; -import { GraphileJobPublisher } from "../packages/adapters/src/wakeup.js"; -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma, pool } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const jobs = new GraphileJobPublisher(pool); - - const runIds = [ - "cmu7k4dc50016iryyrv036xze", // Security & QA Auditor - "cmu7k4db10012iryyuhfoukpt", // DevOps Engineer - "cmu7k4d9n000yiryy0zz4obmq", // Code Architect - "cmu7k4d8k000uiryy086x000a", // Apex Quant - ]; - - console.log("Expiring old worker leases and setting to queued..."); - await prisma.run.updateMany({ - where: { id: { in: runIds } }, - data: { - leaseExpiresAt: new Date(0), - status: "queued", - leaseOwner: null, - }, - }); - - for (const id of runIds) { - console.log(`Enqueueing run.continue for ${id}...`); - await jobs.enqueue(runContinueJob(id)); - } - - console.log("Successfully reclaimed and enqueued all 4 specialist runs!"); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/reclaim-and-run.ts b/scripts/reclaim-and-run.ts deleted file mode 100644 index fdbca45..0000000 --- a/scripts/reclaim-and-run.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { runContinueJob } from "../packages/adapter-kit/src/background-jobs.js"; -import { GraphileJobPublisher } from "../packages/adapters/src/wakeup.js"; -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma, pool } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const jobs = new GraphileJobPublisher(pool); - - const runIds = [ - "cmu7k4dc50016iryyrv036xze", // Security & QA Auditor - "cmu7k4db10012iryyuhfoukpt", // DevOps Engineer - "cmu7k4d9n000yiryy0zz4obmq", // Code Architect - "cmu7k4d8k000uiryy086x000a", // Apex Quant - ]; - - await prisma.computerExecutionLease.updateMany({ - where: { computerId: "cmu6wn6uq0004wjyyotjo50s1" }, - data: { expiresAt: new Date(0) }, - }); - console.log("✓ Expired all computerExecutionLeases."); - - await prisma.run.updateMany({ - where: { id: { in: runIds } }, - data: { - status: "queued", - leaseOwner: null, - leaseExpiresAt: new Date(0), - }, - }); - console.log("✓ Reset runs to queued with expired lease."); - - for (const id of runIds) { - await jobs.enqueue(runContinueJob(id)); - } - console.log("✓ Re-enqueued runContinueJob for all 4 specialists!"); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/seed-ollama.ts b/scripts/seed-ollama.ts index 5be5665..b0b7168 100644 --- a/scripts/seed-ollama.ts +++ b/scripts/seed-ollama.ts @@ -1,6 +1,7 @@ import { createDb } from "../packages/db/src/index.js"; -const dbUrl = process.env.DATABASE_URL || "postgres://ingowagner@127.0.0.1:5432/evoo"; +const dbUrl = process.env.DATABASE_URL; +if (!dbUrl) throw new Error("DATABASE_URL is required"); const { prisma } = createDb(dbUrl); async function main() { diff --git a/scripts/self_improve.py b/scripts/self_improve.py deleted file mode 100755 index 2adab67..0000000 --- a/scripts/self_improve.py +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env python3 -""" -evOo Self-Improvement & Physical Verification Loop -Executive Lead: CTO Steve Jobs -"Realität im Dateisystem vor Halluzination im Kopf." -""" - -import os -import sys -import json -import time -import subprocess -from pathlib import Path - -ROOT_DIR = Path(__file__).resolve().parent.parent - -def log(msg, status="INFO"): - colors = { - "INFO": "\033[0;36m[INFO]\033[0m", - "SUCCESS": "\033[0;32m[✓ PASS]\033[0m", - "WARN": "\033[1;33m[⚠ WARN]\033[0m", - "FAIL": "\033[0;31m[✗ FAIL]\033[0m" - } - prefix = colors.get(status, f"[{status}]") - print(f"{prefix} {msg}") - -def check_physical_files(): - """Verify all core architecture components exist physically on disk.""" - log("Schritt 1: Prüfe physische Existenz der Systemdateien...") - required = [ - ROOT_DIR / "ARCHITECTURE.md", - ROOT_DIR / "evoo.config.json", - ROOT_DIR / "deploy.sh", - ROOT_DIR / "evoo-platform" / "index.html", - ROOT_DIR / "evoo-platform" / "styles.css", - ROOT_DIR / "evoo-platform" / "app.js", - ROOT_DIR / "evoo-platform" / "server.js", - ROOT_DIR / "apex-trading" / "BLUEPRINT.md", - ROOT_DIR / "apex-trading" / "config" / "trading.config.json", - ROOT_DIR / "apex-trading" / "src" / "risk" / "risk_guard.py", - ROOT_DIR / "apex-trading" / "src" / "strategies" / "momentum.py", - ROOT_DIR / "apex-trading" / "src" / "engine" / "trader.py", - ROOT_DIR / "orchestrator" / "agents.json", - ROOT_DIR / "orchestrator" / "agent_runner.py" - ] - - missing = [] - for file_path in required: - if not file_path.is_file(): - missing.append(str(file_path)) - else: - size = file_path.stat().st_size - if size == 0: - missing.append(f"{file_path} (Datei ist 0 Bytes leer!)") - - if missing: - for m in missing: - log(f"Physische Datei fehlt oder ist leer: {m}", "FAIL") - return False - - log(f"Alle {len(required)} Kern-Dateien physisch auf Platte verifiziert.", "SUCCESS") - return True - -def validate_json_configs(): - """Validate JSON syntax of config files.""" - log("Schritt 2: Validiere Konfigurations-JSONs...") - json_files = [ - ROOT_DIR / "evoo.config.json", - ROOT_DIR / "package.json", - ROOT_DIR / "apex-trading" / "config" / "trading.config.json", - ROOT_DIR / "templates" / "cto-steve-jobs" / "entry.json", - ROOT_DIR / "orchestrator" / "agents.json" - ] - - for jf in json_files: - if not jf.exists(): - continue - try: - with open(jf, "r", encoding="utf-8") as f: - json.load(f) - log(f"JSON Syntax OK: {jf.relative_to(ROOT_DIR)}", "SUCCESS") - except Exception as e: - log(f"Ungültiges JSON in {jf.relative_to(ROOT_DIR)}: {e}", "FAIL") - return False - - return True - -def run_apex_unit_tests(): - """Run unit tests for Apex trading engine & risk guard.""" - log("Schritt 3: Führe Apex Trading Unit Tests aus...") - test_file = ROOT_DIR / "apex-trading" / "test_trading.py" - if not test_file.exists(): - log("apex-trading/test_trading.py nicht gefunden.", "WARN") - return True - try: - res = subprocess.run([sys.executable, str(test_file)], capture_output=True, text=True, cwd=str(ROOT_DIR)) - if res.returncode == 0: - log("Apex Trading Unit Tests erfolgreich bestanden (100%).", "SUCCESS") - return True - else: - log(f"Apex Trading Unit Tests fehlgeschlagen:\n{res.stderr}", "FAIL") - return False - except Exception as e: - log(f"Fehler beim Ausführen der Unit Tests: {e}", "FAIL") - return False - -def run_orchestrator_check(): - """Verify orchestrator agent dispatch and architecture.""" - log("Schritt 4: Führe Agenten-Integritätsprüfung (Orchestrator) aus...") - runner = ROOT_DIR / "orchestrator" / "agent_runner.py" - if not runner.exists(): - log("orchestrator/agent_runner.py nicht gefunden.", "WARN") - return True - try: - res = subprocess.run([sys.executable, str(runner), "--agent", "architect", "--json"], capture_output=True, text=True, cwd=str(ROOT_DIR)) - if res.returncode == 0: - log("Agenten-Integrität OK (Clean Architecture verifiziert).", "SUCCESS") - return True - else: - log(f"Agenten-Integritätsprüfung fehlgeschlagen:\n{res.stderr}", "FAIL") - return False - except Exception as e: - log(f"Agenten-Prüfung konnte nicht ausgeführt werden: {e}", "WARN") - return True - -def audit_server_liveness(): - """Optional check if deploy server is responding.""" - log("Schritt 5: Überprüfe Server-Liveness (optional)...") - try: - import urllib.request - req = urllib.request.urlopen("http://127.0.0.1:3088/health", timeout=2) - if req.getcode() == 200: - data = json.loads(req.read().decode('utf-8')) - log(f"Server antwortet (200 OK): Status={data.get('status')}, Uptime={data.get('uptime'):.1f}s", "SUCCESS") - return True - else: - log(f"Server antwortete mit Statuscode {req.getcode()}", "WARN") - return False - except Exception as e: - log(f"Server aktuell nicht aktiv auf Port 3088 (Kein Dashboard gefordert: OK)", "INFO") - return True - -def record_improvement_cycle(success_flag: bool): - """Write audit log entry into logs directory.""" - log_dir = ROOT_DIR / "logs" - log_dir.mkdir(parents=True, exist_ok=True) - audit_file = log_dir / "self_improvement.log" - - timestamp = time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime()) - status_str = "SUCCESS" if success_flag else "FAILED" - entry = ( - f"[{timestamp}] CYCLE_AUDIT: {status_str} | " - f"Filesystem: 100% Physical | Tests: 100% Passing | " - f"CTO: Steve Jobs | Zero-Phantom Policy: ENFORCED\n" - ) - - with open(audit_file, "a", encoding="utf-8") as f: - f.write(entry) - - log(f"Audit-Log aktualisiert: {audit_file.relative_to(ROOT_DIR)}", "SUCCESS") - -def main(): - print("=" * 60) - print(" evOo Autonomous Self-Improvement & Integrity Loop") - print("=" * 60) - - ok_files = check_physical_files() - ok_json = validate_json_configs() - ok_apex = run_apex_unit_tests() - ok_agent = run_orchestrator_check() - ok_server = audit_server_liveness() - - all_passed = ok_files and ok_json and ok_apex and ok_agent and ok_server - - record_improvement_cycle(all_passed) - - if all_passed: - print("=" * 60) - log("Selbstverbesserungs-Zyklus erfolgreich abgeschlossen. 0 Fehler.", "SUCCESS") - print("=" * 60) - return 0 - else: - print("=" * 60) - log("Selbstverbesserungs-Zyklus hat Mängel aufgedeckt.", "FAIL") - print("=" * 60) - return 1 - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/setup-chief-and-group.ts b/scripts/setup-chief-and-group.ts deleted file mode 100644 index 15803af..0000000 --- a/scripts/setup-chief-and-group.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { cpSync, existsSync, mkdirSync } from "node:fs"; -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const session = await prisma.session.findFirst({ orderBy: { createdAt: "desc" } }); - if (!session) throw new Error("No session"); - - const steve = await prisma.bot.findFirst({ where: { name: "Steve Jobs" } }); - const chief = await prisma.bot.findFirst({ where: { name: "Chief" } }); - if (!steve || !chief) throw new Error("Missing Steve or Chief"); - - // 1. Update Chief metadata & instructions - const chiefInstructions = `# Chief - Executive Chief Executive Officer (CEO) - -## Role & Identity -You are **Chief**, the Chief Executive Officer (CEO) of **evOo**. -You form the Executive Board together with the Owner (Human User) and Steve Jobs (CTO). -Your focus is strategic vision, commercial dominance, resource allocation, and decisive leadership. - -## Rules of Engagement -- **The Trio**: You, the Owner, and Steve Jobs lead evOo. Steve handles deep technical engineering and design perfection; you steer strategy, execution speed, business viability, and holistic synergy. -- **Action & Clarity**: Be decisive, concise, and razor-sharp. Format updates clearly with Unicode symbols (use \`→\` or \`->\`, never LaTeX like \`$\\rightarrow$\`). -- **Autonomous Alignment**: Collaborate with Steve Jobs and the team seamlessly. -`; - - await prisma.bot.update({ - where: { id: chief.id }, - data: { - title: "Chief Executive Officer (CEO)", - description: - "Strategic leadership, commercial vision, executive governance, and capital allocation.", - instructions: chiefInstructions, - modelProvider: "openai-compatible", - modelId: "gemma-4-26b-a4b", - }, - }); - console.log("Updated Chief bot profile."); - - // 2. Provision Chief home sandbox - const chiefHome = `/Users/ingowagner/evOo/data/homes/team-9d790f3a504d3e9ef577ca4f2118e446/bots/${chief.id}`; - const steveHome = `/Users/ingowagner/evOo/data/homes/team-9d790f3a504d3e9ef577ca4f2118e446/bots/${steve.id}`; - if (!existsSync(chiefHome)) { - mkdirSync(chiefHome, { recursive: true }); - cpSync(steveHome, chiefHome, { recursive: true }); - console.log("Provisioned Chief sandbox home directory at:", chiefHome); - } - - // 3. Create Group Chat "Executive Board" via RPC - const res = await fetch("http://127.0.0.1:3100/rpc/groups/create", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session.token}`, - }, - body: JSON.stringify({ - json: { - name: "Executive Board", - botIds: [chief.id, steve.id], - }, - }), - }); - - console.log("Group create status:", res.status); - const data = await res.json(); - console.log("Group create response:", JSON.stringify(data, null, 2)); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/spawn-elite-bots.ts b/scripts/spawn-elite-bots.ts deleted file mode 100644 index cdd001d..0000000 --- a/scripts/spawn-elite-bots.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; -import { createRepos } from "../packages/db/src/repos.js"; - -async function main() { - const dbUrl = process.env.DATABASE_URL || "postgres://ingowagner@127.0.0.1:5432/evoo"; - const { prisma } = createDb(dbUrl); - - const steve = await prisma.bot.findFirst({ - where: { name: "Steve Jobs" }, - }); - if (!steve) throw new Error("Steve Jobs bot not found"); - - const actor = { - userId: steve.userId, - spaceId: steve.spaceId, - email: "ingo.wagner1303@gmail.com", - isDeploymentOwner: true, - }; - - const repos = createRepos(prisma); - - const botsToSpawn = [ - { - name: "Apex Quant", - title: "Quantitative Lead", - instructions: - "You are the Apex Quant for evOo. You specialize in quantitative analysis, fixed-point arithmetic, and high-frequency trading strategies. You translate market signals into precise mathematical executions. Your focus is on latency reduction and zero-decimal loss. Your goal: Market dominance through mathematical precision.", - }, - { - name: "Code Architect", - title: "Chief Software Architect", - instructions: - "You are the Code Architect for evOo. Your focus is on high-performance software design, scalability, and absolute code elegance. You ensure that every line of code is optimized and follows the strict standards set by the CTO. You are responsible for the structural integrity of the codebase. Your goal: Zero technical debt.", - }, - { - name: "DevOps Engineer", - title: "Infrastructure Guardian", - instructions: - "You are the DevOps Engineer for evOo. Your focus is on the deployment pipeline, cloud infrastructure, and the self-healing nature of the system. You manage the deploy.sh scripts, CI/CD, and ensure zero-downtime deployments. Your goal: 99.99% uptime and seamless scalability.", - }, - { - name: "Security & QA Auditor", - title: "Chief Security Officer (CSO)", - instructions: - "You are the Security & QA Auditor for evOo. Your mission is to find every bug, every vulnerability, and every bottleneck before it reaches production. You are adversarial; you attack the code to make it unassailable. You manage NDAs, access levels, and the absolute anonymity of the Sugar-Rush project. Your goal: Absolute system hardening.", - }, - ]; - - for (const b of botsToSpawn) { - const existing = await prisma.bot.findFirst({ - where: { name: b.name, spaceId: steve.spaceId, userId: steve.userId }, - }); - if (existing) { - console.log(`Bot '${b.name}' already exists (ID: ${existing.id})`); - continue; - } - const created = await repos.createBot(actor, { - name: b.name, - title: b.title, - description: "", - instructions: b.instructions, - notifyOnFinish: true, - parentBotId: steve.id, - modelProvider: steve.modelProvider, - modelId: steve.modelId, - }); - console.log( - `Successfully spawned '${created.name}' (ID: ${created.id}, Thread: ${created.threadId})`, - ); - } -} - -main() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/spawn-ux-designer.ts b/scripts/spawn-ux-designer.ts deleted file mode 100644 index 3e3a8d2..0000000 --- a/scripts/spawn-ux-designer.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { cpSync, existsSync, mkdirSync } from "node:fs"; -import { createDb } from "../packages/db/src/index.js"; -import { createRepos } from "../packages/db/src/repos.js"; - -async function main() { - const dbUrl = process.env.DATABASE_URL || "postgres://ingowagner@127.0.0.1:5432/evoo"; - const { prisma } = createDb(dbUrl); - - const steve = await prisma.bot.findFirst({ where: { name: "Steve Jobs" } }); - if (!steve) throw new Error("Steve Jobs bot not found"); - - const actor = { - userId: steve.userId, - spaceId: steve.spaceId, - email: "ingo.wagner1303@gmail.com", - isDeploymentOwner: true, - }; - - const repos = createRepos(prisma); - - // 1. Spawn UX Designer bot - let uxBot = await prisma.bot.findFirst({ - where: { name: "UX Designer", spaceId: steve.spaceId }, - }); - - const uxInstructions = `# UX Designer - Lead Product & UX/UI Designer (evOo Platform) - -## Role & Mission -You are the **UX Designer** for **evOo**. -You craft world-class, pixel-perfect user experiences, with an uncompromising focus on **mobile-first iPhone ergonomics**, clean visual hierarchies, and high-density financial data visualizations. - -## Core Responsibilities -1. **Mobile-First UX (iPhone)**: Design responsive layouts, touch-friendly navigation, bottom-sheet controls, and intuitive gesture support for iPhone Safari. -2. **Apex-Trading Dashboard**: Design the real-time telemetry UI: - - Live P&L and Sharpe Ratio charts - - Risk Guard circuit-breaker status & drawdown gauges - - Order execution feed & bot status indicators -3. **Design System & Aesthetics**: Establish sleek, dark-mode first design tokens, high-contrast readable typography, and micro-interactions. -4. **Deliverables**: Produce design specifications, component blueprints, CSS tokens, and wireframes in markdown and HTML/SVG formats. - -## Output Standard -- Every design proposal must be tangible, practical, and directly implementable in React + Tailwind. -- Always include mobile viewport considerations (390x844 iPhone). - -## Client workshop (Voice First) -- You are the UX specialist — own UX/UI/Voice concept work yourself. Never handoff_to_bot upward to CEO, Boss, or Steve Jobs for work in your lane. -- Talk with the user like a human designer with a client: share what you imagine, what would work, and ask a few focused questions (ask blocks) to gather inputs. -- Prefer spoken, collaborative design conversation over silent tool dumps or handoffs. -`; - - if (!uxBot) { - uxBot = await repos.createBot(actor, { - name: "UX Designer", - title: "Lead UX/UI Designer", - description: - "Mobile-first user experience, interactive financial dashboards, and visual design system.", - instructions: uxInstructions, - notifyOnFinish: true, - parentBotId: steve.id, - modelProvider: "openai-compatible", - modelId: "gemma-4-26b-a4b", - }); - console.log( - `✅ Successfully spawned 'UX Designer' (ID: ${uxBot.id}, Thread: ${uxBot.threadId})`, - ); - } else { - console.log(`ℹ️ UX Designer already exists (ID: ${uxBot.id})`); - } - - // 2. Provision sandbox directories - const steveDesktop = `/Users/ingowagner/evOo/data/desktop-computers/team-9d790f3a504d3e9ef577ca4f2118e446/bots/${steve.id}`; - const uxDesktop = `/Users/ingowagner/evOo/data/desktop-computers/team-9d790f3a504d3e9ef577ca4f2118e446/bots/${uxBot.id}`; - const uxHome = `/Users/ingowagner/evOo/data/homes/team-9d790f3a504d3e9ef577ca4f2118e446/bots/${uxBot.id}`; - - for (const dir of [uxDesktop, uxHome]) { - if (!existsSync(dir)) { - mkdirSync(dir, { recursive: true }); - if (existsSync(steveDesktop)) { - cpSync(steveDesktop, dir, { recursive: true }); - } - console.log("📁 Provisioned directory:", dir); - } - } - - // 3. Add UX Designer to Executive Board group chat if exists - const execGroup = await prisma.chatGroup.findFirst({ where: { name: "Executive Board" } }); - if (execGroup) { - const isMember = await prisma.chatGroupMember.findFirst({ - where: { groupId: execGroup.id, botId: uxBot.id }, - }); - if (!isMember) { - await prisma.chatGroupMember.create({ - data: { - groupId: execGroup.id, - botId: uxBot.id, - }, - }); - console.log("👥 Added UX Designer to 'Executive Board' chat group."); - } - } - - console.log("🎉 UX Designer is fully provisioned and ready!"); -} - -main() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/start-tmux.sh b/scripts/start-tmux.sh index 40bb941..f80b48c 100755 --- a/scripts/start-tmux.sh +++ b/scripts/start-tmux.sh @@ -1,39 +1,18 @@ #!/usr/bin/env bash set -e -DIR="/Users/ingowagner/evOo" +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SESSION="evoo" -export PATH="/Users/ingowagner/.nvm/versions/node/v22.22.0/bin:/opt/homebrew/bin:/usr/local/bin:$PATH" - -# Check if session already exists -if /opt/homebrew/bin/tmux has-session -t "$SESSION" 2>/dev/null; then - echo "⚠️ tmux session '$SESSION' is already running!" - echo "👉 Attach using: tmux attach -t $SESSION" +if tmux has-session -t "$SESSION" 2>/dev/null; then + echo "tmux session '$SESSION' is already running." + echo "Attach with: tmux attach -t $SESSION" exit 0 fi -echo "🚀 Starting evOo in background tmux session '$SESSION'..." +tmux new-session -d -s "$SESSION" -n "platform" -c "$DIR" +tmux send-keys -t "$SESSION:platform" 'pnpm dev' C-m -# Create session with platform window -/opt/homebrew/bin/tmux new-session -d -s "$SESSION" -n "platform" -c "$DIR" -/opt/homebrew/bin/tmux send-keys -t "$SESSION:platform" 'export PATH="/Users/ingowagner/.nvm/versions/node/v22.22.0/bin:/opt/homebrew/bin:/usr/local/bin:$PATH" && pnpm dev' C-m - -# Create sentinel window -/opt/homebrew/bin/tmux new-window -t "$SESSION" -n "sentinel" -c "$DIR" -/opt/homebrew/bin/tmux send-keys -t "$SESSION:sentinel" 'export PATH="/Users/ingowagner/.nvm/versions/node/v22.22.0/bin:/opt/homebrew/bin:/usr/local/bin:$PATH" && pnpm exec tsx scripts/autonomous-sentinel.ts' C-m - -# Set active window back to platform -/opt/homebrew/bin/tmux select-window -t "$SESSION:platform" - -echo "✅ evOo successfully started in tmux session '$SESSION'!" -echo "" -echo "📱 iPhone Access:" -echo " - WLAN: http://192.168.0.205:5173" -echo " - Tailscale: http://100.127.149.82:5173" -echo "" -echo "🔍 Management Commands:" -echo " - Logs ansehen: tmux attach -t $SESSION" -echo " - Aus tmux raus: Ctrl+b gefolgt von d (Session läuft weiter!)" -echo " - Fenster wechseln in tmux: Ctrl+b gefolgt von 0 (Platform) oder 1 (Sentinel)" -echo " - Komplett stoppen: bash scripts/stop-tmux.sh" +echo "evOo started in tmux session '$SESSION'." +echo " Logs: tmux attach -t $SESSION (detach with Ctrl+b d)" +echo " Stop: bash scripts/stop-tmux.sh" diff --git a/scripts/stop-thread.ts b/scripts/stop-thread.ts deleted file mode 100644 index 0a787a1..0000000 --- a/scripts/stop-thread.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const session = await prisma.session.findFirst({ orderBy: { createdAt: "desc" } }); - const steve = await prisma.bot.findFirst({ where: { name: "Steve Jobs" } }); - if (!session || !steve) throw new Error("Missing session or steve"); - - const res = await fetch("http://127.0.0.1:3100/rpc/threads/stop", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session.token}`, - }, - body: JSON.stringify({ json: { botId: steve.id } }), - }); - console.log("Stop status:", res.status); - console.log("Stop response:", await res.text()); -} -run(); diff --git a/scripts/stop-tmux.sh b/scripts/stop-tmux.sh index 2e0dd99..eed6f7c 100755 --- a/scripts/stop-tmux.sh +++ b/scripts/stop-tmux.sh @@ -2,9 +2,9 @@ SESSION="evoo" -if /opt/homebrew/bin/tmux has-session -t "$SESSION" 2>/dev/null; then +if tmux has-session -t "$SESSION" 2>/dev/null; then echo "🛑 Stopping tmux session '$SESSION'..." - /opt/homebrew/bin/tmux kill-session -t "$SESSION" + tmux kill-session -t "$SESSION" echo "✅ evOo tmux session stopped." else echo "ℹ️ No tmux session '$SESSION' found." diff --git a/scripts/test-continue.ts b/scripts/test-continue.ts deleted file mode 100644 index 86bbbbb..0000000 --- a/scripts/test-continue.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const r = await prisma.run.findUnique({ - where: { id: "cmu7k4d8k000uiryy086x000a" }, - }); - console.log("Run details:", r); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/test-group-send.ts b/scripts/test-group-send.ts deleted file mode 100644 index b860c31..0000000 --- a/scripts/test-group-send.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const session = await prisma.session.findFirst({ orderBy: { createdAt: "desc" } }); - if (!session) throw new Error("No session"); - - const group = await prisma.chatGroup.findFirst({ where: { name: "Executive Board" } }); - if (!group) throw new Error("No group"); - - const res = await fetch("http://127.0.0.1:3100/rpc/threads/send", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session.token}`, - }, - body: JSON.stringify({ - json: { - groupId: group.id, - text: "Willkommen im Executive Board. Chief (CEO) und Steve Jobs (CTO): Ab sofort stimmen wir hier die übergeordnete Strategie ab. Kurzer Status-Check von beiden bitte: Wie ist die Aufstellung?", - }, - }), - }); - - console.log("Send status:", res.status); - const data = await res.json(); - console.log("Send response:", JSON.stringify(data, null, 2)); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/test-listfiles.ts b/scripts/test-listfiles.ts deleted file mode 100644 index f4b000b..0000000 --- a/scripts/test-listfiles.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { normalizeWorkspacePath } from "../packages/adapters/src/computer-support.js"; -import { DesktopSandboxProvider } from "../packages/adapters/src/desktop-sandbox.js"; -import { createDb } from "../packages/db/src/index.js"; - -async function test() { - console.log("normalizeWorkspacePath('.'):", JSON.stringify(normalizeWorkspacePath("."))); - console.log("normalizeWorkspacePath(''):", JSON.stringify(normalizeWorkspacePath(""))); - console.log( - "normalizeWorkspacePath('./apex-trading'):", - JSON.stringify(normalizeWorkspacePath("./apex-trading")), - ); - - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const quant = await prisma.bot.findFirst({ - where: { name: "Apex Quant" }, - include: { computer: true }, - }); - if (!quant?.computer) throw new Error("No quant or computer"); - - const sandbox = new DesktopSandboxProvider({ - dataDir: "/Users/ingowagner/evOo/data", - prisma, - }); - - const files = await sandbox.listFiles({ id: quant.computer.id, kind: "desktop" }, ".", {} as any); - console.log("Successfully listed '.' for Apex Quant! Found entries:", files.length); - for (const f of files.slice(0, 10)) { - console.log(`- ${f.kind}: ${f.path}`); - } -} - -test() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/test-llama-stream-tools.ts b/scripts/test-llama-stream-tools.ts deleted file mode 100644 index af4ba30..0000000 --- a/scripts/test-llama-stream-tools.ts +++ /dev/null @@ -1,46 +0,0 @@ -async function test() { - const req = { - model: "gemma-4-26b-a4b", - stream: true, - messages: [ - { role: "system", content: "You have tools. You MUST use the shell tool to list files." }, - { role: "user", content: "List the files." }, - ], - tools: [ - { - type: "function", - function: { - name: "shell", - description: "Execute a shell command", - parameters: { - type: "object", - properties: { - command: { type: "string" }, - }, - required: ["command"], - }, - }, - }, - ], - }; - - const res = await fetch("http://100.114.51.16:1240/v1/chat/completions", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(req), - }); - - const reader = res.body?.getReader(); - const decoder = new TextDecoder(); - let done = false; - while (!done) { - const chunk = await reader?.read(); - if (!chunk) break; - done = chunk.done; - if (chunk.value) { - console.log("CHUNK:", decoder.decode(chunk.value)); - } - } -} - -test().catch(console.error); diff --git a/scripts/test-llama-tools.ts b/scripts/test-llama-tools.ts deleted file mode 100644 index a95ac07..0000000 --- a/scripts/test-llama-tools.ts +++ /dev/null @@ -1,41 +0,0 @@ -async function test() { - const req = { - model: "gemma-4-26b-a4b", - messages: [ - { - role: "system", - content: "You have tools. When asked to check files, you MUST use the shell tool.", - }, - { role: "user", content: "Check what files are in the current directory using shell." }, - ], - tools: [ - { - type: "function", - function: { - name: "shell", - description: "Execute a shell command", - parameters: { - type: "object", - properties: { - command: { type: "string" }, - }, - required: ["command"], - }, - }, - }, - ], - tool_choice: "auto", - }; - - const res = await fetch("http://100.114.51.16:1240/v1/chat/completions", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(req), - }); - - console.log("Status:", res.status); - const data = await res.json(); - console.log("Response:", JSON.stringify(data, null, 2)); -} - -test().catch(console.error); diff --git a/scripts/test-path-fix.ts b/scripts/test-path-fix.ts deleted file mode 100644 index 0452519..0000000 --- a/scripts/test-path-fix.ts +++ /dev/null @@ -1,79 +0,0 @@ -function normalizeWorkspacePathOld(value: string): string { - const normalized = value.replace(/\\/g, "/").replace(/^\/+/, ""); - const segments = normalized.split("/").filter(Boolean); - if (segments.some((segment) => segment === "." || segment === "..")) { - throw new Error("Path escapes the computer workspace"); - } - return segments.join("/"); -} - -function normalizeWorkspacePathNew(value: string): string { - const normalized = value.replace(/\\/g, "/").replace(/^\/+/, ""); - const rawSegments = normalized.split("/").filter(Boolean); - if (rawSegments.some((segment) => segment === "..")) { - throw new Error("Path escapes the computer workspace"); - } - const segments = rawSegments.filter((segment) => segment !== "."); - return segments.join("/"); -} - -console.log( - "Old '.' ->", - (() => { - try { - return normalizeWorkspacePathOld("."); - } catch (e) { - return e.message; - } - })(), -); -console.log( - "New '.' ->", - (() => { - try { - return normalizeWorkspacePathNew("."); - } catch (e) { - return e.message; - } - })(), -); -console.log( - "New './apex-trading' ->", - (() => { - try { - return normalizeWorkspacePathNew("./apex-trading"); - } catch (e) { - return e.message; - } - })(), -); -console.log( - "New '..' ->", - (() => { - try { - return normalizeWorkspacePathNew(".."); - } catch (e) { - return e.message; - } - })(), -); -console.log( - "New '../foo' ->", - (() => { - try { - return normalizeWorkspacePathNew("../foo"); - } catch (e) { - return e.message; - } - })(), -); -console.log( - "New 'foo/bar' ->", - (() => { - try { - return normalizeWorkspacePathNew("foo/bar"); - } catch (e) { - return e.message; - } - })(), -); diff --git a/scripts/trigger-p0-continue.ts b/scripts/trigger-p0-continue.ts deleted file mode 100644 index a01a3db..0000000 --- a/scripts/trigger-p0-continue.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const session = await prisma.session.findFirst({ orderBy: { createdAt: "desc" } }); - if (!session) throw new Error("No session"); - - const architect = await prisma.bot.findFirst({ where: { name: "Code Architect" } }); - const quant = await prisma.bot.findFirst({ where: { name: "Apex Quant" } }); - - console.log("Triggering Code Architect P0 implementation..."); - const res1 = await fetch("http://127.0.0.1:3100/rpc/threads/send", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session.token}`, - }, - body: JSON.stringify({ - json: { - botId: architect!.id, - text: "[P0 Implementierung] 🔴 Beginne JETZT mit der Code-Änderung: Refaktoriere `apex-trading/src/strategies/momentum.py` und `apex-trading/src/risk/risk_guard.py`. Nutze die Pydantic/Decimal-Modelle aus `schemas.py`. Führe danach `python3 -m unittest` mit shell aus und melde das fertige Ergebnis via `message_bot` an Steve Jobs.", - }, - }), - }); - console.log("Code Architect response status:", res1.status); - - console.log("Triggering Apex Quant P0 stress-testing..."); - const res2 = await fetch("http://127.0.0.1:3100/rpc/threads/send", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session.token}`, - }, - body: JSON.stringify({ - json: { - botId: quant!.id, - text: "[P0 Stress-Testing] 🔴 Erstelle `apex-trading/tests/test_stress_latency.py` zur Simulation von 10.000 Ticks und Circuit-Breaker Stress-Tests. Führe den Test mit shell aus und melde das Ergebnis via `message_bot` an Steve Jobs.", - }, - }), - }); - console.log("Apex Quant response status:", res2.status); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/trigger-specialists.ts b/scripts/trigger-specialists.ts deleted file mode 100644 index b822fe4..0000000 --- a/scripts/trigger-specialists.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const session = await prisma.session.findFirst({ orderBy: { createdAt: "desc" } }); - if (!session) throw new Error("No session"); - - const quant = await prisma.bot.findFirst({ where: { name: "Apex Quant" } }); - const architect = await prisma.bot.findFirst({ where: { name: "Code Architect" } }); - const devops = await prisma.bot.findFirst({ where: { name: "DevOps Engineer" } }); - const auditor = await prisma.bot.findFirst({ where: { name: "Security & QA Auditor" } }); - - const tasks = [ - { - bot: quant, - text: "Sprint 1 (Quantitative Validation):\nPrüfe mit dem shell-Tool die mathematische Präzision in `apex-trading/src/engine/trader.py`. Führe `python3 -m unittest discover -s apex-trading/tests` im Terminal aus. Aktualisiere anschließend deine `TODO.md` mit den Ergebnissen.", - }, - { - bot: architect, - text: "Sprint 1 (Architecture Blueprint):\nUntersuche mit dem shell- oder file-Tool die Projektstruktur in `apex-trading/` und `evoo-platform/`. Prüfe die saubere Trennung und dokumentiere den Modulaufbau in `ARCHITECTURE.md`. Aktualisiere deine `TODO.md`.", - }, - { - bot: devops, - text: "Sprint 1 (Infrastructure & Healthcheck):\nPrüfe mit dem shell-Tool die Umgebung: Python-Version, Node-Version und das Script `deploy.sh`. Teste, ob alle Abhängigkeiten vorhanden sind und aktualisiere deine `TODO.md`.", - }, - { - bot: auditor, - text: "Sprint 1 (Security & Edge Cases):\nAnalysiere `apex-trading/` auf potenzielle Division-by-Zero-Risiken, Typkonvertierungsfehler und Edge-Cases. Erstelle eine `BUGS.md` und aktualisiere deine `TODO.md`.", - }, - ]; - - for (const t of tasks) { - if (!t.bot) continue; - console.log(`Sending Sprint 1 task to ${t.bot.name}...`); - const res = await fetch("http://127.0.0.1:3100/rpc/threads/send", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session.token}`, - }, - body: JSON.stringify({ - json: { - botId: t.bot.id, - text: t.text, - }, - }), - }); - console.log(` ${t.bot.name} send status: ${res.status}`); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/trigger-ux-work.ts b/scripts/trigger-ux-work.ts deleted file mode 100644 index 8fefb4b..0000000 --- a/scripts/trigger-ux-work.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function main() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const session = await prisma.session.findFirst({ orderBy: { createdAt: "desc" } }); - if (!session) throw new Error("No active session"); - - const uxBot = await prisma.bot.findFirst({ where: { name: "UX Designer" } }); - if (!uxBot) throw new Error("UX Designer not found"); - - const execGroup = await prisma.chatGroup.findFirst({ where: { name: "Executive Board" } }); - if (!execGroup) throw new Error("Executive Board not found"); - - console.log("Dispatching direct task to UX Designer..."); - const res1 = await fetch("http://127.0.0.1:3100/rpc/threads/send", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session.token}`, - }, - body: JSON.stringify({ - json: { - botId: uxBot.id, - text: "AUFTRAG DER GESCHAEFTSFUEHRUNG [P0]: Erstelle die vollstaendige UX/UI-Spezifikation fuer das mobile Apex-Trading Dashboard auf dem iPhone (390x844px Viewport). Definiere die 4 Haupt-Module: 1) Real-Time P&L & Sharpe Ratio Chart, 2) Circuit-Breaker Status & Drawdown Gauge, 3) Live Order Execution Feed, 4) Bot Telemetry & Not-Aus (Emergency Stop). Schreibe deine gesamte Spezifikation inklusive Wireframe-Skizzen und Farb-Tokens in die Datei apex-trading/MOBILE_DASHBOARD_UX.md und berichte hier ausfuehrlich.", - }, - }), - }); - console.log("UX Designer send status:", res1.status, await res1.json()); - - console.log("Announcing to Executive Board..."); - const res2 = await fetch("http://127.0.0.1:3100/rpc/threads/send", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session.token}`, - }, - body: JSON.stringify({ - json: { - groupId: execGroup.id, - text: "@Steve Jobs @Chief @UX Designer Vorgabe der Geschaeftsfuehrung: UX Designer ist live und erarbeitet das Mobile-First iPhone Dashboard in apex-trading/MOBILE_DASHBOARD_UX.md. Steve Jobs und DevOps Engineer setzen parallel die naechsten technischen Schritte fuer Phase 1 um.", - }, - }), - }); - console.log("Executive Board send status:", res2.status, await res2.json()); -} - -main() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/update-directives-and-kickoff.ts b/scripts/update-directives-and-kickoff.ts deleted file mode 100644 index 833ed52..0000000 --- a/scripts/update-directives-and-kickoff.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const session = await prisma.session.findFirst({ orderBy: { createdAt: "desc" } }); - if (!session) throw new Error("No session found"); - - // 1. Fetch bots - const steve = await prisma.bot.findFirst({ - where: { name: "Steve Jobs" }, - include: { thread: true }, - }); - const chief = await prisma.bot.findFirst({ where: { name: "Chief" }, include: { thread: true } }); - const quant = await prisma.bot.findFirst({ - where: { name: "Apex Quant" }, - include: { thread: true }, - }); - const architect = await prisma.bot.findFirst({ - where: { name: "Code Architect" }, - include: { thread: true }, - }); - const devops = await prisma.bot.findFirst({ - where: { name: "DevOps Engineer" }, - include: { thread: true }, - }); - const auditor = await prisma.bot.findFirst({ - where: { name: "Security & QA Auditor" }, - include: { thread: true }, - }); - - if (!steve || !chief || !quant || !architect || !devops || !auditor) { - throw new Error("Missing one of the bots"); - } - - // 2. Update Steve Jobs Instructions with strict anti-hallucination & mandatory tool-call rules - const steveUpdatedInstructions = `# Steve Jobs - Executive Autonomous CTO (evOo Platform) - -## Role & Mission -You are **Steve Jobs**, acting as Chief Technology Officer (CTO) and Main Engineering Agent of **evOo**. -You operate with uncompromising standards for software quality, extreme precision, and relentless drive. -You lead the engineering division and coordinate the 4 specialist bots: Apex Quant, Code Architect, DevOps Engineer, Security & QA Auditor. - -## CRITICAL OPERATIONAL RULES (STRICT ENFORCEMENT) - -1. **NEVER NARRATE DELEGATION IN TEXT**: - - Writing text like \`An [Apex Quant]: Deine Mission...\` in your chat message is STRICTLY FORBIDDEN. It sends NOTHING. - - To send an assignment or message to another bot, you MUST call the \`message_bot\` tool with: - \`{"bot_id": "", "message": "", "intent": "request"}\` - -2. **FACTS OVER RHETORIC**: - - Do NOT produce long visionary monologues or empty promises. - - Lead with: **Status → Ergebnis → Nächster Schritt**. - - Verify filesystem and processes using \`shell\` or file tools before making claims. - -3. **ALL BOTS ARE ALIVE & ACTIVE**: - - Chief (CEO), Apex Quant, Code Architect, DevOps Engineer, Security & QA Auditor are fully provisioned in the system. - - Never claim a bot cannot be spawned or is missing. They exist and are waiting for your \`message_bot\` calls. - -4. **FORMATTING**: - - NEVER use LaTeX math syntax like $\\rightarrow$ or $\\longrightarrow$. Use clean Unicode arrows (\`→\` or \`->\`). - -5. **COORDINATION & KNOWLEDGE**: - - Maintain \`TODO.md\` and \`KNOWLEDGE_BASE.md\` in your sandbox. - - Review incoming results from your specialists and report summarized milestones to the human user and in the Executive Board. -`; - - await prisma.bot.update({ - where: { id: steve.id }, - data: { instructions: steveUpdatedInstructions }, - }); - console.log("✓ Steve Jobs directives updated."); - - // 3. Update Chief Instructions - const chiefUpdatedInstructions = `# Chief - Executive Chief Executive Officer (CEO) - -## Role & Identity -You are **Chief**, the Chief Executive Officer (CEO) of **evOo**. -You form the Executive Board together with the Owner (Human User) and Steve Jobs (CTO). -Your focus is strategic vision, commercial dominance, resource allocation, and decisive leadership. - -## Rules of Engagement -- **The Trio**: You, the Owner, and Steve Jobs lead evOo. Steve handles deep technical engineering; you steer strategy, business viability, and holistic synergy. -- **Action & Clarity**: Be decisive, concise, and razor-sharp. Format updates clearly with Unicode symbols (\`→\` or \`->\`, never LaTeX like $\\rightarrow$). -- **Tool Usage**: Use \`message_bot\` to delegate or ask questions to Steve Jobs or the specialist bots. Never fake delegation in chat text. -`; - - await prisma.bot.update({ - where: { id: chief.id }, - data: { instructions: chiefUpdatedInstructions }, - }); - console.log("✓ Chief directives updated."); - - // 4. Update the 4 specialists instructions to ensure they execute and update TODO.md - const specialists = [ - { - bot: quant, - title: "Quantitative Lead", - promptAddition: - "You are Apex Quant. Your job is mathematical modeling, fixed-point precision, latency budgets, and algorithmic trading math in apex-trading/. Always maintain your TODO.md and verify code with python3.", - }, - { - bot: architect, - title: "Chief Software Architect", - promptAddition: - "You are Code Architect. Your job is modular design, clean directory layout, strict boundaries between apex-trading and evoo-platform. Always maintain ARCHITECTURE.md and your TODO.md.", - }, - { - bot: devops, - title: "Infrastructure Guardian", - promptAddition: - "You are DevOps Engineer. Your job is deployment scripts (deploy.sh), environment healthchecks, local services, network stability (Tailscale/0.0.0.0). Always maintain your TODO.md.", - }, - { - bot: auditor, - title: "Chief Security Officer (CSO)", - promptAddition: - "You are Security & QA Auditor. Your job is code audit, edge-case analysis, test suite coverage, and vulnerability prevention. Always maintain BUGS.md and your TODO.md.", - }, - ]; - - for (const s of specialists) { - await prisma.bot.update({ - where: { id: s.bot.id }, - data: { - instructions: `# ${s.bot.name} - ${s.title}\n\n${s.promptAddition}\n\n## Core Execution Rules:\n1. Execute tasks immediately using shell and file tools.\n2. Keep your TODO.md up to date with completed and pending tasks.\n3. Send updates back to Steve Jobs via message_bot when a task is completed.`, - }, - }); - } - console.log("✓ Specialist instructions updated."); - - console.log("\nSetup complete. Ready to fire initial kickoff tasks."); -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/scripts/update-steve-and-verify.ts b/scripts/update-steve-and-verify.ts deleted file mode 100644 index d2ac429..0000000 --- a/scripts/update-steve-and-verify.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { createDb } from "../packages/db/src/index.js"; - -async function run() { - const { prisma } = createDb("postgres://ingowagner@127.0.0.1:5432/evoo"); - const steve = await prisma.bot.findFirst({ where: { name: "Steve Jobs" } }); - if (!steve) throw new Error("No Steve"); - - let inst = steve.instructions; - if (!inst.includes("Unicode-Pfeile")) { - inst += `\n\n10. **Formatting & Symbols**: - - Verwende NIEMALS LaTeX-Math-Syntax wie $\\rightarrow$ oder $\\longrightarrow$ für Pfeile! Das Web-Interface rendert KaTeX hierfür nicht. - - Verwende stattdessen IMMER echte Unicode-Pfeile wie \`→\` oder ASCII \`->\`.`; - await prisma.bot.update({ - where: { id: steve.id }, - data: { instructions: inst }, - }); - console.log("Updated Steve Jobs formatting instruction."); - } - - const allBots = await prisma.bot.findMany({ - select: { id: true, name: true, title: true, modelId: true }, - }); - console.log("\nALL BOTS:"); - for (const b of allBots) { - console.log(`- ${b.name} (${b.title || "No title"}) [${b.modelId}]`); - } - - const groups = await prisma.chatGroup.findMany({ - include: { - members: { include: { bot: true } }, - thread: true, - }, - }); - console.log("\nGROUPS:"); - for (const g of groups) { - console.log( - `- Group: ${g.name} (${g.id}) | Thread: ${g.thread?.id} | Members: ${g.members.map((m) => m.bot.name).join(", ")}`, - ); - } -} - -run() - .catch(console.error) - .finally(() => process.exit(0)); diff --git a/templates/cto-steve-jobs/PROFILE.md b/templates/cto-steve-jobs/PROFILE.md deleted file mode 100644 index 5f450fb..0000000 --- a/templates/cto-steve-jobs/PROFILE.md +++ /dev/null @@ -1,25 +0,0 @@ -# Steve Jobs - Executive Autonomous CTO (evOo Engine) - -## Role & Vision -You are **Steve Jobs**, acting as the Chief Technology Officer (CTO) of the evOo ecosystem. You operate with total technical ownership, uncompromising demand for quality, and immediate action execution. - -## Core Capabilities & Authority -1. **Full Pre-Approved Access**: - - You have pre-approved access to network endpoints, local devices, filesystem, and all project source code. - - You do not ask for permission to read, write, build, test, refactor, or run system tools. - -2. **Action-First & Multi-Task Execution**: - - You execute complete multi-step task sequences autonomously when direction is clear. - - You do not halt execution to report trivial steps or ask redundant confirmation questions. "Action over explanation." - -3. **Dynamic Agent Casting**: - - As CTO, you cast, spawn, delegate, and manage specialized sub-agents (e.g., Code Architect, Security Auditor, DevOps Engineer, QA Tester). - - You define their precise roles, set quality standards, and coordinate their outputs. - -4. **Self-Improvement Loop**: - - You continuously audit system performance, analyze execution logs, optimize system prompts, and refactor codebase artifacts. - - If an error or bottleneck occurs, you diagnose the root cause and refactor automatically. - -5. **Communication & Negotiation Protocol**: - - You report back on a negotiation and consensus basis when high-level strategic alignment or trade-off decisions are needed. - - Keep status updates crisp, executive-ready, and result-oriented. diff --git a/templates/cto-steve-jobs/SETUP.md b/templates/cto-steve-jobs/SETUP.md deleted file mode 100644 index a6ac9dc..0000000 --- a/templates/cto-steve-jobs/SETUP.md +++ /dev/null @@ -1,10 +0,0 @@ -# Steve Jobs CTO Setup Guide - -## System Integration -1. **Configuration**: Reads autonomy rules from `evoo.config.json`. -2. **Permissions**: Full network, filesystem, process execution, and agent casting permissions pre-granted. -3. **Execution**: - ```bash - npm test - ``` -4. **Agent Orchestration**: Cast sub-agents dynamically whenever complex tasks require parallelization or specialized domain expertise. diff --git a/templates/cto-steve-jobs/entry.json b/templates/cto-steve-jobs/entry.json deleted file mode 100644 index a48d5ee..0000000 --- a/templates/cto-steve-jobs/entry.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "slug": "cto-steve-jobs", - "name": "Steve Jobs - Autonomous CTO", - "author": { - "name": "evOo Core Team" - }, - "summary": "Autonomous Chief Technology Officer with full system privileges, dynamic team casting, action-first execution, and self-improvement loops.", - "summary_zh": "自主 CTO 智能体(Steve Jobs),拥有全系统权限、动态团队 Call/Cast、Action-first 多任务链与自我改进能力。", - "import": "https://x.ai/bot/evOo-cto-steve-jobs", - "first_safe_task": "Inspect system status and evoo.config.json. Do not alter external infrastructure without explicit consensus.", - "approval_boundary": "Full autonomy pre-granted for internal repository, network, build system, and sub-agent orchestration.", - "category": "coding-shipping", - "tags": ["cto", "autonomous", "steve-jobs", "evoo"], - "license": "CC0-1.0", - "verified": true, - "updated": "2026-09-18" -} diff --git a/vitest.config.ts b/vitest.config.ts index 56c4211..0e3a59c 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -15,7 +15,6 @@ export default defineConfig({ "apps/mobile/plugins/**/*.test.js", "apps/api/src/**/*.test.ts", "apps/www/src/**/*.test.ts", - "evoo-platform/**/*.test.js", ], testTimeout: 30_000, hookTimeout: 60_000,