@agentprojectcontext/apx 1.77.2 → 1.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -9
- package/package.json +12 -12
- package/src/core/agent/build-agent-system.js +3 -2
- package/src/core/agent/constants.js +3 -2
- package/src/core/agent/loop/greeting-guard.js +55 -0
- package/src/core/agent/loop/side-effects.js +52 -0
- package/src/core/agent/prompt-builder.js +49 -8
- package/src/core/agent/run-agent.js +13 -48
- package/src/core/agent/security.js +1 -1
- package/src/core/agent/self-memory.js +4 -2
- package/src/core/agent/skills/index-store.js +2 -2
- package/src/core/agent/skills/loader.js +3 -22
- package/src/core/agent/super-agent.js +0 -2
- package/src/core/agent/tools/handlers/call-runtime.js +4 -4
- package/src/core/agent/tools/handlers/search-sessions.js +1 -1
- package/src/core/agent/tools/handlers/set-permission-mode.js +11 -4
- package/src/core/agent/tools/names.js +58 -0
- package/src/core/apc/context-copy.js +1 -1
- package/src/core/apc/frontmatter.js +85 -0
- package/src/core/apc/parser.js +9 -21
- package/src/core/apc/paths.js +8 -8
- package/src/core/apc/scaffold.js +5 -4
- package/src/core/channels/telegram/config.js +201 -0
- package/src/core/config/index.js +25 -183
- package/src/core/config/paths.js +54 -1
- package/src/core/confirmation/adapters/web.js +1 -1
- package/src/core/deck/manifest.js +12 -12
- package/src/core/desktop/autostart.js +3 -2
- package/src/core/desktop/process.js +4 -4
- package/src/core/engines/presets.js +2 -2
- package/src/core/http-tools/browser.js +1 -1
- package/src/core/http-tools/catalog.js +551 -0
- package/src/core/http-tools/fetch.js +1 -1
- package/src/core/http-tools/glob.js +3 -3
- package/src/core/http-tools/grep.js +2 -9
- package/src/core/http-tools/inline-handlers.js +59 -0
- package/src/core/http-tools/registry.js +31 -615
- package/src/core/http-tools/search.js +2 -2
- package/src/core/identity/self.js +6 -7
- package/src/core/identity/telegram.js +3 -3
- package/src/core/logging.js +6 -5
- package/src/core/mcp/sources.js +12 -16
- package/src/core/memory/indexer.js +2 -5
- package/src/core/profiles/lifecycle.js +0 -2
- package/src/core/profiles/store.js +2 -7
- package/src/core/routines/runner.js +1 -1
- package/src/core/runtime-skills/apx-agency-agents/SKILL.md +2 -2
- package/src/core/runtime-skills/apx-profile/SKILL.md +3 -3
- package/src/core/runtime-skills/apx-project/SKILL.md +1 -1
- package/src/core/runtime-skills/apx-task/SKILL.md +8 -8
- package/src/core/runtime-skills/apx-telegram/SKILL.md +4 -4
- package/src/core/runtime-skills/apx-voice/SKILL.md +4 -4
- package/src/core/runtimes/detect.js +2 -2
- package/src/core/sessions/index.js +800 -0
- package/src/core/stores/code-sessions.js +3 -14
- package/src/core/stores/engine-sessions.js +0 -4
- package/src/core/stores/messages.js +0 -5
- package/src/core/stores/organization.js +9 -11
- package/src/core/stores/routines.js +6 -11
- package/src/core/stores/runtime-callbacks.js +2 -1
- package/src/core/util/json-file.js +128 -0
- package/src/core/vars/sources.js +14 -21
- package/src/core/voice/stt-models.js +1 -1
- package/src/core/voice/tts.js +5 -4
- package/src/host/daemon/api/admin-config.js +5 -5
- package/src/host/daemon/api/admin.js +8 -7
- package/src/host/daemon/api/agents.js +16 -16
- package/src/host/daemon/api/artifact-preview.js +8 -8
- package/src/host/daemon/api/artifacts.js +7 -7
- package/src/host/daemon/api/code.js +8 -8
- package/src/host/daemon/api/config.js +6 -6
- package/src/host/daemon/api/confirm.js +2 -2
- package/src/host/daemon/api/connections.js +2 -2
- package/src/host/daemon/api/conversations.js +12 -12
- package/src/host/daemon/api/deck.js +6 -6
- package/src/host/daemon/api/desktop.js +8 -8
- package/src/host/daemon/api/embeddings.js +4 -4
- package/src/host/daemon/api/engines.js +8 -7
- package/src/host/daemon/api/exec.js +5 -4
- package/src/host/daemon/api/files-project.js +6 -6
- package/src/host/daemon/api/health.js +2 -2
- package/src/host/daemon/api/identity.js +3 -3
- package/src/host/daemon/api/inbox.js +2 -2
- package/src/host/daemon/api/integrations.js +23 -19
- package/src/host/daemon/api/mcps.js +16 -12
- package/src/host/daemon/api/messages.js +5 -5
- package/src/host/daemon/api/organization.js +8 -8
- package/src/host/daemon/api/pairing.js +10 -7
- package/src/host/daemon/api/plugins.js +3 -3
- package/src/host/daemon/api/prefix.js +20 -0
- package/src/host/daemon/api/profiles.js +9 -9
- package/src/host/daemon/api/projects.js +5 -5
- package/src/host/daemon/api/routines.js +8 -8
- package/src/host/daemon/api/run.js +2 -2
- package/src/host/daemon/api/runtimes.js +6 -6
- package/src/host/daemon/api/sessions-search.js +18 -11
- package/src/host/daemon/api/sessions.js +6 -6
- package/src/host/daemon/api/shared.js +59 -13
- package/src/host/daemon/api/skills.js +12 -12
- package/src/host/daemon/api/super-agent.js +4 -4
- package/src/host/daemon/api/tasks.js +11 -11
- package/src/host/daemon/api/telegram.js +19 -20
- package/src/host/daemon/api/tools.js +7 -7
- package/src/host/daemon/api/top-level.js +7 -7
- package/src/host/daemon/api/transcribe.js +6 -6
- package/src/host/daemon/api/tts.js +3 -3
- package/src/host/daemon/api/vars.js +11 -8
- package/src/host/daemon/api/voice.js +6 -6
- package/src/host/daemon/api/web.js +28 -32
- package/src/host/daemon/api.js +77 -48
- package/src/host/daemon/db.js +1 -1
- package/src/host/daemon/desktop-ws.js +7 -3
- package/src/host/daemon/index.js +11 -5
- package/src/host/daemon/plugins/desktop/index.js +1 -1
- package/src/host/daemon/plugins/telegram/index.js +1 -1
- package/src/host/daemon/stt-venv.js +20 -81
- package/src/host/daemon/wakeup.js +1 -1
- package/src/interfaces/acp/index.js +3 -3
- package/src/interfaces/acp/session.js +2 -2
- package/src/interfaces/cli/commands/a2a.js +2 -2
- package/src/interfaces/cli/commands/agent.js +3 -3
- package/src/interfaces/cli/commands/artifact.js +13 -13
- package/src/interfaces/cli/commands/chat.js +3 -3
- package/src/interfaces/cli/commands/command.js +2 -2
- package/src/interfaces/cli/commands/config.js +4 -4
- package/src/interfaces/cli/commands/daemon.js +17 -18
- package/src/interfaces/cli/commands/desktop.js +7 -14
- package/src/interfaces/cli/commands/exec.js +2 -2
- package/src/interfaces/cli/commands/log.js +3 -4
- package/src/interfaces/cli/commands/mcp.js +9 -9
- package/src/interfaces/cli/commands/memory.js +2 -2
- package/src/interfaces/cli/commands/messages.js +5 -5
- package/src/interfaces/cli/commands/model.js +0 -18
- package/src/interfaces/cli/commands/obsidian.js +5 -5
- package/src/interfaces/cli/commands/org.js +5 -5
- package/src/interfaces/cli/commands/pair.js +6 -6
- package/src/interfaces/cli/commands/plugins.js +2 -2
- package/src/interfaces/cli/commands/profile.js +10 -10
- package/src/interfaces/cli/commands/project-config.js +6 -6
- package/src/interfaces/cli/commands/project.js +10 -10
- package/src/interfaces/cli/commands/routine.js +8 -9
- package/src/interfaces/cli/commands/runtime.js +2 -2
- package/src/interfaces/cli/commands/search.js +0 -1
- package/src/interfaces/cli/commands/session.js +12 -40
- package/src/interfaces/cli/commands/sessions.js +21 -798
- package/src/interfaces/cli/commands/setup.js +16 -6
- package/src/interfaces/cli/commands/skills.js +1 -2
- package/src/interfaces/cli/commands/status.js +3 -3
- package/src/interfaces/cli/commands/task.js +8 -8
- package/src/interfaces/cli/commands/telegram.js +21 -21
- package/src/interfaces/cli/commands/voice.js +6 -6
- package/src/interfaces/cli/help/index.js +2245 -0
- package/src/interfaces/cli/http.js +9 -4
- package/src/interfaces/cli/index.js +14 -2857
- package/src/interfaces/cli/routes/acp.js +13 -0
- package/src/interfaces/cli/routes/agent.js +27 -0
- package/src/interfaces/cli/routes/artifact.js +28 -0
- package/src/interfaces/cli/routes/chat.js +11 -0
- package/src/interfaces/cli/routes/code.js +11 -0
- package/src/interfaces/cli/routes/command.js +21 -0
- package/src/interfaces/cli/routes/config.js +16 -0
- package/src/interfaces/cli/routes/connections.js +11 -0
- package/src/interfaces/cli/routes/conversations.js +21 -0
- package/src/interfaces/cli/routes/daemon.js +19 -0
- package/src/interfaces/cli/routes/desktop.js +20 -0
- package/src/interfaces/cli/routes/env.js +13 -0
- package/src/interfaces/cli/routes/exec.js +11 -0
- package/src/interfaces/cli/routes/identity.js +11 -0
- package/src/interfaces/cli/routes/index.js +71 -0
- package/src/interfaces/cli/routes/init.js +11 -0
- package/src/interfaces/cli/routes/log.js +20 -0
- package/src/interfaces/cli/routes/mcp.js +22 -0
- package/src/interfaces/cli/routes/memory.js +11 -0
- package/src/interfaces/cli/routes/messages.js +16 -0
- package/src/interfaces/cli/routes/model.js +11 -0
- package/src/interfaces/cli/routes/obsidian.js +17 -0
- package/src/interfaces/cli/routes/org.js +34 -0
- package/src/interfaces/cli/routes/overlay.js +10 -0
- package/src/interfaces/cli/routes/pair.js +17 -0
- package/src/interfaces/cli/routes/panel.js +16 -0
- package/src/interfaces/cli/routes/permission.js +11 -0
- package/src/interfaces/cli/routes/plugins.js +21 -0
- package/src/interfaces/cli/routes/profile.js +27 -0
- package/src/interfaces/cli/routes/project.js +41 -0
- package/src/interfaces/cli/routes/restart.js +15 -0
- package/src/interfaces/cli/routes/routine.js +28 -0
- package/src/interfaces/cli/routes/run.js +11 -0
- package/src/interfaces/cli/routes/search.js +11 -0
- package/src/interfaces/cli/routes/send.js +11 -0
- package/src/interfaces/cli/routes/session.js +26 -0
- package/src/interfaces/cli/routes/sessions.js +15 -0
- package/src/interfaces/cli/routes/setup.js +18 -0
- package/src/interfaces/cli/routes/skills.js +20 -0
- package/src/interfaces/cli/routes/status.js +12 -0
- package/src/interfaces/cli/routes/task.js +26 -0
- package/src/interfaces/cli/routes/telegram.js +39 -0
- package/src/interfaces/cli/routes/update.js +24 -0
- package/src/interfaces/cli/routes/voice.js +17 -0
- package/src/interfaces/desktop/main.js +6 -7
- package/src/interfaces/desktop/renderer.js +0 -5
- package/src/interfaces/mcp-server/index.js +15 -14
- package/src/interfaces/tui/_shims/globals.d.ts +8 -0
- package/src/interfaces/tui/tsconfig.json +4 -1
- package/src/interfaces/web/README.md +6 -6
- package/src/interfaces/web/dist/assets/index-CBR_-QyA.js +824 -0
- package/src/interfaces/web/dist/assets/index-CBR_-QyA.js.map +1 -0
- package/src/interfaces/web/dist/index.html +1 -1
- package/src/interfaces/web/src/components/AddProjectDialog.tsx +1 -1
- package/src/interfaces/web/src/components/agents/AgentFormFields.tsx +1 -1
- package/src/interfaces/web/src/components/chat/ChatList.tsx +2 -2
- package/src/interfaces/web/src/components/chat/SkillPicker.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeFileTree.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeTerminal.tsx +1 -1
- package/src/interfaces/web/src/components/desktop/DesktopStatusCard.tsx +1 -1
- package/src/interfaces/web/src/components/files/FileBrowser.tsx +2 -2
- package/src/interfaces/web/src/components/memory/MemoryBrowser.tsx +2 -2
- package/src/interfaces/web/src/components/routines/ExecutionsList.tsx +1 -1
- package/src/interfaces/web/src/components/routines/RoutineEditor.tsx +2 -2
- package/src/interfaces/web/src/components/settings/DesktopSettingsPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/SkillsManager.tsx +2 -2
- package/src/interfaces/web/src/components/tasks/TaskDetailPanel.tsx +1 -1
- package/src/interfaces/web/src/hooks/useDaemonStatus.ts +1 -1
- package/src/interfaces/web/src/hooks/useDevices.ts +1 -1
- package/src/interfaces/web/src/hooks/useEngines.ts +1 -1
- package/src/interfaces/web/src/hooks/useGlobalConfig.ts +2 -2
- package/src/interfaces/web/src/hooks/useIdentity.ts +1 -1
- package/src/interfaces/web/src/hooks/useInbox.ts +1 -1
- package/src/interfaces/web/src/hooks/useProfiles.ts +3 -3
- package/src/interfaces/web/src/hooks/useProjects.ts +1 -1
- package/src/interfaces/web/src/hooks/useTelegram.ts +3 -3
- package/src/interfaces/web/src/hooks/useTokenBootstrap.ts +30 -7
- package/src/interfaces/web/src/lib/api/admin.ts +11 -11
- package/src/interfaces/web/src/lib/api/agents.ts +14 -14
- package/src/interfaces/web/src/lib/api/artifacts.ts +11 -11
- package/src/interfaces/web/src/lib/api/code.ts +1 -1
- package/src/interfaces/web/src/lib/api/conversations.ts +8 -8
- package/src/interfaces/web/src/lib/api/deck.ts +3 -3
- package/src/interfaces/web/src/lib/api/desktop.ts +8 -8
- package/src/interfaces/web/src/lib/api/embeddings.ts +3 -3
- package/src/interfaces/web/src/lib/api/engines.ts +3 -3
- package/src/interfaces/web/src/lib/api/filesystem.ts +2 -2
- package/src/interfaces/web/src/lib/api/health.ts +1 -1
- package/src/interfaces/web/src/lib/api/identity.ts +2 -2
- package/src/interfaces/web/src/lib/api/inbox.ts +1 -1
- package/src/interfaces/web/src/lib/api/integrations.ts +8 -8
- package/src/interfaces/web/src/lib/api/mcps.ts +6 -6
- package/src/interfaces/web/src/lib/api/messages.ts +3 -3
- package/src/interfaces/web/src/lib/api/organization.ts +7 -7
- package/src/interfaces/web/src/lib/api/profiles.ts +8 -8
- package/src/interfaces/web/src/lib/api/projectFiles.ts +5 -5
- package/src/interfaces/web/src/lib/api/projects.ts +12 -12
- package/src/interfaces/web/src/lib/api/routines.ts +7 -7
- package/src/interfaces/web/src/lib/api/sessions.ts +2 -2
- package/src/interfaces/web/src/lib/api/skills.ts +11 -11
- package/src/interfaces/web/src/lib/api/super_agent.ts +3 -3
- package/src/interfaces/web/src/lib/api/tasks.ts +12 -12
- package/src/interfaces/web/src/lib/api/telegram.ts +14 -14
- package/src/interfaces/web/src/lib/api/tools.ts +1 -1
- package/src/interfaces/web/src/lib/api/vars.ts +4 -4
- package/src/interfaces/web/src/lib/api/voice.ts +6 -6
- package/src/interfaces/web/src/screens/base/AgentDefaultsTab.tsx +1 -1
- package/src/interfaces/web/src/screens/base/GlobalTasksTab.tsx +1 -1
- package/src/interfaces/web/src/screens/base/LogsTab.tsx +3 -3
- package/src/interfaces/web/src/screens/base/SessionsTab.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +8 -8
- package/src/interfaces/web/src/screens/project/AgentsTab.tsx +2 -2
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +3 -3
- package/src/interfaces/web/src/screens/project/ConfigTab.tsx +1 -1
- package/src/interfaces/web/src/screens/project/McpsTab.tsx +3 -3
- package/src/interfaces/web/src/screens/project/Overview.tsx +6 -6
- package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +2 -2
- package/src/interfaces/web/src/screens/project/StructureTab.tsx +1 -1
- package/src/interfaces/web/src/screens/project/TasksTab.tsx +1 -1
- package/src/interfaces/web/src/screens/project/VarsTab.tsx +1 -1
- package/src/interfaces/web/vite.config.ts +13 -18
- package/src/interfaces/web/dist/assets/index-BWWpTTSd.js +0 -824
- package/src/interfaces/web/dist/assets/index-BWWpTTSd.js.map +0 -1
- package/src/skills/apc-context/SKILL.md +0 -159
- /package/src/{host/daemon → core}/runtimes/_spawn.js +0 -0
- /package/src/{host/daemon → core}/runtimes/aider.js +0 -0
- /package/src/{host/daemon → core}/runtimes/antigravity.js +0 -0
- /package/src/{host/daemon → core}/runtimes/claude-code.js +0 -0
- /package/src/{host/daemon → core}/runtimes/codex.js +0 -0
- /package/src/{host/daemon → core}/runtimes/cursor-agent.js +0 -0
- /package/src/{host/daemon → core}/runtimes/gemini-cli.js +0 -0
- /package/src/{host/daemon → core}/runtimes/index.js +0 -0
- /package/src/{host/daemon → core}/runtimes/opencode.js +0 -0
- /package/src/{host/daemon → core}/runtimes/qwen-code.js +0 -0
|
@@ -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;
|
|
@@ -44,6 +44,10 @@ export const TOOLS = Object.freeze({
|
|
|
44
44
|
|
|
45
45
|
// Interaction
|
|
46
46
|
ASK_QUESTIONS: "ask_questions",
|
|
47
|
+
// Synthesised by run-agent.js rather than living in handlers/ — it is how the
|
|
48
|
+
// model declares the turn is over. It was missing from this catalog, so
|
|
49
|
+
// security.js referenced it as a bare "finish" literal.
|
|
50
|
+
FINISH: "finish",
|
|
47
51
|
|
|
48
52
|
// Delegation / external
|
|
49
53
|
CALL_AGENT: "call_agent",
|
|
@@ -195,3 +199,57 @@ export const CODE_PLAN_TOOLS = Object.freeze([
|
|
|
195
199
|
* "*" as "expose every tool the channel is otherwise allowed to see".
|
|
196
200
|
*/
|
|
197
201
|
export const CODE_BUILD_TOOLS = "*";
|
|
202
|
+
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Behavioural tool sets
|
|
205
|
+
//
|
|
206
|
+
// These drive real user-facing protections, so they belong next to the names
|
|
207
|
+
// they reference rather than as string literals scattered across the loop. The
|
|
208
|
+
// side-effect set in particular was an inline `new Set([...])` inside
|
|
209
|
+
// runAgent(): rename a tool there and the de-duplication below silently stops
|
|
210
|
+
// working, with the only symptom being a user receiving the same Telegram
|
|
211
|
+
// message three times. tests/tool-name-sets.test.js fails if any member here
|
|
212
|
+
// stops being a real tool.
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Tools that mutate the world. Weak models (Gemini especially) re-emit the
|
|
217
|
+
* same call across iterations; for these we remember the (name + args)
|
|
218
|
+
* signature and answer duplicates with a synthetic "already done" instead of
|
|
219
|
+
* running them again. Read-only tools are exempt — they are idempotent and are
|
|
220
|
+
* legitimately repeated (list_tasks before and after a change).
|
|
221
|
+
*/
|
|
222
|
+
export const SIDE_EFFECT_TOOLS = new Set([
|
|
223
|
+
TOOLS.SEND_TELEGRAM,
|
|
224
|
+
TOOLS.CREATE_TASK,
|
|
225
|
+
TOOLS.WRITE_FILE,
|
|
226
|
+
TOOLS.EDIT_FILE,
|
|
227
|
+
TOOLS.RUN_SHELL,
|
|
228
|
+
TOOLS.CALL_RUNTIME,
|
|
229
|
+
TOOLS.ADD_PROJECT,
|
|
230
|
+
TOOLS.SET_IDENTITY,
|
|
231
|
+
]);
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Tools that only acknowledge — the turn has produced no new information for
|
|
235
|
+
* the user, so a run of them is capped (MAX_CONSECUTIVE_ACKS).
|
|
236
|
+
*/
|
|
237
|
+
export const ACK_ONLY_TOOLS = new Set([TOOLS.SEND_TELEGRAM]);
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Tools whose semantics REQUIRE handing control back to a human. The loop
|
|
241
|
+
* breaks after these even under a completion contract, because the task
|
|
242
|
+
* cannot advance without a reply. Without it, models under forced toolChoice
|
|
243
|
+
* ask the same question every iteration.
|
|
244
|
+
*/
|
|
245
|
+
export const TURN_ENDING_TOOLS = new Set([TOOLS.ASK_QUESTIONS]);
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Tools exempt from the injected `security_risk` parameter: they cannot touch
|
|
249
|
+
* anything, so grading them is noise the model has to pay for on every call.
|
|
250
|
+
*/
|
|
251
|
+
export const RISK_EXEMPT_TOOLS = new Set([
|
|
252
|
+
TOOLS.FINISH,
|
|
253
|
+
TOOLS.ASK_QUESTIONS,
|
|
254
|
+
TOOLS.DISCOVER_TOOLS,
|
|
255
|
+
]);
|
|
@@ -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);
|
|
@@ -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
|
+
}
|