@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
|
@@ -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
|
+
};
|
|
@@ -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
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import os from "node:os";
|
|
9
9
|
import qrcode from "qrcode-terminal";
|
|
10
10
|
import { http } from "../http.js";
|
|
11
|
+
import { CHANNELS } from "#core/constants/channels.js";
|
|
11
12
|
|
|
12
13
|
const c = {
|
|
13
14
|
reset: "\x1b[0m",
|
|
@@ -88,10 +89,10 @@ async function runPairing(args, channel) {
|
|
|
88
89
|
// 2. QR payload differs per channel — no mixing.
|
|
89
90
|
// deck: JSON {v,url,pid,fp} that the Deck app parses.
|
|
90
91
|
// web : the scan-to-login URL the browser understands.
|
|
91
|
-
const qrData = channel ===
|
|
92
|
+
const qrData = channel === CHANNELS.WEB
|
|
92
93
|
? webLink
|
|
93
94
|
: JSON.stringify({ v: 1, url: baseUrl, pid: init.pairing_id, fp: init.fingerprint });
|
|
94
|
-
const title = channel ===
|
|
95
|
+
const title = channel === CHANNELS.WEB ? "APX pairing (web)" : "APX pairing (deck)";
|
|
95
96
|
|
|
96
97
|
console.log("");
|
|
97
98
|
console.log(` ${fmt.bold(title)} ${fmt.gray("·")} ${fmt.dim(`expires in ${ttlSec}s`)}`);
|
|
@@ -100,7 +101,7 @@ async function runPairing(args, channel) {
|
|
|
100
101
|
console.log(qr);
|
|
101
102
|
});
|
|
102
103
|
|
|
103
|
-
if (channel ===
|
|
104
|
+
if (channel === CHANNELS.WEB) {
|
|
104
105
|
console.log(` ${fmt.gray("scan with the phone camera — opens the web already linked")}`);
|
|
105
106
|
console.log(` ${fmt.gray("link:")} ${fmt.cyan(webLink)}`);
|
|
106
107
|
console.log(` ${fmt.gray("code:")} ${fmt.bold(init.pairing_id)} ${fmt.dim("(or paste it in the web pairing screen)")}`);
|
|
@@ -111,7 +112,7 @@ async function runPairing(args, channel) {
|
|
|
111
112
|
console.log("");
|
|
112
113
|
|
|
113
114
|
// 3. Poll /pair/status until confirmed or expired.
|
|
114
|
-
const reRun = channel ===
|
|
115
|
+
const reRun = channel === CHANNELS.WEB ? "apx pair web" : "apx pair";
|
|
115
116
|
const deadline = Date.now() + (init.ttl_ms || 90_000) + 5_000;
|
|
116
117
|
while (Date.now() < deadline) {
|
|
117
118
|
await sleep(1500);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Uses the daemon's tools/search.js module directly (no HTTP roundtrip,
|
|
4
4
|
// no need to have `apx daemon start` running).
|
|
5
5
|
|
|
6
|
-
import { webSearch } from "#core/tools/search.js";
|
|
6
|
+
import { webSearch } from "#core/http-tools/search.js";
|
|
7
7
|
|
|
8
8
|
const DIM = "\x1b[2m";
|
|
9
9
|
const BOLD = "\x1b[1m";
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import path from "node:path";
|
|
8
|
+
import { apcProjectFile } from "#core/apc/paths.js";
|
|
8
9
|
|
|
9
10
|
// ── shared helpers ───────────────────────────────────────────────────────────
|
|
10
11
|
|
|
@@ -124,7 +125,7 @@ function readApxProjects(opts) {
|
|
|
124
125
|
const proj = { path: path.resolve(e.path), name: null, apxId: null };
|
|
125
126
|
try {
|
|
126
127
|
const pj = JSON.parse(
|
|
127
|
-
fs.readFileSync(
|
|
128
|
+
fs.readFileSync(apcProjectFile(proj.path), "utf8")
|
|
128
129
|
);
|
|
129
130
|
if (pj.name) proj.name = pj.name;
|
|
130
131
|
if (pj.apx_id) proj.apxId = pj.apx_id;
|
|
@@ -608,7 +609,7 @@ const apxEngine = {
|
|
|
608
609
|
let apxId = null;
|
|
609
610
|
try {
|
|
610
611
|
const pj = JSON.parse(
|
|
611
|
-
fs.readFileSync(
|
|
612
|
+
fs.readFileSync(apcProjectFile(dir), "utf8")
|
|
612
613
|
);
|
|
613
614
|
apxId = pj.apx_id || null;
|
|
614
615
|
} catch {}
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import readline from "node:readline";
|
|
6
6
|
import { findApfRoot } from "#core/apc/parser.js";
|
|
7
|
+
import { apcSkillsDir } from "#core/apc/paths.js";
|
|
7
8
|
import { http } from "../http.js";
|
|
8
9
|
import {
|
|
9
10
|
IDE_TARGETS,
|
|
@@ -11,6 +12,8 @@ import {
|
|
|
11
12
|
installGlobalSkills,
|
|
12
13
|
listBundledSkillSlugs,
|
|
13
14
|
listBundledSkills,
|
|
15
|
+
listEngineSkills,
|
|
16
|
+
listLegacyPruneSlugs,
|
|
14
17
|
} from "#core/apc/scaffold.js";
|
|
15
18
|
|
|
16
19
|
// ---------------------------------------------------------------------------
|
|
@@ -100,37 +103,27 @@ export async function cmdSkillsAdd(args) {
|
|
|
100
103
|
// ---------------------------------------------------------------------------
|
|
101
104
|
|
|
102
105
|
export async function cmdSkillsSync(args) {
|
|
103
|
-
const includeOptional = !!args?.flags?.["include-optional"] || !!args?.flags?.optional;
|
|
104
|
-
const includeInternal = !!args?.flags?.["include-internal"] || !!args?.flags?.internal;
|
|
105
106
|
const prune = args?.flags?.["no-prune"] ? false : true;
|
|
106
107
|
|
|
107
|
-
const results = installGlobalSkills({
|
|
108
|
+
const results = installGlobalSkills({ prune });
|
|
108
109
|
const home = os.homedir();
|
|
109
110
|
|
|
110
|
-
// Group by skill so the output is dense and scannable.
|
|
111
111
|
const bySkill = {};
|
|
112
|
-
const scopeOf = {};
|
|
113
112
|
for (const r of results) {
|
|
114
113
|
if (!bySkill[r.skill]) bySkill[r.skill] = [];
|
|
115
|
-
bySkill[r.skill].push({ dir: r.dir.replace(home, "~"), status: r.status });
|
|
116
|
-
scopeOf[r.skill] = r.scope;
|
|
114
|
+
bySkill[r.skill].push({ dir: r.dir.replace(home, "~"), status: r.status, scope: r.scope });
|
|
117
115
|
}
|
|
118
116
|
|
|
119
|
-
const
|
|
120
|
-
if (
|
|
121
|
-
console.log("(no
|
|
117
|
+
const engineSet = listEngineSkills().map((s) => s.slug);
|
|
118
|
+
if (engineSet.length === 0) {
|
|
119
|
+
console.log("(no engine skills found in skills/engines/)");
|
|
122
120
|
return;
|
|
123
121
|
}
|
|
124
122
|
|
|
125
|
-
|
|
126
|
-
if (includeOptional) filters.push("+optional");
|
|
127
|
-
if (includeInternal) filters.push("+internal");
|
|
128
|
-
console.log(
|
|
129
|
-
`Syncing ${slugs.length} bundled skill(s) to global skill dirs` +
|
|
130
|
-
(filters.length ? ` [${filters.join(" ")}]` : "") + ":\n"
|
|
131
|
-
);
|
|
123
|
+
console.log(`Syncing engine skill set (${engineSet.join(", ")}) to global dirs:\n`);
|
|
132
124
|
|
|
133
|
-
const
|
|
125
|
+
const slugs = Object.keys(bySkill).sort();
|
|
126
|
+
const sw = Math.max(...slugs.map((s) => s.length), 8);
|
|
134
127
|
const totals = { unchanged: 0, updated: 0, created: 0, pruned: 0 };
|
|
135
128
|
for (const slug of slugs) {
|
|
136
129
|
const entries = bySkill[slug];
|
|
@@ -141,9 +134,8 @@ export async function cmdSkillsSync(args) {
|
|
|
141
134
|
for (const k of ["created", "updated", "unchanged", "pruned"]) {
|
|
142
135
|
if (counts[k]) parts.push(`${counts[k]} ${k}`);
|
|
143
136
|
}
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
console.log(` ${slug.padEnd(sw)}${tag.padEnd(11)} ${parts.join(", ")}`);
|
|
137
|
+
const tag = entries[0]?.scope === "legacy" ? " [legacy]" : "";
|
|
138
|
+
console.log(` ${slug.padEnd(sw)}${tag.padEnd(10)} ${parts.join(", ")}`);
|
|
147
139
|
}
|
|
148
140
|
console.log("");
|
|
149
141
|
console.log(`Targets: .claude/skills, .cursor/skills, .codex/skills, .agents/skills`);
|
|
@@ -153,21 +145,6 @@ export async function cmdSkillsSync(args) {
|
|
|
153
145
|
}
|
|
154
146
|
console.log(`Totals: ${totalParts.join(", ") || "(no changes)"}`);
|
|
155
147
|
|
|
156
|
-
// Hint about non-default tiers.
|
|
157
|
-
const skipped = listBundledSkills().filter(
|
|
158
|
-
(s) =>
|
|
159
|
-
(s.scope === "internal" && !includeInternal) ||
|
|
160
|
-
(s.scope === "optional" && !includeOptional)
|
|
161
|
-
);
|
|
162
|
-
if (skipped.length > 0) {
|
|
163
|
-
console.log("");
|
|
164
|
-
console.log("Skipped (not pushed by default):");
|
|
165
|
-
for (const s of skipped) console.log(` ${s.slug.padEnd(sw)} scope=${s.scope}`);
|
|
166
|
-
console.log("");
|
|
167
|
-
console.log("Re-run with --include-optional / --include-internal to push them too,");
|
|
168
|
-
console.log("or `apx skills add <slug> --global` for one-off install.");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
148
|
if (args?.flags?.verbose) {
|
|
172
149
|
console.log("");
|
|
173
150
|
for (const slug of slugs) {
|
|
@@ -184,8 +161,8 @@ export async function cmdSkillsSync(args) {
|
|
|
184
161
|
// ---------------------------------------------------------------------------
|
|
185
162
|
|
|
186
163
|
export async function cmdSkillsList(args = {}) {
|
|
187
|
-
// --all queries the daemon, which returns project + global + bundled
|
|
188
|
-
//
|
|
164
|
+
// --all queries the daemon, which returns project + global + bundled
|
|
165
|
+
// (the same catalog the super-agent sees and the web picker uses).
|
|
189
166
|
// Without --all we only list `.apc/skills/` (what the user installed in
|
|
190
167
|
// THIS project), matching the historical behaviour.
|
|
191
168
|
if (args?.flags?.all) {
|
|
@@ -205,7 +182,7 @@ export async function cmdSkillsList(args = {}) {
|
|
|
205
182
|
}
|
|
206
183
|
|
|
207
184
|
const root = findApfRoot();
|
|
208
|
-
const skillsDir = root ?
|
|
185
|
+
const skillsDir = root ? apcSkillsDir(root) : null;
|
|
209
186
|
const files = skillsDir && fs.existsSync(skillsDir)
|
|
210
187
|
? fs.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))
|
|
211
188
|
: [];
|
|
@@ -224,17 +201,15 @@ export async function cmdSkillsList(args = {}) {
|
|
|
224
201
|
export async function cmdSkillsStatus() {
|
|
225
202
|
const root = findApfRoot();
|
|
226
203
|
|
|
227
|
-
|
|
228
|
-
const bundled = listBundledSkills();
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
optional: bundled.filter((s) => s.scope === "optional"),
|
|
232
|
-
internal: bundled.filter((s) => s.scope === "internal"),
|
|
233
|
-
};
|
|
204
|
+
const engineSet = listEngineSkills(); // what we publish to engines
|
|
205
|
+
const bundled = listBundledSkills(); // what stays in-repo for the super-agent
|
|
206
|
+
const legacy = listLegacyPruneSlugs(); // slugs APX shipped historically — pruned on sync
|
|
207
|
+
|
|
234
208
|
console.log(
|
|
235
|
-
`
|
|
236
|
-
|
|
209
|
+
`Engine skill set (replicated to global dirs): ${engineSet.length} ` +
|
|
210
|
+
`(${engineSet.map((s) => s.slug).join(", ") || "—"})`
|
|
237
211
|
);
|
|
212
|
+
console.log(`In-repo bundled skills (super-agent only): ${bundled.length}`);
|
|
238
213
|
console.log("");
|
|
239
214
|
console.log(`Global skill dirs:`);
|
|
240
215
|
const GLOBAL_DIRS = [
|
|
@@ -243,17 +218,23 @@ export async function cmdSkillsStatus() {
|
|
|
243
218
|
{ label: "Codex", dir: path.join(os.homedir(), ".codex", "skills") },
|
|
244
219
|
{ label: "Antigravity / others", dir: path.join(os.homedir(), ".agents", "skills") },
|
|
245
220
|
];
|
|
246
|
-
const
|
|
221
|
+
const allSlugs = [...engineSet.map((s) => s.slug), ...legacy];
|
|
222
|
+
const sw = Math.max(...allSlugs.map((s) => s.length), 8);
|
|
247
223
|
for (const { label, dir } of GLOBAL_DIRS) {
|
|
248
224
|
console.log(`\n ${label} — ${dir.replace(os.homedir(), "~")}`);
|
|
249
|
-
for (const { slug
|
|
225
|
+
for (const { slug } of engineSet) {
|
|
250
226
|
const dest = path.join(dir, slug, "SKILL.md");
|
|
251
227
|
const present = fs.existsSync(dest);
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
228
|
+
const state = present ? "✓ installed" : "✗ MISSING (run `apx skills sync`)";
|
|
229
|
+
console.log(` ${slug.padEnd(sw)} ${state}`);
|
|
230
|
+
}
|
|
231
|
+
const stale = legacy.filter((slug) =>
|
|
232
|
+
fs.existsSync(path.join(dir, slug, "SKILL.md"))
|
|
233
|
+
);
|
|
234
|
+
if (stale.length) {
|
|
235
|
+
for (const slug of stale) {
|
|
236
|
+
console.log(` ${slug.padEnd(sw)} [legacy] ⚠ stale (run \`apx skills sync\` to prune)`);
|
|
237
|
+
}
|
|
257
238
|
}
|
|
258
239
|
}
|
|
259
240
|
|