@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
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
// of truth fixes that for good.
|
|
8
8
|
import { runSuperAgent } from "#core/agent/super-agent.js";
|
|
9
9
|
import { TELEGRAM_TOOL_ITERS } from "#core/agent/constants.js";
|
|
10
|
-
import { stripThinking } from "#core/util/thinking.js";
|
|
10
|
+
import { stripThinking, stripReasoning } from "#core/util/thinking.js";
|
|
11
11
|
import { appendGlobalMessage, getRecentTelegramTurnsFromFs } from "#core/stores/messages.js";
|
|
12
12
|
import { CHANNELS } from "#core/constants/channels.js";
|
|
13
|
+
import { summarizeToolTrace } from "#core/agent/tool-summary.js";
|
|
13
14
|
import { SUPERAGENT_ACTOR_ID } from "#core/identity/index.js";
|
|
14
15
|
import { createTelegramConfirmAdapter } from "#core/confirmation/adapters/telegram.js";
|
|
15
16
|
import { getConfirmationStore as getConfirmStore } from "#core/confirmation/pending-store.js";
|
|
@@ -54,7 +55,9 @@ export function buildStreamHandler(self, { chat_id, update_id, agentDisplay }) {
|
|
|
54
55
|
return;
|
|
55
56
|
}
|
|
56
57
|
if (ev.type === "assistant_text" && ev.text) {
|
|
57
|
-
|
|
58
|
+
// Untagged planning is suppressed mid-stream too, or the user
|
|
59
|
+
// watches the model think in real time.
|
|
60
|
+
const piece = stripReasoning(ev.text).answer.trim();
|
|
58
61
|
if (!piece) return;
|
|
59
62
|
await self._send({ chat_id, text: piece });
|
|
60
63
|
state.lastStreamedText = piece;
|
|
@@ -183,6 +186,7 @@ export async function runFollowupTurn(self, {
|
|
|
183
186
|
let replyAuthor;
|
|
184
187
|
let saUsage = null;
|
|
185
188
|
let saModel = null;
|
|
189
|
+
let saTrace = null;
|
|
186
190
|
try {
|
|
187
191
|
const sa = await runTelegramSuperAgent(self, {
|
|
188
192
|
chat_id,
|
|
@@ -200,6 +204,7 @@ export async function runFollowupTurn(self, {
|
|
|
200
204
|
replyAuthor = sa.name || agentDisplay;
|
|
201
205
|
saUsage = sa.usage;
|
|
202
206
|
saModel = sa.model || state.model || null;
|
|
207
|
+
saTrace = sa.trace || null;
|
|
203
208
|
} catch (e) {
|
|
204
209
|
self.log(`telegram[${self.channel.name}] a2a followup failed: ${e.message}`);
|
|
205
210
|
replyText = telegramErrorText(self, e);
|
|
@@ -216,6 +221,7 @@ export async function runFollowupTurn(self, {
|
|
|
216
221
|
replyKind: "superagent",
|
|
217
222
|
saUsage,
|
|
218
223
|
saModel,
|
|
224
|
+
saTrace,
|
|
219
225
|
streamedCount: state.streamedCount,
|
|
220
226
|
lastStreamedText: state.lastStreamedText,
|
|
221
227
|
agentDisplay,
|
|
@@ -244,10 +250,22 @@ export function telegramErrorText(self, e) {
|
|
|
244
250
|
*/
|
|
245
251
|
export async function sendFinalReply(self, {
|
|
246
252
|
chat_id, update_id, replyText, replyAuthor, replyActorId, replyKind,
|
|
247
|
-
saUsage = null, saModel = null, streamedCount = 0, lastStreamedText = "",
|
|
248
|
-
extraMeta = {},
|
|
253
|
+
saUsage = null, saModel = null, saTrace = null, streamedCount = 0, lastStreamedText = "",
|
|
254
|
+
agentDisplay, extraMeta = {},
|
|
249
255
|
}) {
|
|
250
|
-
|
|
256
|
+
// A model that dumps raw planning must never have it forwarded. When that
|
|
257
|
+
// happens the answer comes back empty and the existing never-silent fallback
|
|
258
|
+
// below sends a short line instead — a worse reply, but not the model's notes.
|
|
259
|
+
const stripped = replyText ? stripReasoning(replyText) : { answer: "", leaked: false };
|
|
260
|
+
const finalClean = stripped.answer.trim();
|
|
261
|
+
if (stripped.leaked) {
|
|
262
|
+
// eslint-disable-next-line no-console
|
|
263
|
+
console.warn(
|
|
264
|
+
`[apx] telegram: suppressed an untagged reasoning dump from ${saModel || "the model"} ` +
|
|
265
|
+
`(${replyText.length} chars). Check the model chain — a router that returns raw ` +
|
|
266
|
+
`chain-of-thought is not usable on a user-facing channel.`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
251
269
|
let toSend = "";
|
|
252
270
|
if (finalClean && finalClean !== lastStreamedText) {
|
|
253
271
|
toSend = finalClean;
|
|
@@ -265,8 +283,15 @@ export async function sendFinalReply(self, {
|
|
|
265
283
|
await self._send({ chat_id, text: toSend });
|
|
266
284
|
const meta = { chat_id, tg_channel: self.channel.name, in_reply_to: update_id, final: true, ...extraMeta };
|
|
267
285
|
if (replyText && stripThinking(replyText) !== replyText) meta.thinking_stripped = true;
|
|
286
|
+
if (stripped.leaked) meta.reasoning_leak_suppressed = true;
|
|
268
287
|
if (saUsage) meta.usage = saUsage;
|
|
269
288
|
if (saModel) meta.model = saModel;
|
|
289
|
+
// A COMPACT summary, not the trace: the full one carries args and results
|
|
290
|
+
// and would bloat the day-file for a detail nobody reads back. What is
|
|
291
|
+
// worth recovering later is "it read three files and sent a message", and
|
|
292
|
+
// whether any of it failed.
|
|
293
|
+
const toolSummary = summarizeToolTrace(saTrace);
|
|
294
|
+
if (toolSummary) meta.tool_summary = toolSummary;
|
|
270
295
|
appendGlobalMessage({
|
|
271
296
|
channel: CHANNELS.TELEGRAM,
|
|
272
297
|
direction: "out",
|
package/src/core/config/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { APX_HOME, CONFIG_PATH } from "./paths.js";
|
|
8
|
-
import { PERMISSION_MODES
|
|
8
|
+
import { PERMISSION_MODES } from "../constants/permissions.js";
|
|
9
9
|
import { agentsMdFile, apcProjectFile } from "../apc/paths.js";
|
|
10
10
|
|
|
11
11
|
export {
|
|
@@ -62,6 +62,10 @@ const DEFAULT_CONFIG = {
|
|
|
62
62
|
// snappier but more "want me to continue?" hand-backs. 0/unset → built-in
|
|
63
63
|
// default (TELEGRAM_TOOL_ITERS in src/core/agent/constants.js).
|
|
64
64
|
telegram_max_iters: 0,
|
|
65
|
+
// Char cap for a FOREIGN project's AGENTS.md in the prompt. 0 = no cap.
|
|
66
|
+
// The project APX runs inside is never capped regardless of this value —
|
|
67
|
+
// a project must always read its own contract whole.
|
|
68
|
+
project_agents_max_chars: 24000,
|
|
65
69
|
// Model fallback: ordered list. Each item carries its own provider
|
|
66
70
|
// prefix; the array order IS the attempt order. The router tries the
|
|
67
71
|
// primary (super_agent.model) first, then walks this list, skipping
|
|
@@ -522,186 +526,24 @@ export function removeProject(cfg, idOrPath) {
|
|
|
522
526
|
if (cfg.projects.length !== before) writeConfig(cfg);
|
|
523
527
|
return { removed: before - cfg.projects.length };
|
|
524
528
|
}
|
|
525
|
-
// ── Telegram channels (multi-channel mode) ──────────────────────────────────
|
|
526
|
-
// Each entry in cfg.telegram.channels[] is { name, bot_token, chat_id,
|
|
527
|
-
// route_to_agent, project, respond_with_engine, poll_interval_ms }.
|
|
528
|
-
// These helpers keep the array shape stable for the CLI and the daemon API.
|
|
529
|
-
|
|
530
|
-
const CHANNEL_FIELDS = [
|
|
531
|
-
"name",
|
|
532
|
-
"bot_token",
|
|
533
|
-
"chat_id",
|
|
534
|
-
"route_to_agent",
|
|
535
|
-
"project",
|
|
536
|
-
"respond_with_engine",
|
|
537
|
-
"poll_interval_ms",
|
|
538
|
-
"owner_user_id",
|
|
539
|
-
];
|
|
540
|
-
|
|
541
|
-
function ensureChannelsArray(cfg) {
|
|
542
|
-
cfg.telegram = cfg.telegram || {};
|
|
543
|
-
if (!Array.isArray(cfg.telegram.channels)) cfg.telegram.channels = [];
|
|
544
|
-
return cfg.telegram.channels;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
export function listTelegramChannels(cfg) {
|
|
548
|
-
return ensureChannelsArray(cfg).slice();
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
export function findTelegramChannel(cfg, name) {
|
|
552
|
-
return ensureChannelsArray(cfg).find((c) => c.name === name) || null;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// Create-or-patch a channel by name. `patch` is a partial channel object;
|
|
556
|
-
// unknown keys are dropped. Returns { created, channel }.
|
|
557
|
-
export function upsertTelegramChannel(cfg, name, patch = {}) {
|
|
558
|
-
if (!name || typeof name !== "string")
|
|
559
|
-
throw new Error("upsertTelegramChannel: name required");
|
|
560
|
-
const channels = ensureChannelsArray(cfg);
|
|
561
|
-
let entry = channels.find((c) => c.name === name);
|
|
562
|
-
const created = !entry;
|
|
563
|
-
if (!entry) {
|
|
564
|
-
entry = { name };
|
|
565
|
-
channels.push(entry);
|
|
566
|
-
}
|
|
567
|
-
for (const k of CHANNEL_FIELDS) {
|
|
568
|
-
if (k === "name") continue;
|
|
569
|
-
if (patch[k] !== undefined) entry[k] = patch[k];
|
|
570
|
-
}
|
|
571
|
-
// Default respond_with_engine to true on create.
|
|
572
|
-
if (created && entry.respond_with_engine === undefined) {
|
|
573
|
-
entry.respond_with_engine = true;
|
|
574
|
-
}
|
|
575
|
-
writeConfig(cfg);
|
|
576
|
-
return { created, channel: entry };
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
export function removeTelegramChannel(cfg, name) {
|
|
580
|
-
const channels = ensureChannelsArray(cfg);
|
|
581
|
-
const before = channels.length;
|
|
582
|
-
cfg.telegram.channels = channels.filter((c) => c.name !== name);
|
|
583
|
-
const removed = before - cfg.telegram.channels.length;
|
|
584
|
-
if (removed > 0) {
|
|
585
|
-
// Explicit user-initiated removal — bypass the credential-loss guard
|
|
586
|
-
// so the write goes through even when this empties the array.
|
|
587
|
-
cfg._allowClear = true;
|
|
588
|
-
writeConfig(cfg);
|
|
589
|
-
}
|
|
590
|
-
return { removed };
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// Clear specific optional fields on a channel (project, route_to_agent, …).
|
|
594
|
-
// Returns { channel } or null when no such channel.
|
|
595
|
-
export function unsetTelegramChannelFields(cfg, name, fields = []) {
|
|
596
|
-
const ch = findTelegramChannel(cfg, name);
|
|
597
|
-
if (!ch) return null;
|
|
598
|
-
let mutated = false;
|
|
599
|
-
for (const f of fields) {
|
|
600
|
-
if (!CHANNEL_FIELDS.includes(f) || f === "name") continue;
|
|
601
|
-
if (f in ch) {
|
|
602
|
-
delete ch[f];
|
|
603
|
-
mutated = true;
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
if (mutated) writeConfig(cfg);
|
|
607
|
-
return { channel: ch };
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// ── Telegram contacts (global roster, keyed by user_id) ─────────────────────
|
|
611
|
-
// Identity of a person is global; the per-channel owner_user_id only marks who
|
|
612
|
-
// owns a given channel. Role lives on the contact (global), per the chosen
|
|
613
|
-
// design — owner_user_id overrides it to "owner" for that channel only.
|
|
614
|
-
|
|
615
|
-
const CONTACT_FIELDS = [
|
|
616
|
-
"user_id",
|
|
617
|
-
"name",
|
|
618
|
-
"username",
|
|
619
|
-
"role",
|
|
620
|
-
"note",
|
|
621
|
-
"first_seen",
|
|
622
|
-
"last_seen",
|
|
623
|
-
];
|
|
624
|
-
|
|
625
|
-
function ensureContactsArray(cfg) {
|
|
626
|
-
cfg.telegram = cfg.telegram || {};
|
|
627
|
-
if (!Array.isArray(cfg.telegram.contacts)) cfg.telegram.contacts = [];
|
|
628
|
-
return cfg.telegram.contacts;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
export function listContacts(cfg) {
|
|
632
|
-
return ensureContactsArray(cfg).slice();
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
export function findContact(cfg, userId) {
|
|
636
|
-
if (userId == null) return null;
|
|
637
|
-
return (
|
|
638
|
-
ensureContactsArray(cfg).find((c) => String(c.user_id) === String(userId)) ||
|
|
639
|
-
null
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// Create-or-patch a contact by user_id. Unknown keys are dropped.
|
|
644
|
-
export function upsertContact(cfg, userId, patch = {}, { persist = true } = {}) {
|
|
645
|
-
if (userId == null) throw new Error("upsertContact: user_id required");
|
|
646
|
-
const contacts = ensureContactsArray(cfg);
|
|
647
|
-
let entry = contacts.find((c) => String(c.user_id) === String(userId));
|
|
648
|
-
const created = !entry;
|
|
649
|
-
if (!entry) {
|
|
650
|
-
entry = { user_id: userId };
|
|
651
|
-
contacts.push(entry);
|
|
652
|
-
}
|
|
653
|
-
for (const k of CONTACT_FIELDS) {
|
|
654
|
-
if (k === "user_id") continue;
|
|
655
|
-
if (patch[k] !== undefined) entry[k] = patch[k];
|
|
656
|
-
}
|
|
657
|
-
if (persist) writeConfig(cfg);
|
|
658
|
-
return { created, contact: entry };
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
export function setContactRole(cfg, userId, role) {
|
|
662
|
-
const { contact } = upsertContact(cfg, userId, { role });
|
|
663
|
-
return contact;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
export function removeContact(cfg, userId) {
|
|
667
|
-
const contacts = ensureContactsArray(cfg);
|
|
668
|
-
const before = contacts.length;
|
|
669
|
-
cfg.telegram.contacts = contacts.filter(
|
|
670
|
-
(c) => String(c.user_id) !== String(userId)
|
|
671
|
-
);
|
|
672
|
-
const removed = before - cfg.telegram.contacts.length;
|
|
673
|
-
if (removed > 0) writeConfig(cfg);
|
|
674
|
-
return { removed };
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
export function setChannelOwner(cfg, channelName, userId) {
|
|
678
|
-
return upsertTelegramChannel(cfg, channelName, { owner_user_id: userId });
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
// ── Telegram roles (role → capability map) ──────────────────────────────────
|
|
682
|
-
|
|
683
|
-
export function listRoles(cfg) {
|
|
684
|
-
cfg.telegram = cfg.telegram || {};
|
|
685
|
-
return { ...(cfg.telegram.roles || {}) };
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
export function setRole(cfg, name, def) {
|
|
689
|
-
if (!name || typeof name !== "string") throw new Error("setRole: name required");
|
|
690
|
-
cfg.telegram = cfg.telegram || {};
|
|
691
|
-
cfg.telegram.roles = cfg.telegram.roles || {};
|
|
692
|
-
cfg.telegram.roles[name] = def;
|
|
693
|
-
writeConfig(cfg);
|
|
694
|
-
return cfg.telegram.roles[name];
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
export function removeRole(cfg, name) {
|
|
698
|
-
cfg.telegram = cfg.telegram || {};
|
|
699
|
-
if (!cfg.telegram.roles || !(name in cfg.telegram.roles)) return { removed: 0 };
|
|
700
|
-
if (name === "owner" || name === "guest") {
|
|
701
|
-
throw new Error(`role "${name}" is built-in and cannot be removed`);
|
|
702
|
-
}
|
|
703
|
-
delete cfg.telegram.roles[name];
|
|
704
|
-
writeConfig(cfg);
|
|
705
|
-
return { removed: 1 };
|
|
706
|
-
}
|
|
707
529
|
|
|
530
|
+
// ── Telegram identity ───────────────────────────────────────────────────────
|
|
531
|
+
// Moved to core/channels/telegram/config.js — it is channel domain, not config
|
|
532
|
+
// plumbing. Re-exported here so the CLI, the daemon API and the plugin keep
|
|
533
|
+
// their existing import paths.
|
|
534
|
+
export {
|
|
535
|
+
listTelegramChannels,
|
|
536
|
+
findTelegramChannel,
|
|
537
|
+
upsertTelegramChannel,
|
|
538
|
+
removeTelegramChannel,
|
|
539
|
+
unsetTelegramChannelFields,
|
|
540
|
+
listContacts,
|
|
541
|
+
findContact,
|
|
542
|
+
upsertContact,
|
|
543
|
+
setContactRole,
|
|
544
|
+
removeContact,
|
|
545
|
+
setChannelOwner,
|
|
546
|
+
listRoles,
|
|
547
|
+
setRole,
|
|
548
|
+
removeRole,
|
|
549
|
+
} from "../channels/telegram/config.js";
|
package/src/core/config/paths.js
CHANGED
|
@@ -4,7 +4,19 @@ import fs from "node:fs";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Where APX keeps everything. `APX_HOME` in the environment relocates it —
|
|
9
|
+
* useful for sandboxes and required by tests that need an isolated home.
|
|
10
|
+
*
|
|
11
|
+
* Resolve lazily when a caller can be reached after the environment changes
|
|
12
|
+
* (tests stub os.homedir mid-run); the eager `APX_HOME` constant below is the
|
|
13
|
+
* common case and stays for the ~30 call sites that read it at import time.
|
|
14
|
+
*/
|
|
15
|
+
export function apxHome() {
|
|
16
|
+
return process.env.APX_HOME || path.join(os.homedir(), ".apx");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const APX_HOME = apxHome();
|
|
8
20
|
export const CONFIG_PATH = path.join(APX_HOME, "config.json");
|
|
9
21
|
export const PID_PATH = path.join(APX_HOME, "daemon.pid");
|
|
10
22
|
export const LOG_PATH = path.join(APX_HOME, "daemon.log");
|
|
@@ -21,6 +33,50 @@ export const PROJECT_STORE_ROOT = path.join(APX_HOME, "projects");
|
|
|
21
33
|
export const DEFAULT_PROJECT_ID = "default";
|
|
22
34
|
export const DEFAULT_PROJECT_STORE = path.join(PROJECT_STORE_ROOT, DEFAULT_PROJECT_ID);
|
|
23
35
|
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Everything else under ~/.apx.
|
|
38
|
+
//
|
|
39
|
+
// These used to be rebuilt from os.homedir() at ~30 call sites, several of them
|
|
40
|
+
// duplicated across modules that then drifted: the TTS scratch dir existed in
|
|
41
|
+
// core/voice/tts.js *and* api/voice.js, the desktop log in core/desktop/
|
|
42
|
+
// process.js *and* cli/commands/desktop.js, the log paths in core/logging.js
|
|
43
|
+
// *and* cli/commands/log.js. One definition each, imported everywhere.
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
/** The super-agent's own notebook (see core/agent/self-memory.js). */
|
|
47
|
+
export const SELF_MEMORY_PATH = path.join(APX_HOME, "memory.md");
|
|
48
|
+
/** Who the super-agent is: name, personality, owner. */
|
|
49
|
+
export const IDENTITY_PATH = path.join(APX_HOME, "identity.json");
|
|
50
|
+
|
|
51
|
+
/** User-installed skills and the RAG index built over them. */
|
|
52
|
+
export const SKILLS_DIR = path.join(APX_HOME, "skills");
|
|
53
|
+
export const SKILLS_INDEX_PATH = path.join(SKILLS_DIR, ".index.json");
|
|
54
|
+
|
|
55
|
+
/** Agent vault — reusable agent definitions, not tied to one project. */
|
|
56
|
+
export const AGENT_VAULT_DIR = path.join(APX_HOME, "agents");
|
|
57
|
+
|
|
58
|
+
/** Ledger of unrequested outbound messages and the user's feedback on them. */
|
|
59
|
+
export const NUDGES_PATH = path.join(APX_HOME, "nudges.json");
|
|
60
|
+
|
|
61
|
+
/** Unified log tree. Everything writes here so one tail follows the system. */
|
|
62
|
+
export const LOG_DIR = path.join(APX_HOME, "logs");
|
|
63
|
+
export const APX_LOG_PATH = path.join(LOG_DIR, "apx.log");
|
|
64
|
+
export const ERROR_TRACE_PATH = path.join(LOG_DIR, "errors.jsonl");
|
|
65
|
+
|
|
66
|
+
/** Desktop capsule (Electron) runtime state. */
|
|
67
|
+
export const DESKTOP_PID_PATH = path.join(APX_HOME, "desktop.pid");
|
|
68
|
+
export const DESKTOP_LOG_PATH = path.join(APX_HOME, "desktop.log");
|
|
69
|
+
export const DESKTOP_AUTOSTART_LOG_PATH = path.join(APX_HOME, "desktop-autostart.log");
|
|
70
|
+
|
|
71
|
+
/** Scratch space. Safe to delete between runs. */
|
|
72
|
+
export const TMP_DIR = path.join(APX_HOME, "tmp");
|
|
73
|
+
export const TTS_TMP_DIR = path.join(TMP_DIR, "tts");
|
|
74
|
+
export const RECORDINGS_TMP_DIR = path.join(TMP_DIR, "recordings");
|
|
75
|
+
|
|
76
|
+
/** Managed runtimes we install on the user's behalf. */
|
|
77
|
+
export const RUNTIME_DIR = path.join(APX_HOME, "runtime");
|
|
78
|
+
export const WHISPER_VENV_DIR = path.join(RUNTIME_DIR, "whisper-venv");
|
|
79
|
+
|
|
24
80
|
export function projectStorageRoot(apxId) {
|
|
25
81
|
return path.join(PROJECT_STORE_ROOT, apxId);
|
|
26
82
|
}
|
|
@@ -25,6 +25,11 @@ export const SECRET_PATHS = [
|
|
|
25
25
|
// Telegram bot tokens live inside an array — handled separately in redact()
|
|
26
26
|
// because dotted paths can't address array entries.
|
|
27
27
|
"telegram.channels.*.bot_token",
|
|
28
|
+
// Same problem: engines.gemini.api_keys is a LIST of spare keys (quota
|
|
29
|
+
// rotation). A dotted path cannot reach into it, so redact() handles it
|
|
30
|
+
// below. Listed here so the "which keys are secrets" question still has one
|
|
31
|
+
// answer to read.
|
|
32
|
+
"engines.gemini.api_keys.*",
|
|
28
33
|
];
|
|
29
34
|
|
|
30
35
|
/** Replace a secret string with the visible marker, preserving the last 5 chars. */
|
|
@@ -64,6 +69,12 @@ export function redactConfig(cfg) {
|
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
}
|
|
72
|
+
// Spare Gemini keys. Missing this would have served every one of them in
|
|
73
|
+
// clear text to anyone who opened Settings → Engines.
|
|
74
|
+
const spareKeys = out?.engines?.gemini?.api_keys;
|
|
75
|
+
if (Array.isArray(spareKeys)) {
|
|
76
|
+
out.engines.gemini.api_keys = spareKeys.map(mark);
|
|
77
|
+
}
|
|
67
78
|
return out;
|
|
68
79
|
}
|
|
69
80
|
|
|
@@ -108,6 +119,17 @@ export function mergeRedactedSecrets(next, prior) {
|
|
|
108
119
|
if (Array.isArray(nextChannels)) {
|
|
109
120
|
next.telegram.channels = mergeRedactedChannels(nextChannels, prior?.telegram?.channels);
|
|
110
121
|
}
|
|
122
|
+
// Spare Gemini keys. Without this, opening Settings → Engines and pressing
|
|
123
|
+
// Save would write the redaction MARKERS over the real keys and silently
|
|
124
|
+
// empty the rotation pool. Matched by position, which is the only identity
|
|
125
|
+
// an entry in this list has.
|
|
126
|
+
const nextSpare = next?.engines?.gemini?.api_keys;
|
|
127
|
+
if (Array.isArray(nextSpare)) {
|
|
128
|
+
const priorSpare = Array.isArray(prior?.engines?.gemini?.api_keys)
|
|
129
|
+
? prior.engines.gemini.api_keys : [];
|
|
130
|
+
next.engines.gemini.api_keys = nextSpare.map((v, i) =>
|
|
131
|
+
isSecretMarker(v) && typeof priorSpare[i] === "string" && priorSpare[i] ? priorSpare[i] : v);
|
|
132
|
+
}
|
|
111
133
|
return next;
|
|
112
134
|
}
|
|
113
135
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// dialog. The dialog is keyed by correlationId.
|
|
12
12
|
//
|
|
13
13
|
// 3. The user responds → frontend POSTs to
|
|
14
|
-
// POST /super-agent/confirm/:correlationId { confirmed: boolean }
|
|
14
|
+
// POST /api/super-agent/confirm/:correlationId { confirmed: boolean }
|
|
15
15
|
//
|
|
16
16
|
// 4. The API handler (api/confirm.js) calls pendingStore.resolve(correlationId,
|
|
17
17
|
// value). This finds the in-memory resolve callback and calls it, unblocking
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// Run the daemon as a real, self-healing service.
|
|
2
|
+
//
|
|
3
|
+
// Today the daemon starts transitively — some CLI command calls ensureDaemon()
|
|
4
|
+
// and one appears. If it dies at 3am nothing brings it back, so the routines
|
|
5
|
+
// that were meant to run at 8:30 do not, and nobody finds out until they
|
|
6
|
+
// wonder why the morning message never arrived. A scheduler you cannot rely on
|
|
7
|
+
// to be running is not a scheduler.
|
|
8
|
+
//
|
|
9
|
+
// Per-user, never sudo, fully reversible. Same shape as core/desktop/autostart.js,
|
|
10
|
+
// which this deliberately mirrors rather than reinvents:
|
|
11
|
+
//
|
|
12
|
+
// macOS → ~/Library/LaunchAgents/dev.apx.daemon.plist (KeepAlive true)
|
|
13
|
+
// linux → ~/.config/systemd/user/apx-daemon.service (Restart=always)
|
|
14
|
+
// win32 → HKCU\…\Run\APXDaemon (see the caveat below)
|
|
15
|
+
//
|
|
16
|
+
// OPT-IN, always. Installing a system service without being asked is the kind
|
|
17
|
+
// of thing that makes people uninstall software, and APX works perfectly well
|
|
18
|
+
// without it.
|
|
19
|
+
//
|
|
20
|
+
// Functions return { ok, … } and never throw or exit, so the CLI and the HTTP
|
|
21
|
+
// layer can both render the result.
|
|
22
|
+
import fs from "node:fs";
|
|
23
|
+
import os from "node:os";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
import { execFileSync } from "node:child_process";
|
|
26
|
+
import { fileURLToPath } from "node:url";
|
|
27
|
+
import { LOG_DIR } from "#core/config/paths.js";
|
|
28
|
+
import { augmentedPath } from "#core/util/path-env.js";
|
|
29
|
+
|
|
30
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
|
|
32
|
+
export const SERVICE_LABEL = "dev.apx.daemon";
|
|
33
|
+
export const MAC_PLIST_PATH = path.join(os.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
|
|
34
|
+
export const LINUX_UNIT_PATH = path.join(os.homedir(), ".config", "systemd", "user", "apx-daemon.service");
|
|
35
|
+
export const WIN_RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
36
|
+
export const WIN_RUN_NAME = "APXDaemon";
|
|
37
|
+
|
|
38
|
+
export const SERVICE_LOG_PATH = path.join(LOG_DIR, "daemon-service.log");
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* [bin, ...args] the supervisor should run.
|
|
42
|
+
*
|
|
43
|
+
* TWO things this must NOT be, both of which look right and are not:
|
|
44
|
+
*
|
|
45
|
+
* 1. The `apx` shim. npm/pnpm shims are shell scripts that `exec node`, and a
|
|
46
|
+
* launchd/systemd environment has a minimal PATH with no nvm and often no
|
|
47
|
+
* /usr/local/bin, so they fail at boot with "node: not found". That exact
|
|
48
|
+
* mistake already cost a day of silent voice failures.
|
|
49
|
+
*
|
|
50
|
+
* 2. `apx daemon start`. That command SPAWNS THE DAEMON DETACHED AND EXITS
|
|
51
|
+
* (cli/http.js autoStart). Under KeepAlive the supervisor would see its
|
|
52
|
+
* child exit within a second, restart it, and loop forever — spawning a new
|
|
53
|
+
* daemon each time and never noticing the one already running. A supervised
|
|
54
|
+
* process must be the daemon itself, in the foreground.
|
|
55
|
+
*/
|
|
56
|
+
export function getDaemonRunner() {
|
|
57
|
+
const entry = path.resolve(__dirname, "..", "..", "host", "daemon", "index.js");
|
|
58
|
+
return [process.execPath, entry];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function escapeXml(s) {
|
|
62
|
+
return String(s).replace(/[<>&'"]/g, (c) =>
|
|
63
|
+
({ "<": "<", ">": ">", "&": "&", "'": "'", '"': """ }[c]));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* launchd plist with KeepAlive TRUE — the whole point. The desktop's plist sets
|
|
68
|
+
* it false because a window the user closed should stay closed; a daemon that
|
|
69
|
+
* exits should come back.
|
|
70
|
+
*/
|
|
71
|
+
export function buildDaemonPlist(runner = getDaemonRunner(), logFile = SERVICE_LOG_PATH) {
|
|
72
|
+
const args = [...runner];
|
|
73
|
+
const argsXml = args.map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
|
|
74
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
75
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
76
|
+
<plist version="1.0">
|
|
77
|
+
<dict>
|
|
78
|
+
<key>Label</key><string>${SERVICE_LABEL}</string>
|
|
79
|
+
<key>ProgramArguments</key>
|
|
80
|
+
<array>
|
|
81
|
+
${argsXml}
|
|
82
|
+
</array>
|
|
83
|
+
<key>EnvironmentVariables</key>
|
|
84
|
+
<dict>
|
|
85
|
+
<key>PATH</key><string>${escapeXml(augmentedPath())}</string>
|
|
86
|
+
</dict>
|
|
87
|
+
<key>RunAtLoad</key><true/>
|
|
88
|
+
<key>KeepAlive</key><true/>
|
|
89
|
+
<key>ThrottleInterval</key><integer>10</integer>
|
|
90
|
+
<key>ProcessType</key><string>Background</string>
|
|
91
|
+
<key>StandardOutPath</key><string>${escapeXml(logFile)}</string>
|
|
92
|
+
<key>StandardErrorPath</key><string>${escapeXml(logFile)}</string>
|
|
93
|
+
</dict>
|
|
94
|
+
</plist>
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** systemd --user unit. `default.target`, not `multi-user.target`: this is a
|
|
99
|
+
* per-user service that should start with the user's session, not at boot. */
|
|
100
|
+
export function buildSystemdUnit(runner = getDaemonRunner()) {
|
|
101
|
+
const exec = [...runner]
|
|
102
|
+
.map((a) => (/\s/.test(a) ? `"${a}"` : a))
|
|
103
|
+
.join(" ");
|
|
104
|
+
return `[Unit]
|
|
105
|
+
Description=APX daemon
|
|
106
|
+
After=network-online.target
|
|
107
|
+
|
|
108
|
+
[Service]
|
|
109
|
+
Type=simple
|
|
110
|
+
ExecStart=${exec}
|
|
111
|
+
Environment=PATH=${augmentedPath()}
|
|
112
|
+
Restart=always
|
|
113
|
+
RestartSec=10
|
|
114
|
+
# Give up only if it is crash-looping, so a genuinely broken install does not
|
|
115
|
+
# spin forever writing to the log.
|
|
116
|
+
StartLimitBurst=5
|
|
117
|
+
StartLimitIntervalSec=120
|
|
118
|
+
|
|
119
|
+
[Install]
|
|
120
|
+
WantedBy=default.target
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** @returns {{installed: boolean, path?: string, platform: string, supervised: boolean, note?: string}} */
|
|
125
|
+
export function serviceStatus(platform = process.platform) {
|
|
126
|
+
if (platform === "darwin") {
|
|
127
|
+
return {
|
|
128
|
+
platform, supervised: true, installed: fs.existsSync(MAC_PLIST_PATH),
|
|
129
|
+
path: MAC_PLIST_PATH,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (platform === "linux") {
|
|
133
|
+
return {
|
|
134
|
+
platform, supervised: true, installed: fs.existsSync(LINUX_UNIT_PATH),
|
|
135
|
+
path: LINUX_UNIT_PATH,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (platform === "win32") {
|
|
139
|
+
let installed = false;
|
|
140
|
+
try {
|
|
141
|
+
const out = execFileSync("reg", ["query", WIN_RUN_KEY, "/v", WIN_RUN_NAME], {
|
|
142
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
143
|
+
}).toString();
|
|
144
|
+
installed = new RegExp(WIN_RUN_NAME).test(out);
|
|
145
|
+
} catch { /* not present */ }
|
|
146
|
+
return {
|
|
147
|
+
platform, installed, supervised: false,
|
|
148
|
+
path: `${WIN_RUN_KEY}\\${WIN_RUN_NAME}`,
|
|
149
|
+
// Said plainly rather than implied. A Run key starts the daemon at login
|
|
150
|
+
// and does NOT restart it if it dies — which is most of what a service is
|
|
151
|
+
// for. Promising self-healing here would be a lie the user only discovers
|
|
152
|
+
// the morning nothing ran.
|
|
153
|
+
note: "starts at login, but does NOT restart the daemon if it dies — " +
|
|
154
|
+
"Windows needs a real service wrapper (nssm/sc.exe) for that, which APX does not ship yet",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return { platform, installed: false, supervised: false, note: `not supported on ${platform}` };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function installService(platform = process.platform) {
|
|
161
|
+
const runner = getDaemonRunner();
|
|
162
|
+
|
|
163
|
+
if (platform === "darwin") {
|
|
164
|
+
try {
|
|
165
|
+
fs.mkdirSync(path.dirname(MAC_PLIST_PATH), { recursive: true });
|
|
166
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
167
|
+
fs.writeFileSync(MAC_PLIST_PATH, buildDaemonPlist(runner), "utf8");
|
|
168
|
+
try { execFileSync("launchctl", ["unload", MAC_PLIST_PATH], { stdio: "ignore" }); } catch { /* not loaded */ }
|
|
169
|
+
execFileSync("launchctl", ["load", "-w", MAC_PLIST_PATH], { stdio: "ignore" });
|
|
170
|
+
return { ok: true, path: MAC_PLIST_PATH, supervised: true, log: SERVICE_LOG_PATH };
|
|
171
|
+
} catch (e) { return { ok: false, error: e.message }; }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (platform === "linux") {
|
|
175
|
+
try {
|
|
176
|
+
fs.mkdirSync(path.dirname(LINUX_UNIT_PATH), { recursive: true });
|
|
177
|
+
fs.writeFileSync(LINUX_UNIT_PATH, buildSystemdUnit(runner), "utf8");
|
|
178
|
+
execFileSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
|
|
179
|
+
execFileSync("systemctl", ["--user", "enable", "--now", "apx-daemon.service"], { stdio: "ignore" });
|
|
180
|
+
return {
|
|
181
|
+
ok: true, path: LINUX_UNIT_PATH, supervised: true,
|
|
182
|
+
// Without lingering, systemd --user stops when the last session ends,
|
|
183
|
+
// so a headless box would silently lose the daemon on logout.
|
|
184
|
+
note: "for a machine you are not logged into, run: loginctl enable-linger $USER",
|
|
185
|
+
};
|
|
186
|
+
} catch (e) { return { ok: false, error: e.message }; }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (platform === "win32") {
|
|
190
|
+
const cmdline = [...runner]
|
|
191
|
+
.map((s) => `"${String(s).replace(/"/g, '\\"')}"`).join(" ");
|
|
192
|
+
try {
|
|
193
|
+
execFileSync("reg", ["add", WIN_RUN_KEY, "/v", WIN_RUN_NAME, "/t", "REG_SZ", "/d", cmdline, "/f"],
|
|
194
|
+
{ stdio: "ignore" });
|
|
195
|
+
return {
|
|
196
|
+
ok: true, path: `${WIN_RUN_KEY}\\${WIN_RUN_NAME}`, supervised: false,
|
|
197
|
+
note: serviceStatus("win32").note,
|
|
198
|
+
};
|
|
199
|
+
} catch (e) { return { ok: false, error: e.message }; }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { ok: false, error: `service installation not supported on platform: ${platform}` };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Idempotent: uninstalling something that was never installed is not an error. */
|
|
206
|
+
export function uninstallService(platform = process.platform) {
|
|
207
|
+
if (platform === "darwin") {
|
|
208
|
+
if (!fs.existsSync(MAC_PLIST_PATH)) return { ok: true, removed: false };
|
|
209
|
+
try {
|
|
210
|
+
try { execFileSync("launchctl", ["unload", "-w", MAC_PLIST_PATH], { stdio: "ignore" }); } catch { /* not loaded */ }
|
|
211
|
+
fs.unlinkSync(MAC_PLIST_PATH);
|
|
212
|
+
return { ok: true, removed: true, path: MAC_PLIST_PATH };
|
|
213
|
+
} catch (e) { return { ok: false, error: e.message }; }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (platform === "linux") {
|
|
217
|
+
if (!fs.existsSync(LINUX_UNIT_PATH)) return { ok: true, removed: false };
|
|
218
|
+
try {
|
|
219
|
+
try {
|
|
220
|
+
execFileSync("systemctl", ["--user", "disable", "--now", "apx-daemon.service"], { stdio: "ignore" });
|
|
221
|
+
} catch { /* already stopped */ }
|
|
222
|
+
fs.unlinkSync(LINUX_UNIT_PATH);
|
|
223
|
+
try { execFileSync("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" }); } catch { /* best effort */ }
|
|
224
|
+
return { ok: true, removed: true, path: LINUX_UNIT_PATH };
|
|
225
|
+
} catch (e) { return { ok: false, error: e.message }; }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (platform === "win32") {
|
|
229
|
+
try {
|
|
230
|
+
execFileSync("reg", ["delete", WIN_RUN_KEY, "/v", WIN_RUN_NAME, "/f"], { stdio: "ignore" });
|
|
231
|
+
return { ok: true, removed: true, path: `${WIN_RUN_KEY}\\${WIN_RUN_NAME}` };
|
|
232
|
+
} catch {
|
|
233
|
+
return { ok: true, removed: false };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return { ok: false, error: `service removal not supported on platform: ${platform}` };
|
|
238
|
+
}
|