@agentprojectcontext/apx 1.77.2 → 1.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -9
- package/package.json +12 -12
- package/src/core/agent/build-agent-system.js +3 -2
- package/src/core/agent/constants.js +3 -2
- package/src/core/agent/loop/greeting-guard.js +55 -0
- package/src/core/agent/loop/side-effects.js +52 -0
- package/src/core/agent/prompt-builder.js +49 -8
- package/src/core/agent/run-agent.js +13 -48
- package/src/core/agent/security.js +1 -1
- package/src/core/agent/self-memory.js +4 -2
- package/src/core/agent/skills/index-store.js +2 -2
- package/src/core/agent/skills/loader.js +3 -22
- package/src/core/agent/super-agent.js +0 -2
- package/src/core/agent/tools/handlers/call-runtime.js +4 -4
- package/src/core/agent/tools/handlers/search-sessions.js +1 -1
- package/src/core/agent/tools/handlers/set-permission-mode.js +11 -4
- package/src/core/agent/tools/names.js +58 -0
- package/src/core/apc/context-copy.js +1 -1
- package/src/core/apc/frontmatter.js +85 -0
- package/src/core/apc/parser.js +9 -21
- package/src/core/apc/paths.js +8 -8
- package/src/core/apc/scaffold.js +5 -4
- package/src/core/channels/telegram/config.js +201 -0
- package/src/core/config/index.js +25 -183
- package/src/core/config/paths.js +54 -1
- package/src/core/confirmation/adapters/web.js +1 -1
- package/src/core/deck/manifest.js +12 -12
- package/src/core/desktop/autostart.js +3 -2
- package/src/core/desktop/process.js +4 -4
- package/src/core/engines/presets.js +2 -2
- package/src/core/http-tools/browser.js +1 -1
- package/src/core/http-tools/catalog.js +551 -0
- package/src/core/http-tools/fetch.js +1 -1
- package/src/core/http-tools/glob.js +3 -3
- package/src/core/http-tools/grep.js +2 -9
- package/src/core/http-tools/inline-handlers.js +59 -0
- package/src/core/http-tools/registry.js +31 -615
- package/src/core/http-tools/search.js +2 -2
- package/src/core/identity/self.js +6 -7
- package/src/core/identity/telegram.js +3 -3
- package/src/core/logging.js +6 -5
- package/src/core/mcp/sources.js +12 -16
- package/src/core/memory/indexer.js +2 -5
- package/src/core/profiles/lifecycle.js +0 -2
- package/src/core/profiles/store.js +2 -7
- package/src/core/routines/runner.js +1 -1
- package/src/core/runtime-skills/apx-agency-agents/SKILL.md +2 -2
- package/src/core/runtime-skills/apx-profile/SKILL.md +3 -3
- package/src/core/runtime-skills/apx-project/SKILL.md +1 -1
- package/src/core/runtime-skills/apx-task/SKILL.md +8 -8
- package/src/core/runtime-skills/apx-telegram/SKILL.md +4 -4
- package/src/core/runtime-skills/apx-voice/SKILL.md +4 -4
- package/src/core/runtimes/detect.js +2 -2
- package/src/core/sessions/index.js +800 -0
- package/src/core/stores/code-sessions.js +3 -14
- package/src/core/stores/engine-sessions.js +0 -4
- package/src/core/stores/messages.js +0 -5
- package/src/core/stores/organization.js +9 -11
- package/src/core/stores/routines.js +6 -11
- package/src/core/stores/runtime-callbacks.js +2 -1
- package/src/core/util/json-file.js +128 -0
- package/src/core/vars/sources.js +14 -21
- package/src/core/voice/stt-models.js +1 -1
- package/src/core/voice/tts.js +5 -4
- package/src/host/daemon/api/admin-config.js +5 -5
- package/src/host/daemon/api/admin.js +8 -7
- package/src/host/daemon/api/agents.js +16 -16
- package/src/host/daemon/api/artifact-preview.js +8 -8
- package/src/host/daemon/api/artifacts.js +7 -7
- package/src/host/daemon/api/code.js +8 -8
- package/src/host/daemon/api/config.js +6 -6
- package/src/host/daemon/api/confirm.js +2 -2
- package/src/host/daemon/api/connections.js +2 -2
- package/src/host/daemon/api/conversations.js +12 -12
- package/src/host/daemon/api/deck.js +6 -6
- package/src/host/daemon/api/desktop.js +8 -8
- package/src/host/daemon/api/embeddings.js +4 -4
- package/src/host/daemon/api/engines.js +8 -7
- package/src/host/daemon/api/exec.js +5 -4
- package/src/host/daemon/api/files-project.js +6 -6
- package/src/host/daemon/api/health.js +2 -2
- package/src/host/daemon/api/identity.js +3 -3
- package/src/host/daemon/api/inbox.js +2 -2
- package/src/host/daemon/api/integrations.js +23 -19
- package/src/host/daemon/api/mcps.js +16 -12
- package/src/host/daemon/api/messages.js +5 -5
- package/src/host/daemon/api/organization.js +8 -8
- package/src/host/daemon/api/pairing.js +10 -7
- package/src/host/daemon/api/plugins.js +3 -3
- package/src/host/daemon/api/prefix.js +20 -0
- package/src/host/daemon/api/profiles.js +9 -9
- package/src/host/daemon/api/projects.js +5 -5
- package/src/host/daemon/api/routines.js +8 -8
- package/src/host/daemon/api/run.js +2 -2
- package/src/host/daemon/api/runtimes.js +6 -6
- package/src/host/daemon/api/sessions-search.js +18 -11
- package/src/host/daemon/api/sessions.js +6 -6
- package/src/host/daemon/api/shared.js +59 -13
- package/src/host/daemon/api/skills.js +12 -12
- package/src/host/daemon/api/super-agent.js +4 -4
- package/src/host/daemon/api/tasks.js +11 -11
- package/src/host/daemon/api/telegram.js +19 -20
- package/src/host/daemon/api/tools.js +7 -7
- package/src/host/daemon/api/top-level.js +7 -7
- package/src/host/daemon/api/transcribe.js +6 -6
- package/src/host/daemon/api/tts.js +3 -3
- package/src/host/daemon/api/vars.js +11 -8
- package/src/host/daemon/api/voice.js +6 -6
- package/src/host/daemon/api/web.js +28 -32
- package/src/host/daemon/api.js +77 -48
- package/src/host/daemon/db.js +1 -1
- package/src/host/daemon/desktop-ws.js +7 -3
- package/src/host/daemon/index.js +11 -5
- package/src/host/daemon/plugins/desktop/index.js +1 -1
- package/src/host/daemon/plugins/telegram/index.js +1 -1
- package/src/host/daemon/stt-venv.js +20 -81
- package/src/host/daemon/wakeup.js +1 -1
- package/src/interfaces/acp/index.js +3 -3
- package/src/interfaces/acp/session.js +2 -2
- package/src/interfaces/cli/commands/a2a.js +2 -2
- package/src/interfaces/cli/commands/agent.js +3 -3
- package/src/interfaces/cli/commands/artifact.js +13 -13
- package/src/interfaces/cli/commands/chat.js +3 -3
- package/src/interfaces/cli/commands/command.js +2 -2
- package/src/interfaces/cli/commands/config.js +4 -4
- package/src/interfaces/cli/commands/daemon.js +17 -18
- package/src/interfaces/cli/commands/desktop.js +7 -14
- package/src/interfaces/cli/commands/exec.js +2 -2
- package/src/interfaces/cli/commands/log.js +3 -4
- package/src/interfaces/cli/commands/mcp.js +9 -9
- package/src/interfaces/cli/commands/memory.js +2 -2
- package/src/interfaces/cli/commands/messages.js +5 -5
- package/src/interfaces/cli/commands/model.js +0 -18
- package/src/interfaces/cli/commands/obsidian.js +5 -5
- package/src/interfaces/cli/commands/org.js +5 -5
- package/src/interfaces/cli/commands/pair.js +6 -6
- package/src/interfaces/cli/commands/plugins.js +2 -2
- package/src/interfaces/cli/commands/profile.js +10 -10
- package/src/interfaces/cli/commands/project-config.js +6 -6
- package/src/interfaces/cli/commands/project.js +10 -10
- package/src/interfaces/cli/commands/routine.js +8 -9
- package/src/interfaces/cli/commands/runtime.js +2 -2
- package/src/interfaces/cli/commands/search.js +0 -1
- package/src/interfaces/cli/commands/session.js +12 -40
- package/src/interfaces/cli/commands/sessions.js +21 -798
- package/src/interfaces/cli/commands/setup.js +16 -6
- package/src/interfaces/cli/commands/skills.js +1 -2
- package/src/interfaces/cli/commands/status.js +3 -3
- package/src/interfaces/cli/commands/task.js +8 -8
- package/src/interfaces/cli/commands/telegram.js +21 -21
- package/src/interfaces/cli/commands/voice.js +6 -6
- package/src/interfaces/cli/help/index.js +2245 -0
- package/src/interfaces/cli/http.js +9 -4
- package/src/interfaces/cli/index.js +14 -2857
- package/src/interfaces/cli/routes/acp.js +13 -0
- package/src/interfaces/cli/routes/agent.js +27 -0
- package/src/interfaces/cli/routes/artifact.js +28 -0
- package/src/interfaces/cli/routes/chat.js +11 -0
- package/src/interfaces/cli/routes/code.js +11 -0
- package/src/interfaces/cli/routes/command.js +21 -0
- package/src/interfaces/cli/routes/config.js +16 -0
- package/src/interfaces/cli/routes/connections.js +11 -0
- package/src/interfaces/cli/routes/conversations.js +21 -0
- package/src/interfaces/cli/routes/daemon.js +19 -0
- package/src/interfaces/cli/routes/desktop.js +20 -0
- package/src/interfaces/cli/routes/env.js +13 -0
- package/src/interfaces/cli/routes/exec.js +11 -0
- package/src/interfaces/cli/routes/identity.js +11 -0
- package/src/interfaces/cli/routes/index.js +71 -0
- package/src/interfaces/cli/routes/init.js +11 -0
- package/src/interfaces/cli/routes/log.js +20 -0
- package/src/interfaces/cli/routes/mcp.js +22 -0
- package/src/interfaces/cli/routes/memory.js +11 -0
- package/src/interfaces/cli/routes/messages.js +16 -0
- package/src/interfaces/cli/routes/model.js +11 -0
- package/src/interfaces/cli/routes/obsidian.js +17 -0
- package/src/interfaces/cli/routes/org.js +34 -0
- package/src/interfaces/cli/routes/overlay.js +10 -0
- package/src/interfaces/cli/routes/pair.js +17 -0
- package/src/interfaces/cli/routes/panel.js +16 -0
- package/src/interfaces/cli/routes/permission.js +11 -0
- package/src/interfaces/cli/routes/plugins.js +21 -0
- package/src/interfaces/cli/routes/profile.js +27 -0
- package/src/interfaces/cli/routes/project.js +41 -0
- package/src/interfaces/cli/routes/restart.js +15 -0
- package/src/interfaces/cli/routes/routine.js +28 -0
- package/src/interfaces/cli/routes/run.js +11 -0
- package/src/interfaces/cli/routes/search.js +11 -0
- package/src/interfaces/cli/routes/send.js +11 -0
- package/src/interfaces/cli/routes/session.js +26 -0
- package/src/interfaces/cli/routes/sessions.js +15 -0
- package/src/interfaces/cli/routes/setup.js +18 -0
- package/src/interfaces/cli/routes/skills.js +20 -0
- package/src/interfaces/cli/routes/status.js +12 -0
- package/src/interfaces/cli/routes/task.js +26 -0
- package/src/interfaces/cli/routes/telegram.js +39 -0
- package/src/interfaces/cli/routes/update.js +24 -0
- package/src/interfaces/cli/routes/voice.js +17 -0
- package/src/interfaces/desktop/main.js +6 -7
- package/src/interfaces/desktop/renderer.js +0 -5
- package/src/interfaces/mcp-server/index.js +15 -14
- package/src/interfaces/tui/_shims/globals.d.ts +8 -0
- package/src/interfaces/tui/tsconfig.json +4 -1
- package/src/interfaces/web/README.md +6 -6
- package/src/interfaces/web/dist/assets/index-CBR_-QyA.js +824 -0
- package/src/interfaces/web/dist/assets/index-CBR_-QyA.js.map +1 -0
- package/src/interfaces/web/dist/index.html +1 -1
- package/src/interfaces/web/src/components/AddProjectDialog.tsx +1 -1
- package/src/interfaces/web/src/components/agents/AgentFormFields.tsx +1 -1
- package/src/interfaces/web/src/components/chat/ChatList.tsx +2 -2
- package/src/interfaces/web/src/components/chat/SkillPicker.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeFileTree.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeTerminal.tsx +1 -1
- package/src/interfaces/web/src/components/desktop/DesktopStatusCard.tsx +1 -1
- package/src/interfaces/web/src/components/files/FileBrowser.tsx +2 -2
- package/src/interfaces/web/src/components/memory/MemoryBrowser.tsx +2 -2
- package/src/interfaces/web/src/components/routines/ExecutionsList.tsx +1 -1
- package/src/interfaces/web/src/components/routines/RoutineEditor.tsx +2 -2
- package/src/interfaces/web/src/components/settings/DesktopSettingsPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/SkillsManager.tsx +2 -2
- package/src/interfaces/web/src/components/tasks/TaskDetailPanel.tsx +1 -1
- package/src/interfaces/web/src/hooks/useDaemonStatus.ts +1 -1
- package/src/interfaces/web/src/hooks/useDevices.ts +1 -1
- package/src/interfaces/web/src/hooks/useEngines.ts +1 -1
- package/src/interfaces/web/src/hooks/useGlobalConfig.ts +2 -2
- package/src/interfaces/web/src/hooks/useIdentity.ts +1 -1
- package/src/interfaces/web/src/hooks/useInbox.ts +1 -1
- package/src/interfaces/web/src/hooks/useProfiles.ts +3 -3
- package/src/interfaces/web/src/hooks/useProjects.ts +1 -1
- package/src/interfaces/web/src/hooks/useTelegram.ts +3 -3
- package/src/interfaces/web/src/hooks/useTokenBootstrap.ts +30 -7
- package/src/interfaces/web/src/lib/api/admin.ts +11 -11
- package/src/interfaces/web/src/lib/api/agents.ts +14 -14
- package/src/interfaces/web/src/lib/api/artifacts.ts +11 -11
- package/src/interfaces/web/src/lib/api/code.ts +1 -1
- package/src/interfaces/web/src/lib/api/conversations.ts +8 -8
- package/src/interfaces/web/src/lib/api/deck.ts +3 -3
- package/src/interfaces/web/src/lib/api/desktop.ts +8 -8
- package/src/interfaces/web/src/lib/api/embeddings.ts +3 -3
- package/src/interfaces/web/src/lib/api/engines.ts +3 -3
- package/src/interfaces/web/src/lib/api/filesystem.ts +2 -2
- package/src/interfaces/web/src/lib/api/health.ts +1 -1
- package/src/interfaces/web/src/lib/api/identity.ts +2 -2
- package/src/interfaces/web/src/lib/api/inbox.ts +1 -1
- package/src/interfaces/web/src/lib/api/integrations.ts +8 -8
- package/src/interfaces/web/src/lib/api/mcps.ts +6 -6
- package/src/interfaces/web/src/lib/api/messages.ts +3 -3
- package/src/interfaces/web/src/lib/api/organization.ts +7 -7
- package/src/interfaces/web/src/lib/api/profiles.ts +8 -8
- package/src/interfaces/web/src/lib/api/projectFiles.ts +5 -5
- package/src/interfaces/web/src/lib/api/projects.ts +12 -12
- package/src/interfaces/web/src/lib/api/routines.ts +7 -7
- package/src/interfaces/web/src/lib/api/sessions.ts +2 -2
- package/src/interfaces/web/src/lib/api/skills.ts +11 -11
- package/src/interfaces/web/src/lib/api/super_agent.ts +3 -3
- package/src/interfaces/web/src/lib/api/tasks.ts +12 -12
- package/src/interfaces/web/src/lib/api/telegram.ts +14 -14
- package/src/interfaces/web/src/lib/api/tools.ts +1 -1
- package/src/interfaces/web/src/lib/api/vars.ts +4 -4
- package/src/interfaces/web/src/lib/api/voice.ts +6 -6
- package/src/interfaces/web/src/screens/base/AgentDefaultsTab.tsx +1 -1
- package/src/interfaces/web/src/screens/base/GlobalTasksTab.tsx +1 -1
- package/src/interfaces/web/src/screens/base/LogsTab.tsx +3 -3
- package/src/interfaces/web/src/screens/base/SessionsTab.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +8 -8
- package/src/interfaces/web/src/screens/project/AgentsTab.tsx +2 -2
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +3 -3
- package/src/interfaces/web/src/screens/project/ConfigTab.tsx +1 -1
- package/src/interfaces/web/src/screens/project/McpsTab.tsx +3 -3
- package/src/interfaces/web/src/screens/project/Overview.tsx +6 -6
- package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +2 -2
- package/src/interfaces/web/src/screens/project/StructureTab.tsx +1 -1
- package/src/interfaces/web/src/screens/project/TasksTab.tsx +1 -1
- package/src/interfaces/web/src/screens/project/VarsTab.tsx +1 -1
- package/src/interfaces/web/vite.config.ts +13 -18
- package/src/interfaces/web/dist/assets/index-BWWpTTSd.js +0 -824
- package/src/interfaces/web/dist/assets/index-BWWpTTSd.js.map +0 -1
- package/src/skills/apc-context/SKILL.md +0 -159
- /package/src/{host/daemon → core}/runtimes/_spawn.js +0 -0
- /package/src/{host/daemon → core}/runtimes/aider.js +0 -0
- /package/src/{host/daemon → core}/runtimes/antigravity.js +0 -0
- /package/src/{host/daemon → core}/runtimes/claude-code.js +0 -0
- /package/src/{host/daemon → core}/runtimes/codex.js +0 -0
- /package/src/{host/daemon → core}/runtimes/cursor-agent.js +0 -0
- /package/src/{host/daemon → core}/runtimes/gemini-cli.js +0 -0
- /package/src/{host/daemon → core}/runtimes/index.js +0 -0
- /package/src/{host/daemon → core}/runtimes/opencode.js +0 -0
- /package/src/{host/daemon → core}/runtimes/qwen-code.js +0 -0
|
@@ -2,23 +2,30 @@
|
|
|
2
2
|
// GET /sessions/search?q=…&project=…&limit=20
|
|
3
3
|
// POST /sessions/:id/compact resolves which project/agent owns the file
|
|
4
4
|
// then delegates to compactConversation.
|
|
5
|
-
import path from "node:path";
|
|
6
5
|
import { readAgents } from "#core/apc/parser.js";
|
|
7
6
|
import { compactConversation } from "#core/stores/conversations-compactor.js";
|
|
8
7
|
import { searchSessions, findSessionFile } from "#core/stores/sessions-search.js";
|
|
8
|
+
import { asyncRoute } from "./shared.js";
|
|
9
|
+
import { resolveProject } from "#core/apc/projects-helpers.js";
|
|
9
10
|
|
|
11
|
+
// Plural on purpose: no ref means "search every project". This is a different
|
|
12
|
+
// operation from core's resolveProject (which resolves exactly one and throws),
|
|
13
|
+
// but the *matching* must agree with it — this used to accept only an id or an
|
|
14
|
+
// exact path, so `?project=savia` matched nothing here while working on every
|
|
15
|
+
// other route.
|
|
10
16
|
function resolveProjects(projects, projectRef) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return
|
|
17
|
+
if (projectRef == null || projectRef === "") {
|
|
18
|
+
return projects.list().map((p) => projects.get(p.id)).filter(Boolean);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return [resolveProject(projects, projectRef)];
|
|
22
|
+
} catch {
|
|
23
|
+
return []; // unknown or ambiguous ref → no results, not a 500
|
|
16
24
|
}
|
|
17
|
-
return all.map((p) => projects.get(p.id)).filter(Boolean);
|
|
18
25
|
}
|
|
19
26
|
|
|
20
|
-
export function register(
|
|
21
|
-
|
|
27
|
+
export function register(api, { projects, config }) {
|
|
28
|
+
api.get("/sessions/search", (req, res) => {
|
|
22
29
|
const { q, project: projectRef, limit = "20" } = req.query;
|
|
23
30
|
if (!q) return res.status(400).json({ error: "q required" });
|
|
24
31
|
const lim = Math.min(parseInt(limit, 10) || 20, 200);
|
|
@@ -27,7 +34,7 @@ export function register(app, { projects, config }) {
|
|
|
27
34
|
res.json({ q, count: results.length, results });
|
|
28
35
|
});
|
|
29
36
|
|
|
30
|
-
|
|
37
|
+
api.post("/sessions/:id/compact", asyncRoute(async (req, res) => {
|
|
31
38
|
const { id } = req.params;
|
|
32
39
|
const { model: modelOverride, project: projectRef } = req.body || {};
|
|
33
40
|
const candidates = resolveProjects(projects, projectRef);
|
|
@@ -57,5 +64,5 @@ export function register(app, { projects, config }) {
|
|
|
57
64
|
} catch (e) {
|
|
58
65
|
res.status(500).json({ error: e.message });
|
|
59
66
|
}
|
|
60
|
-
});
|
|
67
|
+
}));
|
|
61
68
|
}
|
|
@@ -12,16 +12,16 @@ import {
|
|
|
12
12
|
agentSessionsDir,
|
|
13
13
|
createAgentSessionFile,
|
|
14
14
|
} from "#core/stores/sessions.js";
|
|
15
|
-
import { collectAllSessions, filterSessionsByQuery } from "#
|
|
15
|
+
import { collectAllSessions, filterSessionsByQuery } from "#core/sessions/index.js";
|
|
16
16
|
import { pageEnvelope } from "./shared.js";
|
|
17
17
|
|
|
18
|
-
export function register(
|
|
18
|
+
export function register(api, { projects, project }) {
|
|
19
19
|
// Cross-engine sessions (apx · claude · codex), newest first. Returns a
|
|
20
20
|
// { meta, data } envelope (meta = pagination info, data = rows). Paginated
|
|
21
21
|
// via ?limit & ?offset; with no limit, data is the full set as one page.
|
|
22
22
|
// Optional ?q= filters via the same core as `apx session find` (title match,
|
|
23
23
|
// + transcript content when ?deep=1) so terminal and web search are identical.
|
|
24
|
-
|
|
24
|
+
api.get("/sessions", (req, res) => {
|
|
25
25
|
const engineId = req.query.engine ? String(req.query.engine) : null;
|
|
26
26
|
const q = req.query.q ? String(req.query.q) : "";
|
|
27
27
|
const deep = req.query.deep === "1" || req.query.deep === "true";
|
|
@@ -49,7 +49,7 @@ export function register(app, { projects, project }) {
|
|
|
49
49
|
res.json(pageEnvelope(rows, req.query));
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
api.get("/projects/:pid/agents/:slug/sessions", (req, res) => {
|
|
53
53
|
const p = project(req, res);
|
|
54
54
|
if (!p) return;
|
|
55
55
|
const agents = readAgents(p.path);
|
|
@@ -78,7 +78,7 @@ export function register(app, { projects, project }) {
|
|
|
78
78
|
res.json(sessions);
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
api.post("/projects/:pid/agents/:slug/sessions", (req, res) => {
|
|
82
82
|
const p = project(req, res);
|
|
83
83
|
if (!p) return;
|
|
84
84
|
const { title, body = "" } = req.body || {};
|
|
@@ -93,7 +93,7 @@ export function register(app, { projects, project }) {
|
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
// GET session by filename (sid may include or omit the .md extension)
|
|
96
|
-
|
|
96
|
+
api.get("/projects/:pid/sessions/:sid", (req, res) => {
|
|
97
97
|
const p = project(req, res);
|
|
98
98
|
if (!p) return;
|
|
99
99
|
const sid = req.params.sid;
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
//
|
|
3
3
|
// These were inlined in api.js (monolith). They are kept dependency-free so
|
|
4
4
|
// any route module can `import` them without pulling in the world.
|
|
5
|
-
import fs from "node:fs";
|
|
6
5
|
import path from "node:path";
|
|
7
6
|
import { randomUUID } from "node:crypto";
|
|
8
7
|
import { appendErrorTrace, previewText } from "#core/logging.js";
|
|
@@ -11,6 +10,7 @@ import { agentMemoryPath } from "#core/agent/memory.js";
|
|
|
11
10
|
import { apcMemoryFile } from "#core/apc/paths.js";
|
|
12
11
|
import { CHANNELS } from "#core/constants/channels.js";
|
|
13
12
|
import { isKnownSpaRoute } from "./web.js";
|
|
13
|
+
import { apiPath, isApiPath } from "./prefix.js";
|
|
14
14
|
|
|
15
15
|
export const nowIso = () =>
|
|
16
16
|
new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
@@ -20,6 +20,44 @@ export const nowIso = () =>
|
|
|
20
20
|
// full set as a single page (data = 100% of rows) so non-paginated callers get
|
|
21
21
|
// the same shape — meta just reports one page covering everything.
|
|
22
22
|
// meta: { total, offset, limit, pageSize, page, pageCount }
|
|
23
|
+
/**
|
|
24
|
+
* Wrap an async route handler so a rejected promise becomes a 500 instead of
|
|
25
|
+
* an unhandled rejection.
|
|
26
|
+
*
|
|
27
|
+
* Express 4 does not await handlers: if one throws asynchronously, the error
|
|
28
|
+
* never reaches the error middleware, the request hangs, and on Node >= 15 the
|
|
29
|
+
* unhandled rejection takes the whole daemon down. Several routes awaited work
|
|
30
|
+
* with no try/catch, so a single failing preview or engine probe could kill a
|
|
31
|
+
* process serving the SPA, Telegram polling and voice at the same time.
|
|
32
|
+
*
|
|
33
|
+
* api.get("/things", asyncRoute(async (req, res) => { ... }))
|
|
34
|
+
*/
|
|
35
|
+
export function asyncRoute(handler) {
|
|
36
|
+
return (req, res, next) => Promise.resolve(handler(req, res, next)).catch(next);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Terminal error middleware. Mounted after every route; turns anything that
|
|
41
|
+
* reached `next(err)` into a JSON `{ error }` with a real status code.
|
|
42
|
+
*/
|
|
43
|
+
export function errorMiddleware(log = () => {}) {
|
|
44
|
+
// eslint-disable-next-line no-unused-vars -- Express detects the 4-arg shape.
|
|
45
|
+
return (err, req, res, _next) => {
|
|
46
|
+
const status = Number(err?.status || err?.statusCode) || 500;
|
|
47
|
+
const message = err?.message || "internal error";
|
|
48
|
+
log(`error: ${req.method} ${req.originalUrl} → ${status}: ${err?.stack || message}`);
|
|
49
|
+
try {
|
|
50
|
+
appendErrorTrace({
|
|
51
|
+
where: `${req.method} ${req.originalUrl}`,
|
|
52
|
+
error: message,
|
|
53
|
+
trace_id: req.apxTraceId,
|
|
54
|
+
});
|
|
55
|
+
} catch { /* tracing must never mask the original error */ }
|
|
56
|
+
if (res.headersSent) return;
|
|
57
|
+
res.status(status).json({ error: message });
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
23
61
|
export function pageEnvelope(rows, query = {}) {
|
|
24
62
|
const total = rows.length;
|
|
25
63
|
const hasLimit = query.limit != null && query.limit !== "";
|
|
@@ -46,12 +84,19 @@ export function traceIdMiddleware(req, res, next) {
|
|
|
46
84
|
next();
|
|
47
85
|
}
|
|
48
86
|
|
|
49
|
-
// Paths that bypass auth: /health for liveness probes, /pair/* so a
|
|
50
|
-
// client can bootstrap a token without already having one, and
|
|
51
|
-
// /admin/web-token so the local same-origin admin panel can self-bootstrap.
|
|
52
|
-
// /pair/init and /admin/web-token both enforce localhost-only checks of
|
|
87
|
+
// Paths that bypass auth: /api/health for liveness probes, /api/pair/* so a
|
|
88
|
+
// fresh client can bootstrap a token without already having one, and
|
|
89
|
+
// /api/admin/web-token so the local same-origin admin panel can self-bootstrap.
|
|
90
|
+
// /api/pair/init and /api/admin/web-token both enforce localhost-only checks of
|
|
53
91
|
// their own — the auth middleware just gets out of their way.
|
|
54
|
-
|
|
92
|
+
//
|
|
93
|
+
// These are full paths: the middleware runs at app level (before the /api
|
|
94
|
+
// router), so req.path is the complete URL path.
|
|
95
|
+
const UNAUTHENTICATED_PREFIXES = [
|
|
96
|
+
apiPath("/health"),
|
|
97
|
+
apiPath("/pair/"),
|
|
98
|
+
apiPath("/admin/web-token"),
|
|
99
|
+
];
|
|
55
100
|
|
|
56
101
|
// Does this path look like a static asset (has a file extension)? Vite emits
|
|
57
102
|
// hashed, extension-bearing filenames (index-abc123.js, logo.svg, font.woff2),
|
|
@@ -62,18 +107,19 @@ function isStaticAssetPath(p) {
|
|
|
62
107
|
}
|
|
63
108
|
|
|
64
109
|
function isUnauthenticatedPath(p, method = "GET") {
|
|
65
|
-
if (p === "/health") return true;
|
|
66
|
-
if (p === "/admin/web-token") return true;
|
|
67
110
|
for (const prefix of UNAUTHENTICATED_PREFIXES) {
|
|
68
111
|
if (p === prefix.replace(/\/$/, "") || p.startsWith(prefix)) return true;
|
|
69
112
|
}
|
|
113
|
+
// Everything else under /api needs a token, full stop. Checked BEFORE the
|
|
114
|
+
// SPA-bootstrap exemption below, which would otherwise let a data route pass
|
|
115
|
+
// simply because its last segment happens to carry a file extension —
|
|
116
|
+
// GET /api/projects/0/artifacts/report.html is a data route, not an asset.
|
|
117
|
+
if (isApiPath(p)) return false;
|
|
70
118
|
// SPA bootstrap: the admin bundle loads before it holds a bearer, so a GET
|
|
71
119
|
// for a static asset or a known client-router route is served without auth —
|
|
72
|
-
// the bundle then fetches /admin/web-token.
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
// public just because someone forgot to register its prefix (the old
|
|
76
|
-
// denylist failure mode).
|
|
120
|
+
// the bundle then fetches /api/admin/web-token. Since every data route now
|
|
121
|
+
// lives under /api and returned false above, this exemption can only ever
|
|
122
|
+
// reach panel assets and client-router paths.
|
|
77
123
|
if (method === "GET" && (isStaticAssetPath(p) || isKnownSpaRoute(p))) return true;
|
|
78
124
|
return false;
|
|
79
125
|
}
|
|
@@ -113,8 +113,8 @@ function indexStatus() {
|
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
export function register(
|
|
117
|
-
|
|
116
|
+
export function register(api /*, ctx */) {
|
|
117
|
+
api.get("/skills", (req, res) => {
|
|
118
118
|
const projectPath = typeof req.query?.project_path === "string" && req.query.project_path
|
|
119
119
|
? req.query.project_path
|
|
120
120
|
: undefined;
|
|
@@ -149,7 +149,7 @@ export function register(app /*, ctx */) {
|
|
|
149
149
|
|
|
150
150
|
// ---- Full detail (viewer) ----------------------------------------------
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
api.get("/skills/:slug/detail", (req, res) => {
|
|
153
153
|
try {
|
|
154
154
|
const projectPath = typeof req.query?.project_path === "string" && req.query.project_path
|
|
155
155
|
? req.query.project_path
|
|
@@ -175,7 +175,7 @@ export function register(app /*, ctx */) {
|
|
|
175
175
|
|
|
176
176
|
// ---- Enable / disable per scope ----------------------------------------
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
api.put("/skills/enabled", (req, res) => {
|
|
179
179
|
try {
|
|
180
180
|
const { slug, enabled, scope, project_path } = req.body || {};
|
|
181
181
|
if (!slug || typeof slug !== "string") {
|
|
@@ -202,7 +202,7 @@ export function register(app /*, ctx */) {
|
|
|
202
202
|
// ---- Create / import user skills ---------------------------------------
|
|
203
203
|
|
|
204
204
|
// Online editor: write a SKILL.md from slug + description + body.
|
|
205
|
-
|
|
205
|
+
api.post("/skills", (req, res) => {
|
|
206
206
|
try {
|
|
207
207
|
const { slug, description, body, project_path } = req.body || {};
|
|
208
208
|
if (!slug || typeof slug !== "string" || !SLUG_RE.test(slug)) {
|
|
@@ -220,7 +220,7 @@ export function register(app /*, ctx */) {
|
|
|
220
220
|
});
|
|
221
221
|
|
|
222
222
|
// Import from an uploaded .zip (sent as base64 in JSON — skills are tiny).
|
|
223
|
-
|
|
223
|
+
api.post("/skills/import/zip", (req, res) => {
|
|
224
224
|
let tmp;
|
|
225
225
|
try {
|
|
226
226
|
const { data, project_path } = req.body || {};
|
|
@@ -257,7 +257,7 @@ export function register(app /*, ctx */) {
|
|
|
257
257
|
|
|
258
258
|
// Import by cloning a git repo. The repo root (or its single skill subdir)
|
|
259
259
|
// must contain a SKILL.md.
|
|
260
|
-
|
|
260
|
+
api.post("/skills/import/repo", (req, res) => {
|
|
261
261
|
let tmp;
|
|
262
262
|
try {
|
|
263
263
|
const { url, project_path } = req.body || {};
|
|
@@ -293,7 +293,7 @@ export function register(app /*, ctx */) {
|
|
|
293
293
|
}
|
|
294
294
|
});
|
|
295
295
|
|
|
296
|
-
|
|
296
|
+
api.delete("/skills/:slug", (req, res) => {
|
|
297
297
|
try {
|
|
298
298
|
const slug = req.params.slug;
|
|
299
299
|
if (!slug || !SLUG_RE.test(slug)) {
|
|
@@ -344,7 +344,7 @@ export function register(app /*, ctx */) {
|
|
|
344
344
|
|
|
345
345
|
// ---- Inspector config + status -----------------------------------------
|
|
346
346
|
|
|
347
|
-
|
|
347
|
+
api.get("/skills/inspector", (_req, res) => {
|
|
348
348
|
try {
|
|
349
349
|
const cfg = readConfig();
|
|
350
350
|
res.json({
|
|
@@ -358,7 +358,7 @@ export function register(app /*, ctx */) {
|
|
|
358
358
|
}
|
|
359
359
|
});
|
|
360
360
|
|
|
361
|
-
|
|
361
|
+
api.put("/skills/inspector", (req, res) => {
|
|
362
362
|
try {
|
|
363
363
|
const patch = req.body || {};
|
|
364
364
|
const cfg = readConfig();
|
|
@@ -388,7 +388,7 @@ export function register(app /*, ctx */) {
|
|
|
388
388
|
|
|
389
389
|
// ---- Index build --------------------------------------------------------
|
|
390
390
|
|
|
391
|
-
|
|
391
|
+
api.post("/skills/index", async (req, res) => {
|
|
392
392
|
try {
|
|
393
393
|
const { project_path, force } = req.body || {};
|
|
394
394
|
const cfg = readConfig();
|
|
@@ -423,7 +423,7 @@ export function register(app /*, ctx */) {
|
|
|
423
423
|
|
|
424
424
|
// ---- Dry-run ------------------------------------------------------------
|
|
425
425
|
|
|
426
|
-
|
|
426
|
+
api.post("/skills/inspect", async (req, res) => {
|
|
427
427
|
try {
|
|
428
428
|
const { prompt, project_path } = req.body || {};
|
|
429
429
|
if (!prompt || typeof prompt !== "string") {
|
|
@@ -91,8 +91,8 @@ function wrapOnEventForLog(send, { trace_id, channel }) {
|
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export function register(
|
|
95
|
-
|
|
94
|
+
export function register(api, { projects, registries, plugins, project, config }) {
|
|
95
|
+
api.post("/projects/:pid/super-agent/chat/stream", async (req, res) => {
|
|
96
96
|
const p = project(req, res);
|
|
97
97
|
if (!p) return;
|
|
98
98
|
// Optional coding-surface knobs: the terminal Code TUI (apx code, Build
|
|
@@ -223,7 +223,7 @@ export function register(app, { projects, registries, plugins, project, config }
|
|
|
223
223
|
// <id>` when the session lives outside any registered APX project (e.g. a
|
|
224
224
|
// raw Claude/Codex session). Returns { text } so callers can format the
|
|
225
225
|
// summary however they want.
|
|
226
|
-
|
|
226
|
+
api.post("/super-agent/summarize", async (req, res) => {
|
|
227
227
|
const { prompt, context_note: contextNote = "", model, max_tokens } = req.body || {};
|
|
228
228
|
if (!prompt) return res.status(400).json({ error: "prompt required" });
|
|
229
229
|
try {
|
|
@@ -254,7 +254,7 @@ export function register(app, { projects, registries, plugins, project, config }
|
|
|
254
254
|
}
|
|
255
255
|
});
|
|
256
256
|
|
|
257
|
-
|
|
257
|
+
api.post("/projects/:pid/super-agent/chat", async (req, res) => {
|
|
258
258
|
const p = project(req, res);
|
|
259
259
|
if (!p) return;
|
|
260
260
|
const { prompt, previousMessages, model, maxIters, maxTokens, completionContract } =
|
|
@@ -24,11 +24,11 @@ import {
|
|
|
24
24
|
} from "#core/stores/tasks.js";
|
|
25
25
|
import { pageEnvelope } from "./shared.js";
|
|
26
26
|
|
|
27
|
-
export function register(
|
|
27
|
+
export function register(api, { project, projects }) {
|
|
28
28
|
// Global tasks across every project, newest first. Returns a { meta, data }
|
|
29
29
|
// envelope. Paginated via ?limit & ?offset; with no limit, data is the full
|
|
30
30
|
// set as one page.
|
|
31
|
-
|
|
31
|
+
api.get("/tasks", (req, res) => {
|
|
32
32
|
const { state, tag, agent, due_before, due_after, status, updated_since } = req.query;
|
|
33
33
|
|
|
34
34
|
// Resolve the registered projects to what core needs, dropping any the
|
|
@@ -63,7 +63,7 @@ export function register(app, { project, projects }) {
|
|
|
63
63
|
|
|
64
64
|
// Per-project tasks. Returns a { meta, data } envelope; with no ?limit the
|
|
65
65
|
// data array is the full filtered set (one page).
|
|
66
|
-
|
|
66
|
+
api.get("/projects/:pid/tasks", (req, res) => {
|
|
67
67
|
const p = project(req, res);
|
|
68
68
|
if (!p) return;
|
|
69
69
|
const { state, tag, agent, due_before, due_after, status, updated_since } = req.query;
|
|
@@ -79,7 +79,7 @@ export function register(app, { project, projects }) {
|
|
|
79
79
|
res.json(pageEnvelope(all, req.query));
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
api.post("/projects/:pid/tasks", (req, res) => {
|
|
83
83
|
const p = project(req, res);
|
|
84
84
|
if (!p) return;
|
|
85
85
|
try {
|
|
@@ -90,7 +90,7 @@ export function register(app, { project, projects }) {
|
|
|
90
90
|
}
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
api.get("/projects/:pid/tasks/:id", (req, res) => {
|
|
94
94
|
const p = project(req, res);
|
|
95
95
|
if (!p) return;
|
|
96
96
|
const task = getTask(p.storagePath, req.params.id);
|
|
@@ -98,7 +98,7 @@ export function register(app, { project, projects }) {
|
|
|
98
98
|
res.json(task);
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
api.patch("/projects/:pid/tasks/:id", (req, res) => {
|
|
102
102
|
const p = project(req, res);
|
|
103
103
|
if (!p) return;
|
|
104
104
|
const { patch } = req.body || {};
|
|
@@ -110,7 +110,7 @@ export function register(app, { project, projects }) {
|
|
|
110
110
|
res.json(updated);
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
api.post("/projects/:pid/tasks/:id/done", (req, res) => {
|
|
114
114
|
const p = project(req, res);
|
|
115
115
|
if (!p) return;
|
|
116
116
|
const { by = null } = req.body || {};
|
|
@@ -119,7 +119,7 @@ export function register(app, { project, projects }) {
|
|
|
119
119
|
res.json(updated);
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
api.post("/projects/:pid/tasks/:id/drop", (req, res) => {
|
|
123
123
|
const p = project(req, res);
|
|
124
124
|
if (!p) return;
|
|
125
125
|
const { by = null } = req.body || {};
|
|
@@ -128,7 +128,7 @@ export function register(app, { project, projects }) {
|
|
|
128
128
|
res.json(updated);
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
api.post("/projects/:pid/tasks/:id/reopen", (req, res) => {
|
|
132
132
|
const p = project(req, res);
|
|
133
133
|
if (!p) return;
|
|
134
134
|
const updated = reopenTask(p.storagePath, req.params.id);
|
|
@@ -137,7 +137,7 @@ export function register(app, { project, projects }) {
|
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
// Move an open task through its workflow (pending → running → in_review …).
|
|
140
|
-
|
|
140
|
+
api.post("/projects/:pid/tasks/:id/status", (req, res) => {
|
|
141
141
|
const p = project(req, res);
|
|
142
142
|
if (!p) return;
|
|
143
143
|
const { status } = req.body || {};
|
|
@@ -149,7 +149,7 @@ export function register(app, { project, projects }) {
|
|
|
149
149
|
});
|
|
150
150
|
|
|
151
151
|
// Lightweight summary endpoint for status displays.
|
|
152
|
-
|
|
152
|
+
api.get("/projects/:pid/tasks-summary", (req, res) => {
|
|
153
153
|
const p = project(req, res);
|
|
154
154
|
if (!p) return;
|
|
155
155
|
res.json(countTasks(p.storagePath));
|
|
@@ -31,7 +31,6 @@ import {
|
|
|
31
31
|
removeTelegramChannel,
|
|
32
32
|
unsetTelegramChannelFields,
|
|
33
33
|
listContacts,
|
|
34
|
-
findContact,
|
|
35
34
|
upsertContact,
|
|
36
35
|
removeContact,
|
|
37
36
|
listRoles,
|
|
@@ -41,13 +40,13 @@ import {
|
|
|
41
40
|
|
|
42
41
|
import { redactChannel, isSecretMarker } from "#core/config/redact.js";
|
|
43
42
|
|
|
44
|
-
export function register(
|
|
45
|
-
|
|
43
|
+
export function register(api, { telegram }) {
|
|
44
|
+
api.get("/telegram/status", (_req, res) => {
|
|
46
45
|
if (!telegram) return res.json({ enabled: false, channels: [] });
|
|
47
46
|
res.json(telegram.status());
|
|
48
47
|
});
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
api.post("/telegram/start", (_req, res) => {
|
|
51
50
|
if (!telegram)
|
|
52
51
|
return res.status(503).json({ error: "telegram plugin not loaded" });
|
|
53
52
|
try {
|
|
@@ -58,7 +57,7 @@ export function register(app, { telegram }) {
|
|
|
58
57
|
}
|
|
59
58
|
});
|
|
60
59
|
|
|
61
|
-
|
|
60
|
+
api.post("/telegram/stop", (_req, res) => {
|
|
62
61
|
if (!telegram)
|
|
63
62
|
return res.status(503).json({ error: "telegram plugin not loaded" });
|
|
64
63
|
try {
|
|
@@ -69,7 +68,7 @@ export function register(app, { telegram }) {
|
|
|
69
68
|
}
|
|
70
69
|
});
|
|
71
70
|
|
|
72
|
-
|
|
71
|
+
api.post("/telegram/send", async (req, res) => {
|
|
73
72
|
const { chat_id, text, channel } = req.body || {};
|
|
74
73
|
if (!text) return res.status(400).json({ error: "text required" });
|
|
75
74
|
if (!telegram)
|
|
@@ -82,7 +81,7 @@ export function register(app, { telegram }) {
|
|
|
82
81
|
}
|
|
83
82
|
});
|
|
84
83
|
|
|
85
|
-
|
|
84
|
+
api.post("/telegram/send_photo", async (req, res) => {
|
|
86
85
|
const { chat_id, photo, caption, parse_mode, channel } = req.body || {};
|
|
87
86
|
if (!photo)
|
|
88
87
|
return res.status(400).json({ error: "photo required (path or url)" });
|
|
@@ -102,7 +101,7 @@ export function register(app, { telegram }) {
|
|
|
102
101
|
}
|
|
103
102
|
});
|
|
104
103
|
|
|
105
|
-
|
|
104
|
+
api.post("/telegram/send_voice", async (req, res) => {
|
|
106
105
|
const { chat_id, audio, caption, duration, channel } = req.body || {};
|
|
107
106
|
if (!audio) return res.status(400).json({ error: "audio required (path)" });
|
|
108
107
|
if (!telegram)
|
|
@@ -121,7 +120,7 @@ export function register(app, { telegram }) {
|
|
|
121
120
|
}
|
|
122
121
|
});
|
|
123
122
|
|
|
124
|
-
|
|
123
|
+
api.post("/telegram/send_audio", async (req, res) => {
|
|
125
124
|
const { chat_id, audio, caption, title, performer, channel } =
|
|
126
125
|
req.body || {};
|
|
127
126
|
if (!audio) return res.status(400).json({ error: "audio required (path)" });
|
|
@@ -145,7 +144,7 @@ export function register(app, { telegram }) {
|
|
|
145
144
|
// ── Channel CRUD (config-only; caller must POST /admin/reload to apply) ──
|
|
146
145
|
// We read fresh config from disk on each call so concurrent writes from the
|
|
147
146
|
// CLI and the daemon don't clobber one another via stale closures.
|
|
148
|
-
|
|
147
|
+
api.get("/telegram/channels", (_req, res) => {
|
|
149
148
|
try {
|
|
150
149
|
const cfg = readConfig();
|
|
151
150
|
res.json({ channels: listTelegramChannels(cfg).map(redactChannel) });
|
|
@@ -154,7 +153,7 @@ export function register(app, { telegram }) {
|
|
|
154
153
|
}
|
|
155
154
|
});
|
|
156
155
|
|
|
157
|
-
|
|
156
|
+
api.post("/telegram/channels", (req, res) => {
|
|
158
157
|
const body = req.body || {};
|
|
159
158
|
if (!body.name || typeof body.name !== "string") {
|
|
160
159
|
return res.status(400).json({ error: "name required" });
|
|
@@ -173,7 +172,7 @@ export function register(app, { telegram }) {
|
|
|
173
172
|
}
|
|
174
173
|
});
|
|
175
174
|
|
|
176
|
-
|
|
175
|
+
api.patch("/telegram/channels/:name", (req, res) => {
|
|
177
176
|
const { name } = req.params;
|
|
178
177
|
const body = req.body || {};
|
|
179
178
|
try {
|
|
@@ -202,7 +201,7 @@ export function register(app, { telegram }) {
|
|
|
202
201
|
}
|
|
203
202
|
});
|
|
204
203
|
|
|
205
|
-
|
|
204
|
+
api.delete("/telegram/channels/:name", (req, res) => {
|
|
206
205
|
const { name } = req.params;
|
|
207
206
|
try {
|
|
208
207
|
const cfg = readConfig();
|
|
@@ -219,7 +218,7 @@ export function register(app, { telegram }) {
|
|
|
219
218
|
// channel. These edit ~/.apx/config.json directly; the running poller picks
|
|
220
219
|
// changes up on its next inbound message (it re-reads config per message), so
|
|
221
220
|
// no /admin/reload is required for contact/role edits.
|
|
222
|
-
|
|
221
|
+
api.get("/telegram/contacts", (_req, res) => {
|
|
223
222
|
try {
|
|
224
223
|
const cfg = readConfig();
|
|
225
224
|
res.json({
|
|
@@ -235,7 +234,7 @@ export function register(app, { telegram }) {
|
|
|
235
234
|
}
|
|
236
235
|
});
|
|
237
236
|
|
|
238
|
-
|
|
237
|
+
api.patch("/telegram/contacts/:user_id", (req, res) => {
|
|
239
238
|
const { user_id } = req.params;
|
|
240
239
|
const body = req.body || {};
|
|
241
240
|
try {
|
|
@@ -254,7 +253,7 @@ export function register(app, { telegram }) {
|
|
|
254
253
|
}
|
|
255
254
|
});
|
|
256
255
|
|
|
257
|
-
|
|
256
|
+
api.delete("/telegram/contacts/:user_id", (req, res) => {
|
|
258
257
|
const { user_id } = req.params;
|
|
259
258
|
try {
|
|
260
259
|
const cfg = readConfig();
|
|
@@ -267,7 +266,7 @@ export function register(app, { telegram }) {
|
|
|
267
266
|
});
|
|
268
267
|
|
|
269
268
|
// ── Roles (role → { tools }) ────────────────────────────────────────────
|
|
270
|
-
|
|
269
|
+
api.get("/telegram/roles", (_req, res) => {
|
|
271
270
|
try {
|
|
272
271
|
res.json({ roles: listRoles(readConfig()) });
|
|
273
272
|
} catch (e) {
|
|
@@ -275,7 +274,7 @@ export function register(app, { telegram }) {
|
|
|
275
274
|
}
|
|
276
275
|
});
|
|
277
276
|
|
|
278
|
-
|
|
277
|
+
api.put("/telegram/roles/:name", (req, res) => {
|
|
279
278
|
const { name } = req.params;
|
|
280
279
|
const body = req.body || {};
|
|
281
280
|
try {
|
|
@@ -288,7 +287,7 @@ export function register(app, { telegram }) {
|
|
|
288
287
|
}
|
|
289
288
|
});
|
|
290
289
|
|
|
291
|
-
|
|
290
|
+
api.delete("/telegram/roles/:name", (req, res) => {
|
|
292
291
|
const { name } = req.params;
|
|
293
292
|
try {
|
|
294
293
|
const { removed } = removeRole(readConfig(), name);
|
|
@@ -300,7 +299,7 @@ export function register(app, { telegram }) {
|
|
|
300
299
|
});
|
|
301
300
|
|
|
302
301
|
// Alias for proactive daemon-initiated pushes (routines, error handlers, …).
|
|
303
|
-
|
|
302
|
+
api.post("/telegram/notify", async (req, res) => {
|
|
304
303
|
const { chat_id, text, channel } = req.body || {};
|
|
305
304
|
if (!text) return res.status(400).json({ error: "text required" });
|
|
306
305
|
if (!telegram)
|
|
@@ -11,11 +11,11 @@ import { buildRegistryRouter } from "#core/http-tools/registry.js";
|
|
|
11
11
|
import { buildGlobRouter } from "#core/http-tools/glob.js";
|
|
12
12
|
import { buildGrepRouter } from "#core/http-tools/grep.js";
|
|
13
13
|
|
|
14
|
-
export function register(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
export function register(api, { express, projects, registries }) {
|
|
15
|
+
api.use("/tools/fetch", buildFetchRouter(express));
|
|
16
|
+
api.use("/tools/browser", buildBrowserRouter(express));
|
|
17
|
+
api.use("/tools/search", buildSearchRouter(express));
|
|
18
|
+
api.use("/tools/glob", buildGlobRouter(express));
|
|
19
|
+
api.use("/tools/grep", buildGrepRouter(express));
|
|
20
|
+
api.use("/tools", buildRegistryRouter(express, { projects, registries }));
|
|
21
21
|
}
|