@@ -96,14 +94,6 @@ export default async function LocaleLayout({
>
{tNav('about')}
- {showAdminTab ? (
-
- {tNav('admin')}
-
- ) : null}
{/* Right side */}
diff --git a/src/app/api/admin/_lib/auth.ts b/src/app/api/admin/_lib/auth.ts
deleted file mode 100644
index afbbfae..0000000
--- a/src/app/api/admin/_lib/auth.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { NextResponse } from 'next/server'
-import { resolveAdminOperator } from '@/lib/agent-os/admin-auth'
-
-export function parseBearerToken(authHeader: string | null): string | null {
- if (!authHeader) {
- return null
- }
-
- const [type, token] = authHeader.split(' ')
- if (!type || !token) {
- return null
- }
-
- return type.toLowerCase() === 'bearer' ? token : null
-}
-
-export function requireAdminAccess(request: Request):
- | { authorized: true; operator: string }
- | { authorized: false; response: NextResponse } {
- const bearerToken = parseBearerToken(request.headers.get('authorization'))
- const adminKey = request.headers.get('x-admin-key')
- const operatorHint = request.headers.get('x-operator') || request.headers.get('x-client-id')
-
- const resolved = resolveAdminOperator({
- bearerToken,
- adminKey,
- fallbackOperator: operatorHint,
- })
-
- if (!resolved.authorized || !resolved.operator) {
- return {
- authorized: false,
- response: NextResponse.json(
- {
- success: false,
- error: 'Unauthorized',
- details: ['Missing or invalid admin credentials'],
- },
- { status: 401 }
- ),
- }
- }
-
- return {
- authorized: true,
- operator: resolved.operator,
- }
-}
diff --git a/src/app/api/admin/approvals/[id]/decision/route.ts b/src/app/api/admin/approvals/[id]/decision/route.ts
deleted file mode 100644
index f41a023..0000000
--- a/src/app/api/admin/approvals/[id]/decision/route.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { NextResponse } from 'next/server'
-import { ApprovalDecisionRequestSchema } from '@/lib/agent-os/schemas'
-import { decideApproval } from '@/lib/agent-os/store'
-import { requireAdminAccess } from '../../../_lib/auth'
-
-export async function POST(
- request: Request,
- { params }: { params: Promise<{ id: string }> }
-) {
- const auth = requireAdminAccess(request)
- if (!auth.authorized) {
- return auth.response
- }
-
- const { id } = await params
-
- try {
- const body = await request.json()
- const validation = ApprovalDecisionRequestSchema.safeParse(body)
-
- if (!validation.success) {
- return NextResponse.json(
- {
- success: false,
- error: 'Validation error',
- details: validation.error.errors,
- },
- { status: 400 }
- )
- }
-
- const updated = decideApproval({
- approvalId: id,
- decision: validation.data.decision,
- decidedBy: auth.operator,
- comment: validation.data.comment,
- })
-
- if (!updated) {
- return NextResponse.json(
- {
- success: false,
- error: 'Approval ticket not found',
- },
- { status: 404 }
- )
- }
-
- return NextResponse.json({
- success: true,
- approvalTicket: updated.approvalTicket,
- proposal: updated.proposal,
- })
- } catch (error) {
- console.error('[AdminApprovals] Failed to decide approval:', error)
- return NextResponse.json(
- {
- success: false,
- error: 'Internal server error',
- },
- { status: 500 }
- )
- }
-}
diff --git a/src/app/api/admin/approvals/route.ts b/src/app/api/admin/approvals/route.ts
deleted file mode 100644
index adbe1f5..0000000
--- a/src/app/api/admin/approvals/route.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { NextResponse } from 'next/server'
-import { listApprovalTickets, listPolicyProposals } from '@/lib/agent-os/store'
-import { requireAdminAccess } from '../_lib/auth'
-
-export async function GET(request: Request) {
- const auth = requireAdminAccess(request)
- if (!auth.authorized) {
- return auth.response
- }
-
- const proposals = listPolicyProposals()
- const proposalById = new Map(proposals.map((proposal) => [proposal.id, proposal]))
- const approvals = listApprovalTickets().map((ticket) => {
- const proposal = proposalById.get(ticket.proposalId)
- return {
- approvalTicketId: ticket.id,
- proposalId: ticket.proposalId,
- approvalStatus: ticket.status,
- requestedBy: ticket.requestedBy,
- decidedBy: ticket.decidedBy || null,
- decisionComment: ticket.decisionComment || null,
- createdAt: ticket.createdAt,
- updatedAt: ticket.updatedAt,
- proposal: proposal
- ? {
- title: proposal.title,
- status: proposal.status,
- targetType: proposal.targetType,
- targetId: proposal.targetId,
- }
- : null,
- }
- })
-
- return NextResponse.json({
- success: true,
- operator: auth.operator,
- approvals,
- })
-}
diff --git a/src/app/api/admin/auth/token/route.ts b/src/app/api/admin/auth/token/route.ts
deleted file mode 100644
index bd7f5ae..0000000
--- a/src/app/api/admin/auth/token/route.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { NextResponse } from 'next/server'
-import { AdminAuthTokenRequestSchema } from '@/lib/agent-os/schemas'
-import { issueAdminToken, isValidAdminKey } from '@/lib/agent-os/admin-auth'
-
-export async function POST(request: Request) {
- try {
- const body = await request.json()
- const validation = AdminAuthTokenRequestSchema.safeParse(body)
-
- if (!validation.success) {
- return NextResponse.json(
- {
- success: false,
- error: 'Validation error',
- details: validation.error.errors,
- },
- { status: 400 }
- )
- }
-
- const { adminKey, operator } = validation.data
-
- if (!isValidAdminKey(adminKey)) {
- return NextResponse.json(
- {
- success: false,
- error: 'Unauthorized',
- details: ['Invalid admin key'],
- },
- { status: 401 }
- )
- }
-
- const token = issueAdminToken(operator || 'admin-console')
-
- return NextResponse.json({
- success: true,
- token: token.token,
- operator: token.operator,
- expiresAt: new Date(token.expiresAt).toISOString(),
- })
- } catch (error) {
- console.error('[AdminAuth] Failed to issue token:', error)
- return NextResponse.json(
- {
- success: false,
- error: 'Internal server error',
- },
- { status: 500 }
- )
- }
-}
diff --git a/src/app/api/admin/policies/proposals/route.ts b/src/app/api/admin/policies/proposals/route.ts
deleted file mode 100644
index ff6fa63..0000000
--- a/src/app/api/admin/policies/proposals/route.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { NextResponse } from 'next/server'
-import { CreatePolicyProposalRequestSchema } from '@/lib/agent-os/schemas'
-import { createPolicyProposal, listPolicyProposals } from '@/lib/agent-os/store'
-import { requireAdminAccess } from '../../_lib/auth'
-
-export async function GET(request: Request) {
- const auth = requireAdminAccess(request)
- if (!auth.authorized) {
- return auth.response
- }
-
- return NextResponse.json({
- success: true,
- operator: auth.operator,
- proposals: listPolicyProposals(),
- })
-}
-
-export async function POST(request: Request) {
- const auth = requireAdminAccess(request)
- if (!auth.authorized) {
- return auth.response
- }
-
- try {
- const body = await request.json()
- const validation = CreatePolicyProposalRequestSchema.safeParse(body)
-
- if (!validation.success) {
- return NextResponse.json(
- {
- success: false,
- error: 'Validation error',
- details: validation.error.errors,
- },
- { status: 400 }
- )
- }
-
- const created = createPolicyProposal({
- ...validation.data,
- createdBy: auth.operator,
- })
-
- return NextResponse.json({
- success: true,
- proposal: created.proposal,
- approvalTicket: created.approvalTicket,
- })
- } catch (error) {
- console.error('[AdminPolicies] Failed to create proposal:', error)
- return NextResponse.json(
- {
- success: false,
- error: 'Internal server error',
- },
- { status: 500 }
- )
- }
-}
diff --git a/src/app/api/admin/resources/agents/route.ts b/src/app/api/admin/resources/agents/route.ts
deleted file mode 100644
index 98be034..0000000
--- a/src/app/api/admin/resources/agents/route.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { NextResponse } from 'next/server'
-import { listAgentProfiles } from '@/lib/agent-os/resource-pool'
-import { requireAdminAccess } from '../../_lib/auth'
-
-export async function GET(request: Request) {
- const auth = requireAdminAccess(request)
- if (!auth.authorized) {
- return auth.response
- }
-
- return NextResponse.json({
- success: true,
- operator: auth.operator,
- agents: listAgentProfiles(),
- })
-}
diff --git a/src/app/api/admin/resources/mcp/route.ts b/src/app/api/admin/resources/mcp/route.ts
deleted file mode 100644
index d3c486c..0000000
--- a/src/app/api/admin/resources/mcp/route.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { NextResponse } from 'next/server'
-import { listResourceManifests } from '@/lib/agent-os/resource-pool'
-import { requireAdminAccess } from '../../_lib/auth'
-
-export async function GET(request: Request) {
- const auth = requireAdminAccess(request)
- if (!auth.authorized) {
- return auth.response
- }
-
- const mcpResources = listResourceManifests().filter((resource) => resource.kind === 'mcp_tool')
-
- return NextResponse.json({
- success: true,
- operator: auth.operator,
- mcpResources,
- })
-}
diff --git a/src/app/api/admin/resources/route.ts b/src/app/api/admin/resources/route.ts
deleted file mode 100644
index 1b828c3..0000000
--- a/src/app/api/admin/resources/route.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { NextResponse } from 'next/server'
-import {
- listAgentProfiles,
- listN8nProductionWorkflows,
- listPolicyProfiles,
- listResourceManifests,
-} from '@/lib/agent-os/resource-pool'
-import { requireAdminAccess } from '../_lib/auth'
-
-export async function GET(request: Request) {
- const auth = requireAdminAccess(request)
- if (!auth.authorized) {
- return auth.response
- }
-
- const resources = listResourceManifests()
- const tools = resources.filter((item) => item.kind === 'base_tool' || item.kind === 'mcp_tool')
-
- return NextResponse.json({
- success: true,
- operator: auth.operator,
- summary: {
- totalResources: resources.length,
- totalTools: tools.length,
- totalSkills: resources.filter((item) => item.kind === 'skill').length,
- totalAgents: listAgentProfiles().length,
- totalPolicyProfiles: listPolicyProfiles().length,
- totalN8nProductionWorkflows: listN8nProductionWorkflows().length,
- },
- resources,
- n8nProductionWorkflows: listN8nProductionWorkflows(),
- })
-}
diff --git a/src/app/api/admin/resources/skills/route.ts b/src/app/api/admin/resources/skills/route.ts
deleted file mode 100644
index 0a2df62..0000000
--- a/src/app/api/admin/resources/skills/route.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { NextResponse } from 'next/server'
-import {
- listN8nProductionWorkflows,
- listResourceManifests,
-} from '@/lib/agent-os/resource-pool'
-import { requireAdminAccess } from '../../_lib/auth'
-
-export async function GET(request: Request) {
- const auth = requireAdminAccess(request)
- if (!auth.authorized) {
- return auth.response
- }
-
- const skills = listResourceManifests().filter((resource) => resource.kind === 'skill')
-
- return NextResponse.json({
- success: true,
- operator: auth.operator,
- skills,
- n8nProductionWorkflows: listN8nProductionWorkflows(),
- })
-}
diff --git a/src/app/api/admin/resources/tools/route.ts b/src/app/api/admin/resources/tools/route.ts
deleted file mode 100644
index 7e17d50..0000000
--- a/src/app/api/admin/resources/tools/route.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { NextResponse } from 'next/server'
-import { listResourceManifests } from '@/lib/agent-os/resource-pool'
-import { requireAdminAccess } from '../../_lib/auth'
-
-export async function GET(request: Request) {
- const auth = requireAdminAccess(request)
- if (!auth.authorized) {
- return auth.response
- }
-
- const tools = listResourceManifests().filter(
- (resource) => resource.kind === 'base_tool' || resource.kind === 'mcp_tool'
- )
-
- return NextResponse.json({
- success: true,
- operator: auth.operator,
- tools,
- })
-}
diff --git a/src/app/api/admin/runs/route.ts b/src/app/api/admin/runs/route.ts
deleted file mode 100644
index d272c91..0000000
--- a/src/app/api/admin/runs/route.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { NextResponse } from 'next/server'
-import { listRuns } from '@/lib/agent-os/store'
-import { requireAdminAccess } from '../_lib/auth'
-
-export async function GET(request: Request) {
- const auth = requireAdminAccess(request)
- if (!auth.authorized) {
- return auth.response
- }
-
- return NextResponse.json({
- success: true,
- operator: auth.operator,
- runs: listRuns(),
- })
-}
diff --git a/src/app/api/agent/goals/route.ts b/src/app/api/agent/goals/route.ts
deleted file mode 100644
index e8abd07..0000000
--- a/src/app/api/agent/goals/route.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { NextResponse } from 'next/server'
-import { AgentGoalRequestSchema } from '@/lib/agent-os/schemas'
-import { orchestrateGoal } from '@/lib/agent-os/orchestrator'
-import { getRun, listTasks, listToolCalls } from '@/lib/agent-os/store'
-import { requireAdminAccess } from '../../admin/_lib/auth'
-
-export async function POST(request: Request) {
- const auth = requireAdminAccess(request)
- if (!auth.authorized) {
- return auth.response
- }
-
- try {
- const body = await request.json()
- const validation = AgentGoalRequestSchema.safeParse(body)
-
- if (!validation.success) {
- return NextResponse.json(
- {
- success: false,
- error: 'Validation error',
- details: validation.error.errors,
- },
- { status: 400 }
- )
- }
-
- const orchestration = await orchestrateGoal({
- ...validation.data,
- createdBy: auth.operator,
- })
-
- const run = getRun(orchestration.runId)
-
- return NextResponse.json({
- success: true,
- run,
- intents: orchestration.intents,
- selectedAgent: orchestration.agent,
- tasks: listTasks(orchestration.runId),
- toolCalls: listToolCalls(orchestration.runId),
- pendingApprovals: orchestration.pendingApprovals,
- planning: orchestration.planning,
- approvalTicketIds: orchestration.approvalTicketIds,
- })
- } catch (error) {
- console.error('[AgentGoals] Failed to orchestrate goal:', error)
- return NextResponse.json(
- {
- success: false,
- error: 'Internal server error',
- },
- { status: 500 }
- )
- }
-}
diff --git a/src/app/api/agent/runs/[id]/control/route.ts b/src/app/api/agent/runs/[id]/control/route.ts
deleted file mode 100644
index f5a0e3c..0000000
--- a/src/app/api/agent/runs/[id]/control/route.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { NextResponse } from 'next/server'
-import { RunControlRequestSchema } from '@/lib/agent-os/schemas'
-import { getRun, updateRunStatus } from '@/lib/agent-os/store'
-import { requireAdminAccess } from '../../../../admin/_lib/auth'
-
-const ACTION_TO_STATUS = {
- pause: 'PAUSED',
- resume: 'TASK_DISPATCHED',
- rollback: 'ROLLED_BACK',
- fail: 'FAILED',
- complete: 'COMPLETED',
-} as const
-
-export async function POST(
- request: Request,
- { params }: { params: Promise<{ id: string }> }
-) {
- const auth = requireAdminAccess(request)
- if (!auth.authorized) {
- return auth.response
- }
-
- const { id } = await params
-
- if (!getRun(id)) {
- return NextResponse.json(
- {
- success: false,
- error: 'Run not found',
- },
- { status: 404 }
- )
- }
-
- try {
- const body = await request.json()
- const validation = RunControlRequestSchema.safeParse(body)
-
- if (!validation.success) {
- return NextResponse.json(
- {
- success: false,
- error: 'Validation error',
- details: validation.error.errors,
- },
- { status: 400 }
- )
- }
-
- const next = updateRunStatus(
- id,
- ACTION_TO_STATUS[validation.data.action],
- validation.data.note || `Run action: ${validation.data.action}`
- )
-
- return NextResponse.json({
- success: true,
- operator: auth.operator,
- run: next,
- })
- } catch (error) {
- console.error('[AgentRuns] Failed to control run:', error)
- return NextResponse.json(
- {
- success: false,
- error: 'Internal server error',
- },
- { status: 500 }
- )
- }
-}
diff --git a/src/app/api/agent/runs/[id]/route.ts b/src/app/api/agent/runs/[id]/route.ts
deleted file mode 100644
index 05fc294..0000000
--- a/src/app/api/agent/runs/[id]/route.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { NextResponse } from 'next/server'
-import { getRun, listTasks, listToolCalls } from '@/lib/agent-os/store'
-import { requireAdminAccess } from '../../../admin/_lib/auth'
-
-export async function GET(
- request: Request,
- { params }: { params: Promise<{ id: string }> }
-) {
- const auth = requireAdminAccess(request)
- if (!auth.authorized) {
- return auth.response
- }
-
- const { id } = await params
- const run = getRun(id)
-
- if (!run) {
- return NextResponse.json(
- {
- success: false,
- error: 'Run not found',
- },
- { status: 404 }
- )
- }
-
- return NextResponse.json({
- success: true,
- operator: auth.operator,
- run,
- tasks: listTasks(id),
- toolCalls: listToolCalls(id),
- })
-}
diff --git a/src/components/admin/AdminGoalConsole.tsx b/src/components/admin/AdminGoalConsole.tsx
deleted file mode 100644
index 11f58a4..0000000
--- a/src/components/admin/AdminGoalConsole.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-'use client'
-
-import { FormEvent, useState } from 'react'
-import { useTranslations } from 'next-intl'
-
-export function AdminGoalConsole() {
- const t = useTranslations('admin')
- const [adminKey, setAdminKey] = useState('')
- const [goal, setGoal] = useState(() => t('goalDefault'))
- const [loading, setLoading] = useState(false)
- const [responseText, setResponseText] = useState('')
-
- async function handleSubmit(event: FormEvent
) {
- event.preventDefault()
- setLoading(true)
- setResponseText('')
-
- try {
- const response = await fetch('/api/agent/goals', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'x-admin-key': adminKey,
- 'x-operator': 'admin-console-chat',
- },
- body: JSON.stringify({ goal }),
- })
-
- const json = await response.json()
- setResponseText(JSON.stringify(json, null, 2))
- } catch (error) {
- setResponseText(
- JSON.stringify(
- {
- success: false,
- error: error instanceof Error ? error.message : t('unknownError'),
- },
- null,
- 2
- )
- )
- } finally {
- setLoading(false)
- }
- }
-
- return (
-
- {t('goalConsoleTitle')}
-
- {t('goalConsoleDescription')}
-
-
-
-
-
- {responseText || t('noRunYet')}
-
-
- )
-}
diff --git a/src/components/admin/AdminJsonPanel.tsx b/src/components/admin/AdminJsonPanel.tsx
deleted file mode 100644
index 334a90d..0000000
--- a/src/components/admin/AdminJsonPanel.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-'use client'
-
-import { useState } from 'react'
-import { useTranslations } from 'next-intl'
-
-type AdminJsonPanelProps = {
- title: string
- endpoint: string
- method?: 'GET' | 'POST'
- bodyTemplate?: string
- description?: string
-}
-
-export function AdminJsonPanel({
- title,
- endpoint,
- method = 'GET',
- bodyTemplate,
- description,
-}: AdminJsonPanelProps) {
- const t = useTranslations('admin')
- const [adminKey, setAdminKey] = useState('')
- const [body, setBody] = useState(bodyTemplate || '')
- const [loading, setLoading] = useState(false)
- const [responseText, setResponseText] = useState('')
-
- async function handleFetch() {
- setLoading(true)
- setResponseText('')
-
- try {
- const parsedBody = body ? JSON.parse(body) : undefined
- const response = await fetch(endpoint, {
- method,
- headers: {
- 'Content-Type': 'application/json',
- 'x-admin-key': adminKey,
- 'x-operator': 'admin-console-ui',
- },
- body: method === 'POST' ? JSON.stringify(parsedBody || {}) : undefined,
- })
-
- const json = await response.json()
- setResponseText(JSON.stringify(json, null, 2))
- } catch (error) {
- setResponseText(
- JSON.stringify(
- {
- success: false,
- error: error instanceof Error ? error.message : t('unknownError'),
- },
- null,
- 2
- )
- )
- } finally {
- setLoading(false)
- }
- }
-
- return (
-
-
-
{title}
- {description ?
{description}
: null}
-
-
-
-
- {method === 'POST' ? (
-
- ) : null}
-
-
-
-
- {responseText || t('noResponseYet')}
-
-
- )
-}
diff --git a/src/components/admin/AdminPolicyProposalForm.tsx b/src/components/admin/AdminPolicyProposalForm.tsx
deleted file mode 100644
index 434383e..0000000
--- a/src/components/admin/AdminPolicyProposalForm.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-'use client'
-
-import { FormEvent, useState } from 'react'
-import { useTranslations } from 'next-intl'
-
-export function AdminPolicyProposalForm() {
- const t = useTranslations('admin')
- const initialPayload = {
- title: t('policyDefaultTitle'),
- description: t('policyDefaultDescription'),
- targetType: 'agent',
- targetId: 'agent.orchestrator.core',
- changes: {
- commandRules: [
- {
- resourceId: 'tool.shell.exec',
- pattern: 'git push *',
- effect: 'deny',
- },
- ],
- },
- }
-
- const [adminKey, setAdminKey] = useState('')
- const [payload, setPayload] = useState(JSON.stringify(initialPayload, null, 2))
- const [approvalId, setApprovalId] = useState('')
- const [decisionComment, setDecisionComment] = useState('')
- const [responseText, setResponseText] = useState('')
-
- async function handleProposalSubmit(event: FormEvent) {
- event.preventDefault()
- try {
- const parsed = JSON.parse(payload)
- const response = await fetch('/api/admin/policies/proposals', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'x-admin-key': adminKey,
- 'x-operator': 'policy-admin',
- },
- body: JSON.stringify(parsed),
- })
- const json = await response.json()
- setResponseText(JSON.stringify(json, null, 2))
- } catch (error) {
- setResponseText(
- JSON.stringify(
- {
- success: false,
- error: error instanceof Error ? error.message : t('unknownError'),
- },
- null,
- 2
- )
- )
- }
- }
-
- async function handleDecision(decision: 'approved' | 'rejected') {
- if (!approvalId) {
- return
- }
-
- const response = await fetch(`/api/admin/approvals/${approvalId}/decision`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'x-admin-key': adminKey,
- 'x-operator': 'policy-approver',
- },
- body: JSON.stringify({ decision, comment: decisionComment }),
- })
-
- const json = await response.json()
- setResponseText(JSON.stringify(json, null, 2))
- }
-
- return (
-
- )
-}
diff --git a/src/lib/agent-os/admin-auth.ts b/src/lib/agent-os/admin-auth.ts
deleted file mode 100644
index 0b47c5a..0000000
--- a/src/lib/agent-os/admin-auth.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import crypto from 'crypto'
-
-const TOKEN_TTL_MS = 1000 * 60 * 60 * 8
-
-type AdminTokenEntry = {
- token: string
- operator: string
- expiresAt: number
-}
-
-declare global {
- // eslint-disable-next-line no-var
- var __agentParkAdminTokens: Map | undefined
-}
-
-function getTokenStore(): Map {
- if (!globalThis.__agentParkAdminTokens) {
- globalThis.__agentParkAdminTokens = new Map()
- }
- return globalThis.__agentParkAdminTokens
-}
-
-function getExpectedAdminKey(): string | undefined {
- return process.env.ADMIN_CONSOLE_KEY || process.env.WEBHOOK_API_KEY
-}
-
-export function isValidAdminKey(input: string | null | undefined): boolean {
- const expected = getExpectedAdminKey()
- if (!input || !expected) {
- return false
- }
-
- const inputBuffer = Buffer.from(input)
- const expectedBuffer = Buffer.from(expected)
-
- if (inputBuffer.length !== expectedBuffer.length) {
- return false
- }
-
- return crypto.timingSafeEqual(inputBuffer, expectedBuffer)
-}
-
-export function issueAdminToken(operator: string): AdminTokenEntry {
- const entry: AdminTokenEntry = {
- token: crypto.randomUUID(),
- operator,
- expiresAt: Date.now() + TOKEN_TTL_MS,
- }
-
- getTokenStore().set(entry.token, entry)
- return entry
-}
-
-export function verifyAdminToken(token: string | null | undefined): AdminTokenEntry | null {
- if (!token) {
- return null
- }
-
- const entry = getTokenStore().get(token)
- if (!entry) {
- return null
- }
-
- if (entry.expiresAt <= Date.now()) {
- getTokenStore().delete(token)
- return null
- }
-
- return entry
-}
-
-export function resolveAdminOperator(params: {
- bearerToken?: string | null
- adminKey?: string | null
- fallbackOperator?: string | null
-}): { authorized: boolean; operator?: string } {
- const bearerEntry = verifyAdminToken(params.bearerToken)
- if (bearerEntry) {
- return { authorized: true, operator: bearerEntry.operator }
- }
-
- if (isValidAdminKey(params.adminKey)) {
- return {
- authorized: true,
- operator: params.fallbackOperator || 'admin-key',
- }
- }
-
- return { authorized: false }
-}
diff --git a/src/lib/agent-os/model-client.ts b/src/lib/agent-os/model-client.ts
deleted file mode 100644
index 5a409d3..0000000
--- a/src/lib/agent-os/model-client.ts
+++ /dev/null
@@ -1,212 +0,0 @@
-import { z } from 'zod'
-
-const DEFAULT_ENDPOINT = 'https://open.bigmodel.cn/api/coding/paas/v4/chat/completions'
-const DEFAULT_MODEL = 'glm-4.7'
-const DEFAULT_TIMEOUT_MS = 120_000
-const DEFAULT_MAX_TOKENS = 1200
-
-const UsageSchema = z
- .object({
- prompt_tokens: z.number().int().nonnegative().optional(),
- completion_tokens: z.number().int().nonnegative().optional(),
- total_tokens: z.number().int().nonnegative().optional(),
- })
- .partial()
-
-const OpenAiCompatResponseSchema = z.object({
- choices: z
- .array(
- z.object({
- message: z
- .object({
- content: z
- .union([
- z.string(),
- z.array(
- z
- .object({
- type: z.string().optional(),
- text: z.string().optional(),
- })
- .passthrough()
- ),
- ])
- .nullable()
- .optional(),
- })
- .passthrough(),
- })
- )
- .min(1),
- usage: UsageSchema.optional(),
-})
-
-export type AgentModelConfig = {
- endpoint: string
- model: string
- apiKey: string
- timeoutMs: number
-}
-
-export type AgentModelJsonCallResult = {
- data: T
- model: string
- latencyMs: number
- promptTokens?: number
- completionTokens?: number
- totalTokens?: number
-}
-
-function normalizeEndpoint(input: string): string {
- const trimmed = input.trim().replace(/\.+$/, '')
- if (trimmed.endsWith('/chat/completions')) {
- return trimmed
- }
- return `${trimmed.replace(/\/$/, '')}/chat/completions`
-}
-
-function extractTextContent(content: unknown): string {
- if (typeof content === 'string') {
- return content
- }
-
- if (!Array.isArray(content)) {
- return ''
- }
-
- return content
- .map((item) => {
- if (typeof item === 'string') {
- return item
- }
- if (
- typeof item === 'object' &&
- item !== null &&
- 'text' in item &&
- typeof (item as { text?: unknown }).text === 'string'
- ) {
- return (item as { text: string }).text
- }
- return ''
- })
- .join('\n')
-}
-
-function maybeParseJsonBlock(input: string): unknown {
- const trimmed = input.trim()
- if (!trimmed) {
- throw new Error('Model returned empty content.')
- }
-
- try {
- return JSON.parse(trimmed)
- } catch {
- const codeBlockMatch = trimmed.match(/```json\s*([\s\S]*?)```/i)
- if (codeBlockMatch && codeBlockMatch[1]) {
- return JSON.parse(codeBlockMatch[1].trim())
- }
-
- const firstBrace = trimmed.indexOf('{')
- const lastBrace = trimmed.lastIndexOf('}')
- if (firstBrace >= 0 && lastBrace > firstBrace) {
- const candidate = trimmed.slice(firstBrace, lastBrace + 1)
- return JSON.parse(candidate)
- }
-
- throw new Error('Failed to parse JSON payload from model output.')
- }
-}
-
-export function getAgentModelConfig(): AgentModelConfig | null {
- const apiKey =
- process.env.AGENT_OS_MODEL_API_KEY ||
- process.env.BIGMODEL_API_KEY ||
- process.env.ZHIPU_API_KEY
-
- if (!apiKey) {
- return null
- }
-
- const endpointRaw =
- process.env.AGENT_OS_MODEL_ENDPOINT ||
- process.env.BIGMODEL_API_ENDPOINT ||
- process.env.BIGMODEL_BASE_URL ||
- DEFAULT_ENDPOINT
-
- const model =
- process.env.AGENT_OS_MODEL_NAME || process.env.BIGMODEL_MODEL || process.env.MODEL_NAME || DEFAULT_MODEL
-
- const timeoutInput = Number(process.env.AGENT_OS_MODEL_TIMEOUT_MS || DEFAULT_TIMEOUT_MS)
- const timeoutMs = Number.isFinite(timeoutInput) && timeoutInput > 0 ? timeoutInput : DEFAULT_TIMEOUT_MS
-
- return {
- endpoint: normalizeEndpoint(endpointRaw),
- model,
- apiKey,
- timeoutMs,
- }
-}
-
-export async function callAgentModelForJson(input: {
- config: AgentModelConfig
- systemPrompt: string
- userPrompt: string
- schema: z.ZodType
- temperature?: number
- maxTokens?: number
-}): Promise> {
- const start = Date.now()
- const abortController = new AbortController()
- const timeoutId = setTimeout(() => abortController.abort(), input.config.timeoutMs)
-
- try {
- const response = await fetch(input.config.endpoint, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${input.config.apiKey}`,
- },
- body: JSON.stringify({
- model: input.config.model,
- temperature: input.temperature ?? 0.1,
- max_tokens: input.maxTokens ?? DEFAULT_MAX_TOKENS,
- thinking: { type: 'disabled' },
- response_format: { type: 'json_object' },
- messages: [
- { role: 'system', content: input.systemPrompt },
- { role: 'user', content: input.userPrompt },
- ],
- }),
- signal: abortController.signal,
- })
-
- if (!response.ok) {
- const errorBody = await response.text()
- throw new Error(
- `Model endpoint request failed: ${response.status} ${response.statusText}; body=${errorBody}`
- )
- }
-
- const payload = OpenAiCompatResponseSchema.parse(await response.json())
- const firstChoice = payload.choices[0]
- if (!firstChoice) {
- throw new Error('Model returned no choices.')
- }
-
- const rawText = extractTextContent(firstChoice.message.content)
- const parsed = maybeParseJsonBlock(rawText)
- const validated = input.schema.parse(parsed)
- const latencyMs = Date.now() - start
-
- return {
- data: validated,
- model: input.config.model,
- latencyMs,
- promptTokens: payload.usage?.prompt_tokens,
- completionTokens: payload.usage?.completion_tokens,
- totalTokens: payload.usage?.total_tokens,
- }
- } finally {
- clearTimeout(timeoutId)
- }
-}
diff --git a/src/lib/agent-os/n8n-production-workflows.ts b/src/lib/agent-os/n8n-production-workflows.ts
deleted file mode 100644
index b939cde..0000000
--- a/src/lib/agent-os/n8n-production-workflows.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import type { CapabilityId, ResourceManifest } from './types'
-
-export type N8nProductionWorkflowReference = {
- workflowId: string
- name: string
- active: boolean
- nodeCount: number
- capability: CapabilityId
- summary: string
-}
-
-const N8N_PRODUCTION_WORKFLOWS: N8nProductionWorkflowReference[] = [
- {
- workflowId: 'hughGsWismCpk7jd',
- name: '每日Github Trending项目计划新增',
- active: true,
- nodeCount: 18,
- capability: 'discovery_trending',
- summary:
- 'Scheduled trending source scrape, AI relevance filter, dedup check, and webhook task creation.',
- },
- {
- workflowId: 'iw9vx9ih5Lt0Mobk',
- name: 'Topic项目计划新增',
- active: true,
- nodeCount: 20,
- capability: 'discovery_topic',
- summary:
- 'Topic candidate generation, dedup pipeline, AI chain filtering, and low-recall alert hooks.',
- },
- {
- workflowId: 'bAxNZKGq2ApUUiw9',
- name: '前沿信号聚合(多源+AI Agent过滤)',
- active: true,
- nodeCount: 37,
- capability: 'signal_aggregation',
- summary:
- 'Multi-source signals (HN/GitHub/arXiv/Reddit/ProductHunt/HF), AI relevance filtering, and feedback loop to discovery.',
- },
- {
- workflowId: '1AvejnM5n-WPApU1vFt9C',
- name: '项目描述向量化',
- active: true,
- nodeCount: 11,
- capability: 'embedding_refresh',
- summary: 'Batch fetch unembedded projects, call embedding API, and update vectors in DB.',
- },
- {
- workflowId: 'F5cQ06DykBfpeyfqL-pd7',
- name: 'RAG项目搜索',
- active: true,
- nodeCount: 6,
- capability: 'rag_search',
- summary: 'Webhook query to embedding model, vector similarity SQL retrieval, and normalized response.',
- },
- {
- workflowId: 'ewx9Gs6cjrTXvwD0',
- name: 'GitHub Star 每日刷新',
- active: true,
- nodeCount: 7,
- capability: 'github_metrics_refresh',
- summary: 'Daily repository stat refresh and writeback to project metrics fields.',
- },
- {
- workflowId: '1Ig1CyVMsGJFaHOe',
- name: '项目分析入库(多源)',
- active: true,
- nodeCount: 20,
- capability: 'project_ingest',
- summary:
- 'Task queue consume, agent-browser assisted collection, content summarization, tag classification, and ingestion completion callbacks.',
- },
-]
-
-const WORKFLOW_TO_SKILL_ID: Record = {
- hughGsWismCpk7jd: 'skill.discovery.trending.daily-plan',
- iw9vx9ih5Lt0Mobk: 'skill.discovery.topic.daily-plan',
- bAxNZKGq2ApUUiw9: 'skill.discovery.signal.multi-source',
- '1AvejnM5n-WPApU1vFt9C': 'skill.knowledge.embedding.refresh',
- 'F5cQ06DykBfpeyfqL-pd7': 'skill.search.rag.similarity',
- ewx9Gs6cjrTXvwD0: 'skill.metrics.github-stars.refresh',
- '1Ig1CyVMsGJFaHOe': 'skill.ingest.project.multi-source',
-}
-
-export function listN8nProductionWorkflowReferences(): N8nProductionWorkflowReference[] {
- return N8N_PRODUCTION_WORKFLOWS.map((workflow) => ({ ...workflow }))
-}
-
-export function listN8nBackedSkillManifests(): ResourceManifest[] {
- return N8N_PRODUCTION_WORKFLOWS.map((workflow) => ({
- id: WORKFLOW_TO_SKILL_ID[workflow.workflowId] || `skill.n8n.${workflow.workflowId}`,
- kind: 'skill',
- version: '1.0.0',
- status: workflow.active ? 'active' : 'disabled',
- owner: 'n8n.production',
- inputSchemaRef: `schema://skills/${workflow.capability}/input`,
- outputSchemaRef: `schema://skills/${workflow.capability}/output`,
- riskLevel: 'L1',
- defaultPolicy: 'allow',
- tags: ['skill', 'n8n', workflow.capability],
- latencyHintMs: 500,
- metadata: {
- source: 'n8n.production',
- workflowId: workflow.workflowId,
- workflowName: workflow.name,
- nodeCount: workflow.nodeCount,
- summary: workflow.summary,
- },
- }))
-}
-
diff --git a/src/lib/agent-os/orchestrator.test.ts b/src/lib/agent-os/orchestrator.test.ts
deleted file mode 100644
index 772edb9..0000000
--- a/src/lib/agent-os/orchestrator.test.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-import { beforeEach, describe, expect, it } from 'vitest'
-import { orchestrateGoal } from './orchestrator'
-import {
- createPolicyProposal,
- decideApproval,
- getRun,
- listTasks,
- listToolCalls,
- resetAgentOsStateForTests,
-} from './store'
-
-describe('agent-os orchestrator', () => {
- beforeEach(() => {
- resetAgentOsStateForTests()
- })
-
- it('creates run/task/tool-call records for a goal', async () => {
- const result = await orchestrateGoal({
- goal: 'Review code quality and inspect logs',
- createdBy: 'test-user',
- })
-
- const run = getRun(result.runId)
- const tasks = listTasks(result.runId)
- const toolCalls = listToolCalls(result.runId)
-
- expect(run).not.toBeNull()
- expect(tasks.length).toBeGreaterThan(0)
- expect(toolCalls.length).toBe(tasks.length)
- })
-
- it('uses preferred agent when provided', async () => {
- const result = await orchestrateGoal({
- goal: 'Collect GitHub discovery signals',
- createdBy: 'test-user',
- preferredAgentId: 'agent.discovery.scout',
- })
-
- expect(result.agent.id).toBe('agent.discovery.scout')
- })
-
- it('adds low-trust fallback reason when mcp web reader is denied by approved proposal', async () => {
- const firstRun = await orchestrateGoal({
- goal: 'Read this website and inspect the project details',
- createdBy: 'test-user',
- preferredAgentId: 'agent.discovery.scout',
- })
-
- const firstCall = listToolCalls(firstRun.runId)[0]
- expect(firstCall?.resourceId).toBe('mcp.agent_browser.read')
-
- const proposal = createPolicyProposal({
- title: 'Deny agent-browser MCP for discovery scout',
- description: 'Force fallback for trust-level validation.',
- targetType: 'agent',
- targetId: 'agent.discovery.scout',
- createdBy: 'test-user',
- changes: {
- policyOverrides: {
- 'mcp.agent_browser.read': 'deny',
- },
- },
- })
-
- decideApproval({
- approvalId: proposal.approvalTicket.id,
- decision: 'approved',
- decidedBy: 'test-user',
- comment: 'Approved in test',
- })
-
- const secondRun = await orchestrateGoal({
- goal: 'Read this website and inspect the project details',
- createdBy: 'test-user',
- preferredAgentId: 'agent.discovery.scout',
- })
- const secondCall = listToolCalls(secondRun.runId)[0]
- expect(secondCall?.resourceId).toBe('tool.web.read')
- expect(secondCall?.reasons.some((reason) => reason.startsWith('fallback_low_trust:'))).toBe(
- true
- )
- })
-
- it('falls back to heuristic planner when model env is missing', async () => {
- const previousApiKey = process.env.AGENT_OS_MODEL_API_KEY
- const previousBigmodelApiKey = process.env.BIGMODEL_API_KEY
- const previousZhipuApiKey = process.env.ZHIPU_API_KEY
- delete process.env.AGENT_OS_MODEL_API_KEY
- delete process.env.BIGMODEL_API_KEY
- delete process.env.ZHIPU_API_KEY
-
- const runResult = await orchestrateGoal({
- goal: 'Need rag retrieval for agent projects',
- createdBy: 'test-user',
- })
-
- const run = getRun(runResult.runId)
- expect(run?.planning?.source).toBe('heuristic')
- expect(run?.planning?.warning).toContain('fallback')
-
- if (previousApiKey) {
- process.env.AGENT_OS_MODEL_API_KEY = previousApiKey
- }
- if (previousBigmodelApiKey) {
- process.env.BIGMODEL_API_KEY = previousBigmodelApiKey
- }
- if (previousZhipuApiKey) {
- process.env.ZHIPU_API_KEY = previousZhipuApiKey
- }
- })
-})
diff --git a/src/lib/agent-os/orchestrator.ts b/src/lib/agent-os/orchestrator.ts
deleted file mode 100644
index 3524364..0000000
--- a/src/lib/agent-os/orchestrator.ts
+++ /dev/null
@@ -1,671 +0,0 @@
-import { z } from 'zod'
-import { callAgentModelForJson, getAgentModelConfig } from './model-client'
-import { evaluatePolicy } from './policy-engine'
-import {
- getAgentProfileById,
- getResourceManifestById,
- listAgentProfiles,
- listResourceManifests,
-} from './resource-pool'
-import {
- addMemoryEntry,
- addTask,
- addToolCall,
- createPolicyProposal,
- createRun,
- setRunPendingApprovals,
- updateRunStatus,
-} from './store'
-import type {
- AgentManifest,
- AgentRun,
- CapabilityId,
- CapabilityIntent,
- PolicyEffect,
- ResourceManifest,
-} from './types'
-import { CAPABILITY_IDS } from './types'
-
-const CAPABILITY_ALIAS_MAP: Record = {
- web_read: 'web_read',
- read_web: 'web_read',
- code_review: 'code_review',
- code_check: 'code_review',
- log_review: 'log_review',
- log_analysis: 'log_review',
- knowledge_retrieve: 'knowledge_retrieve',
- retrieve: 'knowledge_retrieve',
- knowledge_write: 'knowledge_write',
- ingest_knowledge: 'knowledge_write',
- task_spawn: 'task_spawn',
- spawn_task: 'task_spawn',
- discovery_trending: 'discovery_trending',
- search_trends: 'discovery_trending',
- discovery_topic: 'discovery_topic',
- topic_discovery: 'discovery_topic',
- signal_aggregation: 'signal_aggregation',
- signal_filter: 'signal_aggregation',
- rag_search: 'rag_search',
- rag_retrieve: 'rag_search',
- embedding_refresh: 'embedding_refresh',
- vectorize: 'embedding_refresh',
- github_metrics_refresh: 'github_metrics_refresh',
- metrics_refresh: 'github_metrics_refresh',
- project_ingest: 'project_ingest',
- ingest_project: 'project_ingest',
-}
-
-const PlannerIntentRawSchema = z.object({
- capability: z.string().optional(),
- intent: z.string().optional(),
- reason: z.string().optional(),
- description: z.string().optional(),
- context: z.record(z.unknown()).optional(),
- parameters: z.record(z.unknown()).optional(),
-})
-
-function toCapabilityId(value: string | undefined): CapabilityId | null {
- if (!value) {
- return null
- }
- const normalized = normalizeText(value).replace(/\s+/g, '_')
- return CAPABILITY_ALIAS_MAP[normalized] || null
-}
-
-function normalizePlannerIntents(intents: z.infer[]): CapabilityIntent[] {
- const normalized: CapabilityIntent[] = []
-
- intents.forEach((item) => {
- const capability = toCapabilityId(item.capability || item.intent)
- if (!capability) {
- return
- }
-
- normalized.push({
- capability,
- reason: item.reason || item.description || `Planned by model for ${capability}.`,
- context: item.context || item.parameters,
- })
- })
-
- return normalized
-}
-
-const PlannerResponseSchema = z
- .object({
- intents: z.array(PlannerIntentRawSchema).min(1).max(12).optional(),
- plan: z.array(PlannerIntentRawSchema).min(1).max(12).optional(),
- summary: z.string().min(8).max(1200).optional(),
- suggestedAgentId: z.string().max(200).optional(),
- riskNotes: z.union([z.string().min(1).max(1000), z.array(z.string().min(1).max(300)).max(10)]).optional(),
- answer: z.string().max(1200).optional(),
- })
- .superRefine((value, ctx) => {
- if (!value.intents && !value.plan) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: 'Missing intents/plan field',
- })
- }
- })
- .transform((value, ctx) => {
- const normalizedIntents = normalizePlannerIntents(value.intents || value.plan || [])
- if (normalizedIntents.length === 0) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: 'No valid capability intents found after normalization.',
- })
- return z.NEVER
- }
-
- return {
- intents: normalizedIntents,
- summary: value.summary || value.answer,
- suggestedAgentId: value.suggestedAgentId && value.suggestedAgentId.trim().length > 0
- ? value.suggestedAgentId
- : undefined,
- riskNotes: Array.isArray(value.riskNotes)
- ? value.riskNotes
- : value.riskNotes
- ? [value.riskNotes]
- : [],
- }
- })
-
-const INTENT_TO_RESOURCE_MAP: Record = {
- web_read: ['mcp.agent_browser.read', 'tool.web.read', 'tool.web.search'],
- code_review: ['tool.lsp.query', 'tool.fs.grep', 'tool.git.status_diff', 'tool.lint.run'],
- log_review: ['tool.logs.query', 'tool.logs.trace'],
- knowledge_retrieve: ['tool.knowledge.retrieve', 'tool.memory.read', 'tool.db.query_ro'],
- knowledge_write: ['tool.knowledge.ingest_proposal', 'tool.memory.write_proposal'],
- task_spawn: ['tool.task.spawn'],
- discovery_trending: [
- 'skill.discovery.trending.daily-plan',
- 'mcp.agent_browser.read',
- 'tool.web.search',
- ],
- discovery_topic: ['skill.discovery.topic.daily-plan', 'mcp.agent_browser.read', 'tool.web.search'],
- signal_aggregation: [
- 'skill.discovery.signal.multi-source',
- 'mcp.agent_browser.read',
- 'tool.web.search',
- ],
- rag_search: ['skill.search.rag.similarity', 'tool.knowledge.retrieve', 'tool.db.query_ro'],
- embedding_refresh: [
- 'skill.knowledge.embedding.refresh',
- 'tool.knowledge.ingest_proposal',
- 'tool.db.write_staging',
- ],
- github_metrics_refresh: ['skill.metrics.github-stars.refresh', 'tool.db.write_staging'],
- project_ingest: [
- 'skill.ingest.project.multi-source',
- 'tool.db.write_staging',
- 'tool.knowledge.ingest_proposal',
- ],
-}
-
-const ALLOW_PRIORITY: Record = {
- allow: 3,
- ask: 2,
- deny: 1,
-}
-
-function normalizeText(value: string): string {
- return value.toLowerCase().trim()
-}
-
-function deriveCapabilityIntentsHeuristic(goal: string): CapabilityIntent[] {
- const normalized = normalizeText(goal)
- const intents: CapabilityIntent[] = []
-
- if (
- normalized.includes('网页') ||
- normalized.includes('website') ||
- normalized.includes('web') ||
- normalized.includes('readme')
- ) {
- intents.push({ capability: 'web_read', reason: 'Goal mentions website exploration.' })
- }
-
- if (
- normalized.includes('trend') ||
- normalized.includes('trending') ||
- normalized.includes('daily github') ||
- normalized.includes('每日 github')
- ) {
- intents.push({
- capability: 'discovery_trending',
- reason: 'Goal maps to daily trending discovery workflow.',
- })
- }
-
- if (normalized.includes('topic') || normalized.includes('话题')) {
- intents.push({
- capability: 'discovery_topic',
- reason: 'Goal maps to topic-based discovery workflow.',
- })
- }
-
- if (
- normalized.includes('signal') ||
- normalized.includes('前沿') ||
- normalized.includes('聚合') ||
- normalized.includes('hot topic')
- ) {
- intents.push({
- capability: 'signal_aggregation',
- reason: 'Goal maps to multi-source signal aggregation.',
- })
- }
-
- if (
- normalized.includes('代码') ||
- normalized.includes('review') ||
- normalized.includes('审查') ||
- normalized.includes('lint')
- ) {
- intents.push({ capability: 'code_review', reason: 'Goal mentions code quality checks.' })
- }
-
- if (normalized.includes('日志') || normalized.includes('log') || normalized.includes('trace')) {
- intents.push({ capability: 'log_review', reason: 'Goal requires log or trace analysis.' })
- }
-
- if (
- normalized.includes('知识') ||
- normalized.includes('rag') ||
- normalized.includes('memory') ||
- normalized.includes('knowledge')
- ) {
- intents.push({
- capability: normalized.includes('rag') ? 'rag_search' : 'knowledge_retrieve',
- reason: 'Goal references retrieval from knowledge memory layers.',
- })
- }
-
- if (
- normalized.includes('向量') ||
- normalized.includes('embedding') ||
- normalized.includes('vectorize')
- ) {
- intents.push({
- capability: 'embedding_refresh',
- reason: 'Goal references vector refresh workflow.',
- })
- }
-
- if (normalized.includes('star') || normalized.includes('metrics') || normalized.includes('指标')) {
- intents.push({
- capability: 'github_metrics_refresh',
- reason: 'Goal references repository metric refresh.',
- })
- }
-
- if (
- normalized.includes('写入') ||
- normalized.includes('ingest') ||
- normalized.includes('入库') ||
- normalized.includes('内化')
- ) {
- intents.push({
- capability: normalized.includes('入库') ? 'project_ingest' : 'knowledge_write',
- reason: 'Goal references write-oriented operations.',
- })
- }
-
- if (intents.length === 0) {
- intents.push({
- capability: 'knowledge_retrieve',
- reason: 'Fallback intent for generic orchestration tasks.',
- })
- }
-
- const unique = new Map()
- intents.forEach((intent) => {
- if (!unique.has(intent.capability)) {
- unique.set(intent.capability, intent)
- }
- })
- return Array.from(unique.values())
-}
-
-async function deriveCapabilityIntents(goal: string): Promise<{
- intents: CapabilityIntent[]
- planning: AgentRun['planning']
- summary?: string
- suggestedAgentId?: string
- riskNotes: string[]
-}> {
- const config = getAgentModelConfig()
- if (!config) {
- return {
- intents: deriveCapabilityIntentsHeuristic(goal),
- planning: {
- source: 'heuristic',
- warning: 'Model config missing; fallback to heuristic planner.',
- },
- riskNotes: [],
- }
- }
-
- try {
- const capabilityCatalog = CAPABILITY_IDS.join(', ')
- const modelResult = await callAgentModelForJson({
- config,
- temperature: 0.1,
- schema: PlannerResponseSchema,
- systemPrompt: [
- 'You are the Agent Park Gateway planner.',
- 'Output strictly JSON with this structure: {"intents":[{"capability":"...","reason":"...","context":{}}],"summary":"...","riskNotes":["..."],"suggestedAgentId":"optional"}',
- `Allowed capabilities: ${capabilityCatalog}.`,
- 'Prefer n8n-backed discovery and ingestion capabilities when relevant.',
- 'Always keep production write/deploy risks explicit in riskNotes.',
- 'Never use keys other than capability/reason/context inside intents.',
- ].join(' '),
- userPrompt: [
- 'Convert this goal into capability intents for execution.',
- 'Goal:',
- goal,
- 'Return concise reasons and include suggestedAgentId only if strong confidence.',
- ].join('\n'),
- })
-
- return {
- intents: modelResult.data.intents,
- summary: modelResult.data.summary,
- suggestedAgentId: modelResult.data.suggestedAgentId,
- riskNotes: modelResult.data.riskNotes || [],
- planning: {
- source: 'model',
- model: modelResult.model,
- latencyMs: modelResult.latencyMs,
- promptTokens: modelResult.promptTokens,
- completionTokens: modelResult.completionTokens,
- totalTokens: modelResult.totalTokens,
- },
- }
- } catch (error) {
- return {
- intents: deriveCapabilityIntentsHeuristic(goal),
- planning: {
- source: 'heuristic',
- model: config.model,
- warning:
- error instanceof Error
- ? `Model planner failed, fallback to heuristic: ${error.message}`
- : 'Model planner failed, fallback to heuristic.',
- },
- riskNotes: [],
- }
- }
-}
-
-function pickAgentByGoal(input: {
- goal: string
- intents: CapabilityIntent[]
- preferredAgentId?: string
- suggestedAgentId?: string
-}): AgentManifest {
- const configured = listAgentProfiles()
- const byId = (id: string | undefined) =>
- (id && configured.find((agent) => agent.id === id)) || null
-
- const preferred = byId(input.preferredAgentId)
- if (preferred) {
- return preferred
- }
-
- const suggested = byId(input.suggestedAgentId)
- if (suggested) {
- return suggested
- }
-
- const intentSet = new Set(input.intents.map((intent) => intent.capability))
- if (
- intentSet.has('discovery_trending') ||
- intentSet.has('discovery_topic') ||
- intentSet.has('signal_aggregation')
- ) {
- return configured.find((agent) => agent.id === 'agent.discovery.scout') || configured[0]!
- }
-
- if (intentSet.has('code_review') || intentSet.has('log_review')) {
- return configured.find((agent) => agent.id === 'agent.quality.reviewer') || configured[0]!
- }
-
- if (
- intentSet.has('knowledge_retrieve') ||
- intentSet.has('knowledge_write') ||
- intentSet.has('embedding_refresh') ||
- intentSet.has('rag_search')
- ) {
- return configured.find((agent) => agent.id === 'agent.knowledge.librarian') || configured[0]!
- }
-
- const normalized = normalizeText(input.goal)
- if (normalized.includes('discover') || normalized.includes('发现')) {
- return configured.find((agent) => agent.id === 'agent.discovery.scout') || configured[0]!
- }
-
- return configured.find((agent) => agent.id === 'agent.orchestrator.core') || configured[0]!
-}
-
-function extractIntentCommand(intent: CapabilityIntent): string | undefined {
- const value = intent.context?.command
- return typeof value === 'string' ? value : undefined
-}
-
-function extractIntentPaths(intent: CapabilityIntent): string[] | undefined {
- const raw = intent.context?.paths
- if (!Array.isArray(raw)) {
- return undefined
- }
- return raw.filter((value): value is string => typeof value === 'string')
-}
-
-function selectResource(
- intent: CapabilityIntent,
- agent: AgentManifest,
- repeatedCallCount: number
-): {
- selected?: ResourceManifest
- effect: PolicyEffect
- reasons: string[]
- candidates: string[]
-} {
- const preferredCandidates = INTENT_TO_RESOURCE_MAP[intent.capability] || []
- const allResources = listResourceManifests()
-
- const candidates = preferredCandidates
- .map((resourceId) => getResourceManifestById(resourceId))
- .filter((resource): resource is ResourceManifest => Boolean(resource))
- .filter((resource) => resource.status === 'active')
-
- if (candidates.length === 0) {
- const taggedFallback = allResources.filter(
- (resource) =>
- resource.status === 'active' &&
- resource.tags.some((tag) => agent.enabledResourceTags.includes(tag))
- )
-
- const fallbackCandidate = taggedFallback[0]
- if (!fallbackCandidate) {
- return {
- effect: 'deny',
- reasons: ['no_candidates_for_intent'],
- candidates: [],
- }
- }
-
- const fallbackDecision = evaluatePolicy(agent, fallbackCandidate, {
- workspaceRoot: process.cwd(),
- repeatedCallCount,
- command: extractIntentCommand(intent),
- paths: extractIntentPaths(intent),
- })
-
- return {
- selected: fallbackCandidate,
- effect: fallbackDecision.effect,
- reasons: [...fallbackDecision.reasons, 'fallback_by_tag_scope'],
- candidates: taggedFallback.map((resource) => resource.id),
- }
- }
-
- let selected: ResourceManifest | undefined
- let selectedEffect: PolicyEffect = 'deny'
- let selectedReasons: string[] = ['no_allowed_candidate']
-
- for (const candidate of candidates) {
- const decision = evaluatePolicy(agent, candidate, {
- workspaceRoot: process.cwd(),
- repeatedCallCount,
- command: extractIntentCommand(intent),
- paths: extractIntentPaths(intent),
- })
-
- if (
- !selected ||
- ALLOW_PRIORITY[decision.effect] > ALLOW_PRIORITY[selectedEffect] ||
- (ALLOW_PRIORITY[decision.effect] === ALLOW_PRIORITY[selectedEffect] &&
- candidate.riskLevel < (selected?.riskLevel || 'L3'))
- ) {
- selected = candidate
- selectedEffect = decision.effect
- selectedReasons = decision.reasons
- }
-
- if (decision.effect === 'allow') {
- break
- }
- }
-
- if (intent.capability === 'web_read' && selected && selected.id !== candidates[0]?.id) {
- selectedReasons = [...selectedReasons, `fallback_low_trust:${selected.id}`]
- }
-
- return {
- selected,
- effect: selectedEffect,
- reasons: selectedReasons,
- candidates: candidates.map((resource) => resource.id),
- }
-}
-
-function shouldCreateApprovalTicket(selection: {
- effect: PolicyEffect
- selected?: ResourceManifest
-}): boolean {
- if (selection.effect === 'ask') {
- return true
- }
- return selection.effect === 'deny' && selection.selected?.riskLevel === 'L3'
-}
-
-export async function orchestrateGoal(input: {
- goal: string
- createdBy: string
- preferredAgentId?: string
-}) {
- const derived = await deriveCapabilityIntents(input.goal)
- const agent = pickAgentByGoal({
- goal: input.goal,
- intents: derived.intents,
- preferredAgentId: input.preferredAgentId,
- suggestedAgentId: derived.suggestedAgentId,
- })
-
- const run = createRun({
- goal: input.goal,
- createdBy: input.createdBy,
- agentId: agent.id,
- status: 'PLAN_COMPILED',
- notes: ['Run created by Agent Gateway orchestrator.'],
- planning: derived.planning,
- })
-
- if (derived.summary) {
- updateRunStatus(run.id, 'PLAN_COMPILED', `Planner summary: ${derived.summary}`)
- }
-
- if (derived.riskNotes.length > 0) {
- updateRunStatus(run.id, 'PLAN_COMPILED', `Planner risk notes: ${derived.riskNotes.join(' | ')}`)
- }
-
- let pendingApprovals = 0
- let hasFailure = false
- const approvalTicketIds: string[] = []
-
- derived.intents.forEach((intent, index) => {
- const selected = selectResource(intent, agent, index)
- const requiresApproval = shouldCreateApprovalTicket(selected)
-
- let taskStatus: 'queued' | 'running' | 'completed' | 'failed' | 'blocked_approval' | 'cancelled'
- if (requiresApproval) {
- taskStatus = 'blocked_approval'
- } else if (selected.effect === 'deny') {
- taskStatus = 'failed'
- } else {
- taskStatus = 'completed'
- }
-
- let taskMessage = selected.selected
- ? `Selected ${selected.selected.id} for ${intent.capability}`
- : `No resource selected for ${intent.capability}`
-
- if (requiresApproval) {
- const proposal = createPolicyProposal({
- title: `Run override request for ${selected.selected?.id || 'unknown-resource'}`,
- description: [
- `Run ${run.id} requires approval for ${intent.capability}.`,
- `Requested resource: ${selected.selected?.id || 'none'}.`,
- `Policy decision: ${selected.effect}.`,
- ].join(' '),
- targetType: 'resource',
- targetId: selected.selected?.id || 'resource.unknown',
- changes: {
- runtimeRunId: run.id,
- intent: intent.capability,
- reasons: selected.reasons,
- },
- createdBy: input.createdBy,
- })
- pendingApprovals += 1
- approvalTicketIds.push(proposal.approvalTicket.id)
- taskMessage += ` (approvalTicket=${proposal.approvalTicket.id})`
- }
-
- if (selected.effect === 'deny' && !requiresApproval) {
- hasFailure = true
- taskMessage += ' (hard denied by policy)'
- }
-
- const task = addTask(run.id, {
- intent,
- status: taskStatus,
- selectedResourceId: selected.selected?.id,
- policyEffect: selected.effect,
- message: taskMessage,
- })
-
- addToolCall(run.id, {
- taskId: task.id,
- agentId: agent.id,
- resourceId: selected.selected?.id || 'none',
- policyEffect: selected.effect,
- success: selected.effect !== 'deny',
- inputSummary: `intent=${intent.capability}`,
- outputSummary: selected.selected ? `resource=${selected.selected.id}` : 'selection_failed',
- reasons: selected.reasons,
- })
- })
-
- setRunPendingApprovals(run.id, pendingApprovals)
-
- if (pendingApprovals > 0) {
- updateRunStatus(
- run.id,
- 'APPROVAL_WAITING',
- `Run paused for ${pendingApprovals} approval-required action(s).`
- )
- } else if (hasFailure) {
- updateRunStatus(run.id, 'FAILED', 'Run failed due to policy-denied actions.')
- } else {
- updateRunStatus(run.id, 'COMPLETED', 'Run completed with policy-safe selections.')
- }
-
- addMemoryEntry({
- layer: 'working',
- key: `run:${run.id}:goal`,
- value: input.goal,
- evidence: `createdBy=${input.createdBy}`,
- createdBy: input.createdBy,
- })
-
- addMemoryEntry({
- layer: 'project',
- key: 'agent_os.last_goal',
- value: input.goal,
- evidence: `runId=${run.id}`,
- createdBy: input.createdBy,
- })
-
- if (derived.summary) {
- addMemoryEntry({
- layer: 'knowledge',
- key: `run:${run.id}:planner_summary`,
- value: derived.summary,
- evidence: `agent=${agent.id}`,
- createdBy: input.createdBy,
- })
- }
-
- return {
- runId: run.id,
- agent,
- intents: derived.intents,
- pendingApprovals,
- planning: derived.planning,
- approvalTicketIds,
- }
-}
diff --git a/src/lib/agent-os/policy-engine.test.ts b/src/lib/agent-os/policy-engine.test.ts
deleted file mode 100644
index cb192c3..0000000
--- a/src/lib/agent-os/policy-engine.test.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import { evaluatePolicy } from './policy-engine'
-import { getAgentProfileById, getResourceManifestById } from './resource-pool'
-
-function mustGetAgent(agentId: string) {
- const agent = getAgentProfileById(agentId)
- if (!agent) {
- throw new Error(`Missing agent profile: ${agentId}`)
- }
- return agent
-}
-
-function mustGetResource(resourceId: string) {
- const resource = getResourceManifestById(resourceId)
- if (!resource) {
- throw new Error(`Missing resource manifest: ${resourceId}`)
- }
- return resource
-}
-
-describe('agent-os policy engine', () => {
- it('denies git push command by command rule', () => {
- const agent = mustGetAgent('agent.orchestrator.core')
- const resource = mustGetResource('tool.shell.exec')
-
- const result = evaluatePolicy(agent, resource, { command: 'git push origin main' })
-
- expect(result.effect).toBe('deny')
- expect(result.reasons.some((reason) => reason.includes('command_rule'))).toBe(true)
- })
-
- it('denies .env reads by path rule', () => {
- const agent = mustGetAgent('agent.orchestrator.core')
- const resource = mustGetResource('tool.fs.read')
-
- const result = evaluatePolicy(agent, resource, { paths: ['/tmp/.env.local'] })
-
- expect(result.effect).toBe('deny')
- expect(result.reasons.some((reason) => reason.includes('path_rule'))).toBe(true)
- })
-
- it('degrades repeated calls via doom loop guard', () => {
- const agent = mustGetAgent('agent.discovery.scout')
- const resource = mustGetResource('tool.web.read')
-
- const result = evaluatePolicy(agent, resource, { repeatedCallCount: 5 })
-
- expect(result.effect).toBe('deny')
- expect(result.reasons.some((reason) => reason.includes('doom_loop'))).toBe(true)
- })
-})
diff --git a/src/lib/agent-os/policy-engine.ts b/src/lib/agent-os/policy-engine.ts
deleted file mode 100644
index 5a0159c..0000000
--- a/src/lib/agent-os/policy-engine.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-import path from 'path'
-import type { AgentManifest, PolicyDecision, PolicyEffect, ResourceManifest } from './types'
-
-interface PolicyContext {
- command?: string
- paths?: string[]
- workspaceRoot?: string
- repeatedCallCount?: number
-}
-
-const SEVERITY: Record = {
- allow: 0,
- ask: 1,
- deny: 2,
-}
-
-function chooseMoreStrict(current: PolicyEffect, next: PolicyEffect): PolicyEffect {
- return SEVERITY[next] > SEVERITY[current] ? next : current
-}
-
-function wildcardToRegex(pattern: string): RegExp {
- const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
- const wildcardReplaced = escaped.replace(/\*/g, '.*')
- return new RegExp(`^${wildcardReplaced}$`, 'i')
-}
-
-function matchesPattern(value: string, pattern: string): boolean {
- return wildcardToRegex(pattern).test(value)
-}
-
-function evaluateCommandRules(
- agent: AgentManifest,
- resource: ResourceManifest,
- command: string | undefined
-): PolicyDecision {
- const reasons: string[] = []
- let effect: PolicyEffect = resource.defaultPolicy
-
- if (!command) {
- return { effect, reasons }
- }
-
- for (const rule of agent.commandRules) {
- if (rule.resourceId !== resource.id) {
- continue
- }
- if (matchesPattern(command, rule.pattern)) {
- effect = chooseMoreStrict(effect, rule.effect)
- reasons.push(`command_rule:${rule.pattern}->${rule.effect}`)
- }
- }
-
- return { effect, reasons }
-}
-
-function evaluatePathRules(
- agent: AgentManifest,
- resource: ResourceManifest,
- paths: string[] | undefined
-): PolicyDecision {
- const reasons: string[] = []
- let effect: PolicyEffect = resource.defaultPolicy
-
- if (!paths || paths.length === 0) {
- return { effect, reasons }
- }
-
- for (const inputPath of paths) {
- const baseName = path.basename(inputPath)
- for (const rule of agent.pathRules) {
- if (rule.resourceId !== resource.id) {
- continue
- }
- if (matchesPattern(baseName, rule.pattern)) {
- effect = chooseMoreStrict(effect, rule.effect)
- reasons.push(`path_rule:${rule.pattern}->${rule.effect}:${baseName}`)
- }
- }
- }
-
- return { effect, reasons }
-}
-
-function evaluateExternalDirectory(
- resource: ResourceManifest,
- ctx: PolicyContext
-): PolicyDecision {
- if (!ctx.workspaceRoot || !ctx.paths || ctx.paths.length === 0) {
- return { effect: resource.defaultPolicy, reasons: [] }
- }
-
- let effect: PolicyEffect = resource.defaultPolicy
- const reasons: string[] = []
-
- for (const inputPath of ctx.paths) {
- const resolvedPath = path.resolve(inputPath)
- const relative = path.relative(ctx.workspaceRoot, resolvedPath)
- const outsideWorkspace = relative.startsWith('..') || path.isAbsolute(relative)
-
- if (outsideWorkspace) {
- effect = chooseMoreStrict(effect, 'ask')
- reasons.push(`external_directory:${resolvedPath}`)
- }
- }
-
- return { effect, reasons }
-}
-
-function evaluateDoomLoop(
- resource: ResourceManifest,
- repeatedCallCount: number | undefined
-): PolicyDecision {
- if (!repeatedCallCount || repeatedCallCount < 3) {
- return { effect: resource.defaultPolicy, reasons: [] }
- }
-
- const reasons = [`doom_loop:repeat_count=${repeatedCallCount}`]
- if (repeatedCallCount >= 5) {
- return { effect: 'deny', reasons }
- }
- return { effect: 'ask', reasons }
-}
-
-function evaluateAgentOverrides(agent: AgentManifest, resource: ResourceManifest): PolicyDecision {
- const override = agent.policyOverrides[resource.id]
- if (!override) {
- return { effect: resource.defaultPolicy, reasons: [] }
- }
- return {
- effect: chooseMoreStrict(resource.defaultPolicy, override),
- reasons: [`agent_override:${override}`],
- }
-}
-
-export function evaluatePolicy(
- agent: AgentManifest,
- resource: ResourceManifest,
- ctx: PolicyContext = {}
-): PolicyDecision {
- const reasons: string[] = []
- let effect: PolicyEffect = resource.defaultPolicy
-
- const overrideDecision = evaluateAgentOverrides(agent, resource)
- effect = chooseMoreStrict(effect, overrideDecision.effect)
- reasons.push(...overrideDecision.reasons)
-
- const commandDecision = evaluateCommandRules(agent, resource, ctx.command)
- effect = chooseMoreStrict(effect, commandDecision.effect)
- reasons.push(...commandDecision.reasons)
-
- const pathDecision = evaluatePathRules(agent, resource, ctx.paths)
- effect = chooseMoreStrict(effect, pathDecision.effect)
- reasons.push(...pathDecision.reasons)
-
- const externalDirDecision = evaluateExternalDirectory(resource, ctx)
- effect = chooseMoreStrict(effect, externalDirDecision.effect)
- reasons.push(...externalDirDecision.reasons)
-
- const doomLoopDecision = evaluateDoomLoop(resource, ctx.repeatedCallCount)
- effect = chooseMoreStrict(effect, doomLoopDecision.effect)
- reasons.push(...doomLoopDecision.reasons)
-
- return {
- effect,
- reasons,
- }
-}
diff --git a/src/lib/agent-os/policy-runtime.test.ts b/src/lib/agent-os/policy-runtime.test.ts
deleted file mode 100644
index 6160e05..0000000
--- a/src/lib/agent-os/policy-runtime.test.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import { getAgentProfileById } from './resource-pool'
-import { createPolicyProposal, decideApproval, resetAgentOsStateForTests } from './store'
-
-describe('agent-os runtime policy patch', () => {
- it('applies approved proposal patch to agent profile', () => {
- resetAgentOsStateForTests()
-
- const before = getAgentProfileById('agent.discovery.scout')
- expect(before?.policyOverrides['mcp.agent_browser.read']).toBe('allow')
-
- const proposal = createPolicyProposal({
- title: 'Deny MCP browser for scout',
- description: 'Test runtime patch',
- targetType: 'agent',
- targetId: 'agent.discovery.scout',
- createdBy: 'tester',
- changes: {
- policyOverrides: {
- 'mcp.agent_browser.read': 'deny',
- },
- },
- })
-
- decideApproval({
- approvalId: proposal.approvalTicket.id,
- decision: 'approved',
- decidedBy: 'approver',
- comment: 'approved in test',
- })
-
- const after = getAgentProfileById('agent.discovery.scout')
- expect(after?.policyOverrides['mcp.agent_browser.read']).toBe('deny')
- })
-})
-
diff --git a/src/lib/agent-os/policy-runtime.ts b/src/lib/agent-os/policy-runtime.ts
deleted file mode 100644
index be6a5d1..0000000
--- a/src/lib/agent-os/policy-runtime.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import { z } from 'zod'
-import type { AgentManifest, PolicyEffect, PolicyProposal } from './types'
-
-type RuntimeAgentPatch = {
- policyOverrides?: Record
- commandRules?: AgentManifest['commandRules']
- pathRules?: AgentManifest['pathRules']
- enabledResourceTags?: string[]
-}
-
-const PolicyEffectSchema = z.enum(['allow', 'ask', 'deny'])
-
-const RuntimePatchSchema = z
- .object({
- policyOverrides: z.record(PolicyEffectSchema).optional(),
- commandRules: z
- .array(
- z.object({
- resourceId: z.string().min(1),
- pattern: z.string().min(1),
- effect: PolicyEffectSchema,
- })
- )
- .optional(),
- pathRules: z
- .array(
- z.object({
- resourceId: z.string().min(1),
- pattern: z.string().min(1),
- effect: PolicyEffectSchema,
- })
- )
- .optional(),
- enabledResourceTags: z.array(z.string().min(1)).optional(),
- })
- .strict()
-
-declare global {
- // eslint-disable-next-line no-var
- var __agentParkRuntimePolicyPatchByAgentId: Map | undefined
-}
-
-function getRuntimePatchStore(): Map {
- if (!globalThis.__agentParkRuntimePolicyPatchByAgentId) {
- globalThis.__agentParkRuntimePolicyPatchByAgentId = new Map()
- }
- return globalThis.__agentParkRuntimePolicyPatchByAgentId
-}
-
-function mergePatch(base: RuntimeAgentPatch, incoming: RuntimeAgentPatch): RuntimeAgentPatch {
- return {
- policyOverrides: {
- ...(base.policyOverrides || {}),
- ...(incoming.policyOverrides || {}),
- },
- commandRules: incoming.commandRules || base.commandRules,
- pathRules: incoming.pathRules || base.pathRules,
- enabledResourceTags: incoming.enabledResourceTags || base.enabledResourceTags,
- }
-}
-
-export function applyApprovedProposalPatch(proposal: PolicyProposal): void {
- if (proposal.targetType !== 'agent') {
- return
- }
-
- const parsed = RuntimePatchSchema.safeParse(proposal.changes)
- if (!parsed.success) {
- return
- }
-
- const store = getRuntimePatchStore()
- const current = store.get(proposal.targetId) || {}
- store.set(proposal.targetId, mergePatch(current, parsed.data))
-}
-
-export function getRuntimePatchForAgent(agentId: string): RuntimeAgentPatch | undefined {
- return getRuntimePatchStore().get(agentId)
-}
-
-export function mergeAgentWithRuntimePatch(agent: AgentManifest): AgentManifest {
- const patch = getRuntimePatchForAgent(agent.id)
- if (!patch) {
- return {
- ...agent,
- policyOverrides: { ...agent.policyOverrides },
- commandRules: [...agent.commandRules],
- pathRules: [...agent.pathRules],
- enabledResourceTags: [...agent.enabledResourceTags],
- }
- }
-
- return {
- ...agent,
- policyOverrides: {
- ...agent.policyOverrides,
- ...(patch.policyOverrides || {}),
- },
- commandRules: patch.commandRules ? [...patch.commandRules] : [...agent.commandRules],
- pathRules: patch.pathRules ? [...patch.pathRules] : [...agent.pathRules],
- enabledResourceTags: patch.enabledResourceTags
- ? [...patch.enabledResourceTags]
- : [...agent.enabledResourceTags],
- }
-}
-
-export function resetRuntimePolicyPatchesForTests(): void {
- globalThis.__agentParkRuntimePolicyPatchByAgentId = undefined
-}
-
diff --git a/src/lib/agent-os/resource-pool.ts b/src/lib/agent-os/resource-pool.ts
deleted file mode 100644
index 3730177..0000000
--- a/src/lib/agent-os/resource-pool.ts
+++ /dev/null
@@ -1,646 +0,0 @@
-import { listN8nBackedSkillManifests, listN8nProductionWorkflowReferences } from './n8n-production-workflows'
-import { mergeAgentWithRuntimePatch } from './policy-runtime'
-import type {
- AgentManifest,
- PolicyEffect,
- ResourceManifest,
- ResourceKind,
-} from './types'
-
-const BASE_RESOURCE_MANIFESTS: ResourceManifest[] = [
- {
- id: 'tool.fs.read',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/fs/read/input',
- outputSchemaRef: 'schema://tools/fs/read/output',
- riskLevel: 'L0',
- defaultPolicy: 'allow',
- tags: ['filesystem', 'read'],
- latencyHintMs: 30,
- },
- {
- id: 'tool.fs.list',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/fs/list/input',
- outputSchemaRef: 'schema://tools/fs/list/output',
- riskLevel: 'L0',
- defaultPolicy: 'allow',
- tags: ['filesystem', 'read'],
- latencyHintMs: 30,
- },
- {
- id: 'tool.fs.glob',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/fs/glob/input',
- outputSchemaRef: 'schema://tools/fs/glob/output',
- riskLevel: 'L0',
- defaultPolicy: 'allow',
- tags: ['filesystem', 'read'],
- latencyHintMs: 40,
- },
- {
- id: 'tool.fs.grep',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/fs/grep/input',
- outputSchemaRef: 'schema://tools/fs/grep/output',
- riskLevel: 'L0',
- defaultPolicy: 'allow',
- tags: ['filesystem', 'read'],
- latencyHintMs: 40,
- },
- {
- id: 'tool.fs.write',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/fs/write/input',
- outputSchemaRef: 'schema://tools/fs/write/output',
- riskLevel: 'L2',
- defaultPolicy: 'ask',
- tags: ['filesystem', 'write'],
- latencyHintMs: 60,
- },
- {
- id: 'tool.fs.edit',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/fs/edit/input',
- outputSchemaRef: 'schema://tools/fs/edit/output',
- riskLevel: 'L2',
- defaultPolicy: 'ask',
- tags: ['filesystem', 'write'],
- latencyHintMs: 60,
- },
- {
- id: 'tool.fs.patch',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/fs/patch/input',
- outputSchemaRef: 'schema://tools/fs/patch/output',
- riskLevel: 'L2',
- defaultPolicy: 'ask',
- tags: ['filesystem', 'write'],
- latencyHintMs: 70,
- },
- {
- id: 'tool.shell.exec',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/shell/exec/input',
- outputSchemaRef: 'schema://tools/shell/exec/output',
- riskLevel: 'L2',
- defaultPolicy: 'ask',
- tags: ['shell', 'execution'],
- latencyHintMs: 120,
- },
- {
- id: 'tool.git.status_diff',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/git/status_diff/input',
- outputSchemaRef: 'schema://tools/git/status_diff/output',
- riskLevel: 'L0',
- defaultPolicy: 'allow',
- tags: ['git', 'read'],
- latencyHintMs: 100,
- },
- {
- id: 'tool.git.branch_commit',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/git/branch_commit/input',
- outputSchemaRef: 'schema://tools/git/branch_commit/output',
- riskLevel: 'L2',
- defaultPolicy: 'ask',
- tags: ['git', 'write'],
- latencyHintMs: 120,
- },
- {
- id: 'tool.git.push_pr',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/git/push_pr/input',
- outputSchemaRef: 'schema://tools/git/push_pr/output',
- riskLevel: 'L3',
- defaultPolicy: 'deny',
- tags: ['git', 'write', 'production'],
- latencyHintMs: 150,
- },
- {
- id: 'tool.build.run',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/build/run/input',
- outputSchemaRef: 'schema://tools/build/run/output',
- riskLevel: 'L2',
- defaultPolicy: 'ask',
- tags: ['build'],
- latencyHintMs: 3000,
- },
- {
- id: 'tool.test.run',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/test/run/input',
- outputSchemaRef: 'schema://tools/test/run/output',
- riskLevel: 'L2',
- defaultPolicy: 'ask',
- tags: ['test'],
- latencyHintMs: 5000,
- },
- {
- id: 'tool.lint.run',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/lint/run/input',
- outputSchemaRef: 'schema://tools/lint/run/output',
- riskLevel: 'L1',
- defaultPolicy: 'ask',
- tags: ['lint'],
- latencyHintMs: 2000,
- },
- {
- id: 'tool.lsp.query',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/lsp/query/input',
- outputSchemaRef: 'schema://tools/lsp/query/output',
- riskLevel: 'L0',
- defaultPolicy: 'allow',
- tags: ['code', 'analysis'],
- latencyHintMs: 100,
- },
- {
- id: 'tool.web.read',
- kind: 'mcp_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.browser',
- inputSchemaRef: 'schema://tools/web/read/input',
- outputSchemaRef: 'schema://tools/web/read/output',
- riskLevel: 'L1',
- defaultPolicy: 'allow',
- tags: ['web', 'mcp', 'agent-browser'],
- latencyHintMs: 400,
- },
- {
- id: 'tool.web.search',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/web/search/input',
- outputSchemaRef: 'schema://tools/web/search/output',
- riskLevel: 'L1',
- defaultPolicy: 'ask',
- tags: ['web', 'search'],
- latencyHintMs: 400,
- },
- {
- id: 'tool.db.query_ro',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/db/query_ro/input',
- outputSchemaRef: 'schema://tools/db/query_ro/output',
- riskLevel: 'L1',
- defaultPolicy: 'ask',
- tags: ['database', 'read'],
- latencyHintMs: 80,
- },
- {
- id: 'tool.db.write_staging',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/db/write_staging/input',
- outputSchemaRef: 'schema://tools/db/write_staging/output',
- riskLevel: 'L2',
- defaultPolicy: 'ask',
- tags: ['database', 'write'],
- latencyHintMs: 120,
- },
- {
- id: 'tool.db.write_prod',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/db/write_prod/input',
- outputSchemaRef: 'schema://tools/db/write_prod/output',
- riskLevel: 'L3',
- defaultPolicy: 'deny',
- tags: ['database', 'write', 'production'],
- latencyHintMs: 120,
- },
- {
- id: 'tool.logs.query',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/logs/query/input',
- outputSchemaRef: 'schema://tools/logs/query/output',
- riskLevel: 'L1',
- defaultPolicy: 'ask',
- tags: ['logs'],
- latencyHintMs: 150,
- },
- {
- id: 'tool.logs.trace',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/logs/trace/input',
- outputSchemaRef: 'schema://tools/logs/trace/output',
- riskLevel: 'L1',
- defaultPolicy: 'ask',
- tags: ['logs', 'trace'],
- latencyHintMs: 180,
- },
- {
- id: 'tool.ci.trigger',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/ci/trigger/input',
- outputSchemaRef: 'schema://tools/ci/trigger/output',
- riskLevel: 'L2',
- defaultPolicy: 'ask',
- tags: ['ci'],
- latencyHintMs: 100,
- },
- {
- id: 'tool.deploy.preview',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/deploy/preview/input',
- outputSchemaRef: 'schema://tools/deploy/preview/output',
- riskLevel: 'L2',
- defaultPolicy: 'ask',
- tags: ['deploy'],
- latencyHintMs: 500,
- },
- {
- id: 'tool.deploy.prod',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/deploy/prod/input',
- outputSchemaRef: 'schema://tools/deploy/prod/output',
- riskLevel: 'L3',
- defaultPolicy: 'deny',
- tags: ['deploy', 'production'],
- latencyHintMs: 500,
- },
- {
- id: 'tool.skill.load',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/skill/load/input',
- outputSchemaRef: 'schema://tools/skill/load/output',
- riskLevel: 'L0',
- defaultPolicy: 'allow',
- tags: ['skill'],
- latencyHintMs: 20,
- },
- {
- id: 'tool.task.spawn',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/task/spawn/input',
- outputSchemaRef: 'schema://tools/task/spawn/output',
- riskLevel: 'L1',
- defaultPolicy: 'ask',
- tags: ['task', 'agent'],
- latencyHintMs: 40,
- },
- {
- id: 'tool.memory.read',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/memory/read/input',
- outputSchemaRef: 'schema://tools/memory/read/output',
- riskLevel: 'L0',
- defaultPolicy: 'allow',
- tags: ['memory', 'read'],
- latencyHintMs: 25,
- },
- {
- id: 'tool.memory.write_proposal',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/memory/write_proposal/input',
- outputSchemaRef: 'schema://tools/memory/write_proposal/output',
- riskLevel: 'L1',
- defaultPolicy: 'ask',
- tags: ['memory', 'write'],
- latencyHintMs: 50,
- },
- {
- id: 'tool.knowledge.retrieve',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/knowledge/retrieve/input',
- outputSchemaRef: 'schema://tools/knowledge/retrieve/output',
- riskLevel: 'L0',
- defaultPolicy: 'allow',
- tags: ['knowledge', 'read'],
- latencyHintMs: 100,
- },
- {
- id: 'tool.knowledge.ingest_proposal',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/knowledge/ingest_proposal/input',
- outputSchemaRef: 'schema://tools/knowledge/ingest_proposal/output',
- riskLevel: 'L1',
- defaultPolicy: 'ask',
- tags: ['knowledge', 'write'],
- latencyHintMs: 120,
- },
- {
- id: 'tool.human.approval',
- kind: 'base_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.os',
- inputSchemaRef: 'schema://tools/human/approval/input',
- outputSchemaRef: 'schema://tools/human/approval/output',
- riskLevel: 'L0',
- defaultPolicy: 'allow',
- tags: ['approval', 'control'],
- latencyHintMs: 20,
- },
- {
- id: 'mcp.agent_browser.read',
- kind: 'mcp_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'agent.browser',
- inputSchemaRef: 'schema://mcp/agent_browser/read/input',
- outputSchemaRef: 'schema://mcp/agent_browser/read/output',
- riskLevel: 'L1',
- defaultPolicy: 'allow',
- tags: ['mcp', 'web', 'agent-browser'],
- latencyHintMs: 350,
- },
- {
- id: 'mcp.n8n-mcp',
- kind: 'mcp_tool',
- version: '1.0.0',
- status: 'active',
- owner: 'integration.team',
- inputSchemaRef: 'schema://mcp/n8n/input',
- outputSchemaRef: 'schema://mcp/n8n/output',
- riskLevel: 'L1',
- defaultPolicy: 'ask',
- tags: ['mcp', 'n8n'],
- latencyHintMs: 350,
- },
-]
-
-const AGENT_PROFILES: AgentManifest[] = [
- {
- id: 'agent.orchestrator.core',
- name: 'Orchestrator Core Agent',
- model: 'glm-4.7',
- owner: 'platform.team',
- budgetDaily: 120,
- enabledResourceTags: [
- 'control',
- 'approval',
- 'task',
- 'memory',
- 'knowledge',
- 'skill',
- 'n8n',
- 'read',
- 'analysis',
- ],
- policyOverrides: {
- 'tool.task.spawn': 'ask',
- 'tool.shell.exec': 'ask',
- 'tool.git.push_pr': 'deny',
- 'tool.db.write_prod': 'deny',
- 'tool.deploy.prod': 'deny',
- },
- commandRules: [
- {
- resourceId: 'tool.shell.exec',
- pattern: 'git push *',
- effect: 'deny',
- },
- {
- resourceId: 'tool.shell.exec',
- pattern: 'git *',
- effect: 'allow',
- },
- ],
- pathRules: [
- {
- resourceId: 'tool.fs.read',
- pattern: '*.env*',
- effect: 'deny',
- },
- {
- resourceId: 'tool.fs.write',
- pattern: '*.env*',
- effect: 'deny',
- },
- ],
- },
- {
- id: 'agent.discovery.scout',
- name: 'Discovery Scout Agent',
- model: 'glm-4.7',
- owner: 'discovery.team',
- budgetDaily: 100,
- enabledResourceTags: ['discovery', 'web', 'mcp', 'read', 'skill', 'n8n'],
- policyOverrides: {
- 'mcp.agent_browser.read': 'allow',
- 'tool.web.read': 'allow',
- 'tool.db.write_prod': 'deny',
- 'tool.deploy.prod': 'deny',
- },
- commandRules: [],
- pathRules: [],
- },
- {
- id: 'agent.quality.reviewer',
- name: 'Quality Reviewer Agent',
- model: 'glm-4.7',
- owner: 'quality.team',
- budgetDaily: 80,
- enabledResourceTags: ['logs', 'read', 'analysis', 'test', 'lint', 'skill', 'n8n'],
- policyOverrides: {
- 'tool.logs.query': 'allow',
- 'tool.logs.trace': 'allow',
- 'tool.db.write_prod': 'deny',
- 'tool.deploy.prod': 'deny',
- },
- commandRules: [],
- pathRules: [],
- },
- {
- id: 'agent.knowledge.librarian',
- name: 'Knowledge Librarian Agent',
- model: 'glm-4.7',
- owner: 'knowledge.team',
- budgetDaily: 90,
- enabledResourceTags: ['knowledge', 'memory', 'read', 'write', 'skill', 'n8n'],
- policyOverrides: {
- 'tool.knowledge.retrieve': 'allow',
- 'tool.memory.read': 'allow',
- 'tool.db.write_prod': 'deny',
- 'tool.deploy.prod': 'deny',
- },
- commandRules: [],
- pathRules: [],
- },
-]
-
-const POLICY_PROFILES = [
- {
- id: 'policy.default.safe',
- mode: 'safe',
- description: 'Default deny for high-risk operations; approvals required for ask-level actions.',
- highRiskRequiresApproval: true,
- },
-]
-
-function toAgentProfileResource(agent: AgentManifest): ResourceManifest {
- return {
- id: `profile.${agent.id}`,
- kind: 'agent_profile',
- version: '1.0.0',
- status: 'active',
- owner: agent.owner,
- inputSchemaRef: 'schema://agent_profile/input',
- outputSchemaRef: 'schema://agent_profile/output',
- riskLevel: 'L1',
- defaultPolicy: 'ask',
- tags: ['agent', 'profile'],
- latencyHintMs: 0,
- }
-}
-
-function toPolicyProfileResource(
- policyProfile: (typeof POLICY_PROFILES)[number]
-): ResourceManifest {
- return {
- id: `profile.${policyProfile.id}`,
- kind: 'policy_profile',
- version: '1.0.0',
- status: 'active',
- owner: 'platform.team',
- inputSchemaRef: 'schema://policy_profile/input',
- outputSchemaRef: 'schema://policy_profile/output',
- riskLevel: 'L1',
- defaultPolicy: 'ask',
- tags: ['policy', 'profile'],
- latencyHintMs: 0,
- }
-}
-
-function listUnifiedResources(): ResourceManifest[] {
- const agentProfileResources = listAgentProfiles().map((agent) => toAgentProfileResource(agent))
- const policyProfileResources = POLICY_PROFILES.map((policy) => toPolicyProfileResource(policy))
- const n8nSkillResources = listN8nBackedSkillManifests()
- return [...BASE_RESOURCE_MANIFESTS, ...n8nSkillResources, ...agentProfileResources, ...policyProfileResources]
-}
-
-export function listResourceManifests(kind?: ResourceKind): ResourceManifest[] {
- const resources = listUnifiedResources()
- if (!kind) {
- return resources
- }
- return resources.filter((resource) => resource.kind === kind)
-}
-
-export function getResourceManifestById(resourceId: string): ResourceManifest | null {
- return listUnifiedResources().find((resource) => resource.id === resourceId) || null
-}
-
-export function listAgentProfiles(): AgentManifest[] {
- return AGENT_PROFILES.map((profile) => mergeAgentWithRuntimePatch(profile))
-}
-
-export function getAgentProfileById(agentId: string): AgentManifest | null {
- return listAgentProfiles().find((profile) => profile.id === agentId) || null
-}
-
-export function listPolicyProfiles() {
- return POLICY_PROFILES.map((profile) => ({ ...profile }))
-}
-
-export function listDefaultToolsPolicy(): Array<{
- id: string
- defaultPolicy: PolicyEffect
- riskLevel: ResourceManifest['riskLevel']
-}> {
- return BASE_RESOURCE_MANIFESTS.filter(
- (resource) => resource.kind === 'base_tool' || resource.kind === 'mcp_tool'
- ).map((resource) => ({
- id: resource.id,
- defaultPolicy: resource.defaultPolicy,
- riskLevel: resource.riskLevel,
- }))
-}
-
-export function listN8nProductionWorkflows() {
- return listN8nProductionWorkflowReferences()
-}
-
diff --git a/src/lib/agent-os/schemas.ts b/src/lib/agent-os/schemas.ts
deleted file mode 100644
index 69538d6..0000000
--- a/src/lib/agent-os/schemas.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { z } from 'zod'
-
-export const AdminAuthTokenRequestSchema = z.object({
- adminKey: z.string().min(32),
- operator: z.string().min(2).max(80).optional(),
-})
-
-export const CreatePolicyProposalRequestSchema = z.object({
- title: z.string().min(3).max(200),
- description: z.string().min(5).max(3000),
- targetType: z.enum(['agent', 'resource', 'policy_profile']),
- targetId: z.string().min(1).max(200),
- changes: z.record(z.unknown()),
-})
-
-export const ApprovalDecisionRequestSchema = z.object({
- decision: z.enum(['approved', 'rejected']),
- comment: z.string().max(1000).optional(),
-})
-
-export const AgentGoalRequestSchema = z.object({
- goal: z.string().min(3).max(5000),
- preferredAgentId: z.string().max(200).optional(),
-})
-
-export const RunControlRequestSchema = z.object({
- action: z.enum(['pause', 'resume', 'rollback', 'fail', 'complete']),
- note: z.string().max(1000).optional(),
-})
-
-export type AdminAuthTokenRequest = z.infer
-export type CreatePolicyProposalRequest = z.infer
-export type ApprovalDecisionRequest = z.infer
-export type AgentGoalRequest = z.infer
-export type RunControlRequest = z.infer
diff --git a/src/lib/agent-os/store.ts b/src/lib/agent-os/store.ts
deleted file mode 100644
index 110e9d3..0000000
--- a/src/lib/agent-os/store.ts
+++ /dev/null
@@ -1,331 +0,0 @@
-import crypto from 'crypto'
-
-import { applyApprovedProposalPatch, resetRuntimePolicyPatchesForTests } from './policy-runtime'
-import type {
- AgentRun,
- AgentTask,
- ApprovalStatus,
- ApprovalTicket,
- MemoryEntry,
- PolicyProposal,
- ProposalStatus,
- RunStatus,
- ToolCallAuditRecord,
-} from './types'
-
-interface AgentOsState {
- runs: Map
- tasksByRun: Map
- toolCallsByRun: Map
- proposals: Map
- approvalTickets: Map
- memoryEntries: MemoryEntry[]
-}
-
-declare global {
- // eslint-disable-next-line no-var
- var __agentParkAgentOsState: AgentOsState | undefined
-}
-
-function nowIso(): string {
- return new Date().toISOString()
-}
-
-function getState(): AgentOsState {
- if (!globalThis.__agentParkAgentOsState) {
- globalThis.__agentParkAgentOsState = {
- runs: new Map(),
- tasksByRun: new Map(),
- toolCallsByRun: new Map(),
- proposals: new Map(),
- approvalTickets: new Map(),
- memoryEntries: [],
- }
- }
-
- return globalThis.__agentParkAgentOsState
-}
-
-export function createRun(input: {
- goal: string
- createdBy: string
- agentId: string
- status?: RunStatus
- notes?: string[]
- planning?: AgentRun['planning']
-}): AgentRun {
- const state = getState()
- const timestamp = nowIso()
-
- const run: AgentRun = {
- id: crypto.randomUUID(),
- goal: input.goal,
- createdBy: input.createdBy,
- agentId: input.agentId,
- status: input.status || 'GOAL_ACCEPTED',
- createdAt: timestamp,
- updatedAt: timestamp,
- pendingApprovals: 0,
- notes: input.notes || [],
- planning: input.planning,
- }
-
- state.runs.set(run.id, run)
- state.tasksByRun.set(run.id, [])
- state.toolCallsByRun.set(run.id, [])
-
- return run
-}
-
-export function updateRunStatus(runId: string, status: RunStatus, note?: string): AgentRun | null {
- const state = getState()
- const run = state.runs.get(runId)
- if (!run) {
- return null
- }
-
- const next: AgentRun = {
- ...run,
- status,
- updatedAt: nowIso(),
- notes: note ? [...run.notes, note] : run.notes,
- }
-
- state.runs.set(runId, next)
- return next
-}
-
-export function listRuns(): AgentRun[] {
- const state = getState()
- return Array.from(state.runs.values()).sort((a, b) => b.createdAt.localeCompare(a.createdAt))
-}
-
-export function getRun(runId: string): AgentRun | null {
- return getState().runs.get(runId) || null
-}
-
-export function addTask(
- runId: string,
- task: Omit
-): AgentTask {
- const state = getState()
- const timestamp = nowIso()
-
- const createdTask: AgentTask = {
- ...task,
- id: crypto.randomUUID(),
- runId,
- createdAt: timestamp,
- updatedAt: timestamp,
- }
-
- const existing = state.tasksByRun.get(runId) || []
- state.tasksByRun.set(runId, [...existing, createdTask])
-
- return createdTask
-}
-
-export function updateTaskStatus(runId: string, taskId: string, status: AgentTask['status']): AgentTask | null {
- const state = getState()
- const tasks = state.tasksByRun.get(runId)
- if (!tasks) {
- return null
- }
-
- let updatedTask: AgentTask | null = null
- const nextTasks = tasks.map((task) => {
- if (task.id !== taskId) {
- return task
- }
-
- updatedTask = {
- ...task,
- status,
- updatedAt: nowIso(),
- }
-
- return updatedTask
- })
-
- state.tasksByRun.set(runId, nextTasks)
- return updatedTask
-}
-
-export function listTasks(runId?: string): AgentTask[] {
- const state = getState()
- if (runId) {
- return [...(state.tasksByRun.get(runId) || [])]
- }
-
- return Array.from(state.tasksByRun.values()).flat()
-}
-
-export function addToolCall(
- runId: string,
- record: Omit
-): ToolCallAuditRecord {
- const state = getState()
- const created: ToolCallAuditRecord = {
- ...record,
- id: crypto.randomUUID(),
- runId,
- createdAt: nowIso(),
- }
-
- const existing = state.toolCallsByRun.get(runId) || []
- state.toolCallsByRun.set(runId, [...existing, created])
-
- return created
-}
-
-export function listToolCalls(runId?: string): ToolCallAuditRecord[] {
- const state = getState()
- if (runId) {
- return [...(state.toolCallsByRun.get(runId) || [])]
- }
-
- return Array.from(state.toolCallsByRun.values()).flat()
-}
-
-export function createPolicyProposal(input: {
- title: string
- description: string
- targetType: PolicyProposal['targetType']
- targetId: string
- changes: Record
- createdBy: string
-}): { proposal: PolicyProposal; approvalTicket: ApprovalTicket } {
- const state = getState()
- const timestamp = nowIso()
-
- const proposal: PolicyProposal = {
- id: crypto.randomUUID(),
- title: input.title,
- description: input.description,
- targetType: input.targetType,
- targetId: input.targetId,
- changes: input.changes,
- status: 'pending_approval',
- createdBy: input.createdBy,
- createdAt: timestamp,
- updatedAt: timestamp,
- }
-
- const approvalTicket: ApprovalTicket = {
- id: crypto.randomUUID(),
- proposalId: proposal.id,
- status: 'pending',
- requestedBy: input.createdBy,
- createdAt: timestamp,
- updatedAt: timestamp,
- }
-
- proposal.approvalTicketId = approvalTicket.id
-
- state.proposals.set(proposal.id, proposal)
- state.approvalTickets.set(approvalTicket.id, approvalTicket)
-
- return { proposal, approvalTicket }
-}
-
-export function listPolicyProposals(): PolicyProposal[] {
- return Array.from(getState().proposals.values()).sort((a, b) => b.createdAt.localeCompare(a.createdAt))
-}
-
-export function getApprovalTicket(approvalId: string): ApprovalTicket | null {
- return getState().approvalTickets.get(approvalId) || null
-}
-
-export function listApprovalTickets(): ApprovalTicket[] {
- return Array.from(getState().approvalTickets.values()).sort((a, b) =>
- b.createdAt.localeCompare(a.createdAt)
- )
-}
-
-export function decideApproval(input: {
- approvalId: string
- decision: ApprovalStatus
- decidedBy: string
- comment?: string
-}): { approvalTicket: ApprovalTicket; proposal?: PolicyProposal } | null {
- const state = getState()
- const ticket = state.approvalTickets.get(input.approvalId)
- if (!ticket) {
- return null
- }
-
- const timestamp = nowIso()
- const nextTicket: ApprovalTicket = {
- ...ticket,
- status: input.decision,
- updatedAt: timestamp,
- decidedBy: input.decidedBy,
- decisionComment: input.comment,
- }
-
- state.approvalTickets.set(input.approvalId, nextTicket)
-
- const proposal = state.proposals.get(ticket.proposalId)
- if (!proposal) {
- return { approvalTicket: nextTicket }
- }
-
- const proposalStatus: ProposalStatus = input.decision === 'approved' ? 'approved' : 'rejected'
- const nextProposal: PolicyProposal = {
- ...proposal,
- status: proposalStatus,
- updatedAt: timestamp,
- }
-
- state.proposals.set(nextProposal.id, nextProposal)
- if (proposalStatus === 'approved') {
- applyApprovedProposalPatch(nextProposal)
- }
-
- return {
- approvalTicket: nextTicket,
- proposal: nextProposal,
- }
-}
-
-export function addMemoryEntry(input: Omit): MemoryEntry {
- const state = getState()
- const entry: MemoryEntry = {
- ...input,
- id: crypto.randomUUID(),
- createdAt: nowIso(),
- }
- state.memoryEntries.push(entry)
- return entry
-}
-
-export function listMemoryEntries(layer?: MemoryEntry['layer']): MemoryEntry[] {
- const entries = getState().memoryEntries
- if (!layer) {
- return [...entries]
- }
- return entries.filter((entry) => entry.layer === layer)
-}
-
-export function setRunPendingApprovals(runId: string, pendingApprovals: number): AgentRun | null {
- const state = getState()
- const run = state.runs.get(runId)
- if (!run) {
- return null
- }
-
- const next: AgentRun = {
- ...run,
- pendingApprovals,
- updatedAt: nowIso(),
- status: pendingApprovals > 0 ? 'APPROVAL_WAITING' : run.status,
- }
-
- state.runs.set(runId, next)
- return next
-}
-
-export function resetAgentOsStateForTests(): void {
- globalThis.__agentParkAgentOsState = undefined
- resetRuntimePolicyPatchesForTests()
-}
diff --git a/src/lib/agent-os/types.ts b/src/lib/agent-os/types.ts
deleted file mode 100644
index 894a51c..0000000
--- a/src/lib/agent-os/types.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-export type ResourceKind =
- | 'base_tool'
- | 'mcp_tool'
- | 'skill'
- | 'agent_profile'
- | 'policy_profile'
-
-export type ResourceStatus = 'active' | 'disabled' | 'deprecated'
-export type RiskLevel = 'L0' | 'L1' | 'L2' | 'L3'
-export type PolicyEffect = 'allow' | 'ask' | 'deny'
-
-export const CAPABILITY_IDS = [
- 'web_read',
- 'code_review',
- 'log_review',
- 'knowledge_retrieve',
- 'knowledge_write',
- 'task_spawn',
- 'discovery_trending',
- 'discovery_topic',
- 'signal_aggregation',
- 'rag_search',
- 'embedding_refresh',
- 'github_metrics_refresh',
- 'project_ingest',
-] as const
-
-export type CapabilityId = (typeof CAPABILITY_IDS)[number]
-
-export interface ResourceManifest {
- id: string
- kind: ResourceKind
- version: string
- status: ResourceStatus
- owner: string
- inputSchemaRef: string
- outputSchemaRef: string
- riskLevel: RiskLevel
- defaultPolicy: PolicyEffect
- tags: string[]
- costHint?: number
- latencyHintMs?: number
- metadata?: Record
-}
-
-export interface CapabilityIntent {
- capability: CapabilityId
- reason: string
- context?: Record
-}
-
-export interface ToolSelectionDecision {
- intent: CapabilityIntent
- selectedResourceId?: string
- candidateResourceIds: string[]
- effect: PolicyEffect
- reasons: string[]
-}
-
-export interface AgentManifest {
- id: string
- name: string
- model: string
- owner: string
- budgetDaily: number
- enabledResourceTags: string[]
- policyOverrides: Record
- commandRules: Array<{
- resourceId: string
- pattern: string
- effect: PolicyEffect
- }>
- pathRules: Array<{
- resourceId: string
- pattern: string
- effect: PolicyEffect
- }>
-}
-
-export type RunStatus =
- | 'GOAL_ACCEPTED'
- | 'PLAN_COMPILED'
- | 'TASK_DISPATCHED'
- | 'TOOL_EXECUTING'
- | 'EVIDENCE_VALIDATING'
- | 'QUALITY_REVIEWING'
- | 'MEMORY_COMMITTING'
- | 'APPROVAL_WAITING'
- | 'COMPLETED'
- | 'FAILED'
- | 'PAUSED'
- | 'ROLLED_BACK'
-
-export type TaskStatus =
- | 'queued'
- | 'running'
- | 'completed'
- | 'failed'
- | 'blocked_approval'
- | 'cancelled'
-
-export interface AgentRun {
- id: string
- goal: string
- createdBy: string
- agentId: string
- status: RunStatus
- createdAt: string
- updatedAt: string
- pendingApprovals: number
- notes: string[]
- planning?: {
- source: 'model' | 'heuristic'
- model?: string
- latencyMs?: number
- promptTokens?: number
- completionTokens?: number
- totalTokens?: number
- warning?: string
- }
-}
-
-export interface AgentTask {
- id: string
- runId: string
- intent: CapabilityIntent
- status: TaskStatus
- selectedResourceId?: string
- policyEffect: PolicyEffect
- createdAt: string
- updatedAt: string
- message: string
-}
-
-export interface ToolCallAuditRecord {
- id: string
- runId: string
- taskId: string
- agentId: string
- resourceId: string
- policyEffect: PolicyEffect
- success: boolean
- inputSummary: string
- outputSummary: string
- reasons: string[]
- createdAt: string
-}
-
-export type ApprovalStatus = 'pending' | 'approved' | 'rejected'
-
-export interface ApprovalTicket {
- id: string
- proposalId: string
- status: ApprovalStatus
- createdAt: string
- updatedAt: string
- requestedBy: string
- decidedBy?: string
- decisionComment?: string
-}
-
-export type ProposalStatus = 'draft' | 'pending_approval' | 'approved' | 'rejected'
-
-export interface PolicyProposal {
- id: string
- title: string
- description: string
- targetType: 'agent' | 'resource' | 'policy_profile'
- targetId: string
- changes: Record
- status: ProposalStatus
- createdBy: string
- createdAt: string
- updatedAt: string
- approvalTicketId?: string
-}
-
-export interface MemoryEntry {
- id: string
- layer: 'working' | 'project' | 'knowledge'
- key: string
- value: string
- evidence?: string
- createdAt: string
- createdBy: string
-}
-
-export interface PolicyDecision {
- effect: PolicyEffect
- reasons: string[]
-}
diff --git a/src/messages/en.json b/src/messages/en.json
index 854d86f..0ed1694 100644
--- a/src/messages/en.json
+++ b/src/messages/en.json
@@ -229,8 +229,7 @@
"projects": "Projects",
"signals": "Signals",
"about": "About",
- "submitProject": "SUBMIT PROJECT",
- "admin": "Admin"
+ "submitProject": "SUBMIT PROJECT"
},
"notFound": {
"title": "Page Not Found",
@@ -258,74 +257,5 @@
"closeAnnouncement": "Close announcement",
"copyright": "© 2025 Agent Park. All rights reserved.",
"designedFor": "DESIGNED FOR AI BUILDERS"
- },
- "admin": {
- "title": "Agent Control Console",
- "subtitle": "Unified operations for agent goal orchestration, resource pools (Skill/MCP/tools), policy proposal approvals, and runtime audit logs.",
- "navChat": "Agent Chat",
- "navResources": "Resource Pool",
- "navPolicies": "Policy Proposals",
- "navRuns": "Run Ops",
- "navLogs": "Audit Logs",
- "cardChatTitle": "Orchestrator Chat",
- "cardChatDescription": "Submit goals and let the gateway derive intents and select resources.",
- "cardResourceTitle": "Resource Pool Management",
- "cardResourceDescription": "Inspect and govern tools, MCP bindings, skills, and agent profiles.",
- "cardPolicyTitle": "Policy Proposal Approval",
- "cardPolicyDescription": "Manage access policy changes via proposal -> approval -> activation flow.",
- "cardLogsTitle": "Audit Logs",
- "cardLogsDescription": "Inspect task-level and tool-call-level runtime records.",
- "goalDefault": "Explore agent projects on GitHub, and run code and log quality checks.",
- "unknownError": "Unknown error",
- "goalConsoleTitle": "Agent Gateway Chat Console",
- "goalConsoleDescription": "Submit a high-level goal. The orchestrator derives capability intents, selects resources from the pool, and records run/task/tool-call audit trails.",
- "adminKeyLabel": "Admin key",
- "adminKeyPlaceholder": "ADMIN_CONSOLE_KEY",
- "goalLabel": "Goal",
- "submitting": "Submitting...",
- "submitGoal": "Submit Goal",
- "noRunYet": "No run result yet.",
- "requestJsonLabel": "Request JSON",
- "loading": "Loading...",
- "executeEndpoint": "Execute {method} {endpoint}",
- "noResponseYet": "No response yet.",
- "policyDefaultTitle": "Block orchestrator git push",
- "policyDefaultDescription": "Tighten shell command policy for orchestrator and deny git push patterns.",
- "policyTitle": "Policy Proposal & Approval",
- "proposalJsonLabel": "Proposal JSON",
- "submitProposal": "Submit Proposal",
- "approvalDecisionTitle": "Approval Decision",
- "approvalTicketIdPlaceholder": "Approval ticket ID",
- "decisionCommentPlaceholder": "Approval comment",
- "approve": "Approve",
- "reject": "Reject",
- "logsTaskTitle": "Task Logs",
- "logsTaskDescription": "Task-level runtime audit stream.",
- "logsToolCallTitle": "Tool Call Logs",
- "logsToolCallDescription": "Tool-call-level audit records with policy decisions.",
- "policyProposalsTitle": "Policy Proposal List",
- "policyProposalsDescription": "Inspect policy proposals and approval status.",
- "approvalTicketsTitle": "Approval Ticket List",
- "approvalTicketsDescription": "Inspect proposal-to-ticket bindings.",
- "agentsTitle": "Agent Profiles",
- "agentsDescription": "Inspect Agent Manifest, budget, tag domains, and policy overrides.",
- "mcpTitle": "MCP Resource Bindings",
- "mcpDescription": "Inspect MCP tools in the resource pool and Agent Browser priority settings.",
- "skillsTitle": "Skill Registry",
- "skillsDescription": "Inspect skill inventory and versions in the pool.",
- "toolsTitle": "Base Tools + MCP Tools",
- "toolsDescription": "Inspect tool risk levels and default allow/ask/deny strategy.",
- "resourcesTabTools": "Tools",
- "resourcesTabSkills": "Skills",
- "resourcesTabAgents": "Agents",
- "resourcesOverviewTitle": "Resource Pool Overview",
- "resourcesOverviewDescription": "Unified view of base tools, MCP tools, skills, agent profiles, and policy profiles.",
- "runDetailTitle": "Run Detail: {id}",
- "runDetailDescription": "Inspect intent, task, and tool-call audit records for this run.",
- "runsListTitle": "Run List",
- "runsListDescription": "Inspect gateway run instances and orchestration status.",
- "runsControlTitle": "Run Control (POST)",
- "runsControlDescription": "Replace REPLACE_RUN_ID in endpoint with a real run ID before control actions.",
- "runsControlDefaultNote": "Manual pause from admin console"
}
}
diff --git a/src/messages/zh.json b/src/messages/zh.json
index 00eca09..9176e21 100644
--- a/src/messages/zh.json
+++ b/src/messages/zh.json
@@ -229,8 +229,7 @@
"projects": "项目列表",
"signals": "前沿信号",
"about": "关于",
- "submitProject": "提交项目",
- "admin": "管理台"
+ "submitProject": "提交项目"
},
"notFound": {
"title": "页面未找到",
@@ -258,74 +257,5 @@
"closeAnnouncement": "关闭公告",
"copyright": "© 2025 Agent Park. 保留所有权利。",
"designedFor": "专为 AI 构建者设计"
- },
- "admin": {
- "title": "Agent 管理控制台",
- "subtitle": "统一管理 Agent 目标编排、资源池(Skill/MCP/工具)、策略提案审批与运行日志审计。",
- "navChat": "Agent 对话",
- "navResources": "资源池",
- "navPolicies": "策略提案",
- "navRuns": "运行管理",
- "navLogs": "运行日志",
- "cardChatTitle": "总控 Agent 对话",
- "cardChatDescription": "下发目标,由 Gateway 自动拆解能力意图并选择资源。",
- "cardResourceTitle": "资源池管理",
- "cardResourceDescription": "查看并治理工具、MCP、Skill 与 Agent Profile。",
- "cardPolicyTitle": "策略提案审批",
- "cardPolicyDescription": "按“提案 -> 审批 -> 生效”流程管理权限与策略。",
- "cardLogsTitle": "日志审计",
- "cardLogsDescription": "查看任务级与工具调用级审计记录。",
- "goalDefault": "探索 GitHub 上的 Agent 项目,并进行代码与日志质量检查。",
- "unknownError": "未知错误",
- "goalConsoleTitle": "Agent 网关对话控制台",
- "goalConsoleDescription": "提交高层目标后,编排器会自动拆解能力意图、从资源池选择工具,并记录 run/task/tool-call 审计链。",
- "adminKeyLabel": "管理员密钥",
- "adminKeyPlaceholder": "ADMIN_CONSOLE_KEY",
- "goalLabel": "目标",
- "submitting": "提交中...",
- "submitGoal": "提交目标",
- "noRunYet": "暂无运行结果。",
- "requestJsonLabel": "请求 JSON",
- "loading": "加载中...",
- "executeEndpoint": "执行 {method} {endpoint}",
- "noResponseYet": "暂无响应。",
- "policyDefaultTitle": "禁止编排器执行 git push",
- "policyDefaultDescription": "收紧编排器的 shell 命令策略,拒绝 git push 模式。",
- "policyTitle": "策略提案与审批",
- "proposalJsonLabel": "提案 JSON",
- "submitProposal": "提交提案",
- "approvalDecisionTitle": "审批决策",
- "approvalTicketIdPlaceholder": "审批单 ID",
- "decisionCommentPlaceholder": "审批备注",
- "approve": "批准",
- "reject": "驳回",
- "logsTaskTitle": "任务日志",
- "logsTaskDescription": "任务级运行审计流。",
- "logsToolCallTitle": "工具调用日志",
- "logsToolCallDescription": "包含策略决策的工具调用级审计记录。",
- "policyProposalsTitle": "策略提案列表",
- "policyProposalsDescription": "查看策略提案及其当前审批状态。",
- "approvalTicketsTitle": "审批单列表",
- "approvalTicketsDescription": "查看提案与审批单绑定关系。",
- "agentsTitle": "Agent 配置",
- "agentsDescription": "查看 Agent Manifest、预算、标签域与策略覆盖。",
- "mcpTitle": "MCP 资源绑定",
- "mcpDescription": "查看资源池中的 MCP 工具及 Agent Browser 优先读取配置。",
- "skillsTitle": "Skill 注册表",
- "skillsDescription": "查看资源池中 Skill 清单与版本。",
- "toolsTitle": "基础工具 + MCP 工具",
- "toolsDescription": "查看工具风险等级与默认 allow/ask/deny 策略。",
- "resourcesTabTools": "工具",
- "resourcesTabSkills": "技能",
- "resourcesTabAgents": "Agent",
- "resourcesOverviewTitle": "资源池总览",
- "resourcesOverviewDescription": "统一查看基础工具、MCP 工具、技能、Agent 配置与策略配置。",
- "runDetailTitle": "运行详情:{id}",
- "runDetailDescription": "查看运行的意图、任务和工具调用审计记录。",
- "runsListTitle": "运行列表",
- "runsListDescription": "查看网关运行实例及当前编排状态。",
- "runsControlTitle": "运行控制(POST)",
- "runsControlDescription": "执行控制动作前,请先将 endpoint 中的 REPLACE_RUN_ID 替换为真实运行 ID。",
- "runsControlDefaultNote": "来自管理台的手动暂停"
}
}