@agentprojectcontext/apx 1.33.0 → 1.34.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 +1 -1
- package/skills/apc-context/SKILL.md +2 -5
- package/skills/apx/SKILL.md +49 -61
- package/src/core/agent/a2a/reply.js +48 -0
- package/src/core/agent/build-agent-system.js +4 -3
- package/src/core/agent/channels/voice-context.js +98 -0
- package/src/core/agent/memory.js +2 -1
- package/src/core/agent/prompt-builder.js +2 -1
- package/src/core/agent/prompts/modes/code-build.md +1 -0
- package/src/core/agent/prompts/modes/code-plan.md +1 -0
- package/src/core/agent/prompts/modes/index.js +28 -0
- package/src/core/agent/skills/loader.js +22 -18
- package/src/core/agent/stream/turn-accumulator.js +73 -0
- package/src/core/agent/suggestions.js +37 -0
- package/src/core/agent/tools/handlers/add-project.js +5 -2
- package/src/core/agent/tools/handlers/call-runtime.js +3 -2
- package/src/core/agent/tools/handlers/transcribe-audio.js +1 -1
- package/src/core/agent/tools/helpers.js +2 -2
- package/src/core/agent/tools/names.js +138 -0
- package/src/core/agent/tools/registry-bridge.js +6 -14
- package/src/core/agent/tools/registry.js +68 -65
- package/src/core/apc/context-copy.js +27 -0
- package/src/core/apc/notes.js +19 -0
- package/src/core/apc/parser.js +13 -6
- package/src/core/apc/paths.js +87 -0
- package/src/core/apc/scaffold.js +82 -74
- package/src/core/apc/skill-sync.js +13 -1
- package/src/core/channels/telegram/dispatch.js +595 -0
- package/src/core/channels/telegram/helpers.js +130 -0
- package/src/core/config/index.js +3 -2
- package/src/core/config/redact.js +95 -0
- package/src/core/constants/channels.js +2 -0
- package/src/core/constants/code-modes.js +10 -0
- package/src/core/constants/index.js +1 -0
- package/src/core/deck/manifest.js +186 -0
- package/src/core/engines/catalog.js +83 -0
- package/src/core/engines/gemini.js +28 -11
- package/src/core/engines/index.js +11 -1
- package/src/core/{tools → http-tools}/browser.js +0 -1
- package/src/core/{tools → http-tools}/fetch.js +0 -1
- package/src/core/{tools → http-tools}/glob.js +0 -1
- package/src/core/{tools → http-tools}/grep.js +0 -1
- package/src/core/{tools → http-tools}/registry.js +0 -1
- package/src/core/{tools → http-tools}/search.js +0 -1
- package/src/core/i18n/en.js +9 -0
- package/src/core/i18n/es.js +12 -0
- package/src/core/i18n/index.js +54 -0
- package/src/core/i18n/pt.js +9 -0
- package/src/core/identity/telegram.js +2 -1
- package/src/core/mcp/runner.js +272 -14
- package/src/core/mcp/sources.js +3 -2
- package/src/core/routines/index.js +16 -0
- package/src/{host/daemon/routines.js → core/routines/runner.js} +36 -103
- package/src/core/runtime-skills/apc-context/SKILL.md +159 -0
- package/src/core/runtime-skills/apx/SKILL.md +95 -0
- package/src/core/runtime-skills/apx-mcp/SKILL.md +116 -0
- package/src/core/runtime-skills/{claude-code.md → claude-code/SKILL.md} +1 -0
- package/src/core/runtime-skills/{codex-cli.md → codex-cli/SKILL.md} +1 -0
- package/src/core/runtime-skills/{opencode-cli.md → opencode-cli/SKILL.md} +1 -0
- package/src/core/runtime-skills/{openrouter.md → openrouter/SKILL.md} +1 -0
- package/src/{host/daemon/env-detect.js → core/runtimes/detect.js} +1 -1
- package/src/core/stores/code-sessions.js +50 -2
- package/src/core/stores/routine-memory.js +1 -1
- package/src/core/stores/sessions-search.js +121 -0
- package/src/core/stores/sessions.js +38 -0
- package/src/core/vars/index.js +14 -0
- package/src/core/vars/interpolate.js +86 -0
- package/src/core/vars/sources.js +151 -0
- package/src/core/voice/audio-decode.js +38 -0
- package/src/core/voice/transcription.js +225 -0
- package/src/host/daemon/api/admin-config.js +5 -82
- package/src/host/daemon/api/agents.js +5 -5
- package/src/host/daemon/api/code.js +17 -169
- package/src/host/daemon/api/config.js +3 -4
- package/src/host/daemon/api/conversations.js +8 -29
- package/src/host/daemon/api/deck.js +37 -404
- package/src/host/daemon/api/engines.js +1 -50
- package/src/host/daemon/api/exec.js +1 -1
- package/src/host/daemon/api/mcps.js +32 -0
- package/src/host/daemon/api/routines.js +1 -1
- package/src/host/daemon/api/runtimes.js +4 -3
- package/src/host/daemon/api/sessions-search.js +24 -140
- package/src/host/daemon/api/sessions.js +12 -30
- package/src/host/daemon/api/shared.js +2 -1
- package/src/host/daemon/api/telegram.js +1 -11
- package/src/host/daemon/api/tools.js +6 -6
- package/src/host/daemon/api/transcribe.js +2 -2
- package/src/host/daemon/api/vars.js +137 -0
- package/src/host/daemon/api/voice.js +13 -290
- package/src/host/daemon/api.js +2 -0
- package/src/host/daemon/db.js +6 -6
- package/src/host/daemon/deck-exec.js +148 -0
- package/src/host/daemon/index.js +3 -3
- package/src/host/daemon/plugins/telegram/index.js +24 -687
- package/src/host/daemon/routines-scheduler.js +64 -0
- package/src/host/daemon/smoke.js +3 -2
- package/src/host/daemon/whisper-server.js +225 -0
- package/src/interfaces/cli/commands/agent.js +3 -2
- package/src/interfaces/cli/commands/command.js +2 -3
- package/src/interfaces/cli/commands/messages.js +6 -2
- package/src/interfaces/cli/commands/pair.js +5 -4
- package/src/interfaces/cli/commands/search.js +1 -1
- package/src/interfaces/cli/commands/sessions.js +3 -2
- package/src/interfaces/cli/commands/skills.js +36 -55
- package/src/interfaces/web/dist/assets/index-DdmSRtsz.css +1 -0
- package/src/interfaces/web/dist/assets/index-M4FspaCH.js +613 -0
- package/src/interfaces/web/dist/assets/index-M4FspaCH.js.map +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +182 -182
- package/src/interfaces/web/src/components/ModelCombobox.tsx +44 -8
- package/src/interfaces/web/src/components/TelegramChannelDialog.tsx +1 -1
- package/src/interfaces/web/src/components/chat/AskAnswersCard.tsx +76 -0
- package/src/interfaces/web/src/components/chat/MessageBubble.tsx +16 -3
- package/src/interfaces/web/src/components/chat/MessageList.tsx +23 -1
- package/src/interfaces/web/src/components/chat/ModelPicker.tsx +3 -1
- package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +4 -4
- package/src/interfaces/web/src/components/code/CodeChangesTab.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeFileTree.tsx +3 -2
- package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +3 -2
- package/src/interfaces/web/src/components/code/CodeTerminal.tsx +3 -2
- package/src/interfaces/web/src/components/config/GlobalConfigEditor.tsx +2 -1
- package/src/interfaces/web/src/components/deck/WidgetRow.tsx +2 -1
- package/src/interfaces/web/src/components/inputs/KeyValueList.tsx +93 -0
- package/src/interfaces/web/src/components/inputs/VarTokenInput.tsx +449 -0
- package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +2 -1
- package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +2 -2
- package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +5 -4
- package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +3 -2
- package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +3 -2
- package/src/interfaces/web/src/components/ui/chat-input.tsx +5 -4
- package/src/interfaces/web/src/components/ui/sidebar.tsx +3 -2
- package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +2 -1
- package/src/interfaces/web/src/constants/index.ts +1 -1
- package/src/interfaces/web/src/i18n/en.ts +174 -7
- package/src/interfaces/web/src/i18n/es.ts +179 -15
- package/src/interfaces/web/src/lib/api/mcps.ts +25 -0
- package/src/interfaces/web/src/lib/api/vars.ts +38 -0
- package/src/interfaces/web/src/lib/api.ts +1 -0
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +8 -31
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +4 -3
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +7 -6
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +4 -3
- package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/project/ConfigTab.tsx +132 -1
- package/src/interfaces/web/src/screens/project/McpsTab.tsx +549 -104
- package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +1 -1
- package/src/interfaces/web/src/screens/project/VarsTab.tsx +300 -0
- package/src/interfaces/web/src/types/daemon.ts +5 -0
- package/src/host/daemon/transcription.js +0 -538
- package/src/host/daemon/whisper-transcribe.py +0 -73
- package/src/interfaces/web/dist/assets/index-7dVT2O1S.css +0 -1
- package/src/interfaces/web/dist/assets/index-DWsE_8Nz.js +0 -602
- package/src/interfaces/web/dist/assets/index-DWsE_8Nz.js.map +0 -1
- /package/src/{host/daemon → core/apc}/projects-helpers.js +0 -0
- /package/src/{host/daemon/plugins → core/channels}/telegram/ask.js +0 -0
- /package/src/{host/daemon/plugins → core/channels}/telegram/media.js +0 -0
- /package/src/core/{tools → http-tools}/index.js +0 -0
- /package/{skills → src/core/runtime-skills}/apx-agency-agents/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-agent/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-mcp-builder/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-project/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-routine/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-runtime/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-sessions/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-skill-builder/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-task/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-telegram/SKILL.md +0 -0
- /package/{skills → src/core/runtime-skills}/apx-voice/SKILL.md +0 -0
- /package/src/{host/daemon/compact.js → core/stores/conversations-compactor.js} +0 -0
- /package/src/{host/daemon → core/stores}/conversations.js +0 -0
- /package/src/{host/daemon → core/util}/thinking.js +0 -0
|
@@ -2,169 +2,53 @@
|
|
|
2
2
|
// GET /sessions/search?q=…&project=…&limit=20
|
|
3
3
|
// POST /sessions/:id/compact resolves which project/agent owns the file
|
|
4
4
|
// then delegates to compactConversation.
|
|
5
|
-
import fs from "node:fs";
|
|
6
5
|
import path from "node:path";
|
|
7
6
|
import { readAgents } from "#core/apc/parser.js";
|
|
8
|
-
import { compactConversation } from "
|
|
7
|
+
import { compactConversation } from "#core/stores/conversations-compactor.js";
|
|
8
|
+
import { searchSessions, findSessionFile } from "#core/stores/sessions-search.js";
|
|
9
|
+
|
|
10
|
+
function resolveProjects(projects, projectRef) {
|
|
11
|
+
const all = projects.list();
|
|
12
|
+
if (projectRef != null) {
|
|
13
|
+
const ref = String(projectRef);
|
|
14
|
+
const found = all.find((p) => String(p.id) === ref || p.path === path.resolve(ref));
|
|
15
|
+
return found ? [projects.get(found.id)] : [];
|
|
16
|
+
}
|
|
17
|
+
return all.map((p) => projects.get(p.id)).filter(Boolean);
|
|
18
|
+
}
|
|
9
19
|
|
|
10
20
|
export function register(app, { projects, config }) {
|
|
11
21
|
app.get("/sessions/search", (req, res) => {
|
|
12
22
|
const { q, project: projectRef, limit = "20" } = req.query;
|
|
13
23
|
if (!q) return res.status(400).json({ error: "q required" });
|
|
14
24
|
const lim = Math.min(parseInt(limit, 10) || 20, 200);
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const targetProjects = (() => {
|
|
19
|
-
if (projectRef != null) {
|
|
20
|
-
const ref = String(projectRef);
|
|
21
|
-
const found = allProjects.find(
|
|
22
|
-
(p) => String(p.id) === ref || p.path === path.resolve(ref)
|
|
23
|
-
);
|
|
24
|
-
return found ? [projects.get(found.id)] : [];
|
|
25
|
-
}
|
|
26
|
-
return allProjects.map((p) => projects.get(p.id)).filter(Boolean);
|
|
27
|
-
})();
|
|
28
|
-
|
|
29
|
-
const matches = [];
|
|
30
|
-
|
|
31
|
-
for (const p of targetProjects) {
|
|
32
|
-
if (!p) continue;
|
|
33
|
-
|
|
34
|
-
// 1) Legacy session files in the repo (.apc/agents/<slug>/sessions/)
|
|
35
|
-
const sessionAgentsDir = path.join(p.path, ".apc", "agents");
|
|
36
|
-
if (fs.existsSync(sessionAgentsDir)) {
|
|
37
|
-
for (const slug of fs.readdirSync(sessionAgentsDir)) {
|
|
38
|
-
const sessionsDir = path.join(sessionAgentsDir, slug, "sessions");
|
|
39
|
-
if (!fs.existsSync(sessionsDir)) continue;
|
|
40
|
-
for (const f of fs
|
|
41
|
-
.readdirSync(sessionsDir)
|
|
42
|
-
.filter((x) => x.endsWith(".md"))) {
|
|
43
|
-
const filePath = path.join(sessionsDir, f);
|
|
44
|
-
try {
|
|
45
|
-
const text = fs.readFileSync(filePath, "utf8");
|
|
46
|
-
if (text.toLowerCase().includes(needle)) {
|
|
47
|
-
const lines = text.split("\n");
|
|
48
|
-
const matchLine = lines.findIndex((l) =>
|
|
49
|
-
l.toLowerCase().includes(needle)
|
|
50
|
-
);
|
|
51
|
-
const excerpt = lines
|
|
52
|
-
.slice(Math.max(0, matchLine - 1), matchLine + 3)
|
|
53
|
-
.join("\n");
|
|
54
|
-
matches.push({
|
|
55
|
-
type: "session",
|
|
56
|
-
project: p.id,
|
|
57
|
-
agent: slug,
|
|
58
|
-
filename: f,
|
|
59
|
-
path: filePath,
|
|
60
|
-
excerpt: excerpt.slice(0, 300),
|
|
61
|
-
});
|
|
62
|
-
if (matches.length >= lim) break;
|
|
63
|
-
}
|
|
64
|
-
} catch {}
|
|
65
|
-
}
|
|
66
|
-
if (matches.length >= lim) break;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (matches.length >= lim) break;
|
|
71
|
-
|
|
72
|
-
// 2) Conversation files in daemon storage (~/.apx/…/conversations/)
|
|
73
|
-
const convAgentsDir = path.join(p.storagePath, "agents");
|
|
74
|
-
if (fs.existsSync(convAgentsDir)) {
|
|
75
|
-
for (const slug of fs.readdirSync(convAgentsDir)) {
|
|
76
|
-
const convDir = path.join(convAgentsDir, slug, "conversations");
|
|
77
|
-
if (!fs.existsSync(convDir)) continue;
|
|
78
|
-
for (const f of fs
|
|
79
|
-
.readdirSync(convDir)
|
|
80
|
-
.filter((x) => x.endsWith(".md"))) {
|
|
81
|
-
const filePath = path.join(convDir, f);
|
|
82
|
-
try {
|
|
83
|
-
const text = fs.readFileSync(filePath, "utf8");
|
|
84
|
-
if (text.toLowerCase().includes(needle)) {
|
|
85
|
-
const lines = text.split("\n");
|
|
86
|
-
const matchLine = lines.findIndex((l) =>
|
|
87
|
-
l.toLowerCase().includes(needle)
|
|
88
|
-
);
|
|
89
|
-
const excerpt = lines
|
|
90
|
-
.slice(Math.max(0, matchLine - 1), matchLine + 3)
|
|
91
|
-
.join("\n");
|
|
92
|
-
matches.push({
|
|
93
|
-
type: "conversation",
|
|
94
|
-
project: p.id,
|
|
95
|
-
agent: slug,
|
|
96
|
-
filename: f,
|
|
97
|
-
path: filePath,
|
|
98
|
-
excerpt: excerpt.slice(0, 300),
|
|
99
|
-
});
|
|
100
|
-
if (matches.length >= lim) break;
|
|
101
|
-
}
|
|
102
|
-
} catch {}
|
|
103
|
-
}
|
|
104
|
-
if (matches.length >= lim) break;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (matches.length >= lim) break;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
res.json({ q, count: matches.length, results: matches });
|
|
25
|
+
const targets = resolveProjects(projects, projectRef);
|
|
26
|
+
const results = searchSessions(targets, q, lim);
|
|
27
|
+
res.json({ q, count: results.length, results });
|
|
112
28
|
});
|
|
113
29
|
|
|
114
30
|
app.post("/sessions/:id/compact", async (req, res) => {
|
|
115
31
|
const { id } = req.params;
|
|
116
32
|
const { model: modelOverride, project: projectRef } = req.body || {};
|
|
117
|
-
|
|
118
|
-
const candidates
|
|
119
|
-
projectRef != null
|
|
120
|
-
? (() => {
|
|
121
|
-
const ref = String(projectRef);
|
|
122
|
-
const found = projects
|
|
123
|
-
.list()
|
|
124
|
-
.find(
|
|
125
|
-
(p) => String(p.id) === ref || p.path === path.resolve(ref)
|
|
126
|
-
);
|
|
127
|
-
return found ? [projects.get(found.id)] : [];
|
|
128
|
-
})()
|
|
129
|
-
: projects.list().map((p) => projects.get(p.id)).filter(Boolean);
|
|
130
|
-
|
|
131
|
-
let found = null;
|
|
132
|
-
const filename = id.endsWith(".md") ? id : `${id}.md`;
|
|
133
|
-
|
|
134
|
-
for (const p of candidates) {
|
|
135
|
-
if (!p) continue;
|
|
136
|
-
const agentsDir = path.join(p.storagePath, "agents");
|
|
137
|
-
if (fs.existsSync(agentsDir)) {
|
|
138
|
-
for (const slug of fs.readdirSync(agentsDir)) {
|
|
139
|
-
const f = path.join(agentsDir, slug, "conversations", filename);
|
|
140
|
-
if (fs.existsSync(f)) {
|
|
141
|
-
found = { p, slug };
|
|
142
|
-
break;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
if (found) break;
|
|
147
|
-
}
|
|
33
|
+
const candidates = resolveProjects(projects, projectRef);
|
|
34
|
+
const found = findSessionFile(candidates, id);
|
|
148
35
|
|
|
149
36
|
if (!found) {
|
|
150
|
-
return res
|
|
151
|
-
.status(404)
|
|
152
|
-
.json({ error: `session/conversation "${id}" not found` });
|
|
37
|
+
return res.status(404).json({ error: `session/conversation "${id}" not found` });
|
|
153
38
|
}
|
|
154
39
|
|
|
155
|
-
const { p,
|
|
40
|
+
const { project: p, agentSlug, filename } = found;
|
|
156
41
|
const agents = readAgents(p.path);
|
|
157
|
-
const agent = agents.find((a) => a.slug ===
|
|
42
|
+
const agent = agents.find((a) => a.slug === agentSlug);
|
|
158
43
|
const modelId = modelOverride || agent?.fields?.Model;
|
|
159
|
-
if (!modelId)
|
|
160
|
-
return res
|
|
161
|
-
|
|
162
|
-
.json({ error: "agent has no model; pass model in body" });
|
|
44
|
+
if (!modelId) {
|
|
45
|
+
return res.status(400).json({ error: "agent has no model; pass model in body" });
|
|
46
|
+
}
|
|
163
47
|
|
|
164
48
|
try {
|
|
165
49
|
const result = await compactConversation({
|
|
166
50
|
storagePath: p.storagePath,
|
|
167
|
-
agentSlug
|
|
51
|
+
agentSlug,
|
|
168
52
|
filename,
|
|
169
53
|
modelId,
|
|
170
54
|
config: p.config || config,
|
|
@@ -4,9 +4,15 @@
|
|
|
4
4
|
// GET /projects/:pid/sessions/:sid by filename (cross-agent lookup)
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import {
|
|
7
|
+
import { readAgents } from "#core/apc/parser.js";
|
|
8
|
+
import {
|
|
9
|
+
parseSessionFrontmatter,
|
|
10
|
+
} from "#core/apc/parser.js";
|
|
11
|
+
import {
|
|
12
|
+
agentSessionsDir,
|
|
13
|
+
createAgentSessionFile,
|
|
14
|
+
} from "#core/stores/sessions.js";
|
|
8
15
|
import { collectAllSessions } from "#interfaces/cli/commands/sessions.js";
|
|
9
|
-
import { nowIso } from "./shared.js";
|
|
10
16
|
|
|
11
17
|
export function register(app, { projects, project }) {
|
|
12
18
|
// Cross-engine sessions (apx · claude · codex), newest first.
|
|
@@ -29,12 +35,7 @@ export function register(app, { projects, project }) {
|
|
|
29
35
|
const agents = readAgents(p.path);
|
|
30
36
|
if (!agents.find((a) => a.slug === req.params.slug))
|
|
31
37
|
return res.status(404).json({ error: "agent not found" });
|
|
32
|
-
const sessionsDir =
|
|
33
|
-
p.storagePath,
|
|
34
|
-
"agents",
|
|
35
|
-
req.params.slug,
|
|
36
|
-
"sessions"
|
|
37
|
-
);
|
|
38
|
+
const sessionsDir = agentSessionsDir(p.storagePath, req.params.slug);
|
|
38
39
|
if (!fs.existsSync(sessionsDir)) return res.json([]);
|
|
39
40
|
const sessions = fs
|
|
40
41
|
.readdirSync(sessionsDir)
|
|
@@ -62,32 +63,13 @@ export function register(app, { projects, project }) {
|
|
|
62
63
|
if (!p) return;
|
|
63
64
|
const { title, body = "" } = req.body || {};
|
|
64
65
|
if (!title) return res.status(400).json({ error: "title required" });
|
|
65
|
-
const
|
|
66
|
+
const { filename, path: filePath } = createAgentSessionFile(
|
|
66
67
|
p.storagePath,
|
|
67
|
-
"agents",
|
|
68
68
|
req.params.slug,
|
|
69
|
-
|
|
69
|
+
{ title, body }
|
|
70
70
|
);
|
|
71
|
-
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
72
|
-
const titleSlug =
|
|
73
|
-
title
|
|
74
|
-
.toLowerCase()
|
|
75
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
76
|
-
.replace(/^-|-$/g, "") || "session";
|
|
77
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
78
|
-
let candidate = path.join(sessionsDir, `${today}-${titleSlug}.md`);
|
|
79
|
-
let n = 2;
|
|
80
|
-
while (fs.existsSync(candidate)) {
|
|
81
|
-
candidate = path.join(sessionsDir, `${today}-${titleSlug}-${n}.md`);
|
|
82
|
-
n++;
|
|
83
|
-
}
|
|
84
|
-
const started = nowIso();
|
|
85
|
-
const content = `---\ntitle: ${title}\nstarted: ${started}\n---\n\n# ${title}\n\n${body}\n`;
|
|
86
|
-
fs.writeFileSync(candidate, content);
|
|
87
71
|
projects.rebuild(p.id);
|
|
88
|
-
res
|
|
89
|
-
.status(201)
|
|
90
|
-
.json({ filename: path.basename(candidate), path: candidate });
|
|
72
|
+
res.status(201).json({ filename, path: filePath });
|
|
91
73
|
});
|
|
92
74
|
|
|
93
75
|
// GET session by filename (sid may include or omit the .md extension)
|
|
@@ -8,6 +8,7 @@ import { randomUUID } from "node:crypto";
|
|
|
8
8
|
import { appendErrorTrace, previewText } from "#core/logging.js";
|
|
9
9
|
import { readAgents } from "#core/apc/parser.js";
|
|
10
10
|
import { agentMemoryPath } from "#core/agent/memory.js";
|
|
11
|
+
import { apcMemoryFile } from "#core/apc/paths.js";
|
|
11
12
|
import { CHANNELS } from "#core/constants/channels.js";
|
|
12
13
|
|
|
13
14
|
export const nowIso = () =>
|
|
@@ -117,7 +118,7 @@ export function makeTopProjectResolver(projects) {
|
|
|
117
118
|
export function resolveMemoryPath(p) {
|
|
118
119
|
const firstAgent = readAgents(p.path)[0];
|
|
119
120
|
if (firstAgent) return agentMemoryPath(p, firstAgent.slug);
|
|
120
|
-
return
|
|
121
|
+
return apcMemoryFile(p.path);
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
// Channel context passed to the super-agent loop. `api` is the default when
|
|
@@ -39,17 +39,7 @@ import {
|
|
|
39
39
|
removeRole,
|
|
40
40
|
} from "#core/config/index.js";
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
if (!channel?.bot_token) return channel;
|
|
44
|
-
return {
|
|
45
|
-
...channel,
|
|
46
|
-
bot_token: `*** set *** (...${String(channel.bot_token).slice(-5)})`,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function isSecretMarker(value) {
|
|
51
|
-
return typeof value === "string" && value.startsWith("*** set ***");
|
|
52
|
-
}
|
|
42
|
+
import { redactChannel, isSecretMarker } from "#core/config/redact.js";
|
|
53
43
|
|
|
54
44
|
export function register(app, { telegram }) {
|
|
55
45
|
app.get("/telegram/status", (_req, res) => {
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
// search / glob / grep → filesystem-bounded
|
|
5
5
|
// registry → /:name wildcard, MOUNT LAST so it
|
|
6
6
|
// doesn't shadow the specific paths
|
|
7
|
-
import { buildBrowserRouter } from "#core/tools/browser.js";
|
|
8
|
-
import { buildFetchRouter } from "#core/tools/fetch.js";
|
|
9
|
-
import { buildSearchRouter } from "#core/tools/search.js";
|
|
10
|
-
import { buildRegistryRouter } from "#core/tools/registry.js";
|
|
11
|
-
import { buildGlobRouter } from "#core/tools/glob.js";
|
|
12
|
-
import { buildGrepRouter } from "#core/tools/grep.js";
|
|
7
|
+
import { buildBrowserRouter } from "#core/http-tools/browser.js";
|
|
8
|
+
import { buildFetchRouter } from "#core/http-tools/fetch.js";
|
|
9
|
+
import { buildSearchRouter } from "#core/http-tools/search.js";
|
|
10
|
+
import { buildRegistryRouter } from "#core/http-tools/registry.js";
|
|
11
|
+
import { buildGlobRouter } from "#core/http-tools/glob.js";
|
|
12
|
+
import { buildGrepRouter } from "#core/http-tools/grep.js";
|
|
13
13
|
|
|
14
14
|
export function register(app, { express, projects, registries }) {
|
|
15
15
|
app.use("/tools/fetch", buildFetchRouter(express));
|
|
@@ -11,7 +11,7 @@ export function register(app) {
|
|
|
11
11
|
// the first real utterance doesn't pay the cold-load cost.
|
|
12
12
|
app.get("/transcribe/warmup", async (_req, res) => {
|
|
13
13
|
try {
|
|
14
|
-
const { warmupWhisper } = await import("../
|
|
14
|
+
const { warmupWhisper } = await import("../whisper-server.js");
|
|
15
15
|
res.json(await warmupWhisper());
|
|
16
16
|
} catch (e) {
|
|
17
17
|
res.status(500).json({ ok: false, error: e.message });
|
|
@@ -29,7 +29,7 @@ export function register(app) {
|
|
|
29
29
|
const language = req.headers["x-language"] || "auto";
|
|
30
30
|
const provider = req.headers["x-provider"];
|
|
31
31
|
try {
|
|
32
|
-
const { transcribeBuffer } = await import("
|
|
32
|
+
const { transcribeBuffer } = await import("#core/voice/transcription.js");
|
|
33
33
|
const result = await transcribeBuffer(buf, format, {
|
|
34
34
|
language: language === "auto" ? undefined : language,
|
|
35
35
|
beam_size: 3,
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Variable management per project. Reads/writes the two APX-owned scopes
|
|
2
|
+
// (project = <storagePath>/vars.json, global = ~/.apx/vars.json) and
|
|
3
|
+
// surfaces a merged effective view with sources annotated.
|
|
4
|
+
//
|
|
5
|
+
// GET /projects/:pid/vars -> { project, global, effective, sources }
|
|
6
|
+
// values masked unless ?reveal=1
|
|
7
|
+
// GET /projects/:pid/vars/:name -> { name, scope, value, masked }
|
|
8
|
+
// ?reveal=1 unmasks
|
|
9
|
+
// POST /projects/:pid/vars -> { ok, name, scope }
|
|
10
|
+
// body { name, value, scope }
|
|
11
|
+
// scope defaults to "project" (or
|
|
12
|
+
// "global" if pid=0).
|
|
13
|
+
// DELETE /projects/:pid/vars/:name?scope=… 204
|
|
14
|
+
//
|
|
15
|
+
// pid=0 (base project) is the conventional bucket for editing global vars
|
|
16
|
+
// from the web UI; project scope is rejected there because there is no
|
|
17
|
+
// storagePath that belongs to a real project.
|
|
18
|
+
import {
|
|
19
|
+
loadAllVars,
|
|
20
|
+
readGlobalVars,
|
|
21
|
+
readProjectVars,
|
|
22
|
+
setVar,
|
|
23
|
+
deleteVar,
|
|
24
|
+
maskValue,
|
|
25
|
+
} from "#core/vars/index.js";
|
|
26
|
+
|
|
27
|
+
function normalizeScope(raw, { isBase }) {
|
|
28
|
+
if (!raw) return isBase ? "global" : "project";
|
|
29
|
+
const s = String(raw).toLowerCase();
|
|
30
|
+
if (s === "project" || s === "global") return s;
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function maskAll(obj) {
|
|
35
|
+
const out = {};
|
|
36
|
+
for (const [k, v] of Object.entries(obj)) out[k] = maskValue(v);
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function register(app, { project }) {
|
|
41
|
+
app.get("/projects/:pid/vars", (req, res) => {
|
|
42
|
+
const p = project(req, res);
|
|
43
|
+
if (!p) return;
|
|
44
|
+
const reveal = req.query?.reveal === "1" || req.query?.reveal === "true";
|
|
45
|
+
const { project: proj, global, effective, sources } = loadAllVars({
|
|
46
|
+
storagePath: p.storagePath,
|
|
47
|
+
});
|
|
48
|
+
res.json({
|
|
49
|
+
scope_hint: String(p.id) === "0" ? "global" : "project",
|
|
50
|
+
project: reveal ? proj : maskAll(proj),
|
|
51
|
+
global: reveal ? global : maskAll(global),
|
|
52
|
+
effective: reveal ? effective : maskAll(effective),
|
|
53
|
+
sources,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
app.get("/projects/:pid/vars/:name", (req, res) => {
|
|
58
|
+
const p = project(req, res);
|
|
59
|
+
if (!p) return;
|
|
60
|
+
const name = req.params.name;
|
|
61
|
+
const proj = p.storagePath ? readProjectVars(p.storagePath) : {};
|
|
62
|
+
const global = readGlobalVars();
|
|
63
|
+
let scope = null;
|
|
64
|
+
let value = null;
|
|
65
|
+
if (Object.prototype.hasOwnProperty.call(proj, name)) {
|
|
66
|
+
scope = "project";
|
|
67
|
+
value = proj[name];
|
|
68
|
+
} else if (Object.prototype.hasOwnProperty.call(global, name)) {
|
|
69
|
+
scope = "global";
|
|
70
|
+
value = global[name];
|
|
71
|
+
} else {
|
|
72
|
+
return res.status(404).json({ error: `variable "${name}" not found` });
|
|
73
|
+
}
|
|
74
|
+
const reveal = req.query?.reveal === "1" || req.query?.reveal === "true";
|
|
75
|
+
res.json({
|
|
76
|
+
name,
|
|
77
|
+
scope,
|
|
78
|
+
value: reveal ? value : maskValue(value),
|
|
79
|
+
masked: !reveal,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
app.post("/projects/:pid/vars", (req, res) => {
|
|
84
|
+
const p = project(req, res);
|
|
85
|
+
if (!p) return;
|
|
86
|
+
const { name, value } = req.body || {};
|
|
87
|
+
if (!name || typeof name !== "string") {
|
|
88
|
+
return res.status(400).json({ error: "name required" });
|
|
89
|
+
}
|
|
90
|
+
if (value === undefined || value === null) {
|
|
91
|
+
return res.status(400).json({ error: "value required" });
|
|
92
|
+
}
|
|
93
|
+
const isBase = String(p.id) === "0";
|
|
94
|
+
const scope = normalizeScope(req.body?.scope, { isBase });
|
|
95
|
+
if (scope === null) {
|
|
96
|
+
return res
|
|
97
|
+
.status(400)
|
|
98
|
+
.json({ error: `unknown scope "${req.body?.scope}" (use project|global)` });
|
|
99
|
+
}
|
|
100
|
+
if (scope === "project" && (!p.storagePath || isBase)) {
|
|
101
|
+
return res.status(400).json({
|
|
102
|
+
error: "project scope is not available for the base workspace — use scope=global",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
setVar({ storagePath: p.storagePath, scope, name, value });
|
|
107
|
+
} catch (e) {
|
|
108
|
+
return res.status(400).json({ error: e.message });
|
|
109
|
+
}
|
|
110
|
+
res.status(201).json({ ok: true, name, scope });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
app.delete("/projects/:pid/vars/:name", (req, res) => {
|
|
114
|
+
const p = project(req, res);
|
|
115
|
+
if (!p) return;
|
|
116
|
+
const isBase = String(p.id) === "0";
|
|
117
|
+
const scope = normalizeScope(req.query?.scope, { isBase });
|
|
118
|
+
if (scope === null) {
|
|
119
|
+
return res
|
|
120
|
+
.status(400)
|
|
121
|
+
.json({ error: `unknown scope "${req.query?.scope}" (use project|global)` });
|
|
122
|
+
}
|
|
123
|
+
if (scope === "project" && (!p.storagePath || isBase)) {
|
|
124
|
+
return res.status(400).json({
|
|
125
|
+
error: "project scope is not available for the base workspace",
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
let removed;
|
|
129
|
+
try {
|
|
130
|
+
removed = deleteVar({ storagePath: p.storagePath, scope, name: req.params.name });
|
|
131
|
+
} catch (e) {
|
|
132
|
+
return res.status(400).json({ error: e.message });
|
|
133
|
+
}
|
|
134
|
+
if (!removed) return res.status(404).end();
|
|
135
|
+
res.status(204).end();
|
|
136
|
+
});
|
|
137
|
+
}
|