Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
bd8507f
feat: start a session directly as an agent profile
SoulKyu Oct 5, 2026
8fca6b2
fix(models): list providers that extensions register at session_start
SoulKyu Oct 5, 2026
4e8ad0a
Merge branch 'fix/deferred-extension-providers' into local
SoulKyu Oct 5, 2026
61b7696
docs: add Agent Ops implementation plan
SoulKyu Oct 6, 2026
1d00696
feat(agent-ops): agent cards and agent-profile session detection
SoulKyu Oct 6, 2026
a4b5d60
fix(agent-ops): sort orphan agent sessions and bound the profile ref …
SoulKyu Oct 6, 2026
188e3bd
feat(agent-ops): agents panel in AppShell
SoulKyu Oct 6, 2026
1467120
feat(agent-ops): task store with lock-based claims and crash recovery
SoulKyu Oct 6, 2026
0518081
fix(agent-ops): release the claim lock on a lost race and validate ta…
SoulKyu Oct 6, 2026
d1a682b
feat(agent-ops): event-driven, terminal-safe task runner with timeout…
SoulKyu Oct 6, 2026
0f2008f
fix(agent-ops): bound runner start by the run timeout and dequeue old…
SoulKyu Oct 6, 2026
4b5eefa
feat(agent-ops): spawn agent-profile runs with event-driven completio…
SoulKyu Oct 6, 2026
4e136b1
feat(agent-ops): task routes with cancel-before-abort and steer
SoulKyu Oct 6, 2026
904a076
feat(agent-ops): task assignment dialog and task list in the agents p…
SoulKyu Oct 6, 2026
4efc033
fix(agent-ops): check trigger tools after extensions bind and shut do…
SoulKyu Oct 6, 2026
74facf3
fix(agent-ops): stop awaiting the prompt send so preflight hangs stay…
SoulKyu Oct 6, 2026
8a5e701
fix(agent-ops): translate orphan cards and load panel sections indepe…
SoulKyu Oct 6, 2026
65bcfcb
docs(agent-ops): add agent-ops topic note and file map
SoulKyu Oct 6, 2026
7acf223
feat(agent-ops): memory approval queue
SoulKyu Oct 6, 2026
bab997e
docs(agent-ops): memory approval note and file map
SoulKyu Oct 6, 2026
f344471
feat(agent-ops): closed tool allowlist for triggers and profile pinning
SoulKyu Oct 6, 2026
ecdf712
docs(agent-ops): triggers allowlist and pin note, redact file map
SoulKyu Oct 6, 2026
60c4813
feat(agent-ops): re-kick the runner when a run ends
SoulKyu Oct 6, 2026
8c1be9c
feat(agent-ops): prune terminal tasks after 14 days
SoulKyu Oct 6, 2026
54ffacd
feat(agent-ops): disk-backed scheduler and fail-closed webhook triggers
SoulKyu Oct 6, 2026
7d80499
docs(agent-ops): scheduler, webhook and runner re-kick notes
SoulKyu Oct 6, 2026
6ad996c
fix(agent-ops): per-trigger webhook throttle
SoulKyu Oct 6, 2026
6437a85
fix(agent-ops): admit scheduled fires like webhooks and isolate trigg…
SoulKyu Oct 6, 2026
b611587
feat(agent-ops): trigger management routes with server-generated webh…
SoulKyu Oct 6, 2026
ac8f61c
feat(agent-ops): triggers section in the agents panel
SoulKyu Oct 6, 2026
478acca
docs(agent-ops): trigger management note and file map
SoulKyu Oct 6, 2026
095d1e3
fix(agent-ops): refuse null trigger limits instead of defaulting them
SoulKyu Oct 6, 2026
35c76f3
fix(agent-ops): start the scheduler without blocking server boot
SoulKyu Oct 6, 2026
88f6262
fix(agent-ops): store webhook secret digests only and answer 400 on a…
SoulKyu Oct 6, 2026
21cb163
fix(agent-ops): let a stale .processing memory be decided again
SoulKyu Oct 6, 2026
8e52f8f
feat(agent-ops): show trigger profile drift and allow an explicit re-pin
SoulKyu Oct 6, 2026
68690c9
perf(agent-ops): bound the task list sent to the panel and render tri…
SoulKyu Oct 6, 2026
2fcb2e2
docs(agent-ops): drift re-pin, bounded task list, stale processing, w…
SoulKyu Oct 6, 2026
b2264f6
chore(agent-ops): drop unused destructured bindings flagged by lint
SoulKyu Oct 6, 2026
4f75270
docs: note the clean env needed to run tests inside a pi process
SoulKyu Oct 6, 2026
6c1f2a8
docs: long-term agents design spec and approved UI mockup
SoulKyu Oct 6, 2026
7bf4f4f
docs: long-term agents implementation plan (phases 1, 4, 2, 3 with ch…
SoulKyu Oct 6, 2026
e03e59e
docs(plan): shared registry error mapping lives in lib, not in a rout…
SoulKyu Oct 6, 2026
dd4160d
feat(agents): long_term profile flag, trust field and newest profile …
SoulKyu Oct 6, 2026
b5e3e93
feat(agents): long-term agent registry with home, space state and trash
SoulKyu Oct 6, 2026
ec5463d
fix(agents): roll back home and space state when the profile write fails
SoulKyu Oct 6, 2026
47eca0d
feat(agents): trusted threads re-snapshot from their profile; display…
SoulKyu Oct 6, 2026
a88eb2d
feat(agents): pinned trusted thread per agent and unread count
SoulKyu Oct 6, 2026
c0ddeca
fix(agents): persist a new thread at creation and restart a thread wh…
SoulKyu Oct 6, 2026
a09f57e
feat(agents): agent routes, thread open, read marker; homes out of th…
SoulKyu Oct 6, 2026
89c4973
feat(agents): i18n strings for the rail and the agent space
SoulKyu Oct 6, 2026
5393798
feat(agents): agent rail, creation form and ?agent= navigation
SoulKyu Oct 6, 2026
500f91c
fix(agents): keep the pending agent thread until another session is p…
SoulKyu Oct 6, 2026
b97b3b3
feat(agents): agent space panels, profile settings, unread divider an…
SoulKyu Oct 6, 2026
f105958
fix(agents): show opened files in the agent view's right panel
SoulKyu Oct 6, 2026
93a2d53
fix(agents): neutral draft after deleting an agent; unread divider be…
SoulKyu Oct 6, 2026
81492e9
docs(agents): long-term agents note and file map
SoulKyu Oct 6, 2026
2e927bd
test(agents): pin the delegable-profile filter of the Agent tool prov…
SoulKyu Oct 6, 2026
83e71db
fix(agents): checkpoint 1 review fixes
SoulKyu Oct 6, 2026
00b4961
docs(agents): checkpoint 1 review corrections
SoulKyu Oct 6, 2026
50a0809
fix(agents): trusted threads resolve the global profile only; guard l…
SoulKyu Oct 6, 2026
7945ec3
docs(agents): security review corrections
SoulKyu Oct 6, 2026
039f4f1
fix(agents): refuse deleting an agent while its thread starts; re-rea…
SoulKyu Oct 6, 2026
789f1a5
fix(agents): delete an idle agent after shutting its thread down; ope…
SoulKyu Oct 6, 2026
b3c8e56
docs(agents): DELETE shuts an idle thread down before the move
SoulKyu Oct 6, 2026
27d1ebd
feat(agents): memory snapshot, forget requests and approval queue in …
SoulKyu Oct 6, 2026
47be0eb
fix(agents): abort the memory poll on agent switch and unmount
SoulKyu Oct 6, 2026
59f7ed8
feat(agents): agent event entries rendered as thread cards, live and …
SoulKyu Oct 6, 2026
f2abb1e
fix(agents): keep entry ids aligned when an event card is appended live
SoulKyu Oct 6, 2026
dda5c06
feat(agents): per-agent thread queue and runner selection
SoulKyu Oct 6, 2026
735f2c0
feat(agents): schedule and task events run in the thread; tasks queue…
SoulKyu Oct 6, 2026
80b4a8f
feat(agents): triggers belong to long-term agents; schedules post in …
SoulKyu Oct 6, 2026
3ecae95
feat(agents): agent_notify push tool and failed-run push
SoulKyu Oct 6, 2026
3f941d4
docs(agents): events, queue, triggers and notify
SoulKyu Oct 6, 2026
4cafa33
fix(agents): hide long-term agents from the legacy overview; notify c…
SoulKyu Oct 6, 2026
53f130e
fix(agents): drop a thread task that ended during the idle wait; sett…
SoulKyu Oct 6, 2026
9708a31
test(agents): pin the overview long-term filter after its split
SoulKyu Oct 6, 2026
0949e8b
feat(agents): webhook runs post a display-only summary card; isolated…
SoulKyu Oct 6, 2026
c1dda06
refactor(agents): remove the Agent Ops overlay, overview and assign-t…
SoulKyu Oct 6, 2026
7089471
docs(agents): webhooks, read-only runs and the removal of the overlay
SoulKyu Oct 6, 2026
5092737
fix(agents): suppress the completion push for isolated runs; redact s…
SoulKyu Oct 6, 2026
79fc1b4
fix(agents): spec order of the right panel, server-provided home path…
SoulKyu Oct 6, 2026
91a4dbb
fix(agents): session detail carries the agent profile trust so a run …
SoulKyu Oct 6, 2026
19244cd
chore: bind to all interfaces by default
SoulKyu Oct 7, 2026
9d871e8
fix(agents): long-term threads and profile runs start on a provider r…
SoulKyu Oct 7, 2026
ed4c7d7
fix(agents): keep the profile thinking level after the deferred model…
SoulKyu Oct 7, 2026
3fd9f82
revert: bind to all interfaces by default
SoulKyu Oct 7, 2026
20524f4
chore: drop internal planning documents
SoulKyu Oct 7, 2026
ecd70ad
feat(agents): add files to the agent home by drag and drop or Browse
SoulKyu Oct 7, 2026
538434d
feat(agents): reset an agent's thread; /new and /clear as built-in co…
SoulKyu Oct 7, 2026
43b1dd2
feat(agents): per-agent MCP server allowlist, none by default
SoulKyu Oct 7, 2026
97a078b
fix(agents): sync the MCP blocklist before the profile is written
SoulKyu Oct 7, 2026
4c6ab11
test(agents): stable mtime check in the MCP overrides test
SoulKyu Oct 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 57 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,23 @@ app/api/
sessions/[id]/state/route.ts GET live wrapper state while running
sessions/[id]/auto-name/route.ts POST generate a session title
sessions/search/route.ts GET session search
agent/new/route.ts POST { cwd, type: prompt|ensure_session (start only), message?, toolNames?, provider?, modelId?, thinkingLevel? }
agent-ops/tasks/[id]/route.ts POST steer a running task | DELETE cancel
agent-ops/triggers/route.ts GET [?agent=] list triggers (hasWebhookSecret, never the secret) | POST create; webhook: true returns the generated secret once
agent-ops/triggers/[id]/route.ts PATCH enabled/edit (re-pins on profile or cwd change) | DELETE
agent-ops/triggers/[id]/secret/route.ts POST rotate the webhook secret, returned once
agent-ops/triggers/[id]/hook/route.ts POST webhook ingestion (secret header, exempt from the session in proxy.ts)
agent-ops/memory/route.ts GET [?agent=] pi-mem0 staged memories (approval queue)
agent-ops/memory/[id]/decision/route.ts POST { approved } write a staged memory's decision file
agents/route.ts GET long-term agents (name, avatar, running, unread) | POST create
agents/[name]/route.ts GET detail | PATCH profile (409 agent_running while the thread runs) | DELETE to .trash
agents/[name]/thread/route.ts POST open or create the pinned thread
agents/[name]/thread/reset/route.ts POST archive the thread to the trash and start a fresh one (409 agent_running)
agents/mcp-servers/route.ts GET names of the global pi-mcp-adapter servers (never their config)
agents/[name]/read/route.ts POST { entryId } set lastReadEntryId
agents/[name]/tasks/route.ts GET the agent's tasks | POST { prompt } queue a thread task (20 000-char cap)
agents/[name]/memory/route.ts GET the agent's recent memories
agents/[name]/memory/forget/route.ts POST request a forget
agent/new/route.ts POST { cwd, type: prompt|ensure_session (start only), message?, toolNames?, provider?, modelId?, thinkingLevel?, agentProfile? }
agent/[id]/route.ts GET state | POST any command
agent/[id]/events/route.ts GET SSE stream
agent/running/route.ts GET running session ids
Expand Down Expand Up @@ -101,9 +117,32 @@ lib/
global-settings-file.ts locked read-modify-write of global settings.json (SettingsManager's lock)
regular-file.ts readRegularFileText(): non-blocking read of a regular file only, optional size cap
default-preferences.ts write defaultModel/defaultThinkingLevel; detect project shadowing
deferred-provider-models.ts models of providers an extension registers only at session_start, re-added to listings
enabled-models.ts pure minimal-edit engine for the enabledModels pattern list
enabled-models-runtime.ts SDK adapter for enabledModels: pattern resolution, provider kinds, settings IO
subagent-settings.ts read/write ~/.pi/agent/agents/settings.json
agents/registry.ts long-term agent files: profile, space state, home, trash; AGENT_NAME_RE
agents/mcp-access.ts per-agent MCP allowlist: lists global adapter servers, writes <home>/.pi/mcp-adapter.json
agents/registry-response.ts registry errors to HTTP responses
agents/thread.ts pinned trusted thread: ensureThread, openThread, unread count
agents/agent-view.ts list/detail views, canEditProfile, unread helpers (client-safe)
agents/events.ts pi-web:agent-event entries: builders, guard, UI mapping, folded-prompt indexes (client-safe)
agents/queue.ts isolated / thread task selectors for the two runners
agents/thread-run.ts thread event run: open, wait idle, card, prompt
agents/agent-notify.ts agent_notify push tool, trusted threads only
agents/memory.ts mem0 snapshot reader + forget requests
agent-ops/task-store.ts JSON task files: immutable terminal states, wx claim lock, recovery
agent-ops/runner.ts FIFO runner: slots, one maxRunMs deadline, fire-and-forget abort
agent-ops/prompt-run.ts prompt send (not awaited), done from events, trigger tool check
agent-ops/spawn.ts startAgentProfileRun: session start for a profile task
agent-ops/kick.ts kickRunner (single entry point), recoverOnce
agent-ops/trigger-store.ts trigger configs (CRUD), closed tool allowlist, profile pin, triggerRunPin
agent-ops/scheduler.ts 60 s tick: scheduled fires, payload ingestion, fire-token purge, task retention
agent-ops/webhook.ts handleHook: fail-closed webhook (secret digest, dedicated throttle, 64 KB stream cap)
agent-ops/hook-path.ts exact hook path the proxy lets through without a session + the secret header name (client-safe)
agent-ops/trigger-api.ts trigger create/patch/rotate/delete logic behind the routes, toPublicTrigger (secret never leaves)
agent-ops/redact.ts verbatim pi-mem0 secret redaction + truncate (server-only)
agent-ops/memory-review.ts pi-mem0 staging reader + decision writer (never the mem0 store)
file-access.ts allowed file roots for /api/files and worktrees
linked-directory.ts directory links leading outside the allowed roots + the allow-link check
file-paths.ts client/server path encoding helpers
Expand Down Expand Up @@ -159,6 +198,15 @@ components/
OAuthPastePanel.tsx paste box for a sign-in's redirected address or code (Models, MCP)
ProjectTrustDialog.tsx trust confirmation listing the project's MCP servers
AgentsConfig.tsx built-in subagent toggle + agent profile editor
agents/AgentRail.tsx long-term agent rail: avatars, running dot, unread badge
agents/AgentAvatar.tsx agent avatar (color + glyph)
agents/NewAgentDialog.tsx create a long-term agent
agents/AgentProfileDialog.tsx edit a long-term agent's profile
agents/AgentEventCard.tsx event card in the thread (orange schedule/task, purple webhook)
agents/QueueTaskDialog.tsx queue a task for an agent's thread
agents/AgentMemoryRecent.tsx recent memories with forget
agents/AgentSpaceLeft.tsx / AgentSpaceRight.tsx agent view panels (home files; profile and status)
agents/dialog-styles.ts shared styles of the agent dialogs
PluginsConfig.tsx Settings › Plugins: installed package plugins
SkillsConfig.tsx Settings › Skills: loaded, search, install
McpConfig.tsx Settings › MCP: servers, switches, exposure, remove/undo, Test, sign-in, Code mode, trust
Expand All @@ -175,7 +223,7 @@ components/
hooks/
useAgentSession.ts messages, streaming, SSE, fork/navigate, reconciliation; built-in slash commands (/session, bare /mcp)
useAudio.ts completion sound + AudioContext unlock
useDragDrop.ts shared drag/drop state
useDragDrop.ts shared drag/drop state (accept predicate, default image-only; skips dropped folders)
useIsMobile.ts responsive breakpoint
useKeyboardShortcuts.ts Esc stops the running agent unless a field or nearer handler took it; Ctrl+Alt+N
useTheme.ts theme state
Expand All @@ -193,10 +241,12 @@ Design decisions and traps live in `docs/agents/`, one note per area. Read every
- [mcp-settings.md](docs/agents/mcp-settings.md): Settings › MCP reads without running anything, masking, the trust dialog's server list, row states, notices, Code mode choice, trust from Settings, Escape stacking, every `mcp.json` write and undo. Files: `app/api/mcp/route.ts`, `app/api/project-trust/route.ts`, `lib/mcp-config-read.ts`, `lib/mcp-config-file.ts`, `lib/mcp-undo.ts`, `lib/mcp-secrets.ts`, `lib/mcp-server-display.ts`, `lib/mcp-json-error.ts`, `lib/project-trust.ts`, `lib/regular-file.ts`, `lib/stacked-dialog.ts`, `lib/settings-navigation.ts`, `components/McpConfig.tsx`, `components/mcp-config-helpers.ts`, `components/ProjectTrustDialog.tsx`, `components/SettingsPanel.tsx`.
- [mcp-test-sign-in.md](docs/agents/mcp-test-sign-in.md): Settings › MCP Test (route checks, bounded connection, `!command` queue, redaction, status store) and OAuth sign-in / sign-out. Files: `app/api/mcp/test/**`, `app/api/mcp/sign-in/**`, `lib/mcp-test.ts`, `lib/mcp-entry-request.ts`, `lib/mcp-status.ts`, `lib/mcp-sign-in.ts`, `lib/mcp-sign-out.ts`, `components/McpSignIn.tsx`, `components/mcp-sign-in-helpers.ts`, `components/OAuthPastePanel.tsx`.
- [mcp-add.md](docs/agents/mcp-add.md): Settings › MCP add (paste re-parsed on the server, host-variable confirmation, literal secrets kept global, fresh-folder trust, the add pane) and the paste importer's escaping and grammars. Files: `lib/mcp-add.ts`, `lib/mcp-import*.ts`, `lib/shell-words.ts`, fresh-folder trust in `lib/project-trust.ts`, `components/McpAddServer.tsx`, `components/mcp-add-helpers.ts`, the `add` action of `app/api/mcp/route.ts`.
- [models.md](docs/agents/models.md): default model and reasoning level, mid-run reasoning changes, remote provider catalogs, `enabledModels` scoping and minimal edits, provider auth listing and credentials. Files: `app/api/models/**`, `app/api/models-config/**`, `app/api/auth/**`, `lib/default-preferences.ts`, `lib/model-scope.ts`, `lib/enabled-models*.ts`, `lib/model-catalog-refresh.ts`, `lib/provider-listing*.ts`, `components/ModelsConfig.tsx`, `components/EnabledModelsSection.tsx`, `components/ModelSelector.tsx`, `components/SelectorRow.tsx`.
- [models.md](docs/agents/models.md): default model and reasoning level, providers registered at session_start, mid-run reasoning changes, remote provider catalogs, `enabledModels` scoping and minimal edits, provider auth listing and credentials. Files: `app/api/models/**`, `app/api/models-config/**`, `app/api/auth/**`, `lib/default-preferences.ts`, `lib/model-scope.ts`, `lib/enabled-models*.ts`, `lib/model-catalog-refresh.ts`, `lib/deferred-provider-models.ts`, `lib/provider-listing*.ts`, `components/ModelsConfig.tsx`, `components/EnabledModelsSection.tsx`, `components/ModelSelector.tsx`, `components/SelectorRow.tsx`.
- [files-and-access.md](docs/agents/files-and-access.md): worktrees and project grouping, the file access allow-list (the `/api/files` security boundary), file tree visibility, web password throttling. Files: `app/api/files/**`, `app/api/cwd/**`, `app/api/worktrees/**`, `app/api/file-index/**`, `app/api/web-auth/**`, `proxy.ts`, `lib/path-security.ts`, `lib/file-access.ts`, `lib/linked-directory.ts`, `lib/session-file-references*.ts`, `lib/file-tree-visibility.ts`, `lib/worktree.ts`, `lib/paths.ts`, `lib/auth-throttle.ts`, `components/FileExplorer.tsx`.
- [settings-ui.md](docs/agents/settings-ui.md): Plugins and Skills routes, sidebar group switches, the shared `SettingsUi` blocks every settings panel and add pane uses. Files: `app/api/plugins/**`, `app/api/skills/**`, `components/SettingsUi.tsx`, `components/settings-ui-helpers.ts`, `components/SkillsConfig.tsx`, `components/PluginsConfig.tsx`; also before adding a settings section or add pane.
- [subagents.md](docs/agents/subagents.md): the built-in subagent setting, profiles and their files, run status, completion notifications. Files: `lib/subagent*.ts`, `app/api/subagents/**`, `components/AgentsConfig.tsx`.
- [agent-ops.md](docs/agents/agent-ops.md): the task store and FIFO runner, cancel/steer ordering, the single deadline, the unawaited prompt send, trigger tool check, the two runners. Files: `lib/agent-ops/**`, `lib/agents/{queue,thread-run}.ts`, `app/api/agent-ops/**`, `components/agents/{AgentTasks,AgentTriggers,TriggerDialog,TriggerSecretDialog,AgentMemory}.tsx`, `task-view.ts`, `trigger-view.ts`.
- [long-term-agents.md](docs/agents/long-term-agents.md): agent data model (profile, space, home, trash), the pinned trusted thread and its re-snapshot rule, unread marker, rail and `?agent=` navigation, event cards, the per-agent queue, trigger binding, `agent_notify`. Files: `lib/agents/**`, `app/api/agents/**`, `components/agents/*`, the agent parts of `components/AppShell.tsx`, `components/ChatWindow.tsx`, `components/MessageView.tsx`, `lib/rpc-manager.ts`, `lib/session-reader.ts`, `lib/web-push.ts`, `lib/initial-navigation.ts`.
- [client-platform.md](docs/agents/client-platform.md): mobile software keyboard and viewport height, completion sound. Files: `hooks/useViewportHeight.ts`, `hooks/useAudio.ts`, the keyboard-open CSS.

---
Expand Down Expand Up @@ -230,3 +280,7 @@ Location: `~/.pi/agent/sessions/<encoded-cwd>/<timestamp>_<uuid>.jsonl`
--accent --user-bg --tool-bg
--font-mono
```

## Learned

- Running the test suite from inside a pi process (agent, subagent): prefix it with `env -i PATH="$PATH" HOME="$HOME" LANG="$LANG"` — inherited `PI_PACKAGE_DIR` & co. break ~44-97 MCP/SDK-resolution tests.
26 changes: 26 additions & 0 deletions app/api/agent-ops/memory/[id]/decision/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { NextResponse } from "next/server";
import { MemoryReviewError, writeDecision } from "@/lib/agent-ops/memory-review";

export const dynamic = "force-dynamic";

// POST /api/agent-ops/memory/[id]/decision body: { approved } - Approve or reject a staged memory.
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
let body: { approved?: unknown };
try {
body = await req.json();
} catch {
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
}
if (typeof body.approved !== "boolean") {
return NextResponse.json({ error: "approved must be a boolean" }, { status: 400 });
}
try {
writeDecision((await params).id, body.approved);
return NextResponse.json({ ok: true });
} catch (error) {
if (error instanceof MemoryReviewError) {
return NextResponse.json({ error: error.message }, { status: error.code === "conflict" ? 409 : 404 });
}
return NextResponse.json({ error: error instanceof Error ? error.message : String(error) }, { status: 500 });
}
}
19 changes: 19 additions & 0 deletions app/api/agent-ops/memory/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NextResponse } from "next/server";
import { listSubagentProfiles } from "@/lib/subagents";
import { listStagedFacts } from "@/lib/agent-ops/memory-review";

export const dynamic = "force-dynamic";

// GET /api/agent-ops/memory[?agent=] - Memories staged by pi-mem0, waiting for or carrying a decision.
export async function GET(req: Request) {
try {
const agent = new URL(req.url).searchParams.get("agent");
const staged = listStagedFacts();
const longTerm = new Set(listSubagentProfiles(process.cwd()).filter((p) => p.longTerm).map((p) => p.name));
// Without ?agent= this is the legacy panel's queue: long-term agents review their memory in their own view.
const facts = agent === null ? staged.filter((fact) => !longTerm.has(fact.agent)) : staged.filter((fact) => fact.agent === agent);
return NextResponse.json({ facts }, { headers: { "Cache-Control": "no-store" } });
} catch (error) {
return NextResponse.json({ error: error instanceof Error ? error.message : String(error) }, { status: 500 });
}
}
54 changes: 54 additions & 0 deletions app/api/agent-ops/tasks/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { NextResponse } from "next/server";
import { getRpcSession } from "@/lib/rpc-manager";
import { recoverOnce } from "@/lib/agent-ops/kick";
import { cancelTask, getTask, updateTask } from "@/lib/agent-ops/task-store";

export const dynamic = "force-dynamic";

type Context = { params: Promise<{ id: string }> };

// POST /api/agent-ops/tasks/[id] body: { message } - Steer a running task's session.
export async function POST(req: Request, { params }: Context) {
recoverOnce();
const task = getTask((await params).id);
if (!task) return NextResponse.json({ error: "Task not found" }, { status: 404 });
let body: { message?: unknown };
try {
body = await req.json();
} catch {
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
}
if (typeof body.message !== "string" || !body.message.trim()) {
return NextResponse.json({ error: "message is required" }, { status: 400 });
}
const session = task.status === "running" && task.sessionId ? getRpcSession(task.sessionId) : undefined;
if (!session) return NextResponse.json({ error: "Task is not running a session" }, { status: 409 });
try {
await session.send({ type: "steer", message: body.message });
} catch (error) {
return NextResponse.json({ error: error instanceof Error ? error.message : String(error) }, { status: 500 });
}
return NextResponse.json({ success: true });
}

// DELETE /api/agent-ops/tasks/[id] - Cancel a queued or running task.
export async function DELETE(_req: Request, { params }: Context) {
recoverOnce();
const { id } = await params;
const task = getTask(id);
if (!task) return NextResponse.json({ error: "Task not found" }, { status: 404 });
let current = task;
if (task.status === "queued") {
try { cancelTask(id); } catch { /* raced with another writer: the re-read below decides */ }
current = getTask(id) ?? task; // the runner may have claimed it meanwhile
}
if (current.status === "running") {
// Status first: a task still starting has no session to abort yet; the runner re-reads
// the status right after start() and aborts the session itself.
try {
updateTask(id, { status: "cancelled", completedAt: new Date().toISOString() });
} catch { /* already terminal: answer with the current status */ }
if (current.sessionId) await getRpcSession(current.sessionId)?.send({ type: "abort" }).catch(() => {});
}
return NextResponse.json({ task: getTask(id) });
}
11 changes: 11 additions & 0 deletions app/api/agent-ops/triggers/[id]/hook/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { kickRunner } from "@/lib/agent-ops/kick";
import { handleHook } from "@/lib/agent-ops/webhook";

export const dynamic = "force-dynamic";

// POST /api/agent-ops/triggers/:id/hook - Webhook ingestion. Exempt from the browser session in proxy.ts:
// authenticated by the trigger's shared secret (header x-agent-ops-secret) behind its own throttle.
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return handleHook(req, id, kickRunner);
}
29 changes: 29 additions & 0 deletions app/api/agent-ops/triggers/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { deleteTriggerById, patchTrigger, triggerResponse, unexpectedErrorResponse } from "@/lib/agent-ops/trigger-api";

export const dynamic = "force-dynamic";

type Context = { params: Promise<{ id: string }> };

// PATCH /api/agent-ops/triggers/[id] body: any of name, profile, promptTemplate, enabled, everyMinutes (null clears), dedupWindowMs, maxActiveTasks
export async function PATCH(req: Request, { params }: Context) {
let body: unknown;
try {
body = await req.json();
} catch {
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
}
try {
return triggerResponse(patchTrigger((await params).id, body));
} catch (error) {
return unexpectedErrorResponse(error);
}
}

// DELETE /api/agent-ops/triggers/[id] - Its fire tokens are purged by the scheduler after 24 h.
export async function DELETE(_req: Request, { params }: Context) {
try {
return triggerResponse(deleteTriggerById((await params).id));
} catch (error) {
return unexpectedErrorResponse(error);
}
}
12 changes: 12 additions & 0 deletions app/api/agent-ops/triggers/[id]/secret/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { rotateSecret, triggerResponse, unexpectedErrorResponse } from "@/lib/agent-ops/trigger-api";

export const dynamic = "force-dynamic";

// POST /api/agent-ops/triggers/[id]/secret - Generate a new webhook secret (the old one stops working) and return it once.
export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
try {
return triggerResponse(rotateSecret((await params).id));
} catch (error) {
return unexpectedErrorResponse(error);
}
}
Loading