@agentprojectcontext/apx 1.77.3 → 1.79.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 +43 -53
- 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/tool-summary.js +65 -0
- package/src/core/agent/tools/handlers/call-runtime.js +4 -4
- package/src/core/agent/tools/handlers/list-commitments.js +80 -0
- package/src/core/agent/tools/handlers/list-tasks.js +66 -27
- package/src/core/agent/tools/handlers/record-commitment.js +68 -0
- package/src/core/agent/tools/handlers/search-sessions.js +1 -1
- package/src/core/agent/tools/handlers/send-telegram.js +68 -2
- package/src/core/agent/tools/handlers/set-permission-mode.js +11 -4
- package/src/core/agent/tools/names.js +64 -0
- package/src/core/agent/tools/registry.js +9 -0
- package/src/core/agent/tools/tool-call-parser.js +70 -1
- 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/ask-callbacks.js +35 -0
- package/src/core/channels/telegram/config.js +201 -0
- package/src/core/channels/telegram/dispatch.js +3 -0
- package/src/core/channels/telegram/reply.js +30 -5
- package/src/core/config/index.js +25 -183
- package/src/core/config/paths.js +57 -1
- package/src/core/config/redact.js +22 -0
- package/src/core/confirmation/adapters/web.js +1 -1
- package/src/core/daemon/service.js +238 -0
- 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/gemini.js +322 -60
- package/src/core/engines/openai-compatible.js +21 -2
- 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/consolidate.js +225 -0
- package/src/core/memory/indexer.js +2 -5
- package/src/core/nudge/index.js +192 -0
- package/src/core/nudge/policy.js +143 -0
- package/src/core/nudge/store.js +141 -0
- package/src/core/profiles/bundled/secretary/PROFILE.md +8 -9
- package/src/core/profiles/bundled/secretary/config.schema.json +33 -3
- package/src/core/profiles/bundled/secretary/routines/day-close.json +7 -3
- package/src/core/profiles/bundled/secretary/routines/day-open.json +7 -3
- package/src/core/profiles/bundled/secretary/routines/watch.json +13 -0
- package/src/core/profiles/lifecycle.js +0 -2
- package/src/core/profiles/store.js +2 -7
- package/src/core/routines/runner.js +103 -4
- package/src/core/routines/signals.js +270 -0
- 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/commitments.js +331 -0
- package/src/core/stores/engine-sessions.js +0 -4
- package/src/core/stores/messages.js +4 -5
- package/src/core/stores/organization.js +9 -11
- package/src/core/stores/routines.js +23 -14
- package/src/core/stores/runtime-callbacks.js +2 -1
- package/src/core/util/json-file.js +128 -0
- package/src/core/util/thinking.js +51 -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/commitments.js +135 -0
- 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/nudges.js +112 -0
- package/src/host/daemon/api/organization.js +8 -8
- package/src/host/daemon/api/pairing.js +6 -6
- 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 +32 -8
- package/src/host/daemon/api/run.js +2 -2
- package/src/host/daemon/api/runtimes.js +6 -6
- package/src/host/daemon/api/self-memory.js +50 -0
- 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 +61 -24
- 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 +9 -7
- package/src/host/daemon/api/web.js +28 -32
- package/src/host/daemon/api.js +83 -48
- package/src/host/daemon/callback-reconciler.js +16 -0
- 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 +8 -2
- package/src/host/daemon/plugins/telegram/index.js +7 -2
- package/src/host/daemon/stt-venv.js +20 -81
- package/src/host/daemon/wakeup.js +18 -4
- 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/commitment.js +154 -0
- package/src/interfaces/cli/commands/config.js +4 -4
- package/src/interfaces/cli/commands/daemon.js +74 -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 +75 -2
- package/src/interfaces/cli/commands/messages.js +5 -5
- package/src/interfaces/cli/commands/model.js +0 -18
- package/src/interfaces/cli/commands/nudge.js +130 -0
- 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/commitment.js +19 -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 +25 -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 +75 -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 +19 -0
- package/src/interfaces/cli/routes/messages.js +16 -0
- package/src/interfaces/cli/routes/model.js +11 -0
- package/src/interfaces/cli/routes/nudge.js +17 -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-CvEoGtTf.js +849 -0
- package/src/interfaces/web/dist/assets/index-CvEoGtTf.js.map +1 -0
- package/src/interfaces/web/dist/assets/index-DzBBXFaO.css +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +11 -10
- package/src/interfaces/web/src/components/AddProjectDialog.tsx +1 -1
- package/src/interfaces/web/src/components/Section.tsx +18 -3
- 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/MessageBubble.tsx +13 -0
- 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/cron/CronPicker.tsx +196 -0
- 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/inbox/InboxList.tsx +145 -0
- package/src/interfaces/web/src/components/memory/MemoryBrowser.tsx +36 -6
- package/src/interfaces/web/src/components/routines/ExecutionsList.tsx +1 -1
- package/src/interfaces/web/src/components/routines/RoutineDetail.tsx +16 -4
- package/src/interfaces/web/src/components/routines/RoutineEditor.tsx +14 -4
- package/src/interfaces/web/src/components/routines/shared.ts +14 -5
- 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/NudgePanel.tsx +183 -0
- package/src/interfaces/web/src/components/settings/ProfilePanel.tsx +36 -12
- 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/components/ui/filter-chips.tsx +47 -0
- package/src/interfaces/web/src/components/ui.tsx +1 -0
- package/src/interfaces/web/src/constants/index.ts +1 -0
- package/src/interfaces/web/src/hooks/useChat.ts +5 -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/useNudges.ts +38 -0
- 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 +3 -3
- package/src/interfaces/web/src/i18n/en.ts +127 -0
- package/src/interfaces/web/src/i18n/es.ts +127 -0
- 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/commitments.ts +57 -0
- 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/notebook.ts +23 -0
- package/src/interfaces/web/src/lib/api/nudges.ts +53 -0
- 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/lib/cron.ts +196 -0
- package/src/interfaces/web/src/lib/when.ts +32 -0
- package/src/interfaces/web/src/screens/InboxScreen.tsx +107 -77
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +5 -2
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +17 -3
- package/src/interfaces/web/src/screens/base/AgentDefaultsTab.tsx +1 -1
- package/src/interfaces/web/src/screens/base/CommitmentsTab.tsx +239 -0
- package/src/interfaces/web/src/screens/base/GlobalTasksTab.tsx +103 -20
- package/src/interfaces/web/src/screens/base/LogsTab.tsx +18 -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 +24 -6
- 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 +15 -13
- 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/src/types/daemon.ts +10 -1
- package/src/interfaces/web/vite.config.ts +13 -18
- package/src/interfaces/web/dist/assets/index-BptlbKjU.js +0 -824
- package/src/interfaces/web/dist/assets/index-BptlbKjU.js.map +0 -1
- package/src/interfaces/web/dist/assets/index-D_EJEA1n.css +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
|
@@ -25,8 +25,12 @@ import {
|
|
|
25
25
|
getPluginService,
|
|
26
26
|
reconcilePluginMcp,
|
|
27
27
|
} from "#core/integrations/index.js";
|
|
28
|
+
import { asyncRoute } from "./shared.js";
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
// Integration scope vocabulary: project | global, accepting "default" for
|
|
31
|
+
// global and both "shared" and "runtime" for project. See the note in
|
|
32
|
+
// api/mcps.js — do not merge these three helpers.
|
|
33
|
+
function normalizeIntegrationScope(raw) {
|
|
30
34
|
if (!raw) return "project";
|
|
31
35
|
const s = String(raw).toLowerCase();
|
|
32
36
|
if (s === "global" || s === "default") return "global";
|
|
@@ -44,7 +48,7 @@ function storagePathForScope(scope, p, projects) {
|
|
|
44
48
|
return p.storagePath || null;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
export function register(
|
|
51
|
+
export function register(api, { projects, project, registries }) {
|
|
48
52
|
// Keep a plugin's optional auto-registered MCP server (svc.mcpServer hook) in
|
|
49
53
|
// lockstep with its stored state. Best-effort: a failure here must not break
|
|
50
54
|
// the configure/validate/deactivate/delete response. `storagePath` is the one
|
|
@@ -62,10 +66,10 @@ export function register(app, { projects, project, registries }) {
|
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
// List stored integrations in the chosen scope (secrets redacted).
|
|
65
|
-
|
|
69
|
+
api.get("/projects/:pid/integrations", (req, res) => {
|
|
66
70
|
const p = project(req, res);
|
|
67
71
|
if (!p) return;
|
|
68
|
-
const scope =
|
|
72
|
+
const scope = normalizeIntegrationScope(req.query?.scope);
|
|
69
73
|
if (scope === null) return res.status(400).json({ error: `unknown scope "${req.query?.scope}"` });
|
|
70
74
|
const storagePath = storagePathForScope(scope, p, projects);
|
|
71
75
|
if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
|
|
@@ -75,7 +79,7 @@ export function register(app, { projects, project, registries }) {
|
|
|
75
79
|
|
|
76
80
|
// The full plugin roster with each plugin's resolved status for this project
|
|
77
81
|
// (project record wins over the default/global one).
|
|
78
|
-
|
|
82
|
+
api.get("/projects/:pid/integrations/catalog", (req, res) => {
|
|
79
83
|
const p = project(req, res);
|
|
80
84
|
if (!p) return;
|
|
81
85
|
const catalog = listCatalog().map((entry) => {
|
|
@@ -97,12 +101,12 @@ export function register(app, { projects, project, registries }) {
|
|
|
97
101
|
});
|
|
98
102
|
|
|
99
103
|
// Status for a single plugin in the chosen scope.
|
|
100
|
-
|
|
104
|
+
api.get("/projects/:pid/integrations/:slug", (req, res) => {
|
|
101
105
|
const p = project(req, res);
|
|
102
106
|
if (!p) return;
|
|
103
107
|
const svc = getPluginService(req.params.slug);
|
|
104
108
|
if (!svc) return res.status(404).json({ error: `unknown plugin "${req.params.slug}"` });
|
|
105
|
-
const scope =
|
|
109
|
+
const scope = normalizeIntegrationScope(req.query?.scope);
|
|
106
110
|
if (scope === null) return res.status(400).json({ error: `unknown scope "${req.query?.scope}"` });
|
|
107
111
|
const storagePath = storagePathForScope(scope, p, projects);
|
|
108
112
|
if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
|
|
@@ -111,12 +115,12 @@ export function register(app, { projects, project, registries }) {
|
|
|
111
115
|
});
|
|
112
116
|
|
|
113
117
|
// Save credentials / config. Creates the record if missing.
|
|
114
|
-
|
|
118
|
+
api.post("/projects/:pid/integrations/:slug/configure", (req, res) => {
|
|
115
119
|
const p = project(req, res);
|
|
116
120
|
if (!p) return;
|
|
117
121
|
const svc = getPluginService(req.params.slug);
|
|
118
122
|
if (!svc) return res.status(404).json({ error: `unknown plugin "${req.params.slug}"` });
|
|
119
|
-
const scope =
|
|
123
|
+
const scope = normalizeIntegrationScope(req.query?.scope);
|
|
120
124
|
if (scope === null) return res.status(400).json({ error: `unknown scope "${req.query?.scope}"` });
|
|
121
125
|
const storagePath = storagePathForScope(scope, p, projects);
|
|
122
126
|
if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
|
|
@@ -132,12 +136,12 @@ export function register(app, { projects, project, registries }) {
|
|
|
132
136
|
});
|
|
133
137
|
|
|
134
138
|
// Verify the stored credentials against the provider, then persist the result.
|
|
135
|
-
|
|
139
|
+
api.post("/projects/:pid/integrations/:slug/validate", asyncRoute(async (req, res) => {
|
|
136
140
|
const p = project(req, res);
|
|
137
141
|
if (!p) return;
|
|
138
142
|
const svc = getPluginService(req.params.slug);
|
|
139
143
|
if (!svc) return res.status(404).json({ error: `unknown plugin "${req.params.slug}"` });
|
|
140
|
-
const scope =
|
|
144
|
+
const scope = normalizeIntegrationScope(req.query?.scope);
|
|
141
145
|
if (scope === null) return res.status(400).json({ error: `unknown scope "${req.query?.scope}"` });
|
|
142
146
|
const storagePath = storagePathForScope(scope, p, projects);
|
|
143
147
|
if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
|
|
@@ -153,15 +157,15 @@ export function register(app, { projects, project, registries }) {
|
|
|
153
157
|
} catch (e) {
|
|
154
158
|
res.status(400).json({ error: e.message });
|
|
155
159
|
}
|
|
156
|
-
});
|
|
160
|
+
}));
|
|
157
161
|
|
|
158
162
|
// Disable a plugin without deleting its stored credentials.
|
|
159
|
-
|
|
163
|
+
api.post("/projects/:pid/integrations/:slug/deactivate", (req, res) => {
|
|
160
164
|
const p = project(req, res);
|
|
161
165
|
if (!p) return;
|
|
162
166
|
const svc = getPluginService(req.params.slug);
|
|
163
167
|
if (!svc) return res.status(404).json({ error: `unknown plugin "${req.params.slug}"` });
|
|
164
|
-
const scope =
|
|
168
|
+
const scope = normalizeIntegrationScope(req.query?.scope);
|
|
165
169
|
if (scope === null) return res.status(400).json({ error: `unknown scope "${req.query?.scope}"` });
|
|
166
170
|
const storagePath = storagePathForScope(scope, p, projects);
|
|
167
171
|
if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
|
|
@@ -174,7 +178,7 @@ export function register(app, { projects, project, registries }) {
|
|
|
174
178
|
});
|
|
175
179
|
|
|
176
180
|
// Plugin-specific read action (e.g. Asana → list workspaces for the token).
|
|
177
|
-
|
|
181
|
+
api.post("/projects/:pid/integrations/:slug/action/:action", asyncRoute(async (req, res) => {
|
|
178
182
|
const p = project(req, res);
|
|
179
183
|
if (!p) return;
|
|
180
184
|
const svc = getPluginService(req.params.slug);
|
|
@@ -183,7 +187,7 @@ export function register(app, { projects, project, registries }) {
|
|
|
183
187
|
if (typeof fn !== "function") {
|
|
184
188
|
return res.status(404).json({ error: `unknown action "${req.params.action}"` });
|
|
185
189
|
}
|
|
186
|
-
const scope =
|
|
190
|
+
const scope = normalizeIntegrationScope(req.query?.scope);
|
|
187
191
|
if (scope === null) return res.status(400).json({ error: `unknown scope "${req.query?.scope}"` });
|
|
188
192
|
const storagePath = storagePathForScope(scope, p, projects);
|
|
189
193
|
if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
|
|
@@ -197,13 +201,13 @@ export function register(app, { projects, project, registries }) {
|
|
|
197
201
|
} catch (e) {
|
|
198
202
|
res.status(400).json({ error: e.message });
|
|
199
203
|
}
|
|
200
|
-
});
|
|
204
|
+
}));
|
|
201
205
|
|
|
202
206
|
// Remove a stored integration entirely.
|
|
203
|
-
|
|
207
|
+
api.delete("/projects/:pid/integrations/:slug", (req, res) => {
|
|
204
208
|
const p = project(req, res);
|
|
205
209
|
if (!p) return;
|
|
206
|
-
const scope =
|
|
210
|
+
const scope = normalizeIntegrationScope(req.query?.scope);
|
|
207
211
|
if (scope === null) return res.status(400).json({ error: `unknown scope "${req.query?.scope}"` });
|
|
208
212
|
const storagePath = storagePathForScope(scope, p, projects);
|
|
209
213
|
if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
|
|
@@ -45,7 +45,11 @@ function writeScope(scope, projectEntry, json) {
|
|
|
45
45
|
return writeApfMcps(projectEntry.path, json);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
// MCP scope vocabulary: shared | runtime | global (with "apc" as a friendly
|
|
49
|
+
// alias for shared). NOT interchangeable with the var or integration scopes —
|
|
50
|
+
// each subsystem has its own set and its own default, and collapsing them into
|
|
51
|
+
// one helper would silently reroute writes to the wrong store.
|
|
52
|
+
function normalizeMcpScope(raw) {
|
|
49
53
|
if (!raw) return "shared";
|
|
50
54
|
const s = String(raw).toLowerCase();
|
|
51
55
|
if (s === "apc") return "shared"; // friendly alias
|
|
@@ -55,17 +59,17 @@ function normalizeScope(raw) {
|
|
|
55
59
|
return s;
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
export function register(
|
|
59
|
-
|
|
62
|
+
export function register(api, { projects, registries, project }) {
|
|
63
|
+
api.get("/projects/:pid/mcps", (req, res) => {
|
|
60
64
|
const p = project(req, res);
|
|
61
65
|
if (!p) return;
|
|
62
66
|
res.json(registries.for(p).list());
|
|
63
67
|
});
|
|
64
68
|
|
|
65
|
-
|
|
69
|
+
api.post("/projects/:pid/mcps", (req, res) => {
|
|
66
70
|
const p = project(req, res);
|
|
67
71
|
if (!p) return;
|
|
68
|
-
const scope =
|
|
72
|
+
const scope = normalizeMcpScope(req.query?.scope);
|
|
69
73
|
if (scope === null) {
|
|
70
74
|
return res.status(400).json({ error: `unknown scope "${req.query?.scope}" (use shared|runtime|global)` });
|
|
71
75
|
}
|
|
@@ -102,10 +106,10 @@ export function register(app, { projects, registries, project }) {
|
|
|
102
106
|
res.status(201).json(entry);
|
|
103
107
|
});
|
|
104
108
|
|
|
105
|
-
|
|
109
|
+
api.delete("/projects/:pid/mcps/:name", (req, res) => {
|
|
106
110
|
const p = project(req, res);
|
|
107
111
|
if (!p) return;
|
|
108
|
-
const scope =
|
|
112
|
+
const scope = normalizeMcpScope(req.query?.scope);
|
|
109
113
|
if (scope === null) {
|
|
110
114
|
return res.status(400).json({ error: `unknown scope "${req.query?.scope}" (use shared|runtime|global)` });
|
|
111
115
|
}
|
|
@@ -155,7 +159,7 @@ export function register(app, { projects, registries, project }) {
|
|
|
155
159
|
res.status(204).end();
|
|
156
160
|
});
|
|
157
161
|
|
|
158
|
-
|
|
162
|
+
api.get("/projects/:pid/mcps/check", (req, res) => {
|
|
159
163
|
const p = project(req, res);
|
|
160
164
|
if (!p) return;
|
|
161
165
|
const reg = registries.for(p);
|
|
@@ -194,7 +198,7 @@ export function register(app, { projects, registries, project }) {
|
|
|
194
198
|
// Full tool catalog — tools/list with input schemas, all pages merged.
|
|
195
199
|
// This is what `apx mcp tools` renders; /test below stays as the
|
|
196
200
|
// lightweight smoke check for the web UI card.
|
|
197
|
-
|
|
201
|
+
api.get("/projects/:pid/mcps/:name/tools", async (req, res) => {
|
|
198
202
|
const p = project(req, res);
|
|
199
203
|
if (!p) return;
|
|
200
204
|
try {
|
|
@@ -205,7 +209,7 @@ export function register(app, { projects, registries, project }) {
|
|
|
205
209
|
}
|
|
206
210
|
});
|
|
207
211
|
|
|
208
|
-
|
|
212
|
+
api.post("/projects/:pid/mcps/:name/call", async (req, res) => {
|
|
209
213
|
const p = project(req, res);
|
|
210
214
|
if (!p) return;
|
|
211
215
|
const { tool, params } = req.body || {};
|
|
@@ -221,7 +225,7 @@ export function register(app, { projects, registries, project }) {
|
|
|
221
225
|
// Smoke test — calls tools/list and reports either the tool catalog or a
|
|
222
226
|
// clean error message. Used by the "Test" button in the MCP card so the
|
|
223
227
|
// user can sanity-check a freshly-saved MCP without firing a real tool.
|
|
224
|
-
|
|
228
|
+
api.post("/projects/:pid/mcps/:name/test", async (req, res) => {
|
|
225
229
|
const p = project(req, res);
|
|
226
230
|
if (!p) return;
|
|
227
231
|
try {
|
|
@@ -242,7 +246,7 @@ export function register(app, { projects, registries, project }) {
|
|
|
242
246
|
|
|
243
247
|
// In-memory log buffer for one MCP — stderr tail (stdio) or fetch summary
|
|
244
248
|
// (http) plus a ring of recent events.
|
|
245
|
-
|
|
249
|
+
api.get("/projects/:pid/mcps/:name/logs", (req, res) => {
|
|
246
250
|
const p = project(req, res);
|
|
247
251
|
if (!p) return;
|
|
248
252
|
const logs = registries.for(p).getLogs(req.params.name);
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
searchProjectMessages,
|
|
12
12
|
} from "#core/stores/messages.js";
|
|
13
13
|
|
|
14
|
-
export function register(
|
|
15
|
-
|
|
14
|
+
export function register(api, { project }) {
|
|
15
|
+
api.get("/projects/:pid/messages", (req, res) => {
|
|
16
16
|
const p = project(req, res);
|
|
17
17
|
if (!p) return;
|
|
18
18
|
const { agent, channel, since, limit = "100" } = req.query;
|
|
@@ -25,7 +25,7 @@ export function register(app, { project }) {
|
|
|
25
25
|
res.json(rows);
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
api.post("/projects/:pid/messages", (req, res) => {
|
|
29
29
|
const p = project(req, res);
|
|
30
30
|
if (!p) return;
|
|
31
31
|
const {
|
|
@@ -55,7 +55,7 @@ export function register(app, { project }) {
|
|
|
55
55
|
res.status(201).json({ ok: true, ts: r.ts });
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
api.get("/projects/:pid/messages/search", (req, res) => {
|
|
59
59
|
const p = project(req, res);
|
|
60
60
|
if (!p) return;
|
|
61
61
|
const { q, limit = "50" } = req.query;
|
|
@@ -70,7 +70,7 @@ export function register(app, { project }) {
|
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
// Cross-project channels (telegram, direct, …)
|
|
73
|
-
|
|
73
|
+
api.get("/messages/global", (req, res) => {
|
|
74
74
|
const { channel, limit = "100", since } = req.query;
|
|
75
75
|
const lim = Math.min(parseInt(limit, 10) || 100, 1000);
|
|
76
76
|
const rows = readGlobalMessages({
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// The interruption budget, as seen from outside.
|
|
2
|
+
//
|
|
3
|
+
// GET /nudges — the ledger, newest first
|
|
4
|
+
// GET /nudges/policy — the effective policy and where it came from
|
|
5
|
+
// PUT /nudges/policy — the user's overrides (config.nudge)
|
|
6
|
+
// POST /nudges/:id/feedback — { useful, note? }
|
|
7
|
+
// POST /nudges/check — dry-run the gate without sending
|
|
8
|
+
//
|
|
9
|
+
// The ledger is the honest answer to "how often does this thing bother me",
|
|
10
|
+
// which is the question that decides whether someone keeps the bot on.
|
|
11
|
+
import { readConfig, writeConfig } from "#core/config/index.js";
|
|
12
|
+
import {
|
|
13
|
+
listNudges, nudgeStats, recordFeedback, canNudge,
|
|
14
|
+
resolveNudgePolicy, DEFAULT_POLICY,
|
|
15
|
+
} from "#core/nudge/index.js";
|
|
16
|
+
import { pageEnvelope } from "./shared.js";
|
|
17
|
+
|
|
18
|
+
export function register(api) {
|
|
19
|
+
api.get("/nudges", (req, res) => {
|
|
20
|
+
try {
|
|
21
|
+
const rows = listNudges({
|
|
22
|
+
limit: req.query.limit || 50,
|
|
23
|
+
kind: req.query.kind || "",
|
|
24
|
+
project_id: req.query.project_id || "",
|
|
25
|
+
with_feedback:
|
|
26
|
+
req.query.with_feedback === "1" ? true
|
|
27
|
+
: req.query.with_feedback === "0" ? false
|
|
28
|
+
: null,
|
|
29
|
+
});
|
|
30
|
+
const envelope = pageEnvelope(rows, req.query);
|
|
31
|
+
envelope.meta = { ...(envelope.meta || {}), stats: nudgeStats() };
|
|
32
|
+
res.json(envelope);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
res.status(500).json({ error: e.message });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
api.get("/nudges/policy", (_req, res) => {
|
|
39
|
+
try {
|
|
40
|
+
const cfg = readConfig();
|
|
41
|
+
const { source, ...policy } = resolveNudgePolicy(cfg);
|
|
42
|
+
res.json({
|
|
43
|
+
policy,
|
|
44
|
+
// Which layers contributed, so the panel can say "this came from your
|
|
45
|
+
// profile" instead of showing a number with no provenance.
|
|
46
|
+
source,
|
|
47
|
+
defaults: DEFAULT_POLICY,
|
|
48
|
+
user_overrides: cfg.nudge || {},
|
|
49
|
+
});
|
|
50
|
+
} catch (e) {
|
|
51
|
+
res.status(500).json({ error: e.message });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
api.put("/nudges/policy", (req, res) => {
|
|
56
|
+
const body = req.body || {};
|
|
57
|
+
try {
|
|
58
|
+
const cfg = readConfig();
|
|
59
|
+
const next = { ...(cfg.nudge || {}) };
|
|
60
|
+
for (const key of Object.keys(DEFAULT_POLICY)) {
|
|
61
|
+
if (!(key in body)) continue;
|
|
62
|
+
// null clears the override and hands the key back to the profile.
|
|
63
|
+
if (body[key] === null) delete next[key];
|
|
64
|
+
else next[key] = body[key];
|
|
65
|
+
}
|
|
66
|
+
cfg.nudge = next;
|
|
67
|
+
writeConfig(cfg);
|
|
68
|
+
const { source, ...policy } = resolveNudgePolicy(cfg);
|
|
69
|
+
res.json({ ok: true, policy, source, user_overrides: next });
|
|
70
|
+
} catch (e) {
|
|
71
|
+
res.status(400).json({ error: e.message });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
api.post("/nudges/:id/feedback", (req, res) => {
|
|
76
|
+
const { useful, note } = req.body || {};
|
|
77
|
+
if (typeof useful !== "boolean") {
|
|
78
|
+
return res.status(400).json({ error: "useful (boolean) required" });
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const entry = recordFeedback(req.params.id, useful, note || "");
|
|
82
|
+
if (!entry) return res.status(404).json({ error: `no nudge: ${req.params.id}` });
|
|
83
|
+
res.json({ ok: true, entry });
|
|
84
|
+
} catch (e) {
|
|
85
|
+
res.status(500).json({ error: e.message });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Dry run. Lets a caller (or a curious user) ask "would this get through?"
|
|
90
|
+
// without spending anything — nothing is recorded here.
|
|
91
|
+
api.post("/nudges/check", (req, res) => {
|
|
92
|
+
const { kind, project_id, severity, unsolicited } = req.body || {};
|
|
93
|
+
try {
|
|
94
|
+
const gate = canNudge(
|
|
95
|
+
{
|
|
96
|
+
kind: kind || "unknown",
|
|
97
|
+
project_id: project_id ?? null,
|
|
98
|
+
severity: severity || "normal",
|
|
99
|
+
unsolicited: unsolicited !== false,
|
|
100
|
+
},
|
|
101
|
+
readConfig(),
|
|
102
|
+
);
|
|
103
|
+
res.json({
|
|
104
|
+
allowed: gate.allowed,
|
|
105
|
+
reason: gate.reason,
|
|
106
|
+
retry_after_ms: gate.retry_after_ms,
|
|
107
|
+
});
|
|
108
|
+
} catch (e) {
|
|
109
|
+
res.status(500).json({ error: e.message });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
@@ -19,14 +19,14 @@ import {
|
|
|
19
19
|
removeRole,
|
|
20
20
|
} from "#core/stores/organization.js";
|
|
21
21
|
|
|
22
|
-
export function register(
|
|
23
|
-
|
|
22
|
+
export function register(api, { project }) {
|
|
23
|
+
api.get("/projects/:pid/organization", (req, res) => {
|
|
24
24
|
const p = project(req, res);
|
|
25
25
|
if (!p) return;
|
|
26
26
|
res.json(readOrganization(p.path));
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
api.post("/projects/:pid/organization/areas", (req, res) => {
|
|
30
30
|
const p = project(req, res);
|
|
31
31
|
if (!p) return;
|
|
32
32
|
try {
|
|
@@ -36,7 +36,7 @@ export function register(app, { project }) {
|
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
api.patch("/projects/:pid/organization/areas/:slug", (req, res) => {
|
|
40
40
|
const p = project(req, res);
|
|
41
41
|
if (!p) return;
|
|
42
42
|
try {
|
|
@@ -48,7 +48,7 @@ export function register(app, { project }) {
|
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
api.delete("/projects/:pid/organization/areas/:slug", (req, res) => {
|
|
52
52
|
const p = project(req, res);
|
|
53
53
|
if (!p) return;
|
|
54
54
|
if (!removeArea(p.path, req.params.slug))
|
|
@@ -56,7 +56,7 @@ export function register(app, { project }) {
|
|
|
56
56
|
res.json({ ok: true });
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
api.post("/projects/:pid/organization/roles", (req, res) => {
|
|
60
60
|
const p = project(req, res);
|
|
61
61
|
if (!p) return;
|
|
62
62
|
try {
|
|
@@ -66,7 +66,7 @@ export function register(app, { project }) {
|
|
|
66
66
|
}
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
api.patch("/projects/:pid/organization/roles/:slug", (req, res) => {
|
|
70
70
|
const p = project(req, res);
|
|
71
71
|
if (!p) return;
|
|
72
72
|
try {
|
|
@@ -78,7 +78,7 @@ export function register(app, { project }) {
|
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
api.delete("/projects/:pid/organization/roles/:slug", (req, res) => {
|
|
82
82
|
const p = project(req, res);
|
|
83
83
|
if (!p) return;
|
|
84
84
|
if (!removeRole(p.path, req.params.slug))
|
|
@@ -64,7 +64,7 @@ function purgeExpired(now = Date.now()) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
export function register(
|
|
67
|
+
export function register(api, ctx) {
|
|
68
68
|
const { tokenStore, config } = ctx;
|
|
69
69
|
if (!tokenStore) {
|
|
70
70
|
// Pairing requires the multi-token store; if running with legacy
|
|
@@ -75,7 +75,7 @@ export function register(app, ctx) {
|
|
|
75
75
|
|
|
76
76
|
// ── POST /pair/init ───────────────────────────────────────────────
|
|
77
77
|
// Localhost-only. Returns { pairing_id, expires_at }. No body needed.
|
|
78
|
-
|
|
78
|
+
api.post("/pair/init", (req, res) => {
|
|
79
79
|
if (!isLocalhost(req)) {
|
|
80
80
|
return res.status(403).json({ error: "pair/init: localhost only" });
|
|
81
81
|
}
|
|
@@ -107,7 +107,7 @@ export function register(app, ctx) {
|
|
|
107
107
|
// ── POST /pair/confirm ────────────────────────────────────────────
|
|
108
108
|
// App side. Body: { pairing_id, label?, fingerprint? }. Returns the
|
|
109
109
|
// new client token. Nonce is one-shot.
|
|
110
|
-
|
|
110
|
+
api.post("/pair/confirm", (req, res) => {
|
|
111
111
|
purgeExpired();
|
|
112
112
|
const { pairing_id, label, fingerprint, kind } = req.body || {};
|
|
113
113
|
if (!pairing_id || typeof pairing_id !== "string") {
|
|
@@ -149,7 +149,7 @@ export function register(app, ctx) {
|
|
|
149
149
|
// ── GET /pair/status/:pairing_id ──────────────────────────────────
|
|
150
150
|
// For the CLI to poll while showing the QR. Returns pending|confirmed
|
|
151
151
|
// |expired. Never leaks the token.
|
|
152
|
-
|
|
152
|
+
api.get("/pair/status/:pairing_id", (req, res) => {
|
|
153
153
|
purgeExpired();
|
|
154
154
|
const s = sessions.get(req.params.pairing_id);
|
|
155
155
|
if (!s) return res.json({ status: "unknown" });
|
|
@@ -171,13 +171,13 @@ export function register(app, ctx) {
|
|
|
171
171
|
// ── GET /pair/list ────────────────────────────────────────────────
|
|
172
172
|
// Authenticated (auth middleware exempts /pair/*, so we re-check by
|
|
173
173
|
// hand here — only the master or an existing client can list peers).
|
|
174
|
-
|
|
174
|
+
api.get("/pair/list", (req, res) => {
|
|
175
175
|
if (!checkBearer(req, tokenStore)) return res.status(401).json({ error: "unauthorized" });
|
|
176
176
|
res.json({ clients: tokenStore.list() });
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
// ── DELETE /pair/revoke/:id ───────────────────────────────────────
|
|
180
|
-
|
|
180
|
+
api.delete("/pair/revoke/:id", (req, res) => {
|
|
181
181
|
if (!checkBearer(req, tokenStore)) return res.status(401).json({ error: "unauthorized" });
|
|
182
182
|
const ok = tokenStore.revoke(req.params.id);
|
|
183
183
|
if (!ok) return res.status(404).json({ error: "no such client" });
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// Daemon plugin status surface.
|
|
2
2
|
// GET /plugins status of every loaded plugin
|
|
3
3
|
// GET /plugins/:id/status single plugin
|
|
4
|
-
export function register(
|
|
5
|
-
|
|
4
|
+
export function register(api, { plugins }) {
|
|
5
|
+
api.get("/plugins", (_req, res) => {
|
|
6
6
|
if (!plugins) return res.json({});
|
|
7
7
|
res.json(plugins.status());
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
api.get("/plugins/:id/status", (req, res) => {
|
|
11
11
|
if (!plugins) return res.status(404).end();
|
|
12
12
|
const inst = plugins.get(req.params.id);
|
|
13
13
|
if (!inst)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// The single mount point for every daemon data route.
|
|
2
|
+
//
|
|
3
|
+
// Before this existed, routes lived at the root and three separate hand-written
|
|
4
|
+
// prefix lists had to agree on which of them were "API" (the SPA fallback in
|
|
5
|
+
// web.js, the auth allowlist in shared.js, and the vite dev proxy). They drifted.
|
|
6
|
+
// Now the answer is structural: a path is an API path iff it starts with /api.
|
|
7
|
+
//
|
|
8
|
+
// Kept in its own module so both the daemon and the route modules can import it
|
|
9
|
+
// without pulling in express or any route registration.
|
|
10
|
+
export const API_PREFIX = "/api";
|
|
11
|
+
|
|
12
|
+
/** True for /api and anything below it. */
|
|
13
|
+
export function isApiPath(p) {
|
|
14
|
+
return p === API_PREFIX || p.startsWith(API_PREFIX + "/");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Prefix a root-relative route path with the API mount point. */
|
|
18
|
+
export function apiPath(p) {
|
|
19
|
+
return `${API_PREFIX}${p.startsWith("/") ? p : `/${p}`}`;
|
|
20
|
+
}
|
|
@@ -82,8 +82,8 @@ function detail(id, { preview = true } = {}) {
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
export function register(
|
|
86
|
-
|
|
85
|
+
export function register(api) {
|
|
86
|
+
api.get("/profiles", (_req, res) => {
|
|
87
87
|
try {
|
|
88
88
|
const cfg = readConfig();
|
|
89
89
|
res.json({
|
|
@@ -96,7 +96,7 @@ export function register(app) {
|
|
|
96
96
|
});
|
|
97
97
|
|
|
98
98
|
// Registered before /profiles/:id so "doctor" isn't swallowed as an id.
|
|
99
|
-
|
|
99
|
+
api.get("/profiles/doctor", (req, res) => {
|
|
100
100
|
try {
|
|
101
101
|
res.json(profileDoctor(req.query?.id || null));
|
|
102
102
|
} catch (e) {
|
|
@@ -104,7 +104,7 @@ export function register(app) {
|
|
|
104
104
|
}
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
api.get("/profiles/:id", (req, res) => {
|
|
108
108
|
try {
|
|
109
109
|
const out = detail(req.params.id, { preview: req.query?.preview !== "0" });
|
|
110
110
|
if (!out) return res.status(404).json({ error: `profile "${req.params.id}" not found` });
|
|
@@ -114,7 +114,7 @@ export function register(app) {
|
|
|
114
114
|
}
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
api.post("/profiles/install", (req, res) => {
|
|
118
118
|
try {
|
|
119
119
|
const { source, force } = req.body || {};
|
|
120
120
|
if (!source) return res.status(400).json({ error: "body needs { source }" });
|
|
@@ -131,7 +131,7 @@ export function register(app) {
|
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
api.post("/profiles/use", (req, res) => {
|
|
135
135
|
try {
|
|
136
136
|
const { id, force } = req.body || {};
|
|
137
137
|
if (!id) return res.status(400).json({ error: "body needs { id }" });
|
|
@@ -148,7 +148,7 @@ export function register(app) {
|
|
|
148
148
|
}
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
api.post("/profiles/off", (_req, res) => {
|
|
152
152
|
try {
|
|
153
153
|
res.json({ ok: true, ...offProfile() });
|
|
154
154
|
} catch (e) {
|
|
@@ -156,7 +156,7 @@ export function register(app) {
|
|
|
156
156
|
}
|
|
157
157
|
});
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
api.patch("/profiles/config", (req, res) => {
|
|
160
160
|
try {
|
|
161
161
|
const { values, id } = req.body || {};
|
|
162
162
|
if (!values || typeof values !== "object") {
|
|
@@ -169,7 +169,7 @@ export function register(app) {
|
|
|
169
169
|
}
|
|
170
170
|
});
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
api.delete("/profiles/:id", (req, res) => {
|
|
173
173
|
try {
|
|
174
174
|
res.json({ ok: true, ...uninstallProfile(req.params.id) });
|
|
175
175
|
} catch (e) {
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
// POST /projects register a project by path
|
|
4
4
|
// DELETE /projects/:id unregister
|
|
5
5
|
// POST /projects/:id/rebuild force a context rebuild from disk
|
|
6
|
-
export function register(
|
|
7
|
-
|
|
6
|
+
export function register(api, { projects, registries, addProjectGlobally }) {
|
|
7
|
+
api.get("/projects", (_req, res) => res.json(projects.list()));
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
api.post("/projects", (req, res) => {
|
|
10
10
|
const { path: p } = req.body || {};
|
|
11
11
|
if (!p) return res.status(400).json({ error: "path required" });
|
|
12
12
|
try {
|
|
@@ -19,12 +19,12 @@ export function register(app, { projects, registries, addProjectGlobally }) {
|
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
api.delete("/projects/:id", (req, res) => {
|
|
23
23
|
const ok = projects.unregister(req.params.id);
|
|
24
24
|
res.status(ok ? 204 : 404).end();
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
api.post("/projects/:id/rebuild", (req, res) => {
|
|
28
28
|
try {
|
|
29
29
|
const result = projects.rebuild(req.params.id);
|
|
30
30
|
res.json({ ok: true, ...result });
|