@agentprojectcontext/apx 1.32.0 → 1.33.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/package.json +6 -1
- package/skills/apc-context/SKILL.md +5 -2
- package/skills/apx/SKILL.md +3 -3
- package/skills/apx-agency-agents/SKILL.md +5 -5
- package/skills/apx-agent/SKILL.md +7 -7
- package/skills/apx-mcp/SKILL.md +6 -4
- package/skills/apx-mcp-builder/SKILL.md +4 -7
- package/skills/apx-project/SKILL.md +4 -5
- package/skills/apx-routine/SKILL.md +14 -12
- package/skills/apx-runtime/SKILL.md +5 -3
- package/skills/apx-sessions/SKILL.md +5 -5
- package/skills/apx-skill-builder/SKILL.md +10 -6
- package/skills/apx-task/SKILL.md +8 -8
- package/skills/apx-telegram/SKILL.md +23 -7
- package/skills/apx-voice/SKILL.md +8 -6
- package/src/core/{agent-system.js → agent/build-agent-system.js} +10 -12
- package/src/core/agent/index.js +0 -2
- package/src/core/{agent-memory.js → agent/memory.js} +2 -2
- package/src/core/agent/model-router.js +21 -43
- package/src/core/agent/prompt-builder.js +17 -63
- package/src/core/agent/prompts/action-discipline.md +24 -0
- package/src/core/agent/prompts/channels/code.md +8 -12
- package/src/core/agent/prompts/channels/desktop.md +6 -4
- package/src/core/agent/prompts/channels/routine.md +10 -1
- package/src/core/agent/prompts/channels/telegram.md +10 -1
- package/src/core/agent/prompts/channels/web_code.md +20 -0
- package/src/core/agent/prompts/modes/voice.md +2 -2
- package/src/core/agent/prompts/super-agent-base.md +2 -2
- package/src/core/agent/run-agent.js +37 -35
- package/src/core/agent/runtime-bridge.js +42 -0
- package/src/core/agent/self-memory.js +19 -9
- package/src/core/agent/skills/catalog.js +65 -0
- package/src/core/agent/skills/index.js +6 -0
- package/src/{host/daemon/skills-loader.js → core/agent/skills/loader.js} +3 -3
- package/src/core/agent/skills/rag.js +91 -0
- package/src/core/agent/skills/trigger.js +71 -0
- package/src/{host/daemon → core/agent}/super-agent.js +5 -5
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/add-project.js +3 -4
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-agent.js +2 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-mcp.js +1 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/call-runtime.js +10 -11
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/create-task.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/discover-tools.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/edit-file.js +1 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/import-agent.js +4 -5
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-agents.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-skills.js +7 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-tasks.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-vault-agents.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/load-skill.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-agent-memory.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-self-memory.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/remember.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/run-shell.js +1 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-messages.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-sessions.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/send-telegram.js +0 -2
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/set-identity.js +1 -3
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/set-permission-mode.js +1 -3
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/tail-messages.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/transcribe-audio.js +1 -1
- package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/write-file.js +1 -2
- package/src/core/agent/tools/helpers.js +74 -0
- package/src/{host/daemon/super-agent-tools → core/agent/tools}/registry-bridge.js +3 -3
- package/src/{host/daemon/super-agent-tools/index.js → core/agent/tools/registry.js} +31 -32
- package/src/core/apc/agents-vault.js +37 -0
- package/src/core/{scaffold.js → apc/scaffold.js} +4 -5
- package/src/core/{config.js → config/index.js} +21 -27
- package/src/core/config/paths.js +32 -0
- package/src/core/constants/actors.js +8 -0
- package/src/core/constants/channels.js +19 -0
- package/src/core/constants/index.js +5 -0
- package/src/core/constants/permissions.js +17 -0
- package/src/core/constants/roles.js +9 -0
- package/src/core/engines/_streaming.js +63 -0
- package/src/core/engines/anthropic.js +11 -22
- package/src/core/engines/ollama.js +7 -16
- package/src/core/identity/index.js +8 -0
- package/src/core/{identity.js → identity/self.js} +5 -5
- package/src/core/{telegram-identity.js → identity/telegram.js} +1 -1
- package/src/core/logging.js +1 -1
- package/src/core/mascot.js +1 -1
- package/src/core/memory/active-threads.js +10 -10
- package/src/core/memory/broker.js +9 -9
- package/src/core/memory/compactor.js +2 -2
- package/src/core/memory/index.js +2 -2
- package/src/core/memory/indexer.js +1 -1
- package/src/core/{code-sessions-store.js → stores/code-sessions.js} +7 -8
- package/src/core/{messages-store.js → stores/messages.js} +6 -4
- package/src/core/stores/routine-memory.js +71 -0
- package/src/core/{routines-store.js → stores/routines.js} +1 -3
- package/src/core/stores/runtime-sessions.js +99 -0
- package/src/core/{tasks-store.js → stores/tasks.js} +3 -8
- package/src/core/update-check.js +1 -1
- package/src/core/util/ids.js +14 -0
- package/src/core/util/index.js +2 -0
- package/src/core/util/time.js +9 -0
- package/src/core/voice/tts.js +1 -1
- package/src/host/daemon/api/admin-config.js +4 -3
- package/src/host/daemon/api/admin.js +1 -1
- package/src/host/daemon/api/agents.js +4 -25
- package/src/host/daemon/api/artifacts.js +26 -1
- package/src/host/daemon/api/code.js +62 -17
- package/src/host/daemon/api/confirm.js +1 -1
- package/src/host/daemon/api/connections.js +2 -2
- package/src/host/daemon/api/conversations.js +2 -2
- package/src/host/daemon/api/deck.js +1 -1
- package/src/host/daemon/api/desktop.js +1 -1
- package/src/host/daemon/api/embeddings.js +4 -4
- package/src/host/daemon/api/engines.js +2 -2
- package/src/host/daemon/api/exec.js +20 -5
- package/src/host/daemon/api/identity.js +1 -1
- package/src/host/daemon/api/mcps.js +1 -1
- package/src/host/daemon/api/messages.js +1 -1
- package/src/host/daemon/api/runtimes.js +9 -8
- package/src/host/daemon/api/sessions-search.js +1 -1
- package/src/host/daemon/api/sessions.js +2 -2
- package/src/host/daemon/api/shared.js +5 -4
- package/src/host/daemon/api/skills.js +30 -0
- package/src/host/daemon/api/super-agent.js +29 -9
- package/src/host/daemon/api/tasks.js +2 -2
- package/src/host/daemon/api/telegram.js +1 -1
- package/src/host/daemon/api/tools.js +6 -6
- package/src/host/daemon/api/tts.js +2 -2
- package/src/host/daemon/api/voice.js +14 -12
- package/src/host/daemon/api.js +2 -0
- package/src/host/daemon/compact.js +1 -1
- package/src/host/daemon/db.js +4 -4
- package/src/host/daemon/desktop-ws.js +1 -1
- package/src/host/daemon/index.js +4 -4
- package/src/host/daemon/plugins/{desktop.js → desktop/index.js} +11 -6
- package/src/host/daemon/plugins/index.js +2 -2
- package/src/host/daemon/plugins/{telegram.js → telegram/index.js} +52 -193
- package/src/host/daemon/plugins/telegram/media.js +162 -0
- package/src/host/daemon/projects-helpers.js +54 -0
- package/src/host/daemon/routines.js +28 -12
- package/src/host/daemon/smoke.js +2 -2
- package/src/host/daemon/token-store.js +1 -1
- package/src/host/daemon/transcription.js +2 -2
- package/src/host/daemon/wakeup.js +2 -2
- package/src/interfaces/cli/commands/agent.js +3 -3
- package/src/interfaces/cli/commands/command.js +1 -1
- package/src/interfaces/cli/commands/config.js +3 -2
- package/src/interfaces/cli/commands/desktop.js +1 -1
- package/src/interfaces/cli/commands/exec.js +2 -1
- package/src/interfaces/cli/commands/identity.js +2 -2
- package/src/interfaces/cli/commands/init.js +1 -1
- package/src/interfaces/cli/commands/mcp.js +1 -1
- package/src/interfaces/cli/commands/memory.js +2 -2
- package/src/interfaces/cli/commands/model.js +16 -6
- package/src/interfaces/cli/commands/project.js +1 -1
- package/src/interfaces/cli/commands/routine.js +58 -0
- package/src/interfaces/cli/commands/search.js +1 -1
- package/src/interfaces/cli/commands/session.js +4 -4
- package/src/interfaces/cli/commands/setup.js +4 -3
- package/src/interfaces/cli/commands/skills.js +25 -4
- package/src/interfaces/cli/commands/status.js +1 -1
- package/src/interfaces/cli/commands/sys.js +11 -4
- package/src/interfaces/cli/commands/update.js +1 -1
- package/src/interfaces/cli/index.js +4 -4
- package/src/interfaces/cli/postinstall.js +2 -2
- package/src/interfaces/mcp-server/index.js +1 -1
- package/src/interfaces/tui/component/prompt/index.tsx +3 -1
- package/src/interfaces/tui/context/sdk-apx.tsx +47 -7
- package/src/interfaces/tui/context/sync-apx.tsx +20 -2
- package/src/interfaces/tui/context/sync.tsx +2 -1
- package/src/interfaces/tui/routes/session/index.tsx +151 -136
- package/src/interfaces/tui/routes/session/sidebar-apx.tsx +37 -15
- package/src/interfaces/tui/run.ts +2 -0
- package/src/interfaces/web/dist/assets/index-7dVT2O1S.css +1 -0
- package/src/interfaces/web/dist/assets/index-DWsE_8Nz.js +602 -0
- package/src/interfaces/web/dist/assets/index-DWsE_8Nz.js.map +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +6 -6
- package/src/interfaces/web/src/App.tsx +53 -32
- package/src/interfaces/web/src/components/RobyBubble.tsx +12 -6
- package/src/interfaces/web/src/components/UiSelect.tsx +13 -3
- package/src/interfaces/web/src/components/chat/SkillPicker.tsx +77 -0
- package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +253 -111
- package/src/interfaces/web/src/components/code/CodeChangesTab.tsx +10 -8
- package/src/interfaces/web/src/components/code/CodeComposer.tsx +20 -17
- package/src/interfaces/web/src/components/code/CodeContextTab.tsx +43 -18
- package/src/interfaces/web/src/components/code/CodeFileTree.tsx +212 -0
- package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +121 -0
- package/src/interfaces/web/src/components/code/CodeProjectPicker.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeSessionList.tsx +30 -26
- package/src/interfaces/web/src/components/code/CodeSidePanel.tsx +40 -21
- package/src/interfaces/web/src/components/code/CodeTerminal.tsx +140 -0
- package/src/interfaces/web/src/components/common/TabLayout.tsx +11 -7
- package/src/interfaces/web/src/components/common/TabNav.tsx +3 -3
- package/src/interfaces/web/src/components/layout/ProjectSidebar.tsx +4 -2
- package/src/interfaces/web/src/components/ui/chat-input.tsx +17 -6
- package/src/interfaces/web/src/hooks/useChat.ts +48 -2
- package/src/interfaces/web/src/hooks/useNavCollapseCtx.tsx +83 -0
- package/src/interfaces/web/src/hooks/usePersonaName.ts +11 -0
- package/src/interfaces/web/src/i18n/en.ts +7 -7
- package/src/interfaces/web/src/i18n/es.ts +8 -8
- package/src/interfaces/web/src/lib/api/agents.ts +1 -1
- package/src/interfaces/web/src/lib/api/artifacts.ts +10 -0
- package/src/interfaces/web/src/lib/api/code.ts +4 -2
- package/src/interfaces/web/src/lib/api/skills.ts +25 -0
- package/src/interfaces/web/src/lib/api.ts +1 -0
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +430 -86
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +5 -18
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +1 -8
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +39 -40
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +16 -16
- package/src/skills/apc-context/SKILL.md +159 -0
- package/src/core/agent/ghost-guard.js +0 -24
- package/src/core/agent/prompts/channels/terminal.md +0 -16
- package/src/host/daemon/apc-runtime-context.js +0 -124
- package/src/host/daemon/super-agent-tools/helpers.js +0 -124
- package/src/host/daemon/tool-call-parser.js +0 -2
- package/src/interfaces/web/dist/assets/index-63P_ji1a.js +0 -571
- package/src/interfaces/web/dist/assets/index-63P_ji1a.js.map +0 -1
- package/src/interfaces/web/dist/assets/index-DLWy6dYz.css +0 -1
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/ask-questions.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-files.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-mcps.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/list-projects.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/read-file.js +0 -0
- /package/src/{host/daemon/super-agent-tools/tools → core/agent/tools/handlers}/search-files.js +0 -0
- /package/src/core/agent/{pseudo-tools.js → tools/pseudo-tools.js} +0 -0
- /package/src/core/agent/{tool-call-parser.js → tools/tool-call-parser.js} +0 -0
- /package/src/core/{parser.js → apc/parser.js} +0 -0
- /package/src/core/{apc-skill-sync.js → apc/skill-sync.js} +0 -0
- /package/src/core/{artifacts-store.js → stores/artifacts.js} +0 -0
- /package/src/{host/daemon → core/stores}/engine-sessions.js +0 -0
- /package/src/core/{session-store.js → stores/sessions.js} +0 -0
- /package/src/host/daemon/plugins/{telegram-ask.js → telegram/ask.js} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// "
|
|
1
|
+
// "Active threads on other channels" — a tiny, recency-based awareness block.
|
|
2
2
|
//
|
|
3
3
|
// Unlike the Memory Broker (semantic RAG + remembered notes), this reads the
|
|
4
4
|
// raw cross-channel message log and surfaces the most recent turn from every
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
import fs from "node:fs";
|
|
13
13
|
import path from "node:path";
|
|
14
|
-
import { GLOBAL_MESSAGES_DIR } from "../config.js";
|
|
15
|
-
import { parseDayJsonl } from "../messages
|
|
14
|
+
import { GLOBAL_MESSAGES_DIR } from "../config/index.js";
|
|
15
|
+
import { parseDayJsonl } from "../stores/messages.js";
|
|
16
16
|
|
|
17
17
|
const BODY_CAP = 70;
|
|
18
18
|
|
|
@@ -20,9 +20,9 @@ function ago(ts) {
|
|
|
20
20
|
const then = Date.parse(ts);
|
|
21
21
|
if (!Number.isFinite(then)) return "";
|
|
22
22
|
const mins = Math.max(0, Math.round((Date.now() - then) / 60000));
|
|
23
|
-
if (mins < 60) return
|
|
23
|
+
if (mins < 60) return `${mins} min ago`;
|
|
24
24
|
const hrs = Math.round(mins / 60);
|
|
25
|
-
return
|
|
25
|
+
return `${hrs} h ago`;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// Prefer the last USER turn (most recognizable for "lo de antes"); fall back to
|
|
@@ -69,7 +69,7 @@ function readChannelRecentTurn(baseDir, channel, sinceMs) {
|
|
|
69
69
|
return turn;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
// Build the "#
|
|
72
|
+
// Build the "# Active threads on other channels" block. Returns "" when there's
|
|
73
73
|
// nothing recent on another channel (or the feature is disabled).
|
|
74
74
|
export function buildActiveThreadsBlock(currentChannel, { config, messagesDir } = {}) {
|
|
75
75
|
try {
|
|
@@ -111,10 +111,10 @@ export function buildActiveThreadsBlock(currentChannel, { config, messagesDir }
|
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
return [
|
|
114
|
-
"#
|
|
115
|
-
|
|
116
|
-
'"
|
|
117
|
-
"
|
|
114
|
+
"# Active threads on other channels",
|
|
115
|
+
"Recent chatter on other surfaces (NOT this chat). If the user says",
|
|
116
|
+
'"let\'s continue" / "the thing from before" / "the telegram one" it\'s probably',
|
|
117
|
+
"one of these — pick it up naturally; use search_messages for exact detail.",
|
|
118
118
|
"",
|
|
119
119
|
...lines,
|
|
120
120
|
].join("\n");
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Memory Broker (Pieza 4) — runs before each super-agent turn and assembles the
|
|
2
|
-
// [
|
|
2
|
+
// [RELEVANT MEMORY] block injected into the system prompt.
|
|
3
3
|
//
|
|
4
4
|
// incoming message
|
|
5
5
|
// → embed it + RAG retriever (Pieza 2) → top-K relevant chunks
|
|
6
6
|
// → read the last N entries of memory.md
|
|
7
|
-
// → merge, dedupe, format as a [
|
|
7
|
+
// → merge, dedupe, format as a [RELEVANT MEMORY] block
|
|
8
8
|
//
|
|
9
9
|
// Silent and bounded: the whole thing races an 800 ms budget. If RAG is slow
|
|
10
10
|
// (Ollama lagging), the block still returns with whatever memory.md gave us —
|
|
@@ -79,7 +79,7 @@ function bulletFor({ date, channel, text }) {
|
|
|
79
79
|
return `• ${d}${c} ${trunc(text)}`.replace(/\s+/g, " ").trim();
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
// Build the [
|
|
82
|
+
// Build the [RELEVANT MEMORY] block. Returns "" when there's nothing useful.
|
|
83
83
|
//
|
|
84
84
|
// opts: { store, config, memoryPath, budgetMs, topK, channel, embed }
|
|
85
85
|
export async function buildMemoryBlock(message, opts = {}) {
|
|
@@ -132,13 +132,13 @@ export async function buildMemoryBlock(message, opts = {}) {
|
|
|
132
132
|
if (bullets.length === 0) return "";
|
|
133
133
|
|
|
134
134
|
return [
|
|
135
|
-
"#
|
|
136
|
-
"
|
|
137
|
-
"
|
|
138
|
-
"
|
|
135
|
+
"# Relevant memory (cross-channel)",
|
|
136
|
+
"Context recovered from your notebook and from the message log across channels.",
|
|
137
|
+
"Treat these as known facts. If a fresh session opens and something here is still",
|
|
138
|
+
"open, bring it up naturally in the user's language (e.g. \"yesterday we were on X — shall we continue?\") without being asked.",
|
|
139
139
|
"",
|
|
140
|
-
"[
|
|
140
|
+
"[RELEVANT MEMORY]",
|
|
141
141
|
...bullets,
|
|
142
|
-
"[/
|
|
142
|
+
"[/RELEVANT MEMORY]",
|
|
143
143
|
].join("\n");
|
|
144
144
|
}
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
import fs from "node:fs";
|
|
17
17
|
import path from "node:path";
|
|
18
|
-
import { GLOBAL_MESSAGES_DIR } from "../config.js";
|
|
19
|
-
import { parseDayJsonl, appendGlobalMessage } from "../messages
|
|
18
|
+
import { GLOBAL_MESSAGES_DIR } from "../config/index.js";
|
|
19
|
+
import { parseDayJsonl, appendGlobalMessage } from "../stores/messages.js";
|
|
20
20
|
import { callEngine } from "../engines/index.js";
|
|
21
21
|
|
|
22
22
|
const DEFAULT_MAX_TURNS = 60;
|
package/src/core/memory/index.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// affected piece simply contributes nothing.
|
|
13
13
|
|
|
14
14
|
import path from "node:path";
|
|
15
|
-
import { APX_HOME } from "../config.js";
|
|
15
|
+
import { APX_HOME } from "../config/index.js";
|
|
16
16
|
import { ensureSelfMemoryFile } from "../agent/self-memory.js";
|
|
17
17
|
import fs from "node:fs";
|
|
18
18
|
import { openMemoryStore } from "./store.js";
|
|
@@ -141,7 +141,7 @@ export function stopMemory() {
|
|
|
141
141
|
_ready = null;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
// Build the [
|
|
144
|
+
// Build the [RELEVANT MEMORY] block for a turn (Pieza 4). Never throws —
|
|
145
145
|
// returns "" on any failure so the prompt builder can drop the block.
|
|
146
146
|
export async function memoryBlockFor(message, { config, channel, budgetMs } = {}) {
|
|
147
147
|
try {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
import fs from "node:fs";
|
|
21
21
|
import path from "node:path";
|
|
22
|
-
import { GLOBAL_MESSAGES_DIR, APX_HOME } from "../config.js";
|
|
22
|
+
import { GLOBAL_MESSAGES_DIR, APX_HOME } from "../config/index.js";
|
|
23
23
|
import { SELF_MEMORY_PATH, parseSelfMemoryEntries } from "../agent/self-memory.js";
|
|
24
24
|
import { embedBatch, embedOne } from "./embeddings.js";
|
|
25
25
|
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
// translation.
|
|
12
12
|
import fs from "node:fs";
|
|
13
13
|
import path from "node:path";
|
|
14
|
-
import {
|
|
14
|
+
import { nowIso } from "../util/time.js";
|
|
15
|
+
import { shortId as makeShortId } from "../util/ids.js";
|
|
15
16
|
|
|
16
17
|
function sessionsDir(storagePath) {
|
|
17
18
|
return path.join(storagePath, "code-sessions");
|
|
@@ -21,13 +22,8 @@ function sessionFile(storagePath, id) {
|
|
|
21
22
|
return path.join(sessionsDir(storagePath), `${id}.json`);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
function nowIso() {
|
|
25
|
-
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
25
|
function shortId() {
|
|
29
|
-
|
|
30
|
-
return "cs_" + parseInt(hex, 16).toString(36).padStart(6, "0").slice(-6);
|
|
26
|
+
return makeShortId("cs");
|
|
31
27
|
}
|
|
32
28
|
|
|
33
29
|
// Atomic write: tmp file + rename so a crash mid-write can't corrupt a session.
|
|
@@ -53,6 +49,7 @@ function toRow(s) {
|
|
|
53
49
|
title: s.title,
|
|
54
50
|
mode: s.mode,
|
|
55
51
|
model: s.model || null,
|
|
52
|
+
agentSlug: s.agentSlug || null,
|
|
56
53
|
createdAt: s.createdAt,
|
|
57
54
|
updatedAt: s.updatedAt,
|
|
58
55
|
messageCount: Array.isArray(s.messages) ? s.messages.length : 0,
|
|
@@ -82,7 +79,7 @@ export function getCodeSession(storagePath, id) {
|
|
|
82
79
|
|
|
83
80
|
/**
|
|
84
81
|
* Create a new session.
|
|
85
|
-
* fields: { projectId, title?, model?, mode?, git? }
|
|
82
|
+
* fields: { projectId, title?, model?, mode?, git?, agentSlug? }
|
|
86
83
|
*/
|
|
87
84
|
export function createCodeSession(storagePath, fields = {}) {
|
|
88
85
|
const id = shortId();
|
|
@@ -95,6 +92,7 @@ export function createCodeSession(storagePath, fields = {}) {
|
|
|
95
92
|
updatedAt: ts,
|
|
96
93
|
model: fields.model || null,
|
|
97
94
|
mode: fields.mode === "plan" ? "plan" : "build",
|
|
95
|
+
agentSlug: fields.agentSlug || null,
|
|
98
96
|
git: fields.git && typeof fields.git === "object" ? fields.git : null,
|
|
99
97
|
messages: [],
|
|
100
98
|
};
|
|
@@ -112,6 +110,7 @@ export function updateCodeSession(storagePath, id, patch = {}) {
|
|
|
112
110
|
if (patch.title != null) session.title = String(patch.title).trim() || session.title;
|
|
113
111
|
if (patch.model !== undefined) session.model = patch.model || null;
|
|
114
112
|
if (patch.mode === "plan" || patch.mode === "build") session.mode = patch.mode;
|
|
113
|
+
if (patch.agentSlug !== undefined) session.agentSlug = patch.agentSlug || null;
|
|
115
114
|
if (patch.git !== undefined) session.git = patch.git;
|
|
116
115
|
session.updatedAt = nowIso();
|
|
117
116
|
writeJson(sessionFile(storagePath, id), session);
|
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
|
|
19
19
|
import fs from "node:fs";
|
|
20
20
|
import path from "node:path";
|
|
21
|
-
import { GLOBAL_MESSAGES_DIR } from "
|
|
21
|
+
import { GLOBAL_MESSAGES_DIR } from "../config/index.js";
|
|
22
|
+
import { CHANNELS } from "../constants/channels.js";
|
|
23
|
+
import { SUPERAGENT_ACTOR_ID } from "../constants/actors.js";
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
import { nowIso } from "../util/time.js";
|
|
24
26
|
|
|
25
27
|
function dayPathJsonl(projectRoot, ts) {
|
|
26
28
|
const day = (ts || nowIso()).slice(0, 10);
|
|
@@ -63,7 +65,7 @@ function inferActorKind({ actor_kind, type, actor_id, meta = {} } = {}) {
|
|
|
63
65
|
if (explicit) return explicit;
|
|
64
66
|
if (type === "compact") return "compact";
|
|
65
67
|
if (type === "user" || type === "tool" || type === "system") return type;
|
|
66
|
-
if (type === "agent") return actor_id ===
|
|
68
|
+
if (type === "agent") return actor_id === SUPERAGENT_ACTOR_ID ? "superagent" : "agent";
|
|
67
69
|
return null;
|
|
68
70
|
}
|
|
69
71
|
|
|
@@ -531,7 +533,7 @@ export function getRecentChannelTurnsFromFs({
|
|
|
531
533
|
|
|
532
534
|
// Telegram-specific wrapper kept for back-compat with existing call sites.
|
|
533
535
|
export function getRecentTelegramTurnsFromFs(opts = {}) {
|
|
534
|
-
return getRecentChannelTurnsFromFs({ ...opts, channel:
|
|
536
|
+
return getRecentChannelTurnsFromFs({ ...opts, channel: CHANNELS.TELEGRAM });
|
|
535
537
|
}
|
|
536
538
|
|
|
537
539
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Per-routine memory.md — durable notes scoped to a single routine.
|
|
2
|
+
//
|
|
3
|
+
// Path: <projectStoragePath>/routines/<routineId>/memory.md
|
|
4
|
+
//
|
|
5
|
+
// The routine handler (host/daemon/routines.js) creates the file on first read
|
|
6
|
+
// and injects a bounded slice into the super-agent prompt via
|
|
7
|
+
// channelMeta.routineMemory. The routine can write back with future tooling;
|
|
8
|
+
// today we only read.
|
|
9
|
+
//
|
|
10
|
+
// Distinct from:
|
|
11
|
+
// - Agent memory (~/.apx/projects/<id>/agents/<slug>/memory.md) — per-agent.
|
|
12
|
+
// - Super-agent self-memory (~/.apx/memory.md) — global to the super-agent.
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
|
|
16
|
+
const PROMPT_LIMIT = 1500;
|
|
17
|
+
|
|
18
|
+
export function routineMemoryDir(storagePath, routineId) {
|
|
19
|
+
return path.join(storagePath, "routines", String(routineId || "_unknown"));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function routineMemoryPath(storagePath, routineId) {
|
|
23
|
+
return path.join(routineMemoryDir(storagePath, routineId), "memory.md");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Read the memory body. Returns "" when the file doesn't exist. Never throws. */
|
|
27
|
+
export function readRoutineMemory(storagePath, routineId) {
|
|
28
|
+
try {
|
|
29
|
+
return fs.readFileSync(routineMemoryPath(storagePath, routineId), "utf8");
|
|
30
|
+
} catch {
|
|
31
|
+
return "";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Bounded slice for the system prompt. Returns "" when empty. */
|
|
36
|
+
export function readRoutineMemoryForPrompt(storagePath, routineId, limit = PROMPT_LIMIT) {
|
|
37
|
+
const body = readRoutineMemory(storagePath, routineId).trim();
|
|
38
|
+
if (!body) return "";
|
|
39
|
+
if (body.length <= limit) return body;
|
|
40
|
+
return body.slice(0, limit).trimEnd() + "\n… (truncated)";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Ensure the routine memory file exists. Returns true if it was created. */
|
|
44
|
+
export function ensureRoutineMemory(storagePath, routineId, routineName = "") {
|
|
45
|
+
const file = routineMemoryPath(storagePath, routineId);
|
|
46
|
+
if (fs.existsSync(file)) return false;
|
|
47
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
48
|
+
const header = routineName
|
|
49
|
+
? `# Routine memory — ${routineName}\n`
|
|
50
|
+
: "# Routine memory\n";
|
|
51
|
+
fs.writeFileSync(file, header);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Append a dated note to the routine memory. Creates the file on first write. */
|
|
56
|
+
export function appendRoutineMemory(storagePath, routineId, note, { routineName = "" } = {}) {
|
|
57
|
+
const text = String(note || "").trim();
|
|
58
|
+
if (!text) throw new Error("nothing to remember (empty note)");
|
|
59
|
+
ensureRoutineMemory(storagePath, routineId, routineName);
|
|
60
|
+
const file = routineMemoryPath(storagePath, routineId);
|
|
61
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
62
|
+
const heading = `## ${today}`;
|
|
63
|
+
const oneLine = text.replace(/\n+/g, " ").trim();
|
|
64
|
+
const bullet = `- ${oneLine}`;
|
|
65
|
+
const existing = fs.readFileSync(file, "utf8");
|
|
66
|
+
const next = existing.includes(heading)
|
|
67
|
+
? existing.trimEnd() + `\n${bullet}\n`
|
|
68
|
+
: existing.trimEnd() + `\n\n${heading}\n${bullet}\n`;
|
|
69
|
+
fs.writeFileSync(file, next);
|
|
70
|
+
return { path: file, note: text };
|
|
71
|
+
}
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import cronParser from "cron-parser";
|
|
6
|
-
|
|
7
|
-
const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
8
|
-
const isoToMs = (iso) => (iso ? Date.parse(iso) : 0);
|
|
6
|
+
import { nowIso, isoToMs } from "../util/time.js";
|
|
9
7
|
|
|
10
8
|
function routinesPath(storagePath) {
|
|
11
9
|
// storagePath is always ~/.apx/projects/{apxId}/ — flat, no .apc subdir needed.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// On-disk session state for external runtimes (Claude Code, Codex, OpenCode,
|
|
2
|
+
// Aider, Cursor Agent, Gemini CLI, Qwen Code). One markdown file per session
|
|
3
|
+
// under <storageRoot>/agents/<slug>/sessions/<id>.md with YAML frontmatter
|
|
4
|
+
// (id, agent, title, task_ref, status, started, completed, result, runtime,
|
|
5
|
+
// external_session_path).
|
|
6
|
+
//
|
|
7
|
+
// The "bridge" prompt-text builder that explains this layout to the external
|
|
8
|
+
// runtime lives in core/agent/runtime-bridge.js. Both used to live together
|
|
9
|
+
// in host/daemon/apc-runtime-context.js; they were split because they have
|
|
10
|
+
// different homes (text → core/agent, state → core/stores).
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { generateSessionId } from "./sessions.js";
|
|
14
|
+
import { nowIso } from "#core/util/time.js";
|
|
15
|
+
|
|
16
|
+
/** Create the APX runtime session file. Returns { id, filename, path }. */
|
|
17
|
+
export function createRuntimeSession({
|
|
18
|
+
projectRoot,
|
|
19
|
+
storageRoot = projectRoot,
|
|
20
|
+
agentSlug,
|
|
21
|
+
runtime,
|
|
22
|
+
taskRef = "",
|
|
23
|
+
title,
|
|
24
|
+
}) {
|
|
25
|
+
const dir = path.join(storageRoot, "agents", agentSlug, "sessions");
|
|
26
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
+
const id = generateSessionId(storageRoot, agentSlug);
|
|
28
|
+
const file = path.join(dir, `${id}.md`);
|
|
29
|
+
const started = nowIso();
|
|
30
|
+
const sessionTitle = title || `Runtime: ${runtime}`;
|
|
31
|
+
const body =
|
|
32
|
+
`---\n` +
|
|
33
|
+
`id: ${id}\n` +
|
|
34
|
+
`agent: ${agentSlug}\n` +
|
|
35
|
+
`title: ${sessionTitle}\n` +
|
|
36
|
+
`task_ref: ${taskRef}\n` +
|
|
37
|
+
`status: 🔄 In progress\n` +
|
|
38
|
+
`started: ${started}\n` +
|
|
39
|
+
`completed: \n` +
|
|
40
|
+
`result: \n` +
|
|
41
|
+
`runtime: ${runtime}\n` +
|
|
42
|
+
`external_session_path: \n` +
|
|
43
|
+
`---\n\n` +
|
|
44
|
+
`# ${sessionTitle}\n\n`;
|
|
45
|
+
fs.writeFileSync(file, body);
|
|
46
|
+
return { id, filename: `${id}.md`, path: file };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Update session frontmatter with the external transcript path + final state. */
|
|
50
|
+
export function closeRuntimeSession({ filePath, externalSessionPath, exitCode, result }) {
|
|
51
|
+
let text = fs.readFileSync(filePath, "utf8");
|
|
52
|
+
text = setField(text, "completed", nowIso());
|
|
53
|
+
if (externalSessionPath) {
|
|
54
|
+
text = setField(text, "external_session_path", externalSessionPath);
|
|
55
|
+
}
|
|
56
|
+
if (typeof exitCode === "number") {
|
|
57
|
+
text = setField(
|
|
58
|
+
text,
|
|
59
|
+
"result",
|
|
60
|
+
`${exitCode === 0 ? "✅" : "⚠️"} exit ${exitCode}: ${(result || "").slice(0, 200)}`
|
|
61
|
+
);
|
|
62
|
+
} else if (result) {
|
|
63
|
+
text = setField(text, "result", result.slice(0, 300));
|
|
64
|
+
}
|
|
65
|
+
text = setField(text, "status", exitCode === 0 ? "✅ Completed" : "⚠️ Closed with error");
|
|
66
|
+
fs.writeFileSync(filePath, text);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function setField(text, field, value) {
|
|
70
|
+
if (!text.startsWith("---\n")) return text;
|
|
71
|
+
const end = text.indexOf("\n---", 4);
|
|
72
|
+
if (end === -1) return text;
|
|
73
|
+
const fmText = text.slice(4, end);
|
|
74
|
+
const lines = fmText.split("\n");
|
|
75
|
+
let found = false;
|
|
76
|
+
const out = lines.map((line) => {
|
|
77
|
+
if (line.startsWith(`${field}:`)) {
|
|
78
|
+
found = true;
|
|
79
|
+
return `${field}: ${value}`;
|
|
80
|
+
}
|
|
81
|
+
return line;
|
|
82
|
+
});
|
|
83
|
+
if (!found) out.push(`${field}: ${value}`);
|
|
84
|
+
return `---\n${out.join("\n")}\n---${text.slice(end + 4)}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Extract a self-reported "APC_RESULT: ..." line from the runtime's stdout
|
|
89
|
+
* (the convention printed in the bridge hint). Returns the captured string
|
|
90
|
+
* or null. Fallback for runtimes that can't shell out to `apx session close`.
|
|
91
|
+
*/
|
|
92
|
+
export function extractRuntimeResult(stdout) {
|
|
93
|
+
if (!stdout || typeof stdout !== "string") return null;
|
|
94
|
+
const m = stdout.match(/^APC_RESULT:\s*(.+?)\s*$/m);
|
|
95
|
+
return m ? m[1].trim() : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Back-compat alias.
|
|
99
|
+
export const extractApfResult = extractRuntimeResult;
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
// caller explicitly re-opens with op="reopen".
|
|
17
17
|
import fs from "node:fs";
|
|
18
18
|
import path from "node:path";
|
|
19
|
-
import {
|
|
19
|
+
import { nowIso } from "../util/time.js";
|
|
20
|
+
import { shortId as makeShortId } from "../util/ids.js";
|
|
20
21
|
|
|
21
22
|
function tasksDir(storagePath) {
|
|
22
23
|
return path.join(storagePath, "tasks");
|
|
@@ -27,14 +28,8 @@ function monthlyFile(storagePath, date = new Date()) {
|
|
|
27
28
|
return path.join(tasksDir(storagePath), `${ym}.jsonl`);
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
function nowIso() {
|
|
31
|
-
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
31
|
function shortId() {
|
|
35
|
-
|
|
36
|
-
const hex = randomUUID().replace(/-/g, "").slice(0, 8);
|
|
37
|
-
return "t_" + parseInt(hex, 16).toString(36).padStart(6, "0").slice(-6);
|
|
32
|
+
return makeShortId("t");
|
|
38
33
|
}
|
|
39
34
|
|
|
40
35
|
function appendEvent(storagePath, event) {
|
package/src/core/update-check.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import fs from "node:fs";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import https from "node:https";
|
|
9
|
-
import { APX_HOME } from "./config.js";
|
|
9
|
+
import { APX_HOME } from "./config/index.js";
|
|
10
10
|
|
|
11
11
|
const PACKAGE_NAME = "@agentprojectcontext/apx";
|
|
12
12
|
const CACHE_PATH = path.join(APX_HOME, "update-check.json");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Short, prefix-able ids derived from a UUID. Stores use these for primary
|
|
2
|
+
// keys when an autoincrement isn't a fit (no SQL row counter, JSON files).
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Six-character base36 id derived from a UUID. Random enough for in-process
|
|
7
|
+
* uniqueness inside a single file/store; combine with a prefix when collisions
|
|
8
|
+
* across stores would be ambiguous.
|
|
9
|
+
*/
|
|
10
|
+
export function shortId(prefix = "") {
|
|
11
|
+
const hex = randomUUID().replace(/-/g, "").slice(0, 8);
|
|
12
|
+
const id = parseInt(hex, 16).toString(36).padStart(6, "0").slice(-6);
|
|
13
|
+
return prefix ? `${prefix}_${id}` : id;
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Time helpers. Single source for ISO timestamps so every store agrees on
|
|
2
|
+
// resolution (seconds) and shape ("YYYY-MM-DDTHH:MM:SSZ").
|
|
3
|
+
export function nowIso() {
|
|
4
|
+
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isoToMs(iso) {
|
|
8
|
+
return iso ? Date.parse(iso) : 0;
|
|
9
|
+
}
|
package/src/core/voice/tts.js
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
// The PATCH variant is intentional: PUT would force the caller to send the
|
|
6
6
|
// whole credentials block, and a UI that forgot one field would wipe secrets.
|
|
7
7
|
// Dotted keys make every edit narrowly-scoped.
|
|
8
|
-
import { readConfig, writeConfig } from "
|
|
9
|
-
import { resolveAgentName } from "
|
|
8
|
+
import { readConfig, writeConfig } from "#core/config/index.js";
|
|
9
|
+
import { resolveAgentName } from "#core/identity/index.js";
|
|
10
10
|
import { setDottedKey, unsetDottedKey } from "../project-config.js";
|
|
11
|
+
import { PERMISSION_MODES, DEFAULT_PERMISSION_MODE } from "#core/constants/permissions.js";
|
|
11
12
|
|
|
12
13
|
const SECRET_PATHS = [
|
|
13
14
|
"engines.anthropic.api_key",
|
|
@@ -148,7 +149,7 @@ export function register(app, { config, scheduler, plugins }) {
|
|
|
148
149
|
name: resolveAgentName(fresh),
|
|
149
150
|
model: sa.model || "",
|
|
150
151
|
system: sa.system || "",
|
|
151
|
-
permission_mode: sa.permission_mode ||
|
|
152
|
+
permission_mode: sa.permission_mode || PERMISSION_MODES.PERMISO,
|
|
152
153
|
allowed_tools: sa.allowed_tools || [],
|
|
153
154
|
model_fallback: sa.model_fallback || { enabled: false, models: [], order: [] },
|
|
154
155
|
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// POST /admin/shutdown — clean exit (50 ms grace so the response flushes).
|
|
4
4
|
//
|
|
5
5
|
// Both are auth-gated (the global middleware applies).
|
|
6
|
-
import { readConfig } from "
|
|
6
|
+
import { readConfig } from "#core/config/index.js";
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import os from "node:os";
|
|
9
9
|
import path from "node:path";
|
|
@@ -6,44 +6,23 @@
|
|
|
6
6
|
// PUT /projects/:pid/agents/:slug/memory
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import path from "node:path";
|
|
9
|
-
import { readAgents, readVaultAgents, readVaultAgent } from "
|
|
9
|
+
import { readAgents, readVaultAgents, readVaultAgent } from "#core/apc/parser.js";
|
|
10
10
|
import {
|
|
11
11
|
writeAgentFile,
|
|
12
12
|
writeVaultAgentFile,
|
|
13
13
|
removeVaultAgent,
|
|
14
14
|
restoreVaultAgent,
|
|
15
15
|
ensureAgentDir,
|
|
16
|
-
} from "
|
|
16
|
+
} from "#core/apc/scaffold.js";
|
|
17
17
|
import {
|
|
18
18
|
ensureAgentRuntimeDir,
|
|
19
19
|
readAgentMemory,
|
|
20
20
|
writeAgentMemory,
|
|
21
21
|
agentMemoryPath,
|
|
22
22
|
legacyAgentMemoryPath,
|
|
23
|
-
} from "
|
|
23
|
+
} from "#core/agent/memory.js";
|
|
24
24
|
import { agentToResponse } from "./shared.js";
|
|
25
|
-
|
|
26
|
-
// Lowercase the patch keys we accept on the vault and turn skills/tools into
|
|
27
|
-
// arrays. The writer takes either case but normalizes; passing this through
|
|
28
|
-
// it keeps the on-disk format consistent.
|
|
29
|
-
const VAULT_PATCH_FIELDS = ["role", "model", "language", "description", "skills", "tools", "is_master"];
|
|
30
|
-
function normalizeVaultPatch(input = {}) {
|
|
31
|
-
const out = {};
|
|
32
|
-
for (const k of VAULT_PATCH_FIELDS) {
|
|
33
|
-
const lower = k;
|
|
34
|
-
const title = k.charAt(0).toUpperCase() + k.slice(1);
|
|
35
|
-
const v = input[lower] ?? input[title];
|
|
36
|
-
if (v === undefined || v === null) continue;
|
|
37
|
-
if (k === "skills" || k === "tools") {
|
|
38
|
-
out[title] = Array.isArray(v)
|
|
39
|
-
? v.map(String).map((s) => s.trim()).filter(Boolean)
|
|
40
|
-
: String(v).split(",").map((s) => s.trim()).filter(Boolean);
|
|
41
|
-
} else {
|
|
42
|
-
out[title] = v;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return out;
|
|
46
|
-
}
|
|
25
|
+
import { normalizeVaultPatch } from "#core/apc/agents-vault.js";
|
|
47
26
|
|
|
48
27
|
export function register(app, { projects, project }) {
|
|
49
28
|
// Vault = global agent templates. Two-layer: bundled defaults shipped with
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
listArtifacts,
|
|
14
14
|
readArtifact,
|
|
15
15
|
removeArtifact,
|
|
16
|
-
} from "
|
|
16
|
+
} from "#core/stores/artifacts.js";
|
|
17
17
|
|
|
18
18
|
// Same heuristic as `apx artifact run` (cli/commands/artifact.js): exec bit
|
|
19
19
|
// OR shebang counts as runnable. We auto-chmod when shebang-only so the
|
|
@@ -119,6 +119,31 @@ export function register(app, { project }) {
|
|
|
119
119
|
}
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
+
app.patch("/projects/:pid/artifacts/:name", (req, res) => {
|
|
123
|
+
const p = project(req, res);
|
|
124
|
+
if (!p) return;
|
|
125
|
+
const name = decodeURIComponent(req.params.name);
|
|
126
|
+
const { content, newName } = req.body || {};
|
|
127
|
+
try {
|
|
128
|
+
const absPath = artifactPath(p.storagePath, name);
|
|
129
|
+
if (!fs.existsSync(absPath)) {
|
|
130
|
+
return res.status(404).json({ error: `artifact "${name}" not found` });
|
|
131
|
+
}
|
|
132
|
+
if (typeof content === "string") {
|
|
133
|
+
fs.writeFileSync(absPath, content, "utf8");
|
|
134
|
+
}
|
|
135
|
+
let finalName = name;
|
|
136
|
+
if (newName && newName !== name) {
|
|
137
|
+
const newAbsPath = artifactPath(p.storagePath, newName);
|
|
138
|
+
fs.renameSync(absPath, newAbsPath);
|
|
139
|
+
finalName = newName;
|
|
140
|
+
}
|
|
141
|
+
res.json({ ok: true, name: finalName });
|
|
142
|
+
} catch (e) {
|
|
143
|
+
res.status(400).json({ error: e.message });
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
122
147
|
app.delete("/projects/:pid/artifacts/:name", (req, res) => {
|
|
123
148
|
const p = project(req, res);
|
|
124
149
|
if (!p) return;
|