@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
|
@@ -30,9 +30,10 @@
|
|
|
30
30
|
|
|
31
31
|
import fs from "node:fs";
|
|
32
32
|
import path from "node:path";
|
|
33
|
-
import
|
|
33
|
+
import { SKILLS_DIR } from "#core/config/paths.js";
|
|
34
34
|
import { fileURLToPath } from "node:url";
|
|
35
35
|
import { apcSkillsDir } from "#core/apc/paths.js";
|
|
36
|
+
import { parseFrontmatter } from "#core/apc/frontmatter.js";
|
|
36
37
|
|
|
37
38
|
const __filename = fileURLToPath(import.meta.url);
|
|
38
39
|
const __dirname = path.dirname(__filename);
|
|
@@ -44,32 +45,12 @@ const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", "..", "..");
|
|
|
44
45
|
// <packageRoot>/skills/ so external tools that copy "skills/" from the repo
|
|
45
46
|
// don't accidentally pull the rich set or the runtime CLI docs.
|
|
46
47
|
const BUILTIN_SKILLS_DIR = path.join(PACKAGE_ROOT, "src", "core", "runtime-skills");
|
|
47
|
-
const GLOBAL_DIR =
|
|
48
|
+
const GLOBAL_DIR = SKILLS_DIR;
|
|
48
49
|
|
|
49
50
|
// ---------------------------------------------------------------------------
|
|
50
51
|
// Frontmatter parsing (minimal — handles the YAML we ship)
|
|
51
52
|
// ---------------------------------------------------------------------------
|
|
52
53
|
|
|
53
|
-
function parseFrontmatter(raw) {
|
|
54
|
-
if (!raw.startsWith("---")) return { fm: {}, body: raw };
|
|
55
|
-
const end = raw.indexOf("\n---", 3);
|
|
56
|
-
if (end < 0) return { fm: {}, body: raw };
|
|
57
|
-
|
|
58
|
-
const fmBlock = raw.slice(3, end).trim();
|
|
59
|
-
const body = raw.slice(end + 4).replace(/^\n/, "");
|
|
60
|
-
|
|
61
|
-
const fm = {};
|
|
62
|
-
for (const line of fmBlock.split("\n")) {
|
|
63
|
-
const m = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:\s*(.*)$/);
|
|
64
|
-
if (!m) continue;
|
|
65
|
-
let val = m[2].trim();
|
|
66
|
-
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
67
|
-
val = val.slice(1, -1);
|
|
68
|
-
}
|
|
69
|
-
fm[m[1]] = val;
|
|
70
|
-
}
|
|
71
|
-
return { fm, body };
|
|
72
|
-
}
|
|
73
54
|
|
|
74
55
|
// ---------------------------------------------------------------------------
|
|
75
56
|
// Directory scanners
|
|
@@ -80,8 +80,6 @@ export async function runSuperAgent({
|
|
|
80
80
|
throw new Error("super-agent not enabled (set super_agent.enabled and .model in ~/.apx/config.json)");
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
const sa = globalConfig.super_agent;
|
|
84
|
-
|
|
85
83
|
// Memory Broker (Pieza 4): assemble the [MEMORIA RELEVANTE] block before the
|
|
86
84
|
// turn. Silent + bounded (≤ broker_budget_ms); skipped for tool-free callers
|
|
87
85
|
// (summarize/ask) where injected context would only confuse the transcript.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// A compact record of what a turn actually DID, small enough to store on every
|
|
2
|
+
// message.
|
|
3
|
+
//
|
|
4
|
+
// The full trace carries arguments and results and can run to kilobytes; on a
|
|
5
|
+
// channel like Telegram, writing it to the ledger on every reply would bloat
|
|
6
|
+
// the day-file for a detail nobody reads back. What people do want, looking at
|
|
7
|
+
// a conversation after the fact, is "it read three files and sent a message" —
|
|
8
|
+
// and whether any of it failed. That fits in a line.
|
|
9
|
+
//
|
|
10
|
+
// Shape: { total, failed, tools: [{ name, count, failed }] }
|
|
11
|
+
|
|
12
|
+
/** How many entries to keep. A turn with 40 distinct tools is a runaway, not a report. */
|
|
13
|
+
const MAX_KINDS = 12;
|
|
14
|
+
|
|
15
|
+
function isError(result) {
|
|
16
|
+
if (!result || typeof result !== "object") return false;
|
|
17
|
+
// Tools signal failure two ways: an `error` field, or the budget's
|
|
18
|
+
// `suppressed`. Both are "it did not happen", which is what the reader needs.
|
|
19
|
+
return Boolean(result.error) || result.suppressed === true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {{tool: string, result?: unknown}[]} trace
|
|
24
|
+
* @returns {{total: number, failed: number, tools: {name: string, count: number, failed: number}[]}|null}
|
|
25
|
+
* null when there is nothing to report, so callers can spread it
|
|
26
|
+
* conditionally without writing an empty object into every message.
|
|
27
|
+
*/
|
|
28
|
+
export function summarizeToolTrace(trace) {
|
|
29
|
+
if (!Array.isArray(trace) || trace.length === 0) return null;
|
|
30
|
+
|
|
31
|
+
const byName = new Map();
|
|
32
|
+
let failed = 0;
|
|
33
|
+
|
|
34
|
+
for (const item of trace) {
|
|
35
|
+
const name = String(item?.tool || "tool");
|
|
36
|
+
const row = byName.get(name) || { name, count: 0, failed: 0 };
|
|
37
|
+
row.count += 1;
|
|
38
|
+
if (isError(item?.result)) {
|
|
39
|
+
row.failed += 1;
|
|
40
|
+
failed += 1;
|
|
41
|
+
}
|
|
42
|
+
byName.set(name, row);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Failures first, then the busiest. If the list is truncated, what went wrong
|
|
46
|
+
// must survive the truncation — that is the half worth reading.
|
|
47
|
+
const tools = [...byName.values()].sort(
|
|
48
|
+
(a, b) => (b.failed - a.failed) || (b.count - a.count) || a.name.localeCompare(b.name),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
total: trace.length,
|
|
53
|
+
failed,
|
|
54
|
+
tools: tools.slice(0, MAX_KINDS),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** One line for a terminal or a log. "" when there is nothing to say. */
|
|
59
|
+
export function formatToolSummary(summary) {
|
|
60
|
+
if (!summary?.tools?.length) return "";
|
|
61
|
+
const parts = summary.tools.map((t) =>
|
|
62
|
+
`${t.name}${t.count > 1 ? `×${t.count}` : ""}${t.failed ? ` (${t.failed} failed)` : ""}`,
|
|
63
|
+
);
|
|
64
|
+
return parts.join(", ");
|
|
65
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { loggerFor } from "#core/logging.js";
|
|
4
3
|
import { readAgents } from "#core/apc/parser.js";
|
|
@@ -15,9 +14,10 @@ import {
|
|
|
15
14
|
findEngineSessionById,
|
|
16
15
|
readEngineSessionContext,
|
|
17
16
|
} from "#core/stores/engine-sessions.js";
|
|
18
|
-
import { runProcess } from "#
|
|
19
|
-
import { getRuntime, RUNTIME_IDS } from "#
|
|
17
|
+
import { runProcess } from "#core/runtimes/_spawn.js";
|
|
18
|
+
import { getRuntime, RUNTIME_IDS } from "#core/runtimes/index.js";
|
|
20
19
|
import { buildAgentSystem, resolveProject } from "../helpers.js";
|
|
20
|
+
import { readJson } from "#core/util/json-file.js";
|
|
21
21
|
|
|
22
22
|
const log = loggerFor("call_runtime");
|
|
23
23
|
|
|
@@ -90,7 +90,7 @@ function resolveProjectForAgent(projects, project, slug) {
|
|
|
90
90
|
|
|
91
91
|
function projectName(project) {
|
|
92
92
|
try {
|
|
93
|
-
const meta =
|
|
93
|
+
const meta = readJson(apcProjectFile(project.path), {});
|
|
94
94
|
if (meta.name) return meta.name;
|
|
95
95
|
} catch {}
|
|
96
96
|
return path.basename(project.path);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { listCommitments, listCommitmentsAcrossProjects } from "#core/stores/commitments.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Read back what is owed, to whom, by when.
|
|
5
|
+
*
|
|
6
|
+
* Cross-project by default. "What do I owe people this week" almost never
|
|
7
|
+
* respects a repo boundary, and forcing the model to pick a project first is
|
|
8
|
+
* how the anchors end up reporting one project's promises as if they were all
|
|
9
|
+
* of them.
|
|
10
|
+
*/
|
|
11
|
+
export default {
|
|
12
|
+
name: "list_commitments",
|
|
13
|
+
schema: {
|
|
14
|
+
type: "function",
|
|
15
|
+
function: {
|
|
16
|
+
name: "list_commitments",
|
|
17
|
+
description:
|
|
18
|
+
"List commitments — things promised to named people. Use for 'what do I owe X', " +
|
|
19
|
+
"'what's overdue', or when preparing a meeting with someone. Searches ALL projects " +
|
|
20
|
+
"unless a project is given. Distinct from list_tasks: these have a counterparty " +
|
|
21
|
+
"waiting, and an overdue one costs trust.",
|
|
22
|
+
parameters: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
project: { type: "string", description: "Optional project id, name or path. Omit to search every project." },
|
|
26
|
+
counterparty: { type: "string", description: "Filter by who is waiting — matches part of the name, case-insensitive." },
|
|
27
|
+
state: { type: "string", enum: ["open", "kept", "missed", "all"], description: "Defaults to open." },
|
|
28
|
+
overdue: { type: "boolean", description: "Only ones past their date and still open." },
|
|
29
|
+
due_before: { type: "string", description: "ISO date — only those due on or before it." },
|
|
30
|
+
limit: { type: "integer", description: "Max rows. Defaults to 50." },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
makeHandler: ({ projects }) => async (args = {}) => {
|
|
36
|
+
const { project: ref, counterparty, state, overdue, due_before, limit } = args;
|
|
37
|
+
const opts = {
|
|
38
|
+
counterparty: counterparty || undefined,
|
|
39
|
+
state: state || undefined,
|
|
40
|
+
overdue: overdue === true || undefined,
|
|
41
|
+
due_before: due_before || undefined,
|
|
42
|
+
limit: Number.isFinite(limit) ? limit : 50,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (ref) {
|
|
46
|
+
const r = String(ref);
|
|
47
|
+
const found = projects.list().find((p) => String(p.id) === r || p.name === r || p.path === r);
|
|
48
|
+
if (!found) return { error: `project not found: ${ref}` };
|
|
49
|
+
const proj = projects.get(found.id);
|
|
50
|
+
if (!proj) return { error: `project storage not loaded: ${ref}` };
|
|
51
|
+
return { commitments: listCommitments(proj.storagePath, opts).map(compact) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const entries = [];
|
|
55
|
+
for (const entry of projects.list()) {
|
|
56
|
+
const p = projects.get(entry.id);
|
|
57
|
+
if (!p?.storagePath) continue;
|
|
58
|
+
entries.push({ id: entry.id, name: entry.name || entry.path, path: entry.path, storagePath: p.storagePath });
|
|
59
|
+
}
|
|
60
|
+
const { commitments, skipped } = listCommitmentsAcrossProjects(entries, opts);
|
|
61
|
+
return {
|
|
62
|
+
commitments: commitments.map(compact),
|
|
63
|
+
// Say what could not be read rather than quietly reporting less.
|
|
64
|
+
...(skipped.length ? { skipped } : {}),
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** Only the fields worth spending prompt tokens on. */
|
|
70
|
+
function compact(c) {
|
|
71
|
+
return {
|
|
72
|
+
id: c.id,
|
|
73
|
+
counterparty: c.counterparty,
|
|
74
|
+
body: c.body,
|
|
75
|
+
due: c.due,
|
|
76
|
+
state: c.state,
|
|
77
|
+
...(c.project_name ? { project: c.project_name } : {}),
|
|
78
|
+
...(c.renegotiated_count ? { moved: c.renegotiated_count } : {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
import { listTasks } from "#core/stores/tasks.js";
|
|
1
|
+
import { listTasks, listTasksAcrossProjects } from "#core/stores/tasks.js";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Tasks, across every project by default.
|
|
5
|
+
*
|
|
6
|
+
* `project` USED TO BE REQUIRED, and that was the bug. The cross-project fold
|
|
7
|
+
* has existed in core since C2 and is exposed over HTTP and in the CLI, but the
|
|
8
|
+
* agent could not reach it — so a chief-of-staff routine asked "what is due
|
|
9
|
+
* today" by calling this once per registered project. On this install that was
|
|
10
|
+
* eleven calls, eleven prompt round-trips, and a morning anchor that ran out of
|
|
11
|
+
* iterations before it managed to say anything.
|
|
12
|
+
*
|
|
13
|
+
* Omitting `project` now means "everywhere", matching list_commitments. Passing
|
|
14
|
+
* one keeps the old behaviour exactly.
|
|
15
|
+
*/
|
|
3
16
|
export default {
|
|
4
17
|
name: "list_tasks",
|
|
5
18
|
schema: {
|
|
@@ -7,46 +20,72 @@ export default {
|
|
|
7
20
|
function: {
|
|
8
21
|
name: "list_tasks",
|
|
9
22
|
description:
|
|
10
|
-
"List tasks
|
|
23
|
+
"List tasks. Use when the user asks 'what's pending', 'qué tengo que hacer', or to recall TODOs. " +
|
|
24
|
+
"Searches ALL projects unless you pass `project` — for anything cross-project " +
|
|
25
|
+
"(what is due today, what is overdue, a morning summary) OMIT it and call this ONCE. " +
|
|
26
|
+
"Never loop over projects calling this per project. Defaults to open tasks.",
|
|
11
27
|
parameters: {
|
|
12
28
|
type: "object",
|
|
13
|
-
required: ["project"],
|
|
14
29
|
properties: {
|
|
15
|
-
project: { type: "string", description: "
|
|
30
|
+
project: { type: "string", description: "Optional project id, name or path. Omit to search every project." },
|
|
16
31
|
state: { type: "string", enum: ["open", "done", "dropped", "all"], description: "Filter by state. Default 'open'." },
|
|
32
|
+
status: { type: "string", enum: ["pending", "running", "in_review", "blocked"], description: "Workflow sub-status of an open task." },
|
|
17
33
|
tag: { type: "string", description: "Filter by exact tag match." },
|
|
18
34
|
agent: { type: "string", description: "Filter by agent slug." },
|
|
19
35
|
due_before: { type: "string", description: "Return only tasks due on or before this ISO date." },
|
|
20
|
-
limit: { type: "number", description: "Cap on rows returned. Default
|
|
36
|
+
limit: { type: "number", description: "Cap on rows returned. Default 100." },
|
|
21
37
|
},
|
|
22
38
|
},
|
|
23
39
|
},
|
|
24
40
|
},
|
|
25
|
-
makeHandler: ({ projects }) => async (
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
const r = String(ref);
|
|
29
|
-
const found = all.find((p) =>
|
|
30
|
-
String(p.id) === r || p.name === r || p.path === r
|
|
31
|
-
);
|
|
32
|
-
if (!found) return { error: `project not found: ${ref}` };
|
|
33
|
-
const proj = projects.get(found.id);
|
|
34
|
-
if (!proj) return { error: `project storage not loaded: ${ref}` };
|
|
35
|
-
const rows = listTasks(proj.storagePath, {
|
|
41
|
+
makeHandler: ({ projects }) => async (args = {}) => {
|
|
42
|
+
const { project: ref, state, status, tag, agent, due_before, limit } = args;
|
|
43
|
+
const opts = {
|
|
36
44
|
state: state || undefined,
|
|
45
|
+
status: status || undefined,
|
|
37
46
|
tag: tag || undefined,
|
|
38
47
|
agent: agent || undefined,
|
|
39
48
|
due_before: due_before || undefined,
|
|
40
|
-
limit: typeof limit === "number" ? limit :
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
49
|
+
limit: typeof limit === "number" ? limit : 100,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (ref) {
|
|
53
|
+
const r = String(ref);
|
|
54
|
+
const found = projects.list().find((p) => String(p.id) === r || p.name === r || p.path === r);
|
|
55
|
+
if (!found) return { error: `project not found: ${ref}` };
|
|
56
|
+
const proj = projects.get(found.id);
|
|
57
|
+
if (!proj) return { error: `project storage not loaded: ${ref}` };
|
|
58
|
+
return listTasks(proj.storagePath, opts).map(compact);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const entries = [];
|
|
62
|
+
for (const entry of projects.list()) {
|
|
63
|
+
const p = projects.get(entry.id);
|
|
64
|
+
if (!p?.storagePath) continue;
|
|
65
|
+
entries.push({ id: entry.id, name: entry.name || entry.path, path: entry.path, storagePath: p.storagePath });
|
|
66
|
+
}
|
|
67
|
+
const { tasks, skipped } = listTasksAcrossProjects(entries, opts);
|
|
68
|
+
return {
|
|
69
|
+
tasks: tasks.map(compact),
|
|
70
|
+
// Say what could not be read rather than quietly reporting less — an
|
|
71
|
+
// anchor that says "nothing due" because a store failed to open is worse
|
|
72
|
+
// than one that says it could not check.
|
|
73
|
+
...(skipped.length ? { skipped } : {}),
|
|
74
|
+
};
|
|
51
75
|
},
|
|
52
76
|
};
|
|
77
|
+
|
|
78
|
+
/** Only the fields worth spending prompt tokens on. */
|
|
79
|
+
function compact(t) {
|
|
80
|
+
return {
|
|
81
|
+
id: t.id,
|
|
82
|
+
state: t.state,
|
|
83
|
+
status: t.status,
|
|
84
|
+
title: t.title,
|
|
85
|
+
tags: t.tags,
|
|
86
|
+
due: t.due,
|
|
87
|
+
agent: t.agent,
|
|
88
|
+
created_at: t.created_at,
|
|
89
|
+
...(t.project_name ? { project: t.project_name } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createCommitment } from "#core/stores/commitments.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Capture a promise made to a person.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately separate from create_task. The model needs a reason to reach
|
|
7
|
+
* for one over the other, and "did you promise this to someone by name?" is a
|
|
8
|
+
* question it can actually answer from the conversation — which is why the
|
|
9
|
+
* description leads with the phrasing that gives it away ("I told Ana I'd send
|
|
10
|
+
* it Friday") rather than with an abstract definition.
|
|
11
|
+
*/
|
|
12
|
+
export default {
|
|
13
|
+
name: "record_commitment",
|
|
14
|
+
schema: {
|
|
15
|
+
type: "function",
|
|
16
|
+
function: {
|
|
17
|
+
name: "record_commitment",
|
|
18
|
+
description:
|
|
19
|
+
"Record something the user PROMISED TO A PERSON — 'I told Ana I'd send it Friday', " +
|
|
20
|
+
"'I said we'd have the quote by the 10th'. Use this instead of create_task whenever " +
|
|
21
|
+
"there is a named counterparty waiting on it: breaking a promise costs trust, and " +
|
|
22
|
+
"these are tracked, chased and reported separately from ordinary work. " +
|
|
23
|
+
"A to-do with no one waiting on it is a task, not a commitment. " +
|
|
24
|
+
"Do not ask permission to record one you clearly heard — record it and say you did.",
|
|
25
|
+
parameters: {
|
|
26
|
+
type: "object",
|
|
27
|
+
required: ["project", "counterparty", "body"],
|
|
28
|
+
properties: {
|
|
29
|
+
project: { type: "string", description: "Project id, name, or path." },
|
|
30
|
+
counterparty: { type: "string", description: "Who is waiting on this. A name as the user says it — free text, not an id." },
|
|
31
|
+
body: { type: "string", description: "What was promised, in one line." },
|
|
32
|
+
due: { type: "string", description: "When it was promised for (ISO date or datetime). Include it whenever the user gave one, even loosely resolved ('Friday' → that date)." },
|
|
33
|
+
origin_channel: { type: "string", description: "Where the promise was made (telegram, meeting, email, …)." },
|
|
34
|
+
origin_message_ref: { type: "string", description: "Optional reference back to the message it came from." },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
makeHandler: ({ projects, channel }) => async (args = {}) => {
|
|
40
|
+
const { project: ref, counterparty, body, due, origin_channel, origin_message_ref } = args;
|
|
41
|
+
if (!ref) return { error: "project required" };
|
|
42
|
+
if (!counterparty) return { error: "counterparty required — without one this is a task, use create_task" };
|
|
43
|
+
if (!body) return { error: "body required" };
|
|
44
|
+
|
|
45
|
+
const r = String(ref);
|
|
46
|
+
const found = projects.list().find((p) => String(p.id) === r || p.name === r || p.path === r);
|
|
47
|
+
if (!found) return { error: `project not found: ${ref}` };
|
|
48
|
+
const proj = projects.get(found.id);
|
|
49
|
+
if (!proj) return { error: `project storage not loaded: ${ref}` };
|
|
50
|
+
|
|
51
|
+
const c = createCommitment(proj.storagePath, {
|
|
52
|
+
counterparty,
|
|
53
|
+
body,
|
|
54
|
+
due: due || null,
|
|
55
|
+
origin_channel: origin_channel || channel || "super-agent",
|
|
56
|
+
origin_message_ref: origin_message_ref || null,
|
|
57
|
+
created_by: "super-agent",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
id: c.id,
|
|
62
|
+
project: { id: proj.id, name: proj.name },
|
|
63
|
+
counterparty: c.counterparty,
|
|
64
|
+
due: c.due,
|
|
65
|
+
state: c.state,
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
ENGINES,
|
|
4
4
|
collectAllSessions,
|
|
5
5
|
findSessionAcrossEngines,
|
|
6
|
-
} from "#
|
|
6
|
+
} from "#core/sessions/index.js";
|
|
7
7
|
|
|
8
8
|
// Your own work sessions. This is how you remember what you did — never tell
|
|
9
9
|
// the user to run `apx session ...`; call this and answer from the result.
|
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
import { canNudge, recordNudge, nudgeFeedbackKeyboard } from "#core/nudge/index.js";
|
|
2
|
+
import { CHANNELS } from "#core/constants/channels.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Is the person we are about to message the same person who is talking to us
|
|
6
|
+
* right now?
|
|
7
|
+
*
|
|
8
|
+
* PUSH PATH 3 OF 4, and the only one whose solicited-ness is not fixed. When
|
|
9
|
+
* the turn came in over Telegram and the text is going back to that same chat,
|
|
10
|
+
* the user is on the other end waiting — that is a reply, not an interruption,
|
|
11
|
+
* and spending their daily budget on it would be wrong. Every other case (a
|
|
12
|
+
* routine, a web turn pushing to the phone, a cron) is APX choosing to speak.
|
|
13
|
+
*/
|
|
14
|
+
function isReplyToTheLiveChat(ctx, chatId) {
|
|
15
|
+
if (ctx?.channel !== CHANNELS.TELEGRAM) return false;
|
|
16
|
+
// buildTelegramMeta (channels/telegram/helpers.js:59) spells it `chatId`.
|
|
17
|
+
// Reading `chat_id` here would have silently made every reply look
|
|
18
|
+
// unsolicited and started charging the user's budget for their own answers.
|
|
19
|
+
const origin = ctx?.channelMeta?.chatId ?? ctx?.channelMeta?.chat_id;
|
|
20
|
+
if (!origin) return false;
|
|
21
|
+
// No chat_id given means "the channel default", which for a Telegram turn is
|
|
22
|
+
// the chat it arrived on.
|
|
23
|
+
if (chatId == null || chatId === "") return true;
|
|
24
|
+
return String(chatId) === String(origin);
|
|
25
|
+
}
|
|
1
26
|
|
|
2
27
|
function decodeBase64(b64) {
|
|
3
28
|
const clean = String(b64).replace(/^data:[a-z/-]+;base64,/, "");
|
|
@@ -76,7 +101,8 @@ export default {
|
|
|
76
101
|
},
|
|
77
102
|
},
|
|
78
103
|
},
|
|
79
|
-
makeHandler: (
|
|
104
|
+
makeHandler: (ctx) => async (args = {}) => {
|
|
105
|
+
const { plugins, requirePermission, globalConfig } = ctx;
|
|
80
106
|
const {
|
|
81
107
|
channel, chat_id, text,
|
|
82
108
|
photo_base64, photo_path, photo_url,
|
|
@@ -100,11 +126,44 @@ export default {
|
|
|
100
126
|
);
|
|
101
127
|
}
|
|
102
128
|
|
|
129
|
+
const solicited = isReplyToTheLiveChat(ctx, chat_id);
|
|
130
|
+
const gate = canNudge(
|
|
131
|
+
{
|
|
132
|
+
kind: ctx?.channelMeta?.routineName ? `routine:${ctx.channelMeta.routineName}` : "agent_message",
|
|
133
|
+
project_id: ctx?.channelMeta?.projectId ?? null,
|
|
134
|
+
// Severity is set by a DETECTOR when one ran (watch routines put their
|
|
135
|
+
// peak signal severity here), never by the model. Letting the model
|
|
136
|
+
// grade its own message would hand it a switch marked "ignore the
|
|
137
|
+
// budget" — and it would find it.
|
|
138
|
+
severity: ctx?.channelMeta?.signalSeverity || "normal",
|
|
139
|
+
unsolicited: !solicited,
|
|
140
|
+
// Set only by an anchor routine (routines/runner.js), never by the
|
|
141
|
+
// model — it has no way to declare its own message scheduled.
|
|
142
|
+
scheduled: ctx?.channelMeta?.scheduledByUser === true,
|
|
143
|
+
channel: "telegram",
|
|
144
|
+
},
|
|
145
|
+
globalConfig || {},
|
|
146
|
+
);
|
|
147
|
+
if (!gate.allowed) {
|
|
148
|
+
// Returned as a tool result, not thrown: the model should learn that it
|
|
149
|
+
// is out of budget and stop trying, which a raw error does not convey.
|
|
150
|
+
return {
|
|
151
|
+
ok: false,
|
|
152
|
+
suppressed: true,
|
|
153
|
+
reason: gate.reason,
|
|
154
|
+
retry_after_ms: gate.retry_after_ms,
|
|
155
|
+
hint:
|
|
156
|
+
"The interruption budget refused this unrequested message. Do not retry now — " +
|
|
157
|
+
"say it in your reply instead, or wait until the user writes to you.",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
103
161
|
const photo = decodePhoto({ photo_base64, photo_path, photo_url });
|
|
104
162
|
if (photo) {
|
|
105
163
|
const result = await telegram.sendPhoto({
|
|
106
164
|
channel, chat_id, photo, caption: text, author: "apx",
|
|
107
165
|
});
|
|
166
|
+
recordNudge(gate, { chat_id, preview: text });
|
|
108
167
|
return { ok: true, kind: "photo", message_id: result.message_id };
|
|
109
168
|
}
|
|
110
169
|
|
|
@@ -113,10 +172,17 @@ export default {
|
|
|
113
172
|
const result = await telegram.sendDocument({
|
|
114
173
|
channel, chat_id, document, caption: text, filename, mime_type,
|
|
115
174
|
});
|
|
175
|
+
recordNudge(gate, { chat_id, preview: text });
|
|
116
176
|
return { ok: true, kind: "document", message_id: result.message_id, filename };
|
|
117
177
|
}
|
|
118
178
|
|
|
119
|
-
const result = await telegram.send({
|
|
179
|
+
const result = await telegram.send({
|
|
180
|
+
channel, chat_id, text,
|
|
181
|
+
// Only unprompted messages carry the feedback keyboard. Asking "was that
|
|
182
|
+
// useful?" under an answer the user just asked for is noise itself.
|
|
183
|
+
reply_markup: gate.unsolicited ? nudgeFeedbackKeyboard(gate.nudge_id) : undefined,
|
|
184
|
+
});
|
|
185
|
+
recordNudge(gate, { chat_id, preview: text });
|
|
120
186
|
return { ok: true, kind: "text", message_id: result.message_id };
|
|
121
187
|
},
|
|
122
188
|
};
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
// Changes the super-agent's own autonomy level. This is the gate that decides
|
|
2
|
+
// whether destructive tools run unattended, so the mode vocabulary must come
|
|
3
|
+
// from one place: it used to be spelled out three times in this file (a Set,
|
|
4
|
+
// the schema enum, and the error message), which meant adding or renaming a
|
|
5
|
+
// mode silently left two of them stale.
|
|
1
6
|
import { readConfig, writeConfig } from "#core/config/index.js";
|
|
7
|
+
import { PERMISSION_MODES } from "#core/constants/permissions.js";
|
|
2
8
|
|
|
3
|
-
const MODES =
|
|
9
|
+
const MODES = Object.values(PERMISSION_MODES);
|
|
10
|
+
const MODE_LIST = MODES.join(", ");
|
|
4
11
|
|
|
5
12
|
export default {
|
|
6
13
|
name: "set_permission_mode",
|
|
@@ -8,11 +15,11 @@ export default {
|
|
|
8
15
|
type: "function",
|
|
9
16
|
function: {
|
|
10
17
|
name: "set_permission_mode",
|
|
11
|
-
description:
|
|
18
|
+
description: `Set APX tool permission mode in ~/.apx/config.json. Modes: ${MODE_LIST}.`,
|
|
12
19
|
parameters: {
|
|
13
20
|
type: "object",
|
|
14
21
|
properties: {
|
|
15
|
-
mode: { type: "string", enum:
|
|
22
|
+
mode: { type: "string", enum: MODES },
|
|
16
23
|
},
|
|
17
24
|
required: ["mode"],
|
|
18
25
|
},
|
|
@@ -20,7 +27,7 @@ export default {
|
|
|
20
27
|
},
|
|
21
28
|
makeHandler: ({ requirePermission }) => async ({ mode, confirmed = false }) => {
|
|
22
29
|
await requirePermission("set_permission_mode", { dangerous: true, confirmed, args: { mode } });
|
|
23
|
-
if (!MODES.
|
|
30
|
+
if (!MODES.includes(mode)) throw new Error(`mode must be one of: ${MODE_LIST}`);
|
|
24
31
|
const cfg = readConfig();
|
|
25
32
|
cfg.super_agent = cfg.super_agent || {};
|
|
26
33
|
cfg.super_agent.permission_mode = mode;
|
|
@@ -41,9 +41,15 @@ export const TOOLS = Object.freeze({
|
|
|
41
41
|
// Tasks
|
|
42
42
|
LIST_TASKS: "list_tasks",
|
|
43
43
|
CREATE_TASK: "create_task",
|
|
44
|
+
RECORD_COMMITMENT: "record_commitment",
|
|
45
|
+
LIST_COMMITMENTS: "list_commitments",
|
|
44
46
|
|
|
45
47
|
// Interaction
|
|
46
48
|
ASK_QUESTIONS: "ask_questions",
|
|
49
|
+
// Synthesised by run-agent.js rather than living in handlers/ — it is how the
|
|
50
|
+
// model declares the turn is over. It was missing from this catalog, so
|
|
51
|
+
// security.js referenced it as a bare "finish" literal.
|
|
52
|
+
FINISH: "finish",
|
|
47
53
|
|
|
48
54
|
// Delegation / external
|
|
49
55
|
CALL_AGENT: "call_agent",
|
|
@@ -131,6 +137,8 @@ export const NATIVE_TOOL_NAMES = new Set([
|
|
|
131
137
|
TOOLS.LOAD_SKILL,
|
|
132
138
|
TOOLS.LIST_TASKS,
|
|
133
139
|
TOOLS.CREATE_TASK,
|
|
140
|
+
TOOLS.RECORD_COMMITMENT,
|
|
141
|
+
TOOLS.LIST_COMMITMENTS,
|
|
134
142
|
TOOLS.ASK_QUESTIONS,
|
|
135
143
|
TOOLS.SEARCH_SESSIONS,
|
|
136
144
|
TOOLS.TRANSCRIBE_AUDIO,
|
|
@@ -178,6 +186,7 @@ export const CODE_PLAN_TOOLS = Object.freeze([
|
|
|
178
186
|
TOOLS.LIST_SKILLS,
|
|
179
187
|
TOOLS.LOAD_SKILL,
|
|
180
188
|
TOOLS.LIST_TASKS,
|
|
189
|
+
TOOLS.LIST_COMMITMENTS,
|
|
181
190
|
TOOLS.ASK_QUESTIONS,
|
|
182
191
|
TOOLS.FETCH,
|
|
183
192
|
TOOLS.SEARCH,
|
|
@@ -195,3 +204,58 @@ export const CODE_PLAN_TOOLS = Object.freeze([
|
|
|
195
204
|
* "*" as "expose every tool the channel is otherwise allowed to see".
|
|
196
205
|
*/
|
|
197
206
|
export const CODE_BUILD_TOOLS = "*";
|
|
207
|
+
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// Behavioural tool sets
|
|
210
|
+
//
|
|
211
|
+
// These drive real user-facing protections, so they belong next to the names
|
|
212
|
+
// they reference rather than as string literals scattered across the loop. The
|
|
213
|
+
// side-effect set in particular was an inline `new Set([...])` inside
|
|
214
|
+
// runAgent(): rename a tool there and the de-duplication below silently stops
|
|
215
|
+
// working, with the only symptom being a user receiving the same Telegram
|
|
216
|
+
// message three times. tests/tool-name-sets.test.js fails if any member here
|
|
217
|
+
// stops being a real tool.
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Tools that mutate the world. Weak models (Gemini especially) re-emit the
|
|
222
|
+
* same call across iterations; for these we remember the (name + args)
|
|
223
|
+
* signature and answer duplicates with a synthetic "already done" instead of
|
|
224
|
+
* running them again. Read-only tools are exempt — they are idempotent and are
|
|
225
|
+
* legitimately repeated (list_tasks before and after a change).
|
|
226
|
+
*/
|
|
227
|
+
export const SIDE_EFFECT_TOOLS = new Set([
|
|
228
|
+
TOOLS.SEND_TELEGRAM,
|
|
229
|
+
TOOLS.CREATE_TASK,
|
|
230
|
+
TOOLS.RECORD_COMMITMENT,
|
|
231
|
+
TOOLS.WRITE_FILE,
|
|
232
|
+
TOOLS.EDIT_FILE,
|
|
233
|
+
TOOLS.RUN_SHELL,
|
|
234
|
+
TOOLS.CALL_RUNTIME,
|
|
235
|
+
TOOLS.ADD_PROJECT,
|
|
236
|
+
TOOLS.SET_IDENTITY,
|
|
237
|
+
]);
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Tools that only acknowledge — the turn has produced no new information for
|
|
241
|
+
* the user, so a run of them is capped (MAX_CONSECUTIVE_ACKS).
|
|
242
|
+
*/
|
|
243
|
+
export const ACK_ONLY_TOOLS = new Set([TOOLS.SEND_TELEGRAM]);
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Tools whose semantics REQUIRE handing control back to a human. The loop
|
|
247
|
+
* breaks after these even under a completion contract, because the task
|
|
248
|
+
* cannot advance without a reply. Without it, models under forced toolChoice
|
|
249
|
+
* ask the same question every iteration.
|
|
250
|
+
*/
|
|
251
|
+
export const TURN_ENDING_TOOLS = new Set([TOOLS.ASK_QUESTIONS]);
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Tools exempt from the injected `security_risk` parameter: they cannot touch
|
|
255
|
+
* anything, so grading them is noise the model has to pay for on every call.
|
|
256
|
+
*/
|
|
257
|
+
export const RISK_EXEMPT_TOOLS = new Set([
|
|
258
|
+
TOOLS.FINISH,
|
|
259
|
+
TOOLS.ASK_QUESTIONS,
|
|
260
|
+
TOOLS.DISCOVER_TOOLS,
|
|
261
|
+
]);
|