@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
|
@@ -14,6 +14,7 @@ import path from "node:path";
|
|
|
14
14
|
import { nowIso } from "../util/time.js";
|
|
15
15
|
import { shortId as makeShortId } from "../util/ids.js";
|
|
16
16
|
import { CODE_MODES, DEFAULT_CODE_MODE } from "../constants/code-modes.js";
|
|
17
|
+
import { readJson, writeJson } from "#core/util/json-file.js";
|
|
17
18
|
|
|
18
19
|
function sessionsDir(storagePath) {
|
|
19
20
|
return path.join(storagePath, "code-sessions");
|
|
@@ -28,20 +29,8 @@ function shortId() {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
// Atomic write: tmp file + rename so a crash mid-write can't corrupt a session.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const tmp = `${file}.${process.pid}.tmp`;
|
|
34
|
-
fs.writeFileSync(tmp, JSON.stringify(obj, null, 2));
|
|
35
|
-
fs.renameSync(tmp, file);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function readJson(file) {
|
|
39
|
-
try {
|
|
40
|
-
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
41
|
-
} catch {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
32
|
+
// readJson/writeJson come from the shared kernel (this module's atomic write
|
|
33
|
+
// was the one the others were missing).
|
|
45
34
|
|
|
46
35
|
/** Lightweight row for the session list (no messages). */
|
|
47
36
|
function toRow(s) {
|
|
@@ -13,10 +13,6 @@ import path from "node:path";
|
|
|
13
13
|
|
|
14
14
|
const homeDir = (opts) => (opts && opts.home) || os.homedir();
|
|
15
15
|
|
|
16
|
-
function encodeClaudeProjectPath(cwd) {
|
|
17
|
-
return String(cwd || "").replace(/[^A-Za-z0-9]/g, "-");
|
|
18
|
-
}
|
|
19
|
-
|
|
20
16
|
function safeStatMtime(p) {
|
|
21
17
|
try {
|
|
22
18
|
return fs.statSync(p).mtimeMs;
|
|
@@ -29,11 +29,6 @@ function dayPathJsonl(projectRoot, ts) {
|
|
|
29
29
|
return path.join(projectRoot, "messages", `${day}.jsonl`);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
function dayPathMd(projectRoot, ts) {
|
|
33
|
-
const day = (ts || nowIso()).slice(0, 10);
|
|
34
|
-
return path.join(projectRoot, "messages", `${day}.md`);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
32
|
// `compact` is a progressive-compaction summary record (Pieza 3): a dense
|
|
38
33
|
// recap of older turns, stored inline in the channel JSONL so the reader can
|
|
39
34
|
// prepend it as a [RESUMEN COMPACTADO] system turn instead of replaying the
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import fs from "node:fs";
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import { apcOrganizationFile } from "../apc/paths.js";
|
|
22
|
+
import { readJson } from "#core/util/json-file.js";
|
|
22
23
|
|
|
23
24
|
export const ORG_SLUG_RE = /^[a-z][a-z0-9_-]*$/;
|
|
24
25
|
|
|
@@ -39,17 +40,14 @@ function emptyOrg() {
|
|
|
39
40
|
|
|
40
41
|
export function readOrganization(root) {
|
|
41
42
|
const file = apcOrganizationFile(root);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
// A corrupt file shouldn't take down the whole project view.
|
|
51
|
-
return emptyOrg();
|
|
52
|
-
}
|
|
43
|
+
// A missing or corrupt file shouldn't take down the whole project view —
|
|
44
|
+
// readJson returns the fallback for both.
|
|
45
|
+
const raw = readJson(file, null);
|
|
46
|
+
if (!raw) return emptyOrg();
|
|
47
|
+
return {
|
|
48
|
+
areas: Array.isArray(raw.areas) ? raw.areas : [],
|
|
49
|
+
roles: Array.isArray(raw.roles) ? raw.roles : [],
|
|
50
|
+
};
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
function writeOrganization(root, org) {
|
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { CronExpressionParser } from "cron-parser";
|
|
6
6
|
import { nowIso, isoToMs } from "../util/time.js";
|
|
7
7
|
import { shortId } from "../util/ids.js";
|
|
8
|
+
import { readJson, writeJson } from "#core/util/json-file.js";
|
|
8
9
|
|
|
9
10
|
function routinesPath(storagePath) {
|
|
10
11
|
// storagePath is always ~/.apx/projects/{apxId}/ — flat, no .apc subdir needed.
|
|
@@ -12,20 +13,14 @@ function routinesPath(storagePath) {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function readFile(projectPath) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
const raw = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
19
|
-
return Array.isArray(raw.routines) ? raw.routines : [];
|
|
20
|
-
} catch {
|
|
21
|
-
return [];
|
|
22
|
-
}
|
|
16
|
+
const raw = readJson(routinesPath(projectPath), {});
|
|
17
|
+
return Array.isArray(raw?.routines) ? raw.routines : [];
|
|
23
18
|
}
|
|
24
19
|
|
|
20
|
+
// Atomic: a crash mid-write used to leave a truncated routines.json, and since
|
|
21
|
+
// the reader swallows parse errors the next read reported "no routines".
|
|
25
22
|
function writeFile(projectPath, routines) {
|
|
26
|
-
|
|
27
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
28
|
-
fs.writeFileSync(p, JSON.stringify({ routines }, null, 2) + "\n");
|
|
23
|
+
writeJson(routinesPath(projectPath), { routines });
|
|
29
24
|
}
|
|
30
25
|
|
|
31
26
|
// --------------------- schedule parsing -------------------------------------
|
|
@@ -18,6 +18,7 @@ import fs from "node:fs";
|
|
|
18
18
|
import path from "node:path";
|
|
19
19
|
import { APX_HOME } from "#core/config/paths.js";
|
|
20
20
|
import { nowIso } from "#core/util/time.js";
|
|
21
|
+
import { readJson } from "#core/util/json-file.js";
|
|
21
22
|
|
|
22
23
|
export const PENDING_CALLBACKS_DIR = path.join(APX_HOME, "pending-callbacks");
|
|
23
24
|
|
|
@@ -63,7 +64,7 @@ export function listPendingCallbacks() {
|
|
|
63
64
|
const out = [];
|
|
64
65
|
for (const f of files) {
|
|
65
66
|
try {
|
|
66
|
-
const entry =
|
|
67
|
+
const entry = readJson(path.join(PENDING_CALLBACKS_DIR, f), null);
|
|
67
68
|
if (entry?.session_id) out.push(entry);
|
|
68
69
|
} catch {
|
|
69
70
|
/* skip corrupt */
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// JSON files on disk — one implementation.
|
|
2
|
+
//
|
|
3
|
+
// Six modules had grown their own read/write pair and 35 more sites inlined
|
|
4
|
+
// `JSON.parse(fs.readFileSync(...))`. They did not agree on the things that
|
|
5
|
+
// matter, so the guarantees your data got depended on which copy happened to
|
|
6
|
+
// touch it:
|
|
7
|
+
//
|
|
8
|
+
// - Atomicity. Only core/stores/code-sessions.js wrote through a temp file
|
|
9
|
+
// and renamed. Everywhere else, a crash or a full disk mid-write left a
|
|
10
|
+
// truncated file — and since the readers swallow parse errors, the next
|
|
11
|
+
// read silently returned "empty" instead of "corrupt". routines.json and
|
|
12
|
+
// the MCP stores were exposed to that.
|
|
13
|
+
// - Permissions. vars and mcp chmod 0600 because they hold tokens; the
|
|
14
|
+
// others did not, so the same kind of data could land world-readable
|
|
15
|
+
// depending on the writer.
|
|
16
|
+
// - Missing-file semantics. Some returned null, some {}, some threw.
|
|
17
|
+
//
|
|
18
|
+
// Reads are forgiving by design (a missing or corrupt file yields the caller's
|
|
19
|
+
// fallback), because every caller here treats "no data" as a valid state.
|
|
20
|
+
// Writes are strict: they create the directory, write atomically, and throw if
|
|
21
|
+
// they cannot.
|
|
22
|
+
import fs from "node:fs";
|
|
23
|
+
import fsp from "node:fs/promises";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
|
|
26
|
+
/** Mode for files that may contain tokens or credentials. */
|
|
27
|
+
export const SECRET_MODE = 0o600;
|
|
28
|
+
|
|
29
|
+
// Twelve call sites still parse inline, on purpose. They are the ones whose
|
|
30
|
+
// failure mode is not "fall back to empty":
|
|
31
|
+
//
|
|
32
|
+
// config/index.js a corrupt ~/.apx/config.json must THROW, loudly. Its
|
|
33
|
+
// silent fallback would be an empty config, i.e. every
|
|
34
|
+
// key reset — the worst possible recovery.
|
|
35
|
+
// mcp/sources.js, these report *which* file failed and why, so the user
|
|
36
|
+
// integrations/sources can fix it; a bare fallback would hide the problem.
|
|
37
|
+
// profiles/lifecycle, these read package.json / manifests where a parse
|
|
38
|
+
// project-config, … error is a real error, not a missing-file state.
|
|
39
|
+
//
|
|
40
|
+
// If you are adding a new read whose answer on failure is "treat it as empty",
|
|
41
|
+
// use readJson. If it is "tell the user", keep it explicit.
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Read and parse a JSON file.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} file
|
|
47
|
+
* @param {unknown} [fallback=null] returned when the file is missing, empty or
|
|
48
|
+
* unparseable. Pass `{}` or `[]` when the caller wants a shape it can spread.
|
|
49
|
+
*/
|
|
50
|
+
export function readJson(file, fallback = null) {
|
|
51
|
+
let raw;
|
|
52
|
+
try {
|
|
53
|
+
raw = fs.readFileSync(file, "utf8");
|
|
54
|
+
} catch {
|
|
55
|
+
return fallback; // missing file is a normal state, not an error
|
|
56
|
+
}
|
|
57
|
+
if (!raw.trim()) return fallback;
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(raw);
|
|
60
|
+
} catch {
|
|
61
|
+
return fallback;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Async variant, for request paths that must not block the event loop. */
|
|
66
|
+
export async function readJsonAsync(file, fallback = null) {
|
|
67
|
+
let raw;
|
|
68
|
+
try {
|
|
69
|
+
raw = await fsp.readFile(file, "utf8");
|
|
70
|
+
} catch {
|
|
71
|
+
return fallback;
|
|
72
|
+
}
|
|
73
|
+
if (!raw.trim()) return fallback;
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(raw);
|
|
76
|
+
} catch {
|
|
77
|
+
return fallback;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Write a value as pretty JSON, atomically.
|
|
83
|
+
*
|
|
84
|
+
* Writes to `<file>.<pid>.tmp` and renames, so a reader never observes a
|
|
85
|
+
* half-written file and a crash cannot destroy the previous contents.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} file
|
|
88
|
+
* @param {unknown} value
|
|
89
|
+
* @param {{ mode?: number }} [opts] `mode: SECRET_MODE` for files holding
|
|
90
|
+
* credentials.
|
|
91
|
+
*/
|
|
92
|
+
export function writeJson(file, value, { mode } = {}) {
|
|
93
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
94
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
95
|
+
try {
|
|
96
|
+
fs.writeFileSync(tmp, JSON.stringify(value, null, 2) + "\n");
|
|
97
|
+
if (mode !== undefined) fs.chmodSync(tmp, mode);
|
|
98
|
+
fs.renameSync(tmp, file);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
// Never leave the temp file behind on a failed write.
|
|
101
|
+
try { fs.unlinkSync(tmp); } catch { /* already gone */ }
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Async variant. Same atomic rename. */
|
|
107
|
+
export async function writeJsonAsync(file, value, { mode } = {}) {
|
|
108
|
+
await fsp.mkdir(path.dirname(file), { recursive: true });
|
|
109
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
110
|
+
try {
|
|
111
|
+
await fsp.writeFile(tmp, JSON.stringify(value, null, 2) + "\n");
|
|
112
|
+
if (mode !== undefined) await fsp.chmod(tmp, mode);
|
|
113
|
+
await fsp.rename(tmp, file);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
try { await fsp.unlink(tmp); } catch { /* already gone */ }
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Read, transform, write back — atomically, with the same fallback rules.
|
|
122
|
+
* Returns the value that was written.
|
|
123
|
+
*/
|
|
124
|
+
export function updateJson(file, updater, { fallback = null, mode } = {}) {
|
|
125
|
+
const next = updater(readJson(file, fallback));
|
|
126
|
+
writeJson(file, next, { mode });
|
|
127
|
+
return next;
|
|
128
|
+
}
|
package/src/core/vars/sources.js
CHANGED
|
@@ -13,16 +13,16 @@
|
|
|
13
13
|
//
|
|
14
14
|
// project wins over global when the same name exists in both.
|
|
15
15
|
|
|
16
|
-
import fs from "node:fs";
|
|
17
|
-
import os from "node:os";
|
|
18
16
|
import path from "node:path";
|
|
17
|
+
import { apxHome } from "#core/config/paths.js";
|
|
18
|
+
import { readJson, writeJson, SECRET_MODE } from "#core/util/json-file.js";
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
const
|
|
20
|
+
|
|
21
|
+
const globalVarsFile = () => path.join(apxHome(), "vars.json");
|
|
22
22
|
const PROJECT_VARS_FILENAME = "vars.json";
|
|
23
23
|
|
|
24
24
|
export function globalVarsPath() {
|
|
25
|
-
return
|
|
25
|
+
return globalVarsFile();
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function projectVarsPath(storagePath) {
|
|
@@ -30,32 +30,25 @@ export function projectVarsPath(storagePath) {
|
|
|
30
30
|
return path.join(storagePath, PROJECT_VARS_FILENAME);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// A vars file must be a plain object; anything else is treated as empty.
|
|
33
34
|
function readJsonSafe(absPath) {
|
|
34
|
-
if (!absPath
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return json && typeof json === "object" && !Array.isArray(json) ? json : {};
|
|
38
|
-
} catch {
|
|
39
|
-
return {};
|
|
40
|
-
}
|
|
35
|
+
if (!absPath) return {};
|
|
36
|
+
const json = readJson(absPath, {});
|
|
37
|
+
return json && typeof json === "object" && !Array.isArray(json) ? json : {};
|
|
41
38
|
}
|
|
42
39
|
|
|
40
|
+
// SECRET_MODE: these files hold tokens. The write is atomic, so a crash can no
|
|
41
|
+
// longer truncate the store.
|
|
43
42
|
function writeJsonSecure(absPath, obj) {
|
|
44
|
-
|
|
45
|
-
fs.writeFileSync(absPath, JSON.stringify(obj, null, 2) + "\n");
|
|
46
|
-
try {
|
|
47
|
-
fs.chmodSync(absPath, 0o600);
|
|
48
|
-
} catch {
|
|
49
|
-
// Best effort on non-POSIX filesystems.
|
|
50
|
-
}
|
|
43
|
+
writeJson(absPath, obj, { mode: SECRET_MODE });
|
|
51
44
|
}
|
|
52
45
|
|
|
53
46
|
export function readGlobalVars() {
|
|
54
|
-
return readJsonSafe(
|
|
47
|
+
return readJsonSafe(globalVarsFile());
|
|
55
48
|
}
|
|
56
49
|
|
|
57
50
|
export function writeGlobalVars(obj) {
|
|
58
|
-
writeJsonSecure(
|
|
51
|
+
writeJsonSecure(globalVarsFile(), obj);
|
|
59
52
|
}
|
|
60
53
|
|
|
61
54
|
export function readProjectVars(storagePath) {
|
package/src/core/voice/tts.js
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
//
|
|
6
6
|
// All audio files land under ~/.apx/tmp/tts/<uuid>.<ext>. Callers are
|
|
7
7
|
// responsible for cleaning up (Telegram plugin already uses similar temp
|
|
8
|
-
// files via /telegram/send_voice).
|
|
8
|
+
// files via /api/telegram/send_voice).
|
|
9
9
|
|
|
10
10
|
import fs from "node:fs";
|
|
11
|
-
import
|
|
12
|
-
|
|
11
|
+
import { TTS_TMP_DIR } from "#core/config/paths.js";
|
|
12
|
+
|
|
13
|
+
export { TTS_TMP_DIR };
|
|
13
14
|
import { readConfig } from "../config/index.js";
|
|
14
15
|
import {
|
|
15
16
|
selectTtsEngine,
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
} from "./engines/index.js";
|
|
20
21
|
import { emotionConfigFor, stripEmotionTags } from "./emotions.js";
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
|
|
23
24
|
|
|
24
25
|
export function ensureTtsTmpDir() {
|
|
25
26
|
fs.mkdirSync(TTS_TMP_DIR, { recursive: true });
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { readConfig, writeConfig } from "#core/config/index.js";
|
|
9
9
|
import { resolveAgentName } from "#core/identity/index.js";
|
|
10
10
|
import { setDottedKey, unsetDottedKey } from "../project-config.js";
|
|
11
|
-
import { PERMISSION_MODES
|
|
11
|
+
import { PERMISSION_MODES } from "#core/constants/permissions.js";
|
|
12
12
|
import {
|
|
13
13
|
redactConfig as redact,
|
|
14
14
|
isSecretMarker,
|
|
@@ -16,8 +16,8 @@ import {
|
|
|
16
16
|
} from "#core/config/redact.js";
|
|
17
17
|
import { collectSecretValues, registerSecretValues } from "#core/config/secret-values.js";
|
|
18
18
|
|
|
19
|
-
export function register(
|
|
20
|
-
|
|
19
|
+
export function register(api, { config, scheduler, plugins }) {
|
|
20
|
+
api.get("/admin/config", (_req, res) => {
|
|
21
21
|
try {
|
|
22
22
|
const fresh = readConfig();
|
|
23
23
|
res.json({ config: redact(fresh) });
|
|
@@ -26,7 +26,7 @@ export function register(app, { config, scheduler, plugins }) {
|
|
|
26
26
|
}
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
api.patch("/admin/config", (req, res) => {
|
|
30
30
|
const { set, unset } = req.body || {};
|
|
31
31
|
let cfg;
|
|
32
32
|
try {
|
|
@@ -68,7 +68,7 @@ export function register(app, { config, scheduler, plugins }) {
|
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
// Convenience: GET the resolved super_agent prompt (used by SettingsScreen)
|
|
71
|
-
|
|
71
|
+
api.get("/admin/super-agent", (_req, res) => {
|
|
72
72
|
try {
|
|
73
73
|
const fresh = readConfig();
|
|
74
74
|
const sa = fresh.super_agent || {};
|
|
@@ -8,11 +8,12 @@ import { execFile } from "node:child_process";
|
|
|
8
8
|
import fs from "node:fs";
|
|
9
9
|
import os from "node:os";
|
|
10
10
|
import path from "node:path";
|
|
11
|
+
import { LOG_DIR } from "#core/config/paths.js";
|
|
11
12
|
|
|
12
|
-
export function register(
|
|
13
|
+
export function register(api, { scheduler, plugins, config, registries }) {
|
|
13
14
|
// Daemon logs: errors.jsonl (structured) or apx.log (plain), newest first.
|
|
14
|
-
|
|
15
|
-
const dir =
|
|
15
|
+
api.get("/admin/logs", (req, res) => {
|
|
16
|
+
const dir = LOG_DIR;
|
|
16
17
|
const which = req.query.file === "apx" ? "apx.log" : "errors.jsonl";
|
|
17
18
|
const file = path.join(dir, which);
|
|
18
19
|
const limit = Math.min(parseInt(req.query.limit, 10) || 200, 2000);
|
|
@@ -26,7 +27,7 @@ export function register(app, { scheduler, plugins, config, registries }) {
|
|
|
26
27
|
res.json({ file: which, lines: tail });
|
|
27
28
|
});
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
api.post("/admin/reload", (_req, res) => {
|
|
30
31
|
try {
|
|
31
32
|
const fresh = readConfig();
|
|
32
33
|
// Mutate in place so every closure that captured `config` sees the new
|
|
@@ -46,7 +47,7 @@ export function register(app, { scheduler, plugins, config, registries }) {
|
|
|
46
47
|
}
|
|
47
48
|
});
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
api.post("/admin/shutdown", (_req, res) => {
|
|
50
51
|
res.json({ ok: true });
|
|
51
52
|
setTimeout(() => process.exit(0), 50);
|
|
52
53
|
});
|
|
@@ -56,7 +57,7 @@ export function register(app, { scheduler, plugins, config, registries }) {
|
|
|
56
57
|
// zenity (if present); Windows uses PowerShell's Shell.Application. If the
|
|
57
58
|
// platform lacks a usable picker — or none is installed — the endpoint
|
|
58
59
|
// returns 501 so the frontend can fall back to the inline directory list.
|
|
59
|
-
|
|
60
|
+
api.get("/admin/fs/pick-dir", (req, res) => {
|
|
60
61
|
// The prompt is attacker-controllable (query string). NEVER interpolate it
|
|
61
62
|
// into a shell string — use execFile (no shell) with an argv array, and
|
|
62
63
|
// pass the prompt out-of-band via an env var so it can't break out of any
|
|
@@ -109,7 +110,7 @@ export function register(app, { scheduler, plugins, config, registries }) {
|
|
|
109
110
|
});
|
|
110
111
|
});
|
|
111
112
|
|
|
112
|
-
|
|
113
|
+
api.get("/admin/fs/dirs", (req, res) => {
|
|
113
114
|
const requested = String(req.query.path || os.homedir());
|
|
114
115
|
const base = path.resolve(requested.replace(/^~(?=$|\/)/, os.homedir()));
|
|
115
116
|
try {
|
|
@@ -70,12 +70,12 @@ function normalizeAutonomy(v) {
|
|
|
70
70
|
return AUTONOMY_VALUES.has(v) ? v : undefined;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
export function register(
|
|
73
|
+
export function register(api, { projects, project }) {
|
|
74
74
|
// Vault = global agent templates. Two-layer: bundled defaults shipped with
|
|
75
75
|
// APX (assets/agent-vault-defaults/) + user overrides/new ones in
|
|
76
76
|
// ~/.apx/agents/. The user layer wins per slug; tombstones in .removed.json
|
|
77
77
|
// hide bundled entries. GET merges both with `source` set per item.
|
|
78
|
-
|
|
78
|
+
api.get("/agents/vault", (req, res) => {
|
|
79
79
|
const includeRemoved = req.query?.include_removed === "1";
|
|
80
80
|
res.json(readVaultAgents({ includeRemoved }).map((a) => ({
|
|
81
81
|
...agentToResponse(a),
|
|
@@ -84,7 +84,7 @@ export function register(app, { projects, project }) {
|
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
// Create or replace a vault template (user layer / copy-on-write).
|
|
87
|
-
|
|
87
|
+
api.post("/agents/vault", (req, res) => {
|
|
88
88
|
const { slug, fields, body = "" } = req.body || {};
|
|
89
89
|
if (!slug || !/^[a-z][a-z0-9_-]*$/.test(slug)) {
|
|
90
90
|
return res.status(400).json({ error: "valid slug required" });
|
|
@@ -100,7 +100,7 @@ export function register(app, { projects, project }) {
|
|
|
100
100
|
|
|
101
101
|
// Patch a vault template. If the slug is bundled-only, copy it to the user
|
|
102
102
|
// layer first (the writer already does this), then apply the merged fields.
|
|
103
|
-
|
|
103
|
+
api.patch("/agents/vault/:slug", (req, res) => {
|
|
104
104
|
const { slug } = req.params;
|
|
105
105
|
const current = readVaultAgent(slug);
|
|
106
106
|
if (!current) return res.status(404).json({ error: `vault agent ${slug} not found` });
|
|
@@ -118,14 +118,14 @@ export function register(app, { projects, project }) {
|
|
|
118
118
|
|
|
119
119
|
// Delete a vault template. Tombstones bundled slugs so they stay hidden;
|
|
120
120
|
// deletes the user-layer file otherwise. POST .../restore lifts a tombstone.
|
|
121
|
-
|
|
121
|
+
api.delete("/agents/vault/:slug", (req, res) => {
|
|
122
122
|
const { slug } = req.params;
|
|
123
123
|
const out = removeVaultAgent(slug);
|
|
124
124
|
if (!out.removed) return res.status(404).json({ error: `vault agent ${slug} not found` });
|
|
125
125
|
res.json({ ok: true, ...out });
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
api.post("/agents/vault/:slug/restore", (req, res) => {
|
|
129
129
|
const { slug } = req.params;
|
|
130
130
|
const out = restoreVaultAgent(slug);
|
|
131
131
|
if (!out.restored) return res.status(404).json({ error: `slug ${slug} was not tombstoned` });
|
|
@@ -134,7 +134,7 @@ export function register(app, { projects, project }) {
|
|
|
134
134
|
});
|
|
135
135
|
|
|
136
136
|
// Import a vault template into a project (copies it to .apc/agents/<slug>.md).
|
|
137
|
-
|
|
137
|
+
api.post("/projects/:pid/agents/import", (req, res) => {
|
|
138
138
|
const p = project(req, res);
|
|
139
139
|
if (!p) return;
|
|
140
140
|
const { slug } = req.body || {};
|
|
@@ -154,7 +154,7 @@ export function register(app, { projects, project }) {
|
|
|
154
154
|
}
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
api.get("/projects/:pid/agents", (req, res) => {
|
|
158
158
|
const p = project(req, res);
|
|
159
159
|
if (!p) return;
|
|
160
160
|
const agents = readAgents(p.path).map(agentToResponse);
|
|
@@ -162,7 +162,7 @@ export function register(app, { projects, project }) {
|
|
|
162
162
|
res.json(agents);
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
api.get("/projects/:pid/agents/:slug", (req, res) => {
|
|
166
166
|
const p = project(req, res);
|
|
167
167
|
if (!p) return;
|
|
168
168
|
const agents = readAgents(p.path);
|
|
@@ -172,7 +172,7 @@ export function register(app, { projects, project }) {
|
|
|
172
172
|
res.json({ ...agentToResponse(a), memory, system: a.body || "" });
|
|
173
173
|
});
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
api.post("/projects/:pid/agents", (req, res) => {
|
|
176
176
|
const p = project(req, res);
|
|
177
177
|
if (!p) return;
|
|
178
178
|
const {
|
|
@@ -213,7 +213,7 @@ export function register(app, { projects, project }) {
|
|
|
213
213
|
|
|
214
214
|
// Edit an existing agent. Merges provided fields into the AGENT.md
|
|
215
215
|
// frontmatter; `system` rewrites the body (the agent's system prompt).
|
|
216
|
-
|
|
216
|
+
api.patch("/projects/:pid/agents/:slug", (req, res) => {
|
|
217
217
|
const p = project(req, res);
|
|
218
218
|
if (!p) return;
|
|
219
219
|
const slug = req.params.slug;
|
|
@@ -255,7 +255,7 @@ export function register(app, { projects, project }) {
|
|
|
255
255
|
});
|
|
256
256
|
|
|
257
257
|
// Delete an agent: removes .apc/agents/<slug>.md and runtime data dir.
|
|
258
|
-
|
|
258
|
+
api.delete("/projects/:pid/agents/:slug", (req, res) => {
|
|
259
259
|
const p = project(req, res);
|
|
260
260
|
if (!p) return;
|
|
261
261
|
const slug = req.params.slug;
|
|
@@ -276,7 +276,7 @@ export function register(app, { projects, project }) {
|
|
|
276
276
|
});
|
|
277
277
|
|
|
278
278
|
// ---- Project-level memory (.apc/memory.md) ----
|
|
279
|
-
|
|
279
|
+
api.get("/projects/:pid/memory", (req, res) => {
|
|
280
280
|
const p = project(req, res);
|
|
281
281
|
if (!p) return;
|
|
282
282
|
const memPath = apcMemoryFile(p.path);
|
|
@@ -284,7 +284,7 @@ export function register(app, { projects, project }) {
|
|
|
284
284
|
res.json({ body, path: memPath });
|
|
285
285
|
});
|
|
286
286
|
|
|
287
|
-
|
|
287
|
+
api.put("/projects/:pid/memory", (req, res) => {
|
|
288
288
|
const p = project(req, res);
|
|
289
289
|
if (!p) return;
|
|
290
290
|
const { body } = req.body || {};
|
|
@@ -298,7 +298,7 @@ export function register(app, { projects, project }) {
|
|
|
298
298
|
});
|
|
299
299
|
|
|
300
300
|
// ---- Per-agent memory ----
|
|
301
|
-
|
|
301
|
+
api.get("/projects/:pid/agents/:slug/memory", (req, res) => {
|
|
302
302
|
const p = project(req, res);
|
|
303
303
|
if (!p) return;
|
|
304
304
|
// Validate the agent exists — otherwise an unknown slug returned 200 with an
|
|
@@ -308,7 +308,7 @@ export function register(app, { projects, project }) {
|
|
|
308
308
|
res.json({ body: readAgentMemory(p, req.params.slug) });
|
|
309
309
|
});
|
|
310
310
|
|
|
311
|
-
|
|
311
|
+
api.put("/projects/:pid/agents/:slug/memory", (req, res) => {
|
|
312
312
|
const p = project(req, res);
|
|
313
313
|
if (!p) return;
|
|
314
314
|
if (!readAgents(p.path).some((a) => a.slug === req.params.slug))
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
import { previews } from "#core/artifacts/preview.js";
|
|
13
13
|
import { tunnels, detectProviders } from "#core/artifacts/tunnel.js";
|
|
14
14
|
|
|
15
|
-
export function register(
|
|
15
|
+
export function register(api, { project }) {
|
|
16
16
|
// Start (or reuse) an ephemeral preview server for an artifact.
|
|
17
|
-
|
|
17
|
+
api.post("/projects/:pid/artifacts/:name/preview", async (req, res) => {
|
|
18
18
|
const p = project(req, res);
|
|
19
19
|
if (!p) return;
|
|
20
20
|
const name = decodeURIComponent(req.params.name);
|
|
@@ -33,24 +33,24 @@ export function register(app, { project }) {
|
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
// List preview servers scoped to a project.
|
|
36
|
-
|
|
36
|
+
api.get("/projects/:pid/previews", (req, res) => {
|
|
37
37
|
const p = project(req, res);
|
|
38
38
|
if (!p) return;
|
|
39
39
|
res.json(previews.list(p.id));
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
// List every live preview (all projects).
|
|
43
|
-
|
|
43
|
+
api.get("/previews", (_req, res) => {
|
|
44
44
|
res.json(previews.list());
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
// Which tunnel providers this host can use, best first.
|
|
48
|
-
|
|
48
|
+
api.get("/previews/tunnel-providers", (_req, res) => {
|
|
49
49
|
res.json({ providers: detectProviders() });
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
// Stop a preview server (also closes its tunnel).
|
|
53
|
-
|
|
53
|
+
api.delete("/previews/:id", async (req, res) => {
|
|
54
54
|
const rec = previews.get(req.params.id);
|
|
55
55
|
if (rec?.tunnel) tunnels.close(rec.tunnel.id);
|
|
56
56
|
const ok = await previews.stop(req.params.id);
|
|
@@ -58,7 +58,7 @@ export function register(app, { project }) {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
// Open a public tunnel to a preview's local port.
|
|
61
|
-
|
|
61
|
+
api.post("/previews/:id/tunnel", async (req, res) => {
|
|
62
62
|
const rec = previews.get(req.params.id);
|
|
63
63
|
if (!rec) return res.status(404).json({ error: "preview not found" });
|
|
64
64
|
if (rec.tunnel) return res.json(rec.tunnel); // already shared
|
|
@@ -72,7 +72,7 @@ export function register(app, { project }) {
|
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
// Close a preview's tunnel but keep the local server running.
|
|
75
|
-
|
|
75
|
+
api.delete("/previews/:id/tunnel", (req, res) => {
|
|
76
76
|
const rec = previews.get(req.params.id);
|
|
77
77
|
if (!rec || !rec.tunnel) return res.status(404).end();
|
|
78
78
|
tunnels.close(rec.tunnel.id);
|