@agentprojectcontext/apx 1.33.1 → 1.35.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 +136 -59
- 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 +178 -124
- package/src/core/agent/prompts/channels/code.md +12 -10
- package/src/core/agent/prompts/channels/desktop.md +5 -32
- package/src/core/agent/prompts/channels/telegram.md +4 -15
- package/src/core/agent/prompts/channels/web_code.md +11 -11
- package/src/core/agent/prompts/core/agent-base.md +24 -0
- package/src/core/agent/prompts/core/project-agent.md +11 -0
- package/src/core/agent/prompts/core/super-agent.md +21 -0
- package/src/core/agent/prompts/discipline/action.md +10 -0
- package/src/core/agent/prompts/discipline/single-segment.md +6 -0
- package/src/core/agent/prompts/discipline/two-segment.md +11 -0
- 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/self-memory.js +43 -1
- package/src/core/agent/skills/index-store.js +307 -0
- package/src/core/agent/skills/index.js +15 -1
- package/src/core/agent/skills/inspector.js +317 -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/super-agent.js +7 -1
- package/src/core/agent/tools/handlers/_git.js +50 -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/git-diff.js +44 -0
- package/src/core/agent/tools/handlers/git-log.js +38 -0
- package/src/core/agent/tools/handlers/git-show.js +34 -0
- package/src/core/agent/tools/handlers/git-status.js +61 -0
- 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 +169 -0
- package/src/core/agent/tools/registry-bridge.js +6 -14
- package/src/core/agent/tools/registry.js +103 -69
- 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 +24 -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 +83 -0
- package/src/core/runtime-skills/apx-agency-agents/SKILL.md +125 -0
- package/src/core/runtime-skills/apx-agent/SKILL.md +97 -0
- package/src/core/runtime-skills/apx-mcp/SKILL.md +111 -0
- package/src/core/runtime-skills/apx-mcp-builder/SKILL.md +169 -0
- package/{skills → src/core/runtime-skills}/apx-project/SKILL.md +20 -29
- package/src/core/runtime-skills/apx-routine/SKILL.md +127 -0
- package/src/core/runtime-skills/apx-runtime/SKILL.md +99 -0
- package/src/core/runtime-skills/apx-sessions/SKILL.md +232 -0
- package/src/core/runtime-skills/apx-skill-builder/SKILL.md +129 -0
- package/{skills → src/core/runtime-skills}/apx-task/SKILL.md +18 -21
- package/src/core/runtime-skills/apx-telegram/SKILL.md +120 -0
- package/src/core/runtime-skills/apx-voice/SKILL.md +117 -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/skills.js +140 -6
- package/src/host/daemon/api/super-agent.js +56 -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 +20 -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/branding.js +53 -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 +290 -55
- package/src/interfaces/cli/index.js +84 -2
- package/src/interfaces/web/dist/assets/index-C0fm31dY.js +618 -0
- package/src/interfaces/web/dist/assets/index-C0fm31dY.js.map +1 -0
- package/src/interfaces/web/dist/assets/index-UcAqlBO6.css +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 +37 -4
- 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 +73 -4
- package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +222 -0
- 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/hooks/useChat.ts +19 -0
- package/src/interfaces/web/src/i18n/en.ts +175 -7
- package/src/interfaces/web/src/i18n/es.ts +180 -15
- package/src/interfaces/web/src/lib/api/mcps.ts +25 -0
- package/src/interfaces/web/src/lib/api/skills.ts +70 -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/SettingsScreen.tsx +6 -2
- 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 +15 -0
- package/skills/apx-agency-agents/SKILL.md +0 -141
- package/skills/apx-agent/SKILL.md +0 -100
- package/skills/apx-mcp-builder/SKILL.md +0 -183
- package/skills/apx-routine/SKILL.md +0 -140
- package/skills/apx-runtime/SKILL.md +0 -117
- package/skills/apx-sessions/SKILL.md +0 -281
- package/skills/apx-skill-builder/SKILL.md +0 -153
- package/skills/apx-telegram/SKILL.md +0 -131
- package/skills/apx-voice/SKILL.md +0 -137
- package/src/core/agent/prompts/action-discipline.md +0 -24
- package/src/core/agent/prompts/super-agent-base.md +0 -42
- 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/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
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// /deck/exec implementation.
|
|
2
|
+
//
|
|
3
|
+
// All shell spawning sits behind this helper so api/deck.js stays a thin HTTP
|
|
4
|
+
// adapter. The OS abstraction is intentionally tiny: pick the "opener" command
|
|
5
|
+
// for the platform and pass `target` as a single arg (no shell). For
|
|
6
|
+
// app-launching on macOS we use `open -a <App>`.
|
|
7
|
+
//
|
|
8
|
+
// Stays in host/daemon/ because it's pure process orchestration (spawn child
|
|
9
|
+
// processes), not domain logic.
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
|
|
12
|
+
const MAC_APPS = {
|
|
13
|
+
// Whitelisted mac app names. Adding here is the only way the deck can
|
|
14
|
+
// launch something — we never honour a free-form `app` string.
|
|
15
|
+
claude: "Claude",
|
|
16
|
+
chatgpt: "ChatGPT",
|
|
17
|
+
cursor: "Cursor",
|
|
18
|
+
vscode: "Visual Studio Code",
|
|
19
|
+
zen: "Zen Browser",
|
|
20
|
+
terminal: "Terminal",
|
|
21
|
+
iterm: "iTerm",
|
|
22
|
+
finder: "Finder",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function platformOpener() {
|
|
26
|
+
if (process.platform === "darwin") return "open";
|
|
27
|
+
if (process.platform === "win32") return "start";
|
|
28
|
+
return "xdg-open";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function spawnDetached(cmd, args) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
34
|
+
let settled = false;
|
|
35
|
+
const done = (err) => {
|
|
36
|
+
if (settled) return;
|
|
37
|
+
settled = true;
|
|
38
|
+
err ? reject(err) : resolve();
|
|
39
|
+
};
|
|
40
|
+
child.on("error", done);
|
|
41
|
+
// Give the process a tick to fail-fast (bad binary); otherwise detach.
|
|
42
|
+
setTimeout(() => {
|
|
43
|
+
try { child.unref(); } catch {}
|
|
44
|
+
done(null);
|
|
45
|
+
}, 250);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Pipe `text` into the platform clipboard (pbcopy / xclip / clip). */
|
|
50
|
+
export async function copyToClipboard(text) {
|
|
51
|
+
const platform = process.platform;
|
|
52
|
+
const cmd =
|
|
53
|
+
platform === "darwin" ? "pbcopy" :
|
|
54
|
+
platform === "win32" ? "clip" :
|
|
55
|
+
"xclip";
|
|
56
|
+
const args = platform === "linux" ? ["-selection", "clipboard"] : [];
|
|
57
|
+
await new Promise((resolve, reject) => {
|
|
58
|
+
const child = spawn(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
59
|
+
child.on("error", reject);
|
|
60
|
+
child.on("close", (code) => (code === 0 ? resolve() : reject(new Error(`${cmd} exited ${code}`))));
|
|
61
|
+
child.stdin.end(text);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Dispatch one /deck/exec action. `ctx.projects` is the daemon's
|
|
67
|
+
* ProjectManager — used to resolve numeric project ids to absolute paths.
|
|
68
|
+
*
|
|
69
|
+
* Supported kinds:
|
|
70
|
+
* open_app { target: "<appKey>" } — mac only
|
|
71
|
+
* open_path { target: "<absPath>" | "<projectId>" } — opens in Finder/default
|
|
72
|
+
* open_path_in { target: "<projectId>", app: "<appKey>" } — mac only
|
|
73
|
+
* open_url { target: "https://..." }
|
|
74
|
+
* copy_clipboard { text: "..." }
|
|
75
|
+
*/
|
|
76
|
+
export async function runDeckExec({ kind, target, appHint, text, ctx }) {
|
|
77
|
+
const platform = process.platform;
|
|
78
|
+
|
|
79
|
+
// Resolve a project id (number or "<n>") into an absolute path via
|
|
80
|
+
// the daemon's project manager. Returns null when the id is bogus.
|
|
81
|
+
const projectPath = (idOrPath) => {
|
|
82
|
+
if (!idOrPath) return null;
|
|
83
|
+
const str = String(idOrPath);
|
|
84
|
+
if (str.startsWith("/")) return str;
|
|
85
|
+
if (!/^\d+$/.test(str)) return null;
|
|
86
|
+
const p = ctx.projects?.get?.(parseInt(str, 10));
|
|
87
|
+
return p?.path || null;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (kind === "open_app") {
|
|
91
|
+
if (platform !== "darwin") throw new Error("open_app only implemented on macOS for now");
|
|
92
|
+
const appName = MAC_APPS[String(target || "").toLowerCase()];
|
|
93
|
+
if (!appName) throw new Error(`unknown app: ${target}`);
|
|
94
|
+
// Two-step launch:
|
|
95
|
+
// 1. `open -a` ensures the app is running (no-op if already up).
|
|
96
|
+
// 2. AppleScript `activate` brings it to the foreground across
|
|
97
|
+
// Spaces / Stage Manager, which `open` alone often skips when
|
|
98
|
+
// the app was already running in the background.
|
|
99
|
+
await spawnDetached("open", ["-a", appName]);
|
|
100
|
+
try {
|
|
101
|
+
await new Promise((resolve) => {
|
|
102
|
+
const child = spawn("osascript", [
|
|
103
|
+
"-e",
|
|
104
|
+
`tell application "${appName}" to activate`,
|
|
105
|
+
], { stdio: "ignore" });
|
|
106
|
+
child.on("close", () => resolve());
|
|
107
|
+
child.on("error", () => resolve());
|
|
108
|
+
setTimeout(() => { try { child.kill(); } catch {} ; resolve(); }, 600);
|
|
109
|
+
});
|
|
110
|
+
} catch {
|
|
111
|
+
// osascript missing or refused — `open -a` already ran.
|
|
112
|
+
}
|
|
113
|
+
return { app: appName };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (kind === "open_path") {
|
|
117
|
+
const resolved = projectPath(target);
|
|
118
|
+
if (!resolved) throw new Error(`open_path: invalid target ${target}`);
|
|
119
|
+
await spawnDetached(platformOpener(), [resolved]);
|
|
120
|
+
return { path: resolved };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (kind === "open_path_in") {
|
|
124
|
+
if (platform !== "darwin") throw new Error("open_path_in only implemented on macOS for now");
|
|
125
|
+
const resolved = projectPath(target);
|
|
126
|
+
if (!resolved) throw new Error(`open_path_in: invalid target ${target}`);
|
|
127
|
+
const appName = MAC_APPS[String(appHint || "").toLowerCase()];
|
|
128
|
+
if (!appName) throw new Error(`open_path_in: unknown app ${appHint}`);
|
|
129
|
+
await spawnDetached("open", ["-a", appName, resolved]);
|
|
130
|
+
return { app: appName, path: resolved };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (kind === "open_url") {
|
|
134
|
+
if (!target || !/^https?:\/\//i.test(String(target))) {
|
|
135
|
+
throw new Error("open_url: target must be http(s) URL");
|
|
136
|
+
}
|
|
137
|
+
await spawnDetached(platformOpener(), [String(target)]);
|
|
138
|
+
return { url: target };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (kind === "copy_clipboard") {
|
|
142
|
+
if (typeof text !== "string") throw new Error("copy_clipboard: text required");
|
|
143
|
+
await copyToClipboard(text);
|
|
144
|
+
return { bytes: text.length };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
throw new Error(`unknown kind: ${kind}`);
|
|
148
|
+
}
|
package/src/host/daemon/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import { ProjectManager } from "./db.js";
|
|
19
19
|
import { McpRegistry } from "#core/mcp/runner.js";
|
|
20
20
|
import { PluginManager } from "./plugins/index.js";
|
|
21
|
-
import { RoutineScheduler } from "./routines.js";
|
|
21
|
+
import { RoutineScheduler } from "./routines-scheduler.js";
|
|
22
22
|
import { buildApi } from "./api.js";
|
|
23
23
|
import { createTokenStore } from "./token-store.js";
|
|
24
24
|
import { triggerWakeup } from "./wakeup.js";
|
|
@@ -218,11 +218,28 @@ async function main() {
|
|
|
218
218
|
// store, and start the incremental RAG indexer. Best-effort — never blocks
|
|
219
219
|
// boot and never throws into the daemon.
|
|
220
220
|
initMemory({ config: cfg, log }).catch((e) => log(`memory: init failed: ${e?.message || e}`));
|
|
221
|
+
// Skill Inspector: if enabled, refresh its vector index in the background so
|
|
222
|
+
// any SKILL.md added/edited while the daemon was down is picked up without a
|
|
223
|
+
// manual `apx skills index`. Best-effort; never blocks boot.
|
|
224
|
+
(async () => {
|
|
225
|
+
try {
|
|
226
|
+
const { isInspectorEnabled } = await import("#core/agent/skills/inspector.js");
|
|
227
|
+
if (!isInspectorEnabled(cfg)) return;
|
|
228
|
+
const { backgroundRefreshIfStale } = await import("#core/agent/skills/index-store.js");
|
|
229
|
+
const r = backgroundRefreshIfStale({
|
|
230
|
+
embedOpts: { globalConfig: cfg },
|
|
231
|
+
onDone: (out) => log(`skill inspector: index refreshed (${out.embedder}, +${out.changed.added.length} -${out.changed.removed.length} ~${out.changed.refreshed.length})`),
|
|
232
|
+
});
|
|
233
|
+
if (r.started) log(`skill inspector: reindexing ${r.missing} new / ${r.stale} stale / ${r.gone} gone skills…`);
|
|
234
|
+
} catch (e) {
|
|
235
|
+
log(`skill inspector: index refresh skipped (${e?.message || e})`);
|
|
236
|
+
}
|
|
237
|
+
})();
|
|
221
238
|
// Fire wake-up message after a short delay so plugins (Telegram) are ready
|
|
222
239
|
setTimeout(() => triggerWakeup(cfg, log), 3000);
|
|
223
240
|
// Preload whisper-server in the background so first desktop transcription is fast.
|
|
224
241
|
// Adopts an existing one if already on the port; otherwise spawns fresh.
|
|
225
|
-
import("./
|
|
242
|
+
import("./whisper-server.js").then(({ preloadWhisperServer }) => {
|
|
226
243
|
preloadWhisperServer((m) => log(m));
|
|
227
244
|
}).catch(() => {});
|
|
228
245
|
});
|
|
@@ -257,7 +274,7 @@ async function main() {
|
|
|
257
274
|
stopMemory();
|
|
258
275
|
registries.shutdown();
|
|
259
276
|
// Best-effort shutdown of whisper-server subprocess.
|
|
260
|
-
import("./
|
|
277
|
+
import("./whisper-server.js").then(({ shutdownWhisperServer }) => {
|
|
261
278
|
shutdownWhisperServer().catch(() => {});
|
|
262
279
|
}).catch(() => {});
|
|
263
280
|
server.close(() => {
|
|
@@ -32,12 +32,12 @@ import path from "node:path";
|
|
|
32
32
|
import { TELEGRAM_STATE_PATH, APX_HOME } from "#core/config/index.js";
|
|
33
33
|
import { callEngine } from "#core/engines/index.js";
|
|
34
34
|
import { runSuperAgent, isSuperAgentEnabled } from "#core/agent/super-agent.js";
|
|
35
|
-
import { stripThinking } from "
|
|
35
|
+
import { stripThinking } from "#core/util/thinking.js";
|
|
36
36
|
import { getRecentTelegramTurnsFromFs, appendGlobalMessage } from "#core/stores/messages.js";
|
|
37
37
|
import { compactChannelIfNeeded } from "#core/memory/index.js";
|
|
38
38
|
import { readAgents } from "#core/apc/parser.js";
|
|
39
39
|
import { buildAgentSystem } from "#core/agent/build-agent-system.js";
|
|
40
|
-
import { transcribe as transcribeAudioFile } from "
|
|
40
|
+
import { transcribe as transcribeAudioFile } from "#core/voice/transcription.js";
|
|
41
41
|
import { resolveAgentName, SUPERAGENT_ACTOR_ID } from "#core/identity/index.js";
|
|
42
42
|
import { registerSender, resolveAllowedTools } from "#core/identity/telegram.js";
|
|
43
43
|
import { buildRelationshipBlock } from "#core/agent/index.js";
|
|
@@ -45,13 +45,13 @@ import { getConfirmationStore as getConfirmStore } from "#core/confirmation/pend
|
|
|
45
45
|
import { CHANNELS } from "#core/constants/channels.js";
|
|
46
46
|
import { tryResolveSkillCommand } from "#core/agent/skills/trigger.js";
|
|
47
47
|
import { createTelegramConfirmAdapter } from "#core/confirmation/adapters/telegram.js";
|
|
48
|
-
import * as askFlow from "
|
|
48
|
+
import * as askFlow from "#core/channels/telegram/ask.js";
|
|
49
49
|
|
|
50
|
-
// API_BASE re-imported from
|
|
50
|
+
// API_BASE re-imported from #core/channels/telegram/media.js below
|
|
51
51
|
const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
52
52
|
|
|
53
|
-
// All non-class-bound
|
|
54
|
-
// focused on the poller class +
|
|
53
|
+
// All non-class-bound channel logic lives in core/channels/telegram/ — this
|
|
54
|
+
// file stays focused on the poller class + plugin lifecycle wiring.
|
|
55
55
|
import {
|
|
56
56
|
buildTelegramMeta,
|
|
57
57
|
loadState,
|
|
@@ -61,11 +61,11 @@ import {
|
|
|
61
61
|
tokenSource,
|
|
62
62
|
resolveChannels,
|
|
63
63
|
sleep,
|
|
64
|
-
} from "
|
|
65
|
-
import { handleUpdate } from "
|
|
64
|
+
} from "#core/channels/telegram/helpers.js";
|
|
65
|
+
import { handleUpdate } from "#core/channels/telegram/dispatch.js";
|
|
66
66
|
|
|
67
67
|
// ---------- media sending helpers (re-exports) ------------------------------
|
|
68
|
-
import { sendPhoto, sendVoice, sendDocument, sendAudio, downloadTelegramFile, API_BASE } from "
|
|
68
|
+
import { sendPhoto, sendVoice, sendDocument, sendAudio, downloadTelegramFile, API_BASE } from "#core/channels/telegram/media.js";
|
|
69
69
|
export { sendPhoto, sendVoice, sendDocument, sendAudio };
|
|
70
70
|
|
|
71
71
|
// ---------- per-channel poller ----------------------------------------------
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Polling timer that fires due routines. Process-state — needs the daemon
|
|
2
|
+
// alive, has no business being in core/. Delegates the actual work to
|
|
3
|
+
// core/routines/runner.js so CLI / HTTP / web / mcp-server / scripts can
|
|
4
|
+
// invoke the same runner without depending on this file.
|
|
5
|
+
import { getDueRoutines } from "#core/stores/routines.js";
|
|
6
|
+
import { runRoutineNow } from "#core/routines/runner.js";
|
|
7
|
+
import { nowIso } from "#core/util/time.js";
|
|
8
|
+
|
|
9
|
+
const TICK_MS = 5_000;
|
|
10
|
+
|
|
11
|
+
export class RoutineScheduler {
|
|
12
|
+
constructor({ projects, plugins, registries, globalConfig, log }) {
|
|
13
|
+
this.projects = projects;
|
|
14
|
+
this.plugins = plugins;
|
|
15
|
+
this.registries = registries;
|
|
16
|
+
this.globalConfig = globalConfig;
|
|
17
|
+
this.log = log || (() => {});
|
|
18
|
+
this._timer = null;
|
|
19
|
+
this._running = false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
start() {
|
|
23
|
+
if (this._timer) return;
|
|
24
|
+
this._timer = setInterval(
|
|
25
|
+
() => this._tick().catch((e) => this.log(`routines tick error: ${e.message}`)),
|
|
26
|
+
TICK_MS
|
|
27
|
+
);
|
|
28
|
+
this._timer.unref?.();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
stop() {
|
|
32
|
+
if (this._timer) {
|
|
33
|
+
clearInterval(this._timer);
|
|
34
|
+
this._timer = null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async _tick() {
|
|
39
|
+
if (this._running) return;
|
|
40
|
+
this._running = true;
|
|
41
|
+
try {
|
|
42
|
+
const nowStr = nowIso();
|
|
43
|
+
for (const proj of this.projects.list().map((p) => this.projects.get(p.id))) {
|
|
44
|
+
if (!proj) continue;
|
|
45
|
+
const due = getDueRoutines(proj.storagePath, nowStr);
|
|
46
|
+
for (const r of due) {
|
|
47
|
+
this.log(`routine ${r.name} (${r.kind}) firing in project #${proj.id}`);
|
|
48
|
+
await runRoutineNow(
|
|
49
|
+
{
|
|
50
|
+
project: proj,
|
|
51
|
+
projects: this.projects,
|
|
52
|
+
plugins: this.plugins,
|
|
53
|
+
registries: this.registries,
|
|
54
|
+
globalConfig: this.globalConfig,
|
|
55
|
+
},
|
|
56
|
+
r
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} finally {
|
|
61
|
+
this._running = false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/host/daemon/smoke.js
CHANGED
|
@@ -9,6 +9,7 @@ import fs from "node:fs";
|
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { ProjectManager } from "./db.js";
|
|
11
11
|
import { McpRegistry } from "#core/mcp/runner.js";
|
|
12
|
+
import { agentsMdFile, apcProjectFile } from "#core/apc/paths.js";
|
|
12
13
|
import { readAgents } from "#core/apc/parser.js";
|
|
13
14
|
|
|
14
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -23,8 +24,8 @@ const EXAMPLE_CANDIDATES = [
|
|
|
23
24
|
path.resolve(__dirname, "..", "..", "..", "..", "apc", "examples", "my-first-project"),
|
|
24
25
|
];
|
|
25
26
|
const EXAMPLE = EXAMPLE_CANDIDATES.find((p) =>
|
|
26
|
-
fs.existsSync(
|
|
27
|
-
fs.existsSync(
|
|
27
|
+
fs.existsSync(agentsMdFile(p)) &&
|
|
28
|
+
fs.existsSync(apcProjectFile(p))
|
|
28
29
|
);
|
|
29
30
|
|
|
30
31
|
function assert(cond, msg) {
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// Subprocess lifecycle for the persistent whisper-server.py.
|
|
2
|
+
//
|
|
3
|
+
// Owns:
|
|
4
|
+
// - the Python child process (spawn, health-watch, kill on shutdown)
|
|
5
|
+
// - port collision recovery (kill an orphan listener and retry)
|
|
6
|
+
// - daemon-boot preload + warmup + graceful teardown
|
|
7
|
+
//
|
|
8
|
+
// Does NOT do the actual transcription — that's an HTTP call to localhost
|
|
9
|
+
// and lives in core/voice/transcription.js. The port number is the single
|
|
10
|
+
// piece of shared state and is exported from core; this file imports it.
|
|
11
|
+
import { spawn, exec } from "node:child_process";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import {
|
|
15
|
+
WHISPER_LOCAL_PORT,
|
|
16
|
+
DEFAULT_LOCAL,
|
|
17
|
+
getConfig,
|
|
18
|
+
} from "#core/voice/transcription.js";
|
|
19
|
+
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = path.dirname(__filename);
|
|
22
|
+
const WHISPER_SERVER = path.join(__dirname, "whisper-server.py");
|
|
23
|
+
|
|
24
|
+
let _serverProcess = null;
|
|
25
|
+
let _serverModel = null;
|
|
26
|
+
|
|
27
|
+
function _sleep(ms) {
|
|
28
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function _isServerHealthy() {
|
|
32
|
+
try {
|
|
33
|
+
const res = await fetch(`http://127.0.0.1:${WHISPER_LOCAL_PORT}/health`, {
|
|
34
|
+
signal: AbortSignal.timeout(800),
|
|
35
|
+
});
|
|
36
|
+
return res.ok;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function _serverModelName() {
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch(`http://127.0.0.1:${WHISPER_LOCAL_PORT}/health`, {
|
|
45
|
+
signal: AbortSignal.timeout(800),
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok) return null;
|
|
48
|
+
const j = await res.json();
|
|
49
|
+
return j?.model || null;
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function _findListenerPid() {
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
exec(`lsof -ti tcp:${WHISPER_LOCAL_PORT} -sTCP:LISTEN`, (err, stdout) => {
|
|
58
|
+
if (err || !stdout) return resolve(null);
|
|
59
|
+
const candidates = stdout.trim().split("\n")
|
|
60
|
+
.map(s => parseInt(s, 10))
|
|
61
|
+
.filter(n => Number.isFinite(n) && n !== process.pid);
|
|
62
|
+
resolve(candidates[0] || null);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function _killOrphanWhisper() {
|
|
68
|
+
try {
|
|
69
|
+
await fetch(`http://127.0.0.1:${WHISPER_LOCAL_PORT}/shutdown`, {
|
|
70
|
+
method: "POST", signal: AbortSignal.timeout(1000),
|
|
71
|
+
});
|
|
72
|
+
await _sleep(600);
|
|
73
|
+
} catch {}
|
|
74
|
+
const pid = await _findListenerPid();
|
|
75
|
+
if (pid && pid !== process.pid) {
|
|
76
|
+
try { process.kill(pid, "SIGTERM"); } catch {}
|
|
77
|
+
await _sleep(400);
|
|
78
|
+
try { process.kill(pid, 0); try { process.kill(pid, "SIGKILL"); } catch {} } catch {}
|
|
79
|
+
await _sleep(300);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function ensureWhisperServer(opts) {
|
|
84
|
+
const model = opts.model || DEFAULT_LOCAL.model;
|
|
85
|
+
|
|
86
|
+
if (_serverProcess && _serverModel === model) {
|
|
87
|
+
if (await _isServerHealthy()) return;
|
|
88
|
+
_serverProcess = null;
|
|
89
|
+
_serverModel = null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!_serverProcess) {
|
|
93
|
+
const existing = await _serverModelName();
|
|
94
|
+
if (existing === model) {
|
|
95
|
+
_serverModel = model;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (existing) {
|
|
99
|
+
await _killOrphanWhisper();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (_serverProcess) {
|
|
104
|
+
try { _serverProcess.kill(); } catch {}
|
|
105
|
+
_serverProcess = null;
|
|
106
|
+
_serverModel = null;
|
|
107
|
+
await _sleep(300);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await _spawnWhisper(opts, model, /* retried */ false);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function _spawnWhisper(opts, model, retried) {
|
|
114
|
+
const args = [
|
|
115
|
+
WHISPER_SERVER,
|
|
116
|
+
"--port", String(WHISPER_LOCAL_PORT),
|
|
117
|
+
"--model", model,
|
|
118
|
+
"--device", String(opts.device || DEFAULT_LOCAL.device),
|
|
119
|
+
"--compute-type", String(opts.compute_type || DEFAULT_LOCAL.compute_type),
|
|
120
|
+
"--idle-minutes", String(opts.idle_minutes ?? DEFAULT_LOCAL.idle_minutes),
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
const proc = spawn("python3", args, {
|
|
124
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
125
|
+
detached: false,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
_serverProcess = proc;
|
|
129
|
+
_serverModel = model;
|
|
130
|
+
|
|
131
|
+
proc.on("exit", () => {
|
|
132
|
+
if (_serverProcess === proc) {
|
|
133
|
+
_serverProcess = null;
|
|
134
|
+
_serverModel = null;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
await new Promise((resolve, reject) => {
|
|
140
|
+
const timeout = setTimeout(
|
|
141
|
+
() => reject(new Error("whisper-server startup timed out (15s)")),
|
|
142
|
+
15_000
|
|
143
|
+
);
|
|
144
|
+
let buf = "";
|
|
145
|
+
proc.stdout.on("data", (chunk) => {
|
|
146
|
+
buf += chunk.toString();
|
|
147
|
+
const nl = buf.indexOf("\n");
|
|
148
|
+
if (nl === -1) return;
|
|
149
|
+
const line = buf.slice(0, nl).trim();
|
|
150
|
+
buf = buf.slice(nl + 1);
|
|
151
|
+
clearTimeout(timeout);
|
|
152
|
+
try {
|
|
153
|
+
const msg = JSON.parse(line);
|
|
154
|
+
if (msg.status === "error") return reject(new Error(msg.error || "whisper-server error"));
|
|
155
|
+
resolve();
|
|
156
|
+
} catch {
|
|
157
|
+
resolve();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
proc.on("exit", (code) => {
|
|
161
|
+
clearTimeout(timeout);
|
|
162
|
+
reject(new Error(`whisper-server exited (code ${code}) before becoming ready`));
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
} catch (e) {
|
|
166
|
+
const msg = e.message || "";
|
|
167
|
+
if (!retried && /address already in use|errno 48|eaddrinuse/i.test(msg)) {
|
|
168
|
+
_serverProcess = null;
|
|
169
|
+
_serverModel = null;
|
|
170
|
+
await _killOrphanWhisper();
|
|
171
|
+
return _spawnWhisper(opts, model, /* retried */ true);
|
|
172
|
+
}
|
|
173
|
+
throw e;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function preloadWhisperServer(log = console.log) {
|
|
178
|
+
try {
|
|
179
|
+
const cfg = await getConfig();
|
|
180
|
+
if (cfg.provider === "openai") return;
|
|
181
|
+
log(`whisper: preloading model "${cfg.local.model}" on port ${WHISPER_LOCAL_PORT}…`);
|
|
182
|
+
await ensureWhisperServer(cfg.local);
|
|
183
|
+
log(`whisper: ready on port ${WHISPER_LOCAL_PORT} (model: ${_serverModel})`);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
log(`whisper: preload failed — ${e.message} (will retry lazily on first request)`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function warmupWhisper() {
|
|
190
|
+
try {
|
|
191
|
+
const cfg = await getConfig();
|
|
192
|
+
if (cfg.provider === "openai") return { ok: true, provider: "openai", loaded: false };
|
|
193
|
+
await ensureWhisperServer(cfg.local);
|
|
194
|
+
let loaded = false;
|
|
195
|
+
try {
|
|
196
|
+
const r = await fetch(`http://127.0.0.1:${WHISPER_LOCAL_PORT}/warmup`, {
|
|
197
|
+
signal: AbortSignal.timeout(40_000),
|
|
198
|
+
});
|
|
199
|
+
const j = await r.json().catch(() => ({}));
|
|
200
|
+
loaded = !!j.loaded;
|
|
201
|
+
} catch {}
|
|
202
|
+
return { ok: true, provider: "local", model: _serverModel, loaded };
|
|
203
|
+
} catch (e) {
|
|
204
|
+
return { ok: false, error: e.message };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function shutdownWhisperServer() {
|
|
209
|
+
if (_serverProcess) {
|
|
210
|
+
try { _serverProcess.kill(); } catch {}
|
|
211
|
+
_serverProcess = null;
|
|
212
|
+
_serverModel = null;
|
|
213
|
+
} else {
|
|
214
|
+
try {
|
|
215
|
+
await fetch(`http://127.0.0.1:${WHISPER_LOCAL_PORT}/shutdown`, {
|
|
216
|
+
method: "POST", signal: AbortSignal.timeout(500),
|
|
217
|
+
});
|
|
218
|
+
} catch {}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export const WHISPER_PATHS = {
|
|
223
|
+
whisper_server: WHISPER_SERVER,
|
|
224
|
+
port: WHISPER_LOCAL_PORT,
|
|
225
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// APX CLI branding — a consistent "you're running APX vX" mark on every command.
|
|
2
|
+
//
|
|
3
|
+
// Two shapes:
|
|
4
|
+
// apxBanner(version, subtitle) big ASCII wordmark for branding-heavy moments
|
|
5
|
+
// (onboarding, top-level entry). Loud on purpose.
|
|
6
|
+
// apxHeader(version, subtitle) one-line "▸ APX CLI · vX · <subtitle>" for the
|
|
7
|
+
// everyday commands. Quiet, never in the way.
|
|
8
|
+
//
|
|
9
|
+
// Both write to STDERR so they never pollute piped stdout (`apx exec … | jq`,
|
|
10
|
+
// `apx config show > file`). Like mascot.js, they always print (so the mark is
|
|
11
|
+
// truly on every run), and self-suppress only when APX_QUIET / APX_NO_BANNER is
|
|
12
|
+
// set — the escape hatch for scripts and CI.
|
|
13
|
+
//
|
|
14
|
+
// Color: reuses raw ANSI like mascot.js. Honors NO_COLOR.
|
|
15
|
+
|
|
16
|
+
const NO_COLOR = !!process.env.NO_COLOR;
|
|
17
|
+
const c = (code) => (s) => (NO_COLOR ? s : `\x1b[${code}m${s}\x1b[0m`);
|
|
18
|
+
const B = c("1");
|
|
19
|
+
const DI = c("2");
|
|
20
|
+
const GR = c("32");
|
|
21
|
+
const CY = c("36");
|
|
22
|
+
const WH = c("97");
|
|
23
|
+
|
|
24
|
+
function suppressed() {
|
|
25
|
+
return !!(process.env.APX_NO_BANNER || process.env.APX_QUIET);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Compact, single-line header. The default for everyday subcommands.
|
|
29
|
+
// ▸ APX CLI · v1.34.0 · skills inspector
|
|
30
|
+
export function apxHeader(version, subtitle = "") {
|
|
31
|
+
if (suppressed()) return;
|
|
32
|
+
const tag = `${GR("▸")} ${B(WH("APX"))} ${DI("CLI")}`;
|
|
33
|
+
const ver = DI(`v${version}`);
|
|
34
|
+
const sub = subtitle ? ` ${DI("·")} ${CY(subtitle)}` : "";
|
|
35
|
+
process.stderr.write(`\n${tag} ${DI("·")} ${ver}${sub}\n\n`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Big ASCII wordmark for branding-heavy commands.
|
|
39
|
+
export function apxBanner(version, subtitle = "") {
|
|
40
|
+
if (suppressed()) return;
|
|
41
|
+
const g = (s) => GR(s);
|
|
42
|
+
const lines = [
|
|
43
|
+
"",
|
|
44
|
+
` ${g("█████╗ ██████╗ ██╗ ██╗")}`,
|
|
45
|
+
` ${g("██╔══██╗██╔══██╗╚██╗██╔╝")}`,
|
|
46
|
+
` ${g("███████║██████╔╝ ╚███╔╝ ")} ${B(WH("Agent Project Context"))}`,
|
|
47
|
+
` ${g("██╔══██║██╔═══╝ ██╔██╗ ")} ${DI(`v${version}`)}`,
|
|
48
|
+
` ${g("██║ ██║██║ ██╔╝ ██╗")}${subtitle ? ` ${CY(subtitle)}` : ""}`,
|
|
49
|
+
` ${g("╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝")}`,
|
|
50
|
+
"",
|
|
51
|
+
];
|
|
52
|
+
process.stderr.write(lines.join("\n") + "\n");
|
|
53
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { findApfRoot, readAgents, readVaultAgents, readVaultAgent, VAULT_DIR, SLUG_RE } from "#core/apc/parser.js";
|
|
4
|
+
import { apcAgentFile } from "#core/apc/paths.js";
|
|
4
5
|
import { writeAgentFile, writeVaultAgentFile, removeVaultAgent, restoreVaultAgent, addImportedAgent, ensureAgentDir } from "#core/apc/scaffold.js";
|
|
5
6
|
import { ensureAgentRuntimeDir, agentMemoryPath } from "#core/agent/memory.js";
|
|
6
7
|
import { http } from "../http.js";
|
|
@@ -194,7 +195,7 @@ export async function cmdAgentImport(args) {
|
|
|
194
195
|
throw new Error(`"${slug}" not found in vault. Available: ${available}`);
|
|
195
196
|
}
|
|
196
197
|
|
|
197
|
-
const alreadyLocal = fs.existsSync(
|
|
198
|
+
const alreadyLocal = fs.existsSync(apcAgentFile(root, slug));
|
|
198
199
|
if (alreadyLocal && !args.flags.force) {
|
|
199
200
|
console.log(dim(` "${slug}" already has a local definition. Use --force to overwrite.`));
|
|
200
201
|
return;
|
|
@@ -202,7 +203,7 @@ export async function cmdAgentImport(args) {
|
|
|
202
203
|
|
|
203
204
|
if (args.flags.copy) {
|
|
204
205
|
// Copy .md into project so user can edit locally
|
|
205
|
-
fs.copyFileSync(vaultPath,
|
|
206
|
+
fs.copyFileSync(vaultPath, apcAgentFile(root, slug));
|
|
206
207
|
console.log(`\n ${bold(slug)} copied from vault to project (now local)\n`);
|
|
207
208
|
} else {
|
|
208
209
|
// Just register as imported — reads from vault at runtime
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { findApfRoot } from "#core/apc/parser.js";
|
|
5
|
+
import { apcCommandsDir } from "#core/apc/paths.js";
|
|
5
6
|
import { http } from "../http.js";
|
|
6
7
|
import { resolveProjectId } from "./project.js";
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
return path.join(root, ".apc", "commands");
|
|
10
|
-
}
|
|
9
|
+
const commandsDir = apcCommandsDir;
|
|
11
10
|
|
|
12
11
|
function listCommandFiles(root) {
|
|
13
12
|
const dir = commandsDir(root);
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { http } from "../http.js";
|
|
2
2
|
import { resolveProjectId } from "./project.js";
|
|
3
|
+
import { CHANNELS } from "#core/constants/channels.js";
|
|
3
4
|
|
|
4
5
|
// Channels that live in ~/.apx/messages/<channel>/ (global, cross-project).
|
|
5
6
|
// Everything else is project-scoped.
|
|
6
|
-
|
|
7
|
+
// DIRECT and WHATSAPP are still placeholders (no plugin lives behind them
|
|
8
|
+
// yet) but they're real channel ids — when those plugins land, message
|
|
9
|
+
// lookup already routes them as project-less / global.
|
|
10
|
+
const GLOBAL_CHANNELS = new Set([CHANNELS.TELEGRAM, CHANNELS.DIRECT, CHANNELS.WHATSAPP]);
|
|
7
11
|
|
|
8
12
|
function isGlobalChannel(channel) {
|
|
9
13
|
return channel && GLOBAL_CHANNELS.has(channel);
|
|
@@ -71,7 +75,7 @@ function printChatRows(rows) {
|
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
export async function cmdMessagesChat(args) {
|
|
74
|
-
const channel = args.flags.channel && args.flags.channel !== true ? args.flags.channel :
|
|
78
|
+
const channel = args.flags.channel && args.flags.channel !== true ? args.flags.channel : CHANNELS.TELEGRAM;
|
|
75
79
|
const n = args.flags.n || args.flags.last || "50";
|
|
76
80
|
const isGlobal = args.flags.global || isGlobalChannel(channel);
|
|
77
81
|
|