@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
|
@@ -28,6 +28,8 @@ import loadSkill from "./handlers/load-skill.js";
|
|
|
28
28
|
import transcribeAudio from "./handlers/transcribe-audio.js";
|
|
29
29
|
import askQuestions from "./handlers/ask-questions.js";
|
|
30
30
|
import createTask from "./handlers/create-task.js";
|
|
31
|
+
import recordCommitment from "./handlers/record-commitment.js";
|
|
32
|
+
import listCommitments from "./handlers/list-commitments.js";
|
|
31
33
|
import listTasks from "./handlers/list-tasks.js";
|
|
32
34
|
import discoverTools from "./handlers/discover-tools.js";
|
|
33
35
|
import gitStatus from "./handlers/git-status.js";
|
|
@@ -81,6 +83,8 @@ const NATIVE_TOOLS = [
|
|
|
81
83
|
askQuestions,
|
|
82
84
|
createTask,
|
|
83
85
|
listTasks,
|
|
86
|
+
recordCommitment,
|
|
87
|
+
listCommitments,
|
|
84
88
|
discoverTools,
|
|
85
89
|
gitStatus,
|
|
86
90
|
gitDiff,
|
|
@@ -151,6 +155,11 @@ export const BASE_TOOL_NAMES = new Set([
|
|
|
151
155
|
// Tasks (very common ask via chat).
|
|
152
156
|
TOOLS.CREATE_TASK,
|
|
153
157
|
TOOLS.LIST_TASKS,
|
|
158
|
+
// Commitments. In the base set on purpose: a promise is caught in passing
|
|
159
|
+
// ("le dije a Ana que el viernes"), and a tool the model has to discover
|
|
160
|
+
// first is a tool it will not reach for mid-sentence.
|
|
161
|
+
TOOLS.RECORD_COMMITMENT,
|
|
162
|
+
TOOLS.LIST_COMMITMENTS,
|
|
154
163
|
// Files + basic shell — frequent enough on chat to keep hot.
|
|
155
164
|
TOOLS.READ_FILE,
|
|
156
165
|
TOOLS.WRITE_FILE,
|
|
@@ -162,7 +162,70 @@ function extractLlamaDottedFunctionCalls(text) {
|
|
|
162
162
|
// trivial wrappers (<tool_call>, ```tool_use, _icall(), etc.) that often sit
|
|
163
163
|
// around them. Used to clean up final answers that the model emitted with
|
|
164
164
|
// leftover textual tool-call gunk.
|
|
165
|
-
|
|
165
|
+
/**
|
|
166
|
+
* The bare `tool_name({...json...})` form.
|
|
167
|
+
*
|
|
168
|
+
* WHERE THIS COMES FROM, and why it is not hypothetical: APX renders past tool
|
|
169
|
+
* results into model context as `[tool result: <name>] <body>`
|
|
170
|
+
* (stores/messages.js). A weaker model reads that pattern in its own history
|
|
171
|
+
* and imitates it in PROSE — gemini-3.5-flash produced
|
|
172
|
+
*
|
|
173
|
+
* [tool result: create_task] create_task({"project":"apx","title":"…"})
|
|
174
|
+
*
|
|
175
|
+
* and then told the user the task was filed. It was not. That is the worst
|
|
176
|
+
* failure mode available: a confident false confirmation, with nothing on disk.
|
|
177
|
+
*
|
|
178
|
+
* GATED ON KNOWN TOOL NAMES, deliberately. The other two passes key off
|
|
179
|
+
* unambiguous markers (`<function.` or a `{name, arguments}` pair); a bare
|
|
180
|
+
* `foo({...})` is ordinary prose about code. Without the allow-list, a model
|
|
181
|
+
* EXPLAINING `create_task({...})` in an answer would silently create a task.
|
|
182
|
+
* So: no known names, no matches.
|
|
183
|
+
*
|
|
184
|
+
* @param {string} text
|
|
185
|
+
* @param {Iterable<string>} knownNames tool names callable on this turn
|
|
186
|
+
*/
|
|
187
|
+
export function extractBareFunctionCalls(text, knownNames) {
|
|
188
|
+
const allowed = new Set(knownNames || []);
|
|
189
|
+
if (!text || typeof text !== "string" || allowed.size === 0) return [];
|
|
190
|
+
|
|
191
|
+
const out = [];
|
|
192
|
+
// `name(` where name is a plausible identifier. The `[tool result: x] `
|
|
193
|
+
// prefix the model copies is left for the cleaner to strip.
|
|
194
|
+
const re = /(^|[^A-Za-z0-9_.])([a-z][a-z0-9_]{2,63})\s*\(\s*(?=\{)/g;
|
|
195
|
+
let m;
|
|
196
|
+
while ((m = re.exec(text)) !== null) {
|
|
197
|
+
const name = m[2];
|
|
198
|
+
if (!allowed.has(name)) continue;
|
|
199
|
+
// The '{' the regex already looked ahead to.
|
|
200
|
+
const i = text.indexOf("{", m.index + m[0].length - 1);
|
|
201
|
+
if (i < 0) continue;
|
|
202
|
+
const balanced = readBalancedJson(text, i);
|
|
203
|
+
if (!balanced.ok) continue;
|
|
204
|
+
let args;
|
|
205
|
+
try {
|
|
206
|
+
args = JSON.parse(text.slice(i, balanced.end));
|
|
207
|
+
} catch {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) continue;
|
|
211
|
+
|
|
212
|
+
// Include the closing paren in the raw span when it is there, so the
|
|
213
|
+
// cleaner removes the whole call rather than leaving a dangling `)`.
|
|
214
|
+
let end = balanced.end;
|
|
215
|
+
const after = text.slice(end).match(/^\s*\)/);
|
|
216
|
+
if (after) end += after[0].length;
|
|
217
|
+
|
|
218
|
+
const rawStart = m.index + (m[1] ? m[1].length : 0);
|
|
219
|
+
out.push({
|
|
220
|
+
id: nextId(),
|
|
221
|
+
type: "function",
|
|
222
|
+
function: { name, arguments: JSON.stringify(args) },
|
|
223
|
+
_raw: text.slice(rawStart, end),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
228
|
+
export function cleanTextOfPseudoToolCalls(text, knownNames) {
|
|
166
229
|
if (!text || typeof text !== "string") return text;
|
|
167
230
|
|
|
168
231
|
// Strip explicit XML-like fences first
|
|
@@ -179,6 +242,12 @@ export function cleanTextOfPseudoToolCalls(text) {
|
|
|
179
242
|
for (const call of extractPseudoToolCalls(out)) {
|
|
180
243
|
if (call._raw) out = out.replace(call._raw, "");
|
|
181
244
|
}
|
|
245
|
+
// The bare `name({...})` form, and the `[tool result: name]` prefix the
|
|
246
|
+
// model copies out of its own transcript.
|
|
247
|
+
for (const call of extractBareFunctionCalls(out, knownNames)) {
|
|
248
|
+
if (call._raw) out = out.replace(call._raw, "");
|
|
249
|
+
}
|
|
250
|
+
out = out.replace(/\[tool result:\s*[^\]]+\]\s*/gi, "");
|
|
182
251
|
// Some models emit a stray `</function>` after the args without the
|
|
183
252
|
// opening tag — sweep those too.
|
|
184
253
|
out = out.replace(/<\/?function(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?>/gi, "");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Snapshot a project's read-aloud context — AGENTS.md + .apc/memory.md —
|
|
2
|
-
// into a single labelled markdown string. Used by /deck/context/copy and
|
|
2
|
+
// into a single labelled markdown string. Used by /api/deck/context/copy and
|
|
3
3
|
// (potentially) any other "give me the context" surface.
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import { AGENTS_MD, apcMemoryFile } from "./paths.js";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// YAML-ish frontmatter — one parser.
|
|
2
|
+
//
|
|
3
|
+
// Four copies existed, and they did not agree on which keys are legal:
|
|
4
|
+
//
|
|
5
|
+
// apc/parser.js /^([a-zA-Z_-]+):/ dashes, no digits
|
|
6
|
+
// agent/skills/loader /^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:/ digits and dashes, strips quotes
|
|
7
|
+
// core/sessions /^([a-zA-Z_]+):/ neither
|
|
8
|
+
// cli/commands/session /^([a-zA-Z_]+):/ neither
|
|
9
|
+
//
|
|
10
|
+
// That is not cosmetic duplication: a key like `route_to_agent2` or
|
|
11
|
+
// `agent-slug` parsed in some code paths and vanished in others, so the same
|
|
12
|
+
// file read by the CLI and by the daemon yielded different data — with no
|
|
13
|
+
// error anywhere, because a non-matching line is simply skipped.
|
|
14
|
+
//
|
|
15
|
+
// This takes the most permissive correct behaviour (identifiers may contain
|
|
16
|
+
// digits and dashes, quoted values are unquoted) and returns everything the
|
|
17
|
+
// callers between them needed: the fields, the body, and where the body starts.
|
|
18
|
+
//
|
|
19
|
+
// Deliberately not a YAML library: the frontmatter APX writes is flat
|
|
20
|
+
// `key: value` and staying strict about that keeps the format predictable for
|
|
21
|
+
// the models that also read these files.
|
|
22
|
+
|
|
23
|
+
const KEY_VALUE = /^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:\s*(.*)$/;
|
|
24
|
+
|
|
25
|
+
function unquote(value) {
|
|
26
|
+
const v = value.trim();
|
|
27
|
+
if (v.length >= 2 && ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'")))) {
|
|
28
|
+
return v.slice(1, -1);
|
|
29
|
+
}
|
|
30
|
+
return v;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse leading `---` frontmatter.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} text
|
|
37
|
+
* @returns {{ fm: Record<string,string>, body: string, bodyStart: number }}
|
|
38
|
+
* `fm` is `{}` when there is no frontmatter; `body` is then the whole text
|
|
39
|
+
* and `bodyStart` is 0.
|
|
40
|
+
*/
|
|
41
|
+
export function parseFrontmatter(text) {
|
|
42
|
+
const raw = String(text ?? "");
|
|
43
|
+
// Accept both "---\n" and a bare leading "---" (the skills loader allowed
|
|
44
|
+
// the latter; session files always write the former).
|
|
45
|
+
if (!raw.startsWith("---")) return { fm: {}, body: raw, bodyStart: 0 };
|
|
46
|
+
|
|
47
|
+
const openLen = raw.startsWith("---\n") ? 4 : 3;
|
|
48
|
+
const end = raw.indexOf("\n---", openLen - 1);
|
|
49
|
+
if (end === -1) return { fm: {}, body: raw, bodyStart: 0 };
|
|
50
|
+
|
|
51
|
+
const fm = {};
|
|
52
|
+
for (const line of raw.slice(openLen, end).split("\n")) {
|
|
53
|
+
const m = line.match(KEY_VALUE);
|
|
54
|
+
if (m) fm[m[1]] = unquote(m[2]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The closing fence is followed by a newline, and authors conventionally
|
|
58
|
+
// leave a blank line after it — consume both so `body` starts at content.
|
|
59
|
+
const bodyStart = end + 4;
|
|
60
|
+
return { fm, body: raw.slice(bodyStart).replace(/^\n+/, ""), bodyStart };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Just the fields, for callers that don't need the body. */
|
|
64
|
+
export function parseFrontmatterFields(text) {
|
|
65
|
+
return parseFrontmatter(text).fm;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Set or replace one field in place, preserving the rest of the file byte for
|
|
70
|
+
* byte. Returns the text unchanged when there is no frontmatter to edit.
|
|
71
|
+
*/
|
|
72
|
+
export function setFrontmatterField(text, field, value) {
|
|
73
|
+
const raw = String(text ?? "");
|
|
74
|
+
if (!raw.startsWith("---\n")) return raw;
|
|
75
|
+
const end = raw.indexOf("\n---", 4);
|
|
76
|
+
if (end === -1) return raw;
|
|
77
|
+
|
|
78
|
+
const lines = raw.slice(4, end).split("\n");
|
|
79
|
+
const idx = lines.findIndex((l) => l.match(KEY_VALUE)?.[1] === field);
|
|
80
|
+
const entry = `${field}: ${value}`;
|
|
81
|
+
if (idx >= 0) lines[idx] = entry;
|
|
82
|
+
else lines.push(entry);
|
|
83
|
+
|
|
84
|
+
return `---\n${lines.join("\n")}${raw.slice(end)}`;
|
|
85
|
+
}
|
package/src/core/apc/parser.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Core parsers for APC — pure ESM, no deps.
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { AGENT_VAULT_DIR } from "../config/paths.js";
|
|
4
5
|
import {
|
|
5
6
|
apcAgentsDir,
|
|
6
7
|
apcAgentFile,
|
|
@@ -124,12 +125,13 @@ export function readAgentsFromDir(root) {
|
|
|
124
125
|
// if it's a bundled slug, deletes the user file otherwise.
|
|
125
126
|
// ---------------------------------------------------------------------------
|
|
126
127
|
|
|
127
|
-
import os from "node:os";
|
|
128
128
|
import { fileURLToPath } from "node:url";
|
|
129
|
+
import { parseFrontmatterFields } from "./frontmatter.js";
|
|
130
|
+
import { readJson } from "#core/util/json-file.js";
|
|
129
131
|
|
|
130
132
|
const __parserDir = path.dirname(fileURLToPath(import.meta.url));
|
|
131
133
|
|
|
132
|
-
export const VAULT_DIR =
|
|
134
|
+
export const VAULT_DIR = AGENT_VAULT_DIR;
|
|
133
135
|
export const BUNDLED_VAULT_DIR = path.resolve(__parserDir, "../../../assets/agent-vault-defaults");
|
|
134
136
|
export const VAULT_TOMBSTONE_PATH = path.join(VAULT_DIR, ".removed.json");
|
|
135
137
|
|
|
@@ -144,10 +146,8 @@ function readVaultDirRaw(dir) {
|
|
|
144
146
|
|
|
145
147
|
export function readVaultTombstones() {
|
|
146
148
|
if (!fs.existsSync(VAULT_TOMBSTONE_PATH)) return new Set();
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return new Set(Array.isArray(raw.slugs) ? raw.slugs : []);
|
|
150
|
-
} catch { return new Set(); }
|
|
149
|
+
const raw = readJson(VAULT_TOMBSTONE_PATH, {});
|
|
150
|
+
return new Set(Array.isArray(raw?.slugs) ? raw.slugs : []);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
export function writeVaultTombstones(slugs) {
|
|
@@ -212,10 +212,7 @@ export { readVaultAgent };
|
|
|
212
212
|
export function importedVaultSlugs(root) {
|
|
213
213
|
const p = apcProjectFile(root);
|
|
214
214
|
if (!fs.existsSync(p)) return [];
|
|
215
|
-
|
|
216
|
-
const cfg = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
217
|
-
return cfg.agents?.imported ?? [];
|
|
218
|
-
} catch { return []; }
|
|
215
|
+
return readJson(p, {})?.agents?.imported ?? [];
|
|
219
216
|
}
|
|
220
217
|
|
|
221
218
|
// Primary entry point.
|
|
@@ -273,14 +270,5 @@ export function findApfRoot(start = process.cwd()) {
|
|
|
273
270
|
// Session / conversation frontmatter
|
|
274
271
|
// ---------------------------------------------------------------------------
|
|
275
272
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const end = text.indexOf("\n---", 4);
|
|
279
|
-
if (end === -1) return {};
|
|
280
|
-
const out = {};
|
|
281
|
-
for (const line of text.slice(4, end).split("\n")) {
|
|
282
|
-
const m = line.match(/^([a-zA-Z_-]+):\s*(.*)$/);
|
|
283
|
-
if (m) out[m[1]] = m[2].trim();
|
|
284
|
-
}
|
|
285
|
-
return out;
|
|
286
|
-
}
|
|
273
|
+
// Kept as the public name; the implementation is the shared parser.
|
|
274
|
+
export const parseSessionFrontmatter = parseFrontmatterFields;
|
package/src/core/apc/paths.js
CHANGED
|
@@ -9,16 +9,16 @@ import fs from "node:fs";
|
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
|
|
11
11
|
// Raw names — exported for the rare caller that needs to glob/match by name.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
const APC_DIR = ".apc";
|
|
13
|
+
const APC_PROJECT_FILE = "project.json";
|
|
14
|
+
const APC_PROJECT_CONFIG_FILE = "config.json";
|
|
15
|
+
const APC_PROJECT_MEMORY_FILE = "memory.md";
|
|
16
|
+
const APC_AGENTS_DIR = "agents";
|
|
17
|
+
const APC_SKILLS_DIR = "skills";
|
|
18
18
|
export const APC_COMMANDS_DIR = "commands";
|
|
19
19
|
export const APC_NOTES_DIR = "notes";
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const APC_MCPS_FILE = "mcps.json";
|
|
21
|
+
const APC_ORGANIZATION_FILE = "organization.json";
|
|
22
22
|
export const APC_REMOVED_FILE = ".removed.json";
|
|
23
23
|
export const AGENTS_MD = "AGENTS.md";
|
|
24
24
|
|
package/src/core/apc/scaffold.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
apcAgentFile,
|
|
19
19
|
agentsMdFile,
|
|
20
20
|
} from "./paths.js";
|
|
21
|
+
import { readJson } from "#core/util/json-file.js";
|
|
21
22
|
|
|
22
23
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
24
|
// Now under src/core/apc/ — one more "../" to escape than before.
|
|
@@ -431,8 +432,8 @@ function writeMigrateMd(apfDir, found) {
|
|
|
431
432
|
export function getOrCreateApxId(root) {
|
|
432
433
|
const p = apcProjectFile(root);
|
|
433
434
|
if (!fs.existsSync(p)) return null;
|
|
434
|
-
|
|
435
|
-
|
|
435
|
+
const cfg = readJson(p, null);
|
|
436
|
+
if (!cfg) return null;
|
|
436
437
|
if (cfg.apx_id) return cfg.apx_id;
|
|
437
438
|
const apxId = crypto.randomUUID().replace(/-/g, "").slice(0, 12);
|
|
438
439
|
console.log(`[apx] Generating new stable ID ${apxId} for project at ${root}`);
|
|
@@ -490,7 +491,7 @@ export function initApf(directory, { name } = {}) {
|
|
|
490
491
|
return { root, agentsMd, projectJson, pendingMigration: scattered };
|
|
491
492
|
}
|
|
492
493
|
|
|
493
|
-
export function ensureAgentDir(root,
|
|
494
|
+
export function ensureAgentDir(root, _slug) {
|
|
494
495
|
fs.mkdirSync(apcAgentsDir(root), { recursive: true });
|
|
495
496
|
return apcAgentsDir(root);
|
|
496
497
|
}
|
|
@@ -587,7 +588,7 @@ export function restoreVaultAgent(slug) {
|
|
|
587
588
|
export function addImportedAgent(root, slug) {
|
|
588
589
|
const p = apcProjectFile(root);
|
|
589
590
|
let cfg = {};
|
|
590
|
-
|
|
591
|
+
cfg = readJson(p, {});
|
|
591
592
|
if (!cfg.agents) cfg.agents = {};
|
|
592
593
|
if (!cfg.agents.imported) cfg.agents.imported = [];
|
|
593
594
|
if (!cfg.agents.imported.includes(slug)) cfg.agents.imported.push(slug);
|
|
@@ -12,6 +12,7 @@ import { getConfirmationStore as getConfirmStore } from "#core/confirmation/pend
|
|
|
12
12
|
import { getRecentTelegramTurnsFromFs, appendGlobalMessage } from "#core/stores/messages.js";
|
|
13
13
|
import { CHANNELS } from "#core/constants/channels.js";
|
|
14
14
|
import { SUPERAGENT_ACTOR_ID } from "#core/identity/index.js";
|
|
15
|
+
import { applyNudgeCallback } from "#core/nudge/index.js";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Route an inbound callback_query. ask_questions button presses are handled
|
|
@@ -24,6 +25,10 @@ export async function handleCallbackQuery(self, callbackQuery) {
|
|
|
24
25
|
await handleAskCallback(self, callbackQuery);
|
|
25
26
|
return;
|
|
26
27
|
}
|
|
28
|
+
if (data.startsWith("apx:nudge:")) {
|
|
29
|
+
await handleNudgeCallback(self, callbackQuery);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
27
32
|
const adapter = createTelegramConfirmAdapter({
|
|
28
33
|
token: resolveBotToken(self.channel),
|
|
29
34
|
chatId: callbackQuery.message?.chat?.id,
|
|
@@ -35,6 +40,33 @@ export async function handleCallbackQuery(self, callbackQuery) {
|
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
42
|
|
|
43
|
+
/**
|
|
44
|
+
* "Was that worth interrupting you for?" — the feedback loop on proactive
|
|
45
|
+
* pushes (core/nudge). One tap, no reply, and the keyboard disappears so the
|
|
46
|
+
* chat does not accumulate stale buttons. Never re-enters the super-agent: an
|
|
47
|
+
* opinion about a message is not a new turn to answer.
|
|
48
|
+
*/
|
|
49
|
+
export async function handleNudgeCallback(self, callbackQuery) {
|
|
50
|
+
const chatId = callbackQuery.message?.chat?.id;
|
|
51
|
+
const result = applyNudgeCallback(callbackQuery.data || "");
|
|
52
|
+
await self._answerCallback({
|
|
53
|
+
callback_query_id: callbackQuery.id,
|
|
54
|
+
text: result?.ack || "",
|
|
55
|
+
});
|
|
56
|
+
if (!result || !chatId) return;
|
|
57
|
+
try {
|
|
58
|
+
await self._editKeyboard({
|
|
59
|
+
chat_id: chatId,
|
|
60
|
+
message_id: callbackQuery.message?.message_id,
|
|
61
|
+
reply_markup: { inline_keyboard: [] },
|
|
62
|
+
});
|
|
63
|
+
} catch { /* best-effort */ }
|
|
64
|
+
self.log(
|
|
65
|
+
`telegram[${self.channel.name}] nudge feedback: ${result.entry?.kind || "?"} → ` +
|
|
66
|
+
`${result.entry?.feedback?.useful ? "useful" : "noise"}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
38
70
|
/**
|
|
39
71
|
* Draw the current question as a fresh message with its inline keyboard, wiping
|
|
40
72
|
* the previous question's keyboard so the chat reads as a clean history.
|
|
@@ -182,6 +214,7 @@ export async function runResumedTurn(self, ctx) {
|
|
|
182
214
|
let replyAuthor;
|
|
183
215
|
let saUsage = null;
|
|
184
216
|
let saModel = null;
|
|
217
|
+
let saTrace = null;
|
|
185
218
|
try {
|
|
186
219
|
const sa = await runTelegramSuperAgent(self, {
|
|
187
220
|
chat_id,
|
|
@@ -218,6 +251,7 @@ export async function runResumedTurn(self, ctx) {
|
|
|
218
251
|
replyAuthor = sa.name || agentDisplay;
|
|
219
252
|
saUsage = sa.usage;
|
|
220
253
|
saModel = sa.model || state.model || null;
|
|
254
|
+
saTrace = sa.trace || null;
|
|
221
255
|
} catch (e) {
|
|
222
256
|
self.log(`telegram[${self.channel.name}] ask resume failed: ${e.message}`);
|
|
223
257
|
replyText = telegramErrorText(self, e);
|
|
@@ -235,6 +269,7 @@ export async function runResumedTurn(self, ctx) {
|
|
|
235
269
|
replyKind: "superagent",
|
|
236
270
|
saUsage,
|
|
237
271
|
saModel,
|
|
272
|
+
saTrace,
|
|
238
273
|
streamedCount: state.streamedCount,
|
|
239
274
|
lastStreamedText: state.lastStreamedText,
|
|
240
275
|
agentDisplay,
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// Telegram identity: channels, contacts and roles.
|
|
2
|
+
//
|
|
3
|
+
// This is channel domain, not configuration plumbing — it happened to live in
|
|
4
|
+
// core/config/index.js because the data is stored in the global config file.
|
|
5
|
+
// That made the config module 711 lines, half of them Telegram, and forced
|
|
6
|
+
// host/daemon/api/telegram.js to import 13 symbols from "the config module".
|
|
7
|
+
//
|
|
8
|
+
// The shapes:
|
|
9
|
+
// cfg.telegram.channels[] — { name, bot_token, chat_id, route_to_agent,
|
|
10
|
+
// project, respond_with_engine, poll_interval_ms, owner_user_id }
|
|
11
|
+
// cfg.telegram.contacts[] — the global roster, keyed by Telegram user_id.
|
|
12
|
+
// Identity is per-person and the same across every chat; a channel's
|
|
13
|
+
// owner_user_id only marks who owns *that* channel.
|
|
14
|
+
// cfg.telegram.roles — role -> capabilities. "owner" is implicit (full),
|
|
15
|
+
// "guest" is the default for unknown senders (no tools).
|
|
16
|
+
//
|
|
17
|
+
// Re-exported from core/config/index.js so existing import sites keep working.
|
|
18
|
+
import { writeConfig } from "../../config/index.js";
|
|
19
|
+
|
|
20
|
+
// ── Telegram channels (multi-channel mode) ──────────────────────────────────
|
|
21
|
+
// Each entry in cfg.telegram.channels[] is { name, bot_token, chat_id,
|
|
22
|
+
// route_to_agent, project, respond_with_engine, poll_interval_ms }.
|
|
23
|
+
// These helpers keep the array shape stable for the CLI and the daemon API.
|
|
24
|
+
|
|
25
|
+
const CHANNEL_FIELDS = [
|
|
26
|
+
"name",
|
|
27
|
+
"bot_token",
|
|
28
|
+
"chat_id",
|
|
29
|
+
"route_to_agent",
|
|
30
|
+
"project",
|
|
31
|
+
"respond_with_engine",
|
|
32
|
+
"poll_interval_ms",
|
|
33
|
+
"owner_user_id",
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
function ensureChannelsArray(cfg) {
|
|
37
|
+
cfg.telegram = cfg.telegram || {};
|
|
38
|
+
if (!Array.isArray(cfg.telegram.channels)) cfg.telegram.channels = [];
|
|
39
|
+
return cfg.telegram.channels;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function listTelegramChannels(cfg) {
|
|
43
|
+
return ensureChannelsArray(cfg).slice();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function findTelegramChannel(cfg, name) {
|
|
47
|
+
return ensureChannelsArray(cfg).find((c) => c.name === name) || null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Create-or-patch a channel by name. `patch` is a partial channel object;
|
|
51
|
+
// unknown keys are dropped. Returns { created, channel }.
|
|
52
|
+
export function upsertTelegramChannel(cfg, name, patch = {}) {
|
|
53
|
+
if (!name || typeof name !== "string")
|
|
54
|
+
throw new Error("upsertTelegramChannel: name required");
|
|
55
|
+
const channels = ensureChannelsArray(cfg);
|
|
56
|
+
let entry = channels.find((c) => c.name === name);
|
|
57
|
+
const created = !entry;
|
|
58
|
+
if (!entry) {
|
|
59
|
+
entry = { name };
|
|
60
|
+
channels.push(entry);
|
|
61
|
+
}
|
|
62
|
+
for (const k of CHANNEL_FIELDS) {
|
|
63
|
+
if (k === "name") continue;
|
|
64
|
+
if (patch[k] !== undefined) entry[k] = patch[k];
|
|
65
|
+
}
|
|
66
|
+
// Default respond_with_engine to true on create.
|
|
67
|
+
if (created && entry.respond_with_engine === undefined) {
|
|
68
|
+
entry.respond_with_engine = true;
|
|
69
|
+
}
|
|
70
|
+
writeConfig(cfg);
|
|
71
|
+
return { created, channel: entry };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function removeTelegramChannel(cfg, name) {
|
|
75
|
+
const channels = ensureChannelsArray(cfg);
|
|
76
|
+
const before = channels.length;
|
|
77
|
+
cfg.telegram.channels = channels.filter((c) => c.name !== name);
|
|
78
|
+
const removed = before - cfg.telegram.channels.length;
|
|
79
|
+
if (removed > 0) {
|
|
80
|
+
// Explicit user-initiated removal — bypass the credential-loss guard
|
|
81
|
+
// so the write goes through even when this empties the array.
|
|
82
|
+
cfg._allowClear = true;
|
|
83
|
+
writeConfig(cfg);
|
|
84
|
+
}
|
|
85
|
+
return { removed };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Clear specific optional fields on a channel (project, route_to_agent, …).
|
|
89
|
+
// Returns { channel } or null when no such channel.
|
|
90
|
+
export function unsetTelegramChannelFields(cfg, name, fields = []) {
|
|
91
|
+
const ch = findTelegramChannel(cfg, name);
|
|
92
|
+
if (!ch) return null;
|
|
93
|
+
let mutated = false;
|
|
94
|
+
for (const f of fields) {
|
|
95
|
+
if (!CHANNEL_FIELDS.includes(f) || f === "name") continue;
|
|
96
|
+
if (f in ch) {
|
|
97
|
+
delete ch[f];
|
|
98
|
+
mutated = true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (mutated) writeConfig(cfg);
|
|
102
|
+
return { channel: ch };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Telegram contacts (global roster, keyed by user_id) ─────────────────────
|
|
106
|
+
// Identity of a person is global; the per-channel owner_user_id only marks who
|
|
107
|
+
// owns a given channel. Role lives on the contact (global), per the chosen
|
|
108
|
+
// design — owner_user_id overrides it to "owner" for that channel only.
|
|
109
|
+
|
|
110
|
+
const CONTACT_FIELDS = [
|
|
111
|
+
"user_id",
|
|
112
|
+
"name",
|
|
113
|
+
"username",
|
|
114
|
+
"role",
|
|
115
|
+
"note",
|
|
116
|
+
"first_seen",
|
|
117
|
+
"last_seen",
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
function ensureContactsArray(cfg) {
|
|
121
|
+
cfg.telegram = cfg.telegram || {};
|
|
122
|
+
if (!Array.isArray(cfg.telegram.contacts)) cfg.telegram.contacts = [];
|
|
123
|
+
return cfg.telegram.contacts;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function listContacts(cfg) {
|
|
127
|
+
return ensureContactsArray(cfg).slice();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function findContact(cfg, userId) {
|
|
131
|
+
if (userId == null) return null;
|
|
132
|
+
return (
|
|
133
|
+
ensureContactsArray(cfg).find((c) => String(c.user_id) === String(userId)) ||
|
|
134
|
+
null
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Create-or-patch a contact by user_id. Unknown keys are dropped.
|
|
139
|
+
export function upsertContact(cfg, userId, patch = {}, { persist = true } = {}) {
|
|
140
|
+
if (userId == null) throw new Error("upsertContact: user_id required");
|
|
141
|
+
const contacts = ensureContactsArray(cfg);
|
|
142
|
+
let entry = contacts.find((c) => String(c.user_id) === String(userId));
|
|
143
|
+
const created = !entry;
|
|
144
|
+
if (!entry) {
|
|
145
|
+
entry = { user_id: userId };
|
|
146
|
+
contacts.push(entry);
|
|
147
|
+
}
|
|
148
|
+
for (const k of CONTACT_FIELDS) {
|
|
149
|
+
if (k === "user_id") continue;
|
|
150
|
+
if (patch[k] !== undefined) entry[k] = patch[k];
|
|
151
|
+
}
|
|
152
|
+
if (persist) writeConfig(cfg);
|
|
153
|
+
return { created, contact: entry };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function setContactRole(cfg, userId, role) {
|
|
157
|
+
const { contact } = upsertContact(cfg, userId, { role });
|
|
158
|
+
return contact;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function removeContact(cfg, userId) {
|
|
162
|
+
const contacts = ensureContactsArray(cfg);
|
|
163
|
+
const before = contacts.length;
|
|
164
|
+
cfg.telegram.contacts = contacts.filter(
|
|
165
|
+
(c) => String(c.user_id) !== String(userId)
|
|
166
|
+
);
|
|
167
|
+
const removed = before - cfg.telegram.contacts.length;
|
|
168
|
+
if (removed > 0) writeConfig(cfg);
|
|
169
|
+
return { removed };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function setChannelOwner(cfg, channelName, userId) {
|
|
173
|
+
return upsertTelegramChannel(cfg, channelName, { owner_user_id: userId });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── Telegram roles (role → capability map) ──────────────────────────────────
|
|
177
|
+
|
|
178
|
+
export function listRoles(cfg) {
|
|
179
|
+
cfg.telegram = cfg.telegram || {};
|
|
180
|
+
return { ...(cfg.telegram.roles || {}) };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function setRole(cfg, name, def) {
|
|
184
|
+
if (!name || typeof name !== "string") throw new Error("setRole: name required");
|
|
185
|
+
cfg.telegram = cfg.telegram || {};
|
|
186
|
+
cfg.telegram.roles = cfg.telegram.roles || {};
|
|
187
|
+
cfg.telegram.roles[name] = def;
|
|
188
|
+
writeConfig(cfg);
|
|
189
|
+
return cfg.telegram.roles[name];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function removeRole(cfg, name) {
|
|
193
|
+
cfg.telegram = cfg.telegram || {};
|
|
194
|
+
if (!cfg.telegram.roles || !(name in cfg.telegram.roles)) return { removed: 0 };
|
|
195
|
+
if (name === "owner" || name === "guest") {
|
|
196
|
+
throw new Error(`role "${name}" is built-in and cannot be removed`);
|
|
197
|
+
}
|
|
198
|
+
delete cfg.telegram.roles[name];
|
|
199
|
+
writeConfig(cfg);
|
|
200
|
+
return { removed: 1 };
|
|
201
|
+
}
|
|
@@ -228,6 +228,7 @@ export async function handleUpdate(self, u) {
|
|
|
228
228
|
let replyKind = "superagent"; // actor_kind: superagent | agent
|
|
229
229
|
let replyModel = null; // model that actually produced the reply
|
|
230
230
|
let replyUsage = null; // token accounting for this turn
|
|
231
|
+
let replyTrace = null; // what the turn actually did (summarised on the message)
|
|
231
232
|
const projectCfg = target.config || self.globalConfig;
|
|
232
233
|
// Display name for the super-agent persona on this channel (from identity.json).
|
|
233
234
|
const agentDisplay = resolveAgentName(self.globalConfig);
|
|
@@ -335,6 +336,7 @@ export async function handleUpdate(self, u) {
|
|
|
335
336
|
replyActorId = SUPERAGENT_ACTOR_ID;
|
|
336
337
|
replyKind = "superagent";
|
|
337
338
|
replyUsage = sa.usage;
|
|
339
|
+
replyTrace = sa.trace || null;
|
|
338
340
|
replyModel = sa.model || state.model || null;
|
|
339
341
|
|
|
340
342
|
// ── ask_questions integration ────────────────────────────────────
|
|
@@ -397,6 +399,7 @@ export async function handleUpdate(self, u) {
|
|
|
397
399
|
replyKind,
|
|
398
400
|
saUsage: replyUsage,
|
|
399
401
|
saModel: replyModel,
|
|
402
|
+
saTrace: replyTrace,
|
|
400
403
|
streamedCount,
|
|
401
404
|
lastStreamedText,
|
|
402
405
|
agentDisplay,
|