@agentprojectcontext/apx 1.33.1 → 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/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 +12 -5
- package/src/core/apc/paths.js +87 -0
- package/src/core/apc/scaffold.js +82 -76
- package/src/core/apc/skill-sync.js +10 -0
- package/src/{host/daemon/plugins → core/channels}/telegram/dispatch.js +38 -16
- 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/{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 -80
- 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 +9 -9
- 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 +2 -1
- 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-Aaiw8BZN.css +0 -1
- package/src/interfaces/web/dist/assets/index-DPqtjDjh.js +0 -602
- package/src/interfaces/web/dist/assets/index-DPqtjDjh.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/helpers.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
package/src/core/apc/scaffold.js
CHANGED
|
@@ -11,14 +11,22 @@ import {
|
|
|
11
11
|
} from "./parser.js";
|
|
12
12
|
import { readApcContextSkill } from "./skill-sync.js";
|
|
13
13
|
import { nowIso } from "../util/time.js";
|
|
14
|
+
import {
|
|
15
|
+
apcDir,
|
|
16
|
+
apcProjectFile,
|
|
17
|
+
apcAgentsDir,
|
|
18
|
+
apcAgentFile,
|
|
19
|
+
agentsMdFile,
|
|
20
|
+
} from "./paths.js";
|
|
14
21
|
|
|
15
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
23
|
// Now under src/core/apc/ — one more "../" to escape than before.
|
|
17
24
|
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", "..");
|
|
25
|
+
// <packageRoot>/skills/<slug>/SKILL.md — the SLIM engine-side set: every skill
|
|
26
|
+
// here is replicated verbatim into project IDE rule files (`apx skills add`) and
|
|
27
|
+
// into ~/.<host>/skills/ (`apx skills sync`). The rich super-agent catalog lives
|
|
28
|
+
// at src/core/runtime-skills/ and is intentionally NOT copied out.
|
|
18
29
|
const BUNDLED_SKILLS_DIR = path.join(PACKAGE_ROOT, "skills");
|
|
19
|
-
// runtime-skills lives at src/core/runtime-skills/, one level up from this
|
|
20
|
-
// file's new home in src/core/apc/ (was a sibling before the Phase 3 move).
|
|
21
|
-
const RUNTIME_SKILLS_DIR = path.join(__dirname, "..", "runtime-skills");
|
|
22
30
|
|
|
23
31
|
export const SPEC_VERSION = "0.1.0";
|
|
24
32
|
|
|
@@ -28,12 +36,12 @@ export const SPEC_VERSION = "0.1.0";
|
|
|
28
36
|
// install/update from the canonical APC repo (see src/interfaces/cli/postinstall.js).
|
|
29
37
|
// ---------------------------------------------------------------------------
|
|
30
38
|
|
|
31
|
-
//
|
|
32
|
-
//
|
|
39
|
+
// Read one slim skill from <packageRoot>/skills/<slug>/SKILL.md. `apc-context`
|
|
40
|
+
// is special-cased to refresh from the canonical APC repo if available.
|
|
33
41
|
function readBundledSkill(slug) {
|
|
34
42
|
if (slug === "apc-context") {
|
|
35
43
|
const synced = readApcContextSkill();
|
|
36
|
-
|
|
44
|
+
if (synced?.text) return synced.text;
|
|
37
45
|
}
|
|
38
46
|
const file = path.join(BUNDLED_SKILLS_DIR, slug, "SKILL.md");
|
|
39
47
|
if (!fs.existsSync(file)) return null;
|
|
@@ -134,19 +142,11 @@ const GLOBAL_SKILL_DIRS = [
|
|
|
134
142
|
path.join(os.homedir(), ".agents", "skills"), // Antigravity/other skills.sh adopters
|
|
135
143
|
];
|
|
136
144
|
|
|
137
|
-
function readRuntimeSkillFiles() {
|
|
138
|
-
if (!fs.existsSync(RUNTIME_SKILLS_DIR)) return [];
|
|
139
|
-
return fs.readdirSync(RUNTIME_SKILLS_DIR)
|
|
140
|
-
.filter((name) => name.endsWith(".md"))
|
|
141
|
-
.sort()
|
|
142
|
-
.map((name) => ({
|
|
143
|
-
slug: path.basename(name, ".md"),
|
|
144
|
-
md: fs.readFileSync(path.join(RUNTIME_SKILLS_DIR, name), "utf8").trim(),
|
|
145
|
-
}));
|
|
146
|
-
}
|
|
147
|
-
|
|
148
145
|
// Install APX + APC context skills into IDE rule files. Returns an array of result objects.
|
|
149
146
|
// targetIds: array of target ids to install; null = all project targets.
|
|
147
|
+
// Writes the slim engine-side skill from <packageRoot>/skills/. The rich
|
|
148
|
+
// super-agent set in src/core/runtime-skills/ is intentionally never written
|
|
149
|
+
// into project IDE files.
|
|
150
150
|
export function installIdeSkills(root, targetIds = null) {
|
|
151
151
|
const apxRaw = readBundledSkill("apx");
|
|
152
152
|
const apcRaw = readBundledSkill("apc-context");
|
|
@@ -197,36 +197,27 @@ export function installIdeSkills(root, targetIds = null) {
|
|
|
197
197
|
// on the user's machine after `npm install -g .` (or `npm update -g apx`)
|
|
198
198
|
// without anyone having to touch this file.
|
|
199
199
|
//
|
|
200
|
-
// Excluded: directory names starting with "." (e.g. .DS_Store)
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
// public → pushed to every global skill dir on install / sync (default).
|
|
205
|
-
// optional → not pushed by default; user opts in with --include-optional
|
|
206
|
-
// or `apx skills add <slug> --global` for one-off install.
|
|
207
|
-
// internal → APX-developer skills (mcp-builder, skill-builder, etc.); never
|
|
208
|
-
// pushed globally, only available to APX itself via the bundled
|
|
209
|
-
// copy. Avoids cluttering other IDEs with stuff their users won't
|
|
210
|
-
// run.
|
|
200
|
+
// Excluded: directory names starting with "." (e.g. .DS_Store).
|
|
201
|
+
// Every slug under <packageRoot>/skills/ is part of the slim engine set and
|
|
202
|
+
// gets pushed to global dirs on `apx skills sync`. No scope filtering — the
|
|
203
|
+
// dir IS the contract.
|
|
211
204
|
export function listBundledSkillSlugs() {
|
|
212
205
|
return discoverBundledSkills().map((s) => s.slug);
|
|
213
206
|
}
|
|
214
207
|
|
|
215
208
|
export function listBundledSkills() {
|
|
216
|
-
return discoverBundledSkills().map(({ slug
|
|
209
|
+
return discoverBundledSkills().map(({ slug }) => ({ slug }));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Backwards-compat alias — every bundled slug here IS an engine slug now.
|
|
213
|
+
export function listEngineSkills() {
|
|
214
|
+
return listBundledSkills();
|
|
217
215
|
}
|
|
218
216
|
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
const end = md.indexOf("\n---", 4);
|
|
224
|
-
if (end === -1) return "public";
|
|
225
|
-
const m = md.slice(4, end).match(/^scope:\s*(\w+)/m);
|
|
226
|
-
if (!m) return "public";
|
|
227
|
-
const s = m[1].toLowerCase();
|
|
228
|
-
if (s === "internal" || s === "optional" || s === "public") return s;
|
|
229
|
-
return "public";
|
|
217
|
+
// Legacy slugs APX used to ship to global dirs but no longer does — exposed so
|
|
218
|
+
// the CLI can report what `installGlobalSkills` will prune.
|
|
219
|
+
export function listLegacyPruneSlugs() {
|
|
220
|
+
return [...PRUNE_LEGACY_SLUGS];
|
|
230
221
|
}
|
|
231
222
|
|
|
232
223
|
function discoverBundledSkills() {
|
|
@@ -239,65 +230,80 @@ function discoverBundledSkills() {
|
|
|
239
230
|
const skillFile = path.join(root, entry.name, "SKILL.md");
|
|
240
231
|
if (!fs.existsSync(skillFile)) continue;
|
|
241
232
|
const md = fs.readFileSync(skillFile, "utf8");
|
|
242
|
-
out.push({ slug: entry.name, md
|
|
233
|
+
out.push({ slug: entry.name, md });
|
|
243
234
|
}
|
|
244
235
|
return out.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
245
236
|
}
|
|
246
237
|
|
|
247
|
-
// Install
|
|
248
|
-
// Cursor, Codex,
|
|
238
|
+
// Install the slim engine skill set to every global ~/.../skills/ dir
|
|
239
|
+
// (Claude Code, Cursor, Codex, Antigravity/skills.sh). External engines only
|
|
240
|
+
// need to know how to talk TO apx — not the full APX sub-skill catalog. The
|
|
241
|
+
// rich bundled set in skills/<slug>/ stays in-repo for the APX super-agent.
|
|
242
|
+
//
|
|
243
|
+
// The set lives at skills/engines/<slug>/SKILL.md and is currently:
|
|
244
|
+
// apx, apx-mcp, apc-context.
|
|
249
245
|
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
// `apx skills add <slug> --global` for a single one).
|
|
246
|
+
// `includeOptional` / `includeInternal` are kept as no-op flags for backward
|
|
247
|
+
// compatibility with `apx skills sync --include-…`; the slim set has no tiers.
|
|
253
248
|
//
|
|
254
|
-
// Pruning:
|
|
255
|
-
//
|
|
256
|
-
// the
|
|
249
|
+
// Pruning: removes stale APX-shipped slugs that are no longer in the engine
|
|
250
|
+
// set (the catalog of slugs APX has ever published, see PRUNE_LEGACY_SLUGS).
|
|
251
|
+
// Skills the user installed themselves are NOT touched.
|
|
257
252
|
//
|
|
258
253
|
// Returns an array of { dir, skill, file, status, scope }.
|
|
259
|
-
// status ∈ {created, updated, unchanged, pruned
|
|
254
|
+
// status ∈ {created, updated, unchanged, pruned}
|
|
255
|
+
const PRUNE_LEGACY_SLUGS = [
|
|
256
|
+
"apx-agency-agents",
|
|
257
|
+
"apx-agent",
|
|
258
|
+
"apx-mcp-builder",
|
|
259
|
+
"apx-project",
|
|
260
|
+
"apx-routine",
|
|
261
|
+
"apx-runtime",
|
|
262
|
+
"apx-sessions",
|
|
263
|
+
"apx-skill-builder",
|
|
264
|
+
"apx-task",
|
|
265
|
+
"apx-telegram",
|
|
266
|
+
"apx-voice",
|
|
267
|
+
// Runtime CLI docs that previously leaked into global dirs — these are
|
|
268
|
+
// loaded in-process by the daemon and should NOT live on disk in IDE skill
|
|
269
|
+
// dirs.
|
|
270
|
+
"claude-code",
|
|
271
|
+
"codex-cli",
|
|
272
|
+
"opencode-cli",
|
|
273
|
+
"openrouter",
|
|
274
|
+
];
|
|
275
|
+
|
|
260
276
|
export function installGlobalSkills({
|
|
261
|
-
includeOptional = false,
|
|
262
|
-
includeInternal = false,
|
|
263
277
|
prune = true,
|
|
278
|
+
// No-ops kept for CLI backward compatibility (the slim engine set has no tiers).
|
|
279
|
+
includeOptional: _includeOptional = false,
|
|
280
|
+
includeInternal: _includeInternal = false,
|
|
264
281
|
} = {}) {
|
|
265
|
-
const
|
|
266
|
-
const wanted = all.filter((s) => {
|
|
267
|
-
if (s.scope === "internal") return includeInternal;
|
|
268
|
-
if (s.scope === "optional") return includeOptional;
|
|
269
|
-
return true; // public
|
|
270
|
-
});
|
|
282
|
+
const wanted = discoverBundledSkills();
|
|
271
283
|
const wantedSlugs = new Set(wanted.map((s) => s.slug));
|
|
272
|
-
const knownSlugs = new Set(all.map((s) => s.slug));
|
|
273
284
|
|
|
274
285
|
const results = [];
|
|
275
286
|
for (const base of GLOBAL_SKILL_DIRS) {
|
|
276
|
-
|
|
277
|
-
for (const { slug, md, scope } of wanted) {
|
|
287
|
+
for (const { slug, md } of wanted) {
|
|
278
288
|
const dest = path.join(base, slug, "SKILL.md");
|
|
279
289
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
280
290
|
const existed = fs.existsSync(dest);
|
|
281
291
|
const previous = existed ? fs.readFileSync(dest, "utf8") : null;
|
|
282
292
|
if (previous === md) {
|
|
283
|
-
results.push({ dir: base, skill: slug, file: dest, status: "unchanged", scope });
|
|
293
|
+
results.push({ dir: base, skill: slug, file: dest, status: "unchanged", scope: "engine" });
|
|
284
294
|
continue;
|
|
285
295
|
}
|
|
286
296
|
fs.writeFileSync(dest, md, "utf8");
|
|
287
|
-
results.push({ dir: base, skill: slug, file: dest, status: existed ? "updated" : "created", scope });
|
|
297
|
+
results.push({ dir: base, skill: slug, file: dest, status: existed ? "updated" : "created", scope: "engine" });
|
|
288
298
|
}
|
|
289
|
-
// Prune anything WE shipped previously but should no longer be there
|
|
290
|
-
// (slug exists in the bundle but isn't `wanted` this run).
|
|
291
299
|
if (prune) {
|
|
292
|
-
for (const
|
|
300
|
+
for (const slug of PRUNE_LEGACY_SLUGS) {
|
|
293
301
|
if (wantedSlugs.has(slug)) continue;
|
|
294
|
-
if (!knownSlugs.has(slug)) continue;
|
|
295
302
|
const dest = path.join(base, slug, "SKILL.md");
|
|
296
303
|
if (!fs.existsSync(dest)) continue;
|
|
297
304
|
fs.unlinkSync(dest);
|
|
298
|
-
// Best-effort: drop the now-empty <slug>/ dir too.
|
|
299
305
|
try { fs.rmdirSync(path.dirname(dest)); } catch {}
|
|
300
|
-
results.push({ dir: base, skill: slug, file: dest, status: "pruned", scope });
|
|
306
|
+
results.push({ dir: base, skill: slug, file: dest, status: "pruned", scope: "legacy" });
|
|
301
307
|
}
|
|
302
308
|
}
|
|
303
309
|
}
|
|
@@ -423,7 +429,7 @@ function writeMigrateMd(apfDir, found) {
|
|
|
423
429
|
// Get the stable APX storage ID for a project, generating one if it doesn't exist.
|
|
424
430
|
// Called by the daemon when registering a project.
|
|
425
431
|
export function getOrCreateApxId(root) {
|
|
426
|
-
const p =
|
|
432
|
+
const p = apcProjectFile(root);
|
|
427
433
|
if (!fs.existsSync(p)) return null;
|
|
428
434
|
let cfg;
|
|
429
435
|
try { cfg = JSON.parse(fs.readFileSync(p, "utf8")); } catch { return null; }
|
|
@@ -439,7 +445,7 @@ export function initApf(directory, { name } = {}) {
|
|
|
439
445
|
const root = path.resolve(directory);
|
|
440
446
|
fs.mkdirSync(root, { recursive: true });
|
|
441
447
|
|
|
442
|
-
const apfDir =
|
|
448
|
+
const apfDir = apcDir(root);
|
|
443
449
|
fs.mkdirSync(path.join(apfDir, "agents"), { recursive: true });
|
|
444
450
|
fs.mkdirSync(path.join(apfDir, "skills"), { recursive: true });
|
|
445
451
|
fs.mkdirSync(path.join(apfDir, "commands"), { recursive: true });
|
|
@@ -469,7 +475,7 @@ export function initApf(directory, { name } = {}) {
|
|
|
469
475
|
fs.writeFileSync(gitignore, APC_GITIGNORE);
|
|
470
476
|
}
|
|
471
477
|
|
|
472
|
-
const agentsMd =
|
|
478
|
+
const agentsMd = agentsMdFile(root);
|
|
473
479
|
if (!fs.existsSync(agentsMd)) {
|
|
474
480
|
fs.writeFileSync(agentsMd, AGENTS_MD_TEMPLATE);
|
|
475
481
|
}
|
|
@@ -485,13 +491,13 @@ export function initApf(directory, { name } = {}) {
|
|
|
485
491
|
}
|
|
486
492
|
|
|
487
493
|
export function ensureAgentDir(root, slug) {
|
|
488
|
-
fs.mkdirSync(
|
|
489
|
-
return
|
|
494
|
+
fs.mkdirSync(apcAgentsDir(root), { recursive: true });
|
|
495
|
+
return apcAgentsDir(root);
|
|
490
496
|
}
|
|
491
497
|
|
|
492
498
|
// Write .apc/agents/<slug>.md — the canonical agent definition file.
|
|
493
499
|
export function writeAgentFile(root, slug, fields, body = "") {
|
|
494
|
-
const dest =
|
|
500
|
+
const dest = apcAgentFile(root, slug);
|
|
495
501
|
const lines = ["---"];
|
|
496
502
|
const order = ["role", "model", "language", "description", "skills", "tools"];
|
|
497
503
|
const written = new Set();
|
|
@@ -579,7 +585,7 @@ export function restoreVaultAgent(slug) {
|
|
|
579
585
|
|
|
580
586
|
// Add a slug to the project's agents.imported list in project.json
|
|
581
587
|
export function addImportedAgent(root, slug) {
|
|
582
|
-
const p =
|
|
588
|
+
const p = apcProjectFile(root);
|
|
583
589
|
let cfg = {};
|
|
584
590
|
try { cfg = JSON.parse(fs.readFileSync(p, "utf8")); } catch {}
|
|
585
591
|
if (!cfg.agents) cfg.agents = {};
|
|
@@ -8,7 +8,10 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
8
8
|
// Repo root is three levels up, not two.
|
|
9
9
|
export const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", "..");
|
|
10
10
|
|
|
11
|
+
// Engine-side slim copy (replicated to ~/.<host>/skills/ by apx skills sync).
|
|
11
12
|
export const APC_SKILL_REL = path.join("skills", "apc-context", "SKILL.md");
|
|
13
|
+
// Runtime-internal copy (loaded by the super-agent — never published outside).
|
|
14
|
+
export const APC_BUILTIN_SKILL_REL = path.join("src", "core", "runtime-skills", "apc-context", "SKILL.md");
|
|
12
15
|
export const APC_SKILL_REMOTE =
|
|
13
16
|
"https://raw.githubusercontent.com/agentprojectcontext/agentprojectcontext/main/skills/apc-context/SKILL.md";
|
|
14
17
|
|
|
@@ -95,5 +98,12 @@ export async function refreshApcContextSkill({ packageRoot = PACKAGE_ROOT, timeo
|
|
|
95
98
|
|
|
96
99
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
97
100
|
fs.writeFileSync(dest, text, "utf8");
|
|
101
|
+
|
|
102
|
+
// Keep the runtime-internal copy (src/core/runtime-skills/apc-context/) in
|
|
103
|
+
// sync — that's where the super-agent loads it from.
|
|
104
|
+
const builtinDest = path.join(packageRoot, APC_BUILTIN_SKILL_REL);
|
|
105
|
+
fs.mkdirSync(path.dirname(builtinDest), { recursive: true });
|
|
106
|
+
fs.writeFileSync(builtinDest, text, "utf8");
|
|
107
|
+
|
|
98
108
|
return { ok: true, source, refreshed: true };
|
|
99
109
|
}
|
|
@@ -1,10 +1,38 @@
|
|
|
1
1
|
// Inbound Telegram update dispatcher.
|
|
2
2
|
//
|
|
3
|
-
// Extracted from the ChannelPoller class so
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// Extracted from the ChannelPoller class so index.js stays under ~800 lines
|
|
4
|
+
// and the routing logic for text/photo/voice/document updates lives on its
|
|
5
|
+
// own. Takes the poller instance as `self`; every `this.X` in the original
|
|
6
|
+
// method becomes `self.X` here. The poller exposes _handleUpdate as a thin
|
|
7
|
+
// facade that delegates to handleUpdate(this, u).
|
|
8
|
+
//
|
|
9
|
+
// IMPORTANT: this module needs the same imports the original index.js had
|
|
10
|
+
// in module scope, because the extracted body references identifiers like
|
|
11
|
+
// `appendGlobalMessage`, `CHANNELS`, `nowIso`, etc. Top-level imports here
|
|
12
|
+
// keep that scope intact — earlier splits forgot them and the bug only
|
|
13
|
+
// surfaced when a real telegram update arrived (ReferenceError at runtime).
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { callEngine } from "#core/engines/index.js";
|
|
16
|
+
import { runSuperAgent, isSuperAgentEnabled } from "#core/agent/super-agent.js";
|
|
17
|
+
import { stripThinking } from "#core/util/thinking.js";
|
|
18
|
+
import { getRecentTelegramTurnsFromFs, appendGlobalMessage } from "#core/stores/messages.js";
|
|
19
|
+
import { compactChannelIfNeeded } from "#core/memory/index.js";
|
|
20
|
+
import { readAgents } from "#core/apc/parser.js";
|
|
21
|
+
import { buildAgentSystem } from "#core/agent/build-agent-system.js";
|
|
22
|
+
import { transcribe as transcribeAudioFile } from "#core/voice/transcription.js";
|
|
23
|
+
import { resolveAgentName, SUPERAGENT_ACTOR_ID } from "#core/identity/index.js";
|
|
24
|
+
import { registerSender, resolveAllowedTools } from "#core/identity/telegram.js";
|
|
25
|
+
import { buildRelationshipBlock } from "#core/agent/index.js";
|
|
26
|
+
import { getConfirmationStore as getConfirmStore } from "#core/confirmation/pending-store.js";
|
|
27
|
+
import { CHANNELS } from "#core/constants/channels.js";
|
|
28
|
+
import { tryResolveSkillCommand } from "#core/agent/skills/trigger.js";
|
|
29
|
+
import { createTelegramConfirmAdapter } from "#core/confirmation/adapters/telegram.js";
|
|
30
|
+
import * as askFlow from "./ask.js";
|
|
31
|
+
import { buildTelegramMeta, resolveBotToken, sleep } from "./helpers.js";
|
|
32
|
+
import { sendPhoto, sendVoice, sendDocument, sendAudio, downloadTelegramFile, API_BASE } from "./media.js";
|
|
33
|
+
import { t, resolveLang } from "#core/i18n/index.js";
|
|
34
|
+
|
|
35
|
+
const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
8
36
|
|
|
9
37
|
export async function handleUpdate(self, u) {
|
|
10
38
|
self.lastUpdateAt = nowIso();
|
|
@@ -259,7 +287,7 @@ export async function handleUpdate(self, u) {
|
|
|
259
287
|
// honor it for future messages.
|
|
260
288
|
if (isReset) {
|
|
261
289
|
try {
|
|
262
|
-
const ack = "
|
|
290
|
+
const ack = t("telegram.reset_ack", { lang: resolveLang(self.globalConfig) });
|
|
263
291
|
await self._send({ chat_id, text: ack });
|
|
264
292
|
appendGlobalMessage({
|
|
265
293
|
channel: CHANNELS.TELEGRAM,
|
|
@@ -343,15 +371,7 @@ export async function handleUpdate(self, u) {
|
|
|
343
371
|
// short localized heads-up the moment real work starts (first tool_start),
|
|
344
372
|
// but only if the agent didn't already write its own "on it" line.
|
|
345
373
|
let sentHeadsUp = false;
|
|
346
|
-
const headsUpPhrase = () => {
|
|
347
|
-
const lang = (self.globalConfig?.user?.language || "es").slice(0, 2);
|
|
348
|
-
const byLang = {
|
|
349
|
-
es: "Dale, estoy con eso… 🛠️",
|
|
350
|
-
en: "On it — working on that… 🛠️",
|
|
351
|
-
pt: "Já estou nisso… 🛠️",
|
|
352
|
-
};
|
|
353
|
-
return byLang[lang] || byLang.es;
|
|
354
|
-
};
|
|
374
|
+
const headsUpPhrase = () => t("telegram.heads_up", { lang: resolveLang(self.globalConfig) });
|
|
355
375
|
if (!replyText && isSuperAgentEnabled(self.globalConfig)) {
|
|
356
376
|
const onEvent = async (ev) => {
|
|
357
377
|
try {
|
|
@@ -523,7 +543,9 @@ export async function handleUpdate(self, u) {
|
|
|
523
543
|
const finalClean = replyText ? stripThinking(replyText).trim() : "";
|
|
524
544
|
let toSend = "";
|
|
525
545
|
if (finalClean && finalClean !== lastStreamedText) toSend = finalClean;
|
|
526
|
-
else if (!finalClean && streamedCount === 0)
|
|
546
|
+
else if (!finalClean && streamedCount === 0) {
|
|
547
|
+
toSend = t("telegram.fallback_listo", { lang: resolveLang(self.globalConfig) });
|
|
548
|
+
}
|
|
527
549
|
|
|
528
550
|
stopTyping();
|
|
529
551
|
if (!toSend) return; // everything was already streamed — nothing left to send
|
package/src/core/config/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import fs from "node:fs";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { APX_HOME, CONFIG_PATH } from "./paths.js";
|
|
8
8
|
import { PERMISSION_MODES, DEFAULT_PERMISSION_MODE } from "../constants/permissions.js";
|
|
9
|
+
import { agentsMdFile, apcProjectFile } from "../apc/paths.js";
|
|
9
10
|
|
|
10
11
|
export {
|
|
11
12
|
APX_HOME,
|
|
@@ -395,10 +396,10 @@ export function effectiveHost(cfg) {
|
|
|
395
396
|
|
|
396
397
|
export function addProject(cfg, projectPath) {
|
|
397
398
|
const abs = path.resolve(projectPath);
|
|
398
|
-
if (!fs.existsSync(
|
|
399
|
+
if (!fs.existsSync(agentsMdFile(abs))) {
|
|
399
400
|
throw new Error(`not an APC project: ${abs} (no AGENTS.md)`);
|
|
400
401
|
}
|
|
401
|
-
if (!fs.existsSync(
|
|
402
|
+
if (!fs.existsSync(apcProjectFile(abs))) {
|
|
402
403
|
throw new Error(`not an APC project: ${abs} (no .apc/project.json)`);
|
|
403
404
|
}
|
|
404
405
|
const exists = cfg.projects.find((p) => path.resolve(p.path) === abs);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Secret redaction for the global config. Wraps any string secret with a
|
|
2
|
+
// `*** set *** (...<suffix>)` marker so the web admin can show "value is set"
|
|
3
|
+
// without leaking it, AND so PATCH callers can echo back the marker to mean
|
|
4
|
+
// "don't touch this one" — see isSecretMarker / mergeRedactedChannels below.
|
|
5
|
+
//
|
|
6
|
+
// The dotted paths in SECRET_PATHS are the single source of truth for "which
|
|
7
|
+
// keys are secrets". Anything new (a new engine api_key, a new TTS provider
|
|
8
|
+
// key, etc.) goes here and every redaction path picks it up.
|
|
9
|
+
|
|
10
|
+
const SECRET_MARKER_PREFIX = "*** set ***";
|
|
11
|
+
|
|
12
|
+
export const SECRET_PATHS = [
|
|
13
|
+
"engines.anthropic.api_key",
|
|
14
|
+
"engines.openai.api_key",
|
|
15
|
+
"engines.groq.api_key",
|
|
16
|
+
"engines.openrouter.api_key",
|
|
17
|
+
"engines.gemini.api_key",
|
|
18
|
+
"voice.tts.elevenlabs.api_key",
|
|
19
|
+
"voice.tts.openai.api_key",
|
|
20
|
+
"voice.tts.gemini.api_key",
|
|
21
|
+
"memory.embeddings.openai.api_key",
|
|
22
|
+
"memory.embeddings.gemini.api_key",
|
|
23
|
+
// Telegram bot tokens live inside an array — handled separately in redact()
|
|
24
|
+
// because dotted paths can't address array entries.
|
|
25
|
+
"telegram.channels.*.bot_token",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** Replace a secret string with the visible marker, preserving the last 5 chars. */
|
|
29
|
+
export function secretMarker(value) {
|
|
30
|
+
if (typeof value !== "string" || !value.length) return value;
|
|
31
|
+
const suffix = value.slice(-5);
|
|
32
|
+
return `${SECRET_MARKER_PREFIX} (...${suffix})`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** True when a value is the placeholder a redacted view sends back unchanged. */
|
|
36
|
+
export function isSecretMarker(value) {
|
|
37
|
+
return typeof value === "string" && value.startsWith(SECRET_MARKER_PREFIX);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Deep-copy of `cfg` with every secret string replaced by its marker. */
|
|
41
|
+
export function redactConfig(cfg) {
|
|
42
|
+
const out = JSON.parse(JSON.stringify(cfg || {}));
|
|
43
|
+
const mark = (val) => (typeof val === "string" && val.length ? secretMarker(val) : val);
|
|
44
|
+
|
|
45
|
+
for (const dotted of SECRET_PATHS) {
|
|
46
|
+
if (dotted.includes("*")) continue;
|
|
47
|
+
const parts = dotted.split(".");
|
|
48
|
+
let cur = out;
|
|
49
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
50
|
+
if (!cur[parts[i]] || typeof cur[parts[i]] !== "object") { cur = null; break; }
|
|
51
|
+
cur = cur[parts[i]];
|
|
52
|
+
}
|
|
53
|
+
if (cur && cur[parts[parts.length - 1]]) {
|
|
54
|
+
cur[parts[parts.length - 1]] = mark(cur[parts[parts.length - 1]]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const channels = out?.telegram?.channels;
|
|
58
|
+
if (Array.isArray(channels)) {
|
|
59
|
+
for (const ch of channels) {
|
|
60
|
+
if (ch && typeof ch.bot_token === "string" && ch.bot_token.length) {
|
|
61
|
+
ch.bot_token = mark(ch.bot_token);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Redact a single Telegram channel record. */
|
|
69
|
+
export function redactChannel(channel) {
|
|
70
|
+
if (!channel?.bot_token) return channel;
|
|
71
|
+
return { ...channel, bot_token: secretMarker(channel.bot_token) };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Merge a PATCH-shape `nextChannels` against the prior on-disk list. Any
|
|
76
|
+
* incoming channel whose bot_token is missing or a marker takes the prior
|
|
77
|
+
* token verbatim — so a UI that echoes the redacted view back doesn't wipe
|
|
78
|
+
* the real secret.
|
|
79
|
+
*/
|
|
80
|
+
export function mergeRedactedChannels(nextChannels, priorChannels) {
|
|
81
|
+
if (!Array.isArray(nextChannels)) return nextChannels;
|
|
82
|
+
const priorByName = new Map(
|
|
83
|
+
(Array.isArray(priorChannels) ? priorChannels : [])
|
|
84
|
+
.filter((c) => c && typeof c.name === "string")
|
|
85
|
+
.map((c) => [c.name, c])
|
|
86
|
+
);
|
|
87
|
+
return nextChannels.map((channel) => {
|
|
88
|
+
if (!channel || typeof channel !== "object") return channel;
|
|
89
|
+
const prior = priorByName.get(channel.name);
|
|
90
|
+
if (prior?.bot_token && (channel.bot_token === undefined || isSecretMarker(channel.bot_token))) {
|
|
91
|
+
return { ...channel, bot_token: prior.bot_token };
|
|
92
|
+
}
|
|
93
|
+
return channel;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -16,4 +16,6 @@ export const CHANNELS = Object.freeze({
|
|
|
16
16
|
DECK: "deck", // Mobile cockpit dashboard
|
|
17
17
|
DESKTOP: "desktop", // Electron capsule (always voice mode)
|
|
18
18
|
CODE: "code", // `apx code` — terminal coding session
|
|
19
|
+
DIRECT: "direct", // Planned: 1:1 channel that isn't a chat platform
|
|
20
|
+
WHATSAPP: "whatsapp", // Planned: WhatsApp bot integration
|
|
19
21
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Code session modes. PLAN = read-only exploration (the agent proposes
|
|
2
|
+
// changes but never mutates); BUILD = unrestricted execution. The value
|
|
3
|
+
// lives in code-sessions.json (session.mode) and is what api/code.js,
|
|
4
|
+
// stores/code-sessions.js, and agent/prompts/modes/ all branch on.
|
|
5
|
+
export const CODE_MODES = Object.freeze({
|
|
6
|
+
PLAN: "plan",
|
|
7
|
+
BUILD: "build",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_CODE_MODE = CODE_MODES.BUILD;
|