@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
package/src/core/mcp/runner.js
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
// MCP runner: spawn child MCP processes
|
|
2
|
-
//
|
|
1
|
+
// MCP runner: spawn child MCP processes (stdio) or talk to remote MCP
|
|
2
|
+
// servers (HTTP). Speaks JSON-RPC 2.0 either way.
|
|
3
|
+
//
|
|
4
|
+
// Variables referenced as `${var.NAME}` in args/env/url/headers are resolved
|
|
5
|
+
// at process/client construction time against project + global vars. Missing
|
|
6
|
+
// references surface as a MissingVarError with the full list so the UI can
|
|
7
|
+
// report "missing TOKEN_A, TOKEN_B" instead of one-at-a-time.
|
|
3
8
|
import { spawn } from "node:child_process";
|
|
4
9
|
import { loadAll } from "./sources.js";
|
|
10
|
+
import { interpolate, MissingVarError } from "#core/vars/interpolate.js";
|
|
11
|
+
import { loadAllVars } from "#core/vars/sources.js";
|
|
5
12
|
|
|
6
13
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
14
|
+
const LOG_CAP = 64; // entries per MCP we keep in memory
|
|
15
|
+
const STDERR_BUF_CAP = 4096; // bytes of stderr tail we hand back
|
|
16
|
+
|
|
17
|
+
function nowIso() {
|
|
18
|
+
return new Date().toISOString();
|
|
19
|
+
}
|
|
7
20
|
|
|
8
21
|
class McpProcess {
|
|
9
22
|
constructor({ name, command, args = [], env = {} }) {
|
|
@@ -11,6 +24,7 @@ class McpProcess {
|
|
|
11
24
|
this.command = command;
|
|
12
25
|
this.args = args;
|
|
13
26
|
this.env = env;
|
|
27
|
+
this.transport = "stdio";
|
|
14
28
|
this.proc = null;
|
|
15
29
|
this.buffer = "";
|
|
16
30
|
this.pending = new Map(); // id -> { resolve, reject, timer }
|
|
@@ -18,14 +32,24 @@ class McpProcess {
|
|
|
18
32
|
this._initPromise = null;
|
|
19
33
|
this._initialized = false;
|
|
20
34
|
this._stderrBuf = "";
|
|
35
|
+
this.logs = []; // { ts, level, msg }
|
|
36
|
+
this.startedAt = null;
|
|
37
|
+
this.lastExitCode = null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_log(level, msg) {
|
|
41
|
+
this.logs.push({ ts: nowIso(), level, msg });
|
|
42
|
+
if (this.logs.length > LOG_CAP) this.logs.shift();
|
|
21
43
|
}
|
|
22
44
|
|
|
23
45
|
start() {
|
|
24
46
|
if (this.proc) return;
|
|
47
|
+
this._log("info", `spawn ${this.command} ${(this.args || []).join(" ")}`);
|
|
25
48
|
this.proc = spawn(this.command, this.args, {
|
|
26
49
|
env: { ...process.env, ...this.env },
|
|
27
50
|
stdio: ["pipe", "pipe", "pipe"],
|
|
28
51
|
});
|
|
52
|
+
this.startedAt = nowIso();
|
|
29
53
|
|
|
30
54
|
this.proc.stdout.setEncoding("utf8");
|
|
31
55
|
this.proc.stderr.setEncoding("utf8");
|
|
@@ -33,12 +57,16 @@ class McpProcess {
|
|
|
33
57
|
this.proc.stdout.on("data", (chunk) => this._onStdout(chunk));
|
|
34
58
|
this.proc.stderr.on("data", (chunk) => {
|
|
35
59
|
this._stderrBuf += chunk;
|
|
36
|
-
if (this._stderrBuf.length >
|
|
37
|
-
this._stderrBuf = this._stderrBuf.slice(-
|
|
60
|
+
if (this._stderrBuf.length > STDERR_BUF_CAP) {
|
|
61
|
+
this._stderrBuf = this._stderrBuf.slice(-STDERR_BUF_CAP);
|
|
38
62
|
}
|
|
63
|
+
const trimmed = chunk.trim();
|
|
64
|
+
if (trimmed) this._log("stderr", trimmed.slice(-512));
|
|
39
65
|
});
|
|
40
66
|
|
|
41
67
|
this.proc.on("exit", (code) => {
|
|
68
|
+
this.lastExitCode = code;
|
|
69
|
+
this._log("info", `exit code=${code}`);
|
|
42
70
|
const err = new Error(
|
|
43
71
|
`MCP "${this.name}" exited with code ${code}. stderr: ${this._stderrBuf.trim()}`
|
|
44
72
|
);
|
|
@@ -133,6 +161,19 @@ class McpProcess {
|
|
|
133
161
|
return this._send("tools/call", { name, arguments: args || {} });
|
|
134
162
|
}
|
|
135
163
|
|
|
164
|
+
getLogs() {
|
|
165
|
+
return {
|
|
166
|
+
transport: "stdio",
|
|
167
|
+
command: this.command,
|
|
168
|
+
args: this.args,
|
|
169
|
+
started_at: this.startedAt,
|
|
170
|
+
running: !!this.proc,
|
|
171
|
+
last_exit_code: this.lastExitCode,
|
|
172
|
+
stderr_tail: this._stderrBuf,
|
|
173
|
+
events: this.logs.slice(),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
136
177
|
stop() {
|
|
137
178
|
if (this.proc) {
|
|
138
179
|
try {
|
|
@@ -143,6 +184,190 @@ class McpProcess {
|
|
|
143
184
|
}
|
|
144
185
|
}
|
|
145
186
|
|
|
187
|
+
// HTTP MCP client. Posts JSON-RPC 2.0 to the configured URL with the
|
|
188
|
+
// configured headers. Each call is a fresh fetch — we do not maintain a
|
|
189
|
+
// long-lived SSE stream. This works for servers that implement the simple
|
|
190
|
+
// JSON-RPC response style (which is most third-party MCP HTTP servers,
|
|
191
|
+
// including Asana's mcp.asana.com endpoint).
|
|
192
|
+
// Header values must be Latin1 — fetch throws "Cannot convert argument to a
|
|
193
|
+
// ByteString" on any code point above 255. We also normalize whitespace that
|
|
194
|
+
// the web editor's contentEditable injects: zero-width chars + non-breaking
|
|
195
|
+
// spaces (U+00A0 — the silent space substitute that makes Asana reject
|
|
196
|
+
// `Bearer\xA0token` with "Authorization header must be in format Bearer
|
|
197
|
+
// <token>"). One spot of sanitization covers every header value the runner
|
|
198
|
+
// sends, regardless of where the poisoned char originated.
|
|
199
|
+
function sanitizeHeaderValue(v) {
|
|
200
|
+
return String(v)
|
|
201
|
+
.replace(/[\u200B-\u200F\u202A-\u202E\u2060\uFEFF]/g, "")
|
|
202
|
+
.replace(/\u00A0/g, " ");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function sanitizeHeaders(h) {
|
|
206
|
+
if (!h) return {};
|
|
207
|
+
const out = {};
|
|
208
|
+
for (const [k, v] of Object.entries(h)) {
|
|
209
|
+
out[sanitizeHeaderValue(k)] = sanitizeHeaderValue(v);
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
class HttpMcpClient {
|
|
215
|
+
constructor({ name, url, headers = {} }) {
|
|
216
|
+
this.name = name;
|
|
217
|
+
this.url = sanitizeHeaderValue(url);
|
|
218
|
+
this.headers = sanitizeHeaders(headers);
|
|
219
|
+
this.transport = "http";
|
|
220
|
+
this._nextId = 1;
|
|
221
|
+
this._initialized = false;
|
|
222
|
+
this._initPromise = null;
|
|
223
|
+
this.logs = [];
|
|
224
|
+
this.startedAt = null;
|
|
225
|
+
this.lastError = null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
_log(level, msg) {
|
|
229
|
+
this.logs.push({ ts: nowIso(), level, msg });
|
|
230
|
+
if (this.logs.length > LOG_CAP) this.logs.shift();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async _rpc(method, params, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
234
|
+
if (!this.startedAt) this.startedAt = nowIso();
|
|
235
|
+
const id = this._nextId++;
|
|
236
|
+
const body = JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
237
|
+
const ctrl = new AbortController();
|
|
238
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
239
|
+
this._log("info", `POST ${method}`);
|
|
240
|
+
let res;
|
|
241
|
+
try {
|
|
242
|
+
res = await fetch(this.url, {
|
|
243
|
+
method: "POST",
|
|
244
|
+
headers: {
|
|
245
|
+
"Content-Type": "application/json",
|
|
246
|
+
Accept: "application/json, text/event-stream",
|
|
247
|
+
"MCP-Protocol-Version": "2024-11-05",
|
|
248
|
+
...this.headers,
|
|
249
|
+
},
|
|
250
|
+
body,
|
|
251
|
+
signal: ctrl.signal,
|
|
252
|
+
});
|
|
253
|
+
} catch (e) {
|
|
254
|
+
this.lastError = e.message;
|
|
255
|
+
this._log("error", `fetch failed: ${e.message}`);
|
|
256
|
+
throw new Error(`MCP "${this.name}" HTTP error: ${e.message}`);
|
|
257
|
+
} finally {
|
|
258
|
+
clearTimeout(timer);
|
|
259
|
+
}
|
|
260
|
+
const contentType = res.headers.get("content-type") || "";
|
|
261
|
+
const text = await res.text();
|
|
262
|
+
if (!res.ok) {
|
|
263
|
+
this.lastError = `HTTP ${res.status}`;
|
|
264
|
+
this._log("error", `HTTP ${res.status} ${text.slice(0, 200)}`);
|
|
265
|
+
throw new Error(
|
|
266
|
+
`MCP "${this.name}" HTTP ${res.status}: ${text.slice(0, 300)}`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
// text/event-stream — pluck the first JSON-RPC payload from the SSE frames.
|
|
270
|
+
let payload;
|
|
271
|
+
if (contentType.includes("text/event-stream")) {
|
|
272
|
+
payload = parseFirstSseJson(text);
|
|
273
|
+
if (!payload) {
|
|
274
|
+
this.lastError = "no JSON in SSE stream";
|
|
275
|
+
throw new Error(`MCP "${this.name}" returned empty SSE stream`);
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
try {
|
|
279
|
+
payload = JSON.parse(text);
|
|
280
|
+
} catch (e) {
|
|
281
|
+
this.lastError = `non-JSON response: ${e.message}`;
|
|
282
|
+
throw new Error(
|
|
283
|
+
`MCP "${this.name}" non-JSON response: ${text.slice(0, 300)}`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (payload.error) {
|
|
288
|
+
this.lastError = payload.error.message || "rpc error";
|
|
289
|
+
throw new Error(payload.error.message || "MCP error");
|
|
290
|
+
}
|
|
291
|
+
return payload.result;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async _ensureInitialized() {
|
|
295
|
+
if (this._initialized) return;
|
|
296
|
+
if (!this._initPromise) {
|
|
297
|
+
this._initPromise = (async () => {
|
|
298
|
+
await this._rpc(
|
|
299
|
+
"initialize",
|
|
300
|
+
{
|
|
301
|
+
protocolVersion: "2024-11-05",
|
|
302
|
+
capabilities: {},
|
|
303
|
+
clientInfo: { name: "apx-daemon", version: "0.1.0" },
|
|
304
|
+
},
|
|
305
|
+
10_000
|
|
306
|
+
);
|
|
307
|
+
// Best-effort notification — many servers ignore this for HTTP.
|
|
308
|
+
try {
|
|
309
|
+
await fetch(this.url, {
|
|
310
|
+
method: "POST",
|
|
311
|
+
headers: {
|
|
312
|
+
"Content-Type": "application/json",
|
|
313
|
+
Accept: "application/json",
|
|
314
|
+
"MCP-Protocol-Version": "2024-11-05",
|
|
315
|
+
...this.headers,
|
|
316
|
+
},
|
|
317
|
+
body: JSON.stringify({
|
|
318
|
+
jsonrpc: "2.0",
|
|
319
|
+
method: "notifications/initialized",
|
|
320
|
+
}),
|
|
321
|
+
});
|
|
322
|
+
} catch {}
|
|
323
|
+
this._initialized = true;
|
|
324
|
+
})();
|
|
325
|
+
}
|
|
326
|
+
return this._initPromise;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async listTools() {
|
|
330
|
+
await this._ensureInitialized();
|
|
331
|
+
return this._rpc("tools/list", {});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async callTool(name, args) {
|
|
335
|
+
await this._ensureInitialized();
|
|
336
|
+
return this._rpc("tools/call", { name, arguments: args || {} });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
getLogs() {
|
|
340
|
+
return {
|
|
341
|
+
transport: "http",
|
|
342
|
+
url: this.url,
|
|
343
|
+
started_at: this.startedAt,
|
|
344
|
+
last_error: this.lastError,
|
|
345
|
+
events: this.logs.slice(),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
stop() {
|
|
350
|
+
this._initialized = false;
|
|
351
|
+
this._initPromise = null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function parseFirstSseJson(raw) {
|
|
356
|
+
for (const block of raw.split(/\r?\n\r?\n/)) {
|
|
357
|
+
const dataLines = [];
|
|
358
|
+
for (const line of block.split(/\r?\n/)) {
|
|
359
|
+
if (line.startsWith("data:")) dataLines.push(line.slice(5).trimStart());
|
|
360
|
+
}
|
|
361
|
+
if (!dataLines.length) continue;
|
|
362
|
+
try {
|
|
363
|
+
return JSON.parse(dataLines.join("\n"));
|
|
364
|
+
} catch {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
|
|
146
371
|
function entryToMeta(e) {
|
|
147
372
|
return {
|
|
148
373
|
name: e.name,
|
|
@@ -158,8 +383,6 @@ function entryToMeta(e) {
|
|
|
158
383
|
}
|
|
159
384
|
|
|
160
385
|
export class McpRegistry {
|
|
161
|
-
// Accepts either the project path string (back-compat) or an object
|
|
162
|
-
// { projectPath, storagePath } so the runtime scope can be aggregated.
|
|
163
386
|
constructor(arg) {
|
|
164
387
|
if (typeof arg === "string" || arg == null) {
|
|
165
388
|
this.projectPath = arg || null;
|
|
@@ -168,7 +391,7 @@ export class McpRegistry {
|
|
|
168
391
|
this.projectPath = arg.projectPath || null;
|
|
169
392
|
this.storagePath = arg.storagePath || null;
|
|
170
393
|
}
|
|
171
|
-
this.processes = new Map(); // mcp name -> McpProcess
|
|
394
|
+
this.processes = new Map(); // mcp name -> McpProcess | HttpMcpClient
|
|
172
395
|
}
|
|
173
396
|
|
|
174
397
|
_load() {
|
|
@@ -196,19 +419,40 @@ export class McpRegistry {
|
|
|
196
419
|
return e ? entryToMeta(e) : null;
|
|
197
420
|
}
|
|
198
421
|
|
|
422
|
+
_resolveVars() {
|
|
423
|
+
return loadAllVars({ storagePath: this.storagePath }).effective;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
_resolveMeta(meta) {
|
|
427
|
+
try {
|
|
428
|
+
return interpolate(meta, this._resolveVars());
|
|
429
|
+
} catch (e) {
|
|
430
|
+
if (e instanceof MissingVarError) {
|
|
431
|
+
const list = e.missing.map((n) => `\${var.${n}}`).join(", ");
|
|
432
|
+
throw new Error(
|
|
433
|
+
`MCP "${meta.name}" has undefined variable${e.missing.length > 1 ? "s" : ""}: ${list}. Define them at /p/<id>/vars (or globally at /p/0/vars).`
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
throw e;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
199
440
|
_ensureProcess(name) {
|
|
200
441
|
let proc = this.processes.get(name);
|
|
201
|
-
if (proc
|
|
442
|
+
if (proc) {
|
|
443
|
+
if (proc.transport === "stdio" && proc.proc) return proc;
|
|
444
|
+
if (proc.transport === "http") return proc;
|
|
445
|
+
}
|
|
202
446
|
const meta = this.getByName(name);
|
|
203
447
|
if (!meta) throw new Error(`MCP "${name}" not registered`);
|
|
204
448
|
if (!meta.enabled) throw new Error(`MCP "${name}" is disabled`);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
449
|
+
const resolved = this._resolveMeta(meta);
|
|
450
|
+
if (resolved.transport === "http" || resolved.url) {
|
|
451
|
+
proc = new HttpMcpClient(resolved);
|
|
452
|
+
} else {
|
|
453
|
+
if (!resolved.command) throw new Error(`MCP "${name}" has no command — invalid registration`);
|
|
454
|
+
proc = new McpProcess(resolved);
|
|
209
455
|
}
|
|
210
|
-
if (!meta.command) throw new Error(`MCP "${name}" has no command — invalid registration`);
|
|
211
|
-
proc = new McpProcess(meta);
|
|
212
456
|
this.processes.set(name, proc);
|
|
213
457
|
return proc;
|
|
214
458
|
}
|
|
@@ -223,6 +467,20 @@ export class McpRegistry {
|
|
|
223
467
|
return proc.listTools();
|
|
224
468
|
}
|
|
225
469
|
|
|
470
|
+
getLogs(name) {
|
|
471
|
+
const proc = this.processes.get(name);
|
|
472
|
+
if (proc) return proc.getLogs();
|
|
473
|
+
const meta = this.getByName(name);
|
|
474
|
+
if (!meta) return null;
|
|
475
|
+
return {
|
|
476
|
+
transport: meta.transport || "stdio",
|
|
477
|
+
running: false,
|
|
478
|
+
started_at: null,
|
|
479
|
+
events: [],
|
|
480
|
+
note: "MCP not started yet — open the Test or Call panel to spawn it.",
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
226
484
|
shutdown() {
|
|
227
485
|
for (const p of this.processes.values()) p.stop();
|
|
228
486
|
this.processes.clear();
|
package/src/core/mcp/sources.js
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
import fs from "node:fs";
|
|
37
37
|
import os from "node:os";
|
|
38
38
|
import path from "node:path";
|
|
39
|
+
import { apcMcpsFile } from "#core/apc/paths.js";
|
|
39
40
|
|
|
40
41
|
const APX_HOME = path.join(os.homedir(), ".apx");
|
|
41
42
|
const GLOBAL_MCPS_FILE = path.join(APX_HOME, "mcps.json");
|
|
@@ -175,7 +176,7 @@ function normalize(name, server, sourceId) {
|
|
|
175
176
|
// ---------------------------------------------------------------------------
|
|
176
177
|
|
|
177
178
|
export function readApfMcps(projectRoot) {
|
|
178
|
-
const p =
|
|
179
|
+
const p = apcMcpsFile(projectRoot);
|
|
179
180
|
if (!fs.existsSync(p)) return { mcpServers: {} };
|
|
180
181
|
try {
|
|
181
182
|
const json = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
@@ -187,7 +188,7 @@ export function readApfMcps(projectRoot) {
|
|
|
187
188
|
}
|
|
188
189
|
|
|
189
190
|
export function writeApfMcps(projectRoot, json) {
|
|
190
|
-
const p =
|
|
191
|
+
const p = apcMcpsFile(projectRoot);
|
|
191
192
|
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
192
193
|
fs.writeFileSync(p, JSON.stringify(json, null, 2) + "\n");
|
|
193
194
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Public entry point for routines. Re-exports the CRUD helpers from
|
|
2
|
+
// core/stores/routines.js plus the runner — so callers (CLI, HTTP, scheduler,
|
|
3
|
+
// MCP server) import everything from one place.
|
|
4
|
+
export {
|
|
5
|
+
listRoutines,
|
|
6
|
+
getRoutine,
|
|
7
|
+
upsertRoutine,
|
|
8
|
+
deleteRoutine,
|
|
9
|
+
setEnabled,
|
|
10
|
+
updateRunState,
|
|
11
|
+
getDueRoutines,
|
|
12
|
+
parseSchedule,
|
|
13
|
+
computeNextRun,
|
|
14
|
+
} from "#core/stores/routines.js";
|
|
15
|
+
|
|
16
|
+
export { runRoutineNow } from "./runner.js";
|
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Routine execution — the domain logic any caller can invoke (daemon
|
|
2
|
+
// scheduler, CLI `apx routine run`, HTTP `/projects/:pid/routines/:name/run`,
|
|
3
|
+
// MCP server, future scripts). The runner orchestrates a 3-phase pipeline:
|
|
4
|
+
// 1. pre_commands (shell)
|
|
5
|
+
// 2. handler (heartbeat / exec_agent / super_agent / telegram / shell)
|
|
6
|
+
// 3. post_commands (shell)
|
|
2
7
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// heartbeat — log a heartbeat message. spec: { channel?, message? }
|
|
8
|
-
// exec_agent — call an agent engine. spec: { agent: slug, prompt }
|
|
9
|
-
// super_agent — call the APX super-agent. spec: { prompt }
|
|
10
|
-
// telegram — send a Telegram message. spec: { channel?, chat_id?, text }
|
|
11
|
-
// shell — run a shell command. spec: { command, timeout_ms? }
|
|
12
|
-
|
|
8
|
+
// `runRoutineNow(ctx, routine)` is the single entry point. Pass a ctx with at
|
|
9
|
+
// least { project, projects, plugins, registries, globalConfig }. The runner
|
|
10
|
+
// is process-state free — the daemon's RoutineScheduler is a separate file
|
|
11
|
+
// (host/daemon/routines-scheduler.js) that just polls and calls this.
|
|
13
12
|
import { spawn } from "node:child_process";
|
|
14
|
-
import { execFile } from "node:child_process";
|
|
15
|
-
import os from "node:os";
|
|
16
13
|
import fs from "node:fs";
|
|
14
|
+
import os from "node:os";
|
|
17
15
|
import path from "node:path";
|
|
18
16
|
import { callEngine } from "#core/engines/index.js";
|
|
19
17
|
import { runSuperAgent } from "#core/agent/super-agent.js";
|
|
@@ -22,32 +20,18 @@ import { readAgents } from "#core/apc/parser.js";
|
|
|
22
20
|
import { buildAgentSystem } from "#core/agent/build-agent-system.js";
|
|
23
21
|
import { resolveAgentName, SUPERAGENT_ACTOR_ID } from "#core/identity/index.js";
|
|
24
22
|
import { resolveArtifactRef, ARTIFACTS_SKIP_SIGNAL } from "#core/stores/artifacts.js";
|
|
25
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
ensureRoutineMemory,
|
|
25
|
+
readRoutineMemoryForPrompt,
|
|
26
|
+
routineMemoryPath,
|
|
27
|
+
} from "#core/stores/routine-memory.js";
|
|
26
28
|
import { CHANNELS } from "#core/constants/channels.js";
|
|
27
29
|
import {
|
|
28
|
-
listRoutines,
|
|
29
|
-
getRoutine,
|
|
30
|
-
upsertRoutine,
|
|
31
|
-
deleteRoutine,
|
|
32
|
-
setEnabled,
|
|
33
30
|
updateRunState,
|
|
34
|
-
getDueRoutines,
|
|
35
31
|
parseSchedule,
|
|
36
32
|
computeNextRun,
|
|
37
33
|
} from "#core/stores/routines.js";
|
|
38
|
-
|
|
39
|
-
export {
|
|
40
|
-
listRoutines,
|
|
41
|
-
getRoutine,
|
|
42
|
-
upsertRoutine,
|
|
43
|
-
deleteRoutine,
|
|
44
|
-
setEnabled,
|
|
45
|
-
parseSchedule,
|
|
46
|
-
computeNextRun,
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const TICK_MS = 5_000;
|
|
50
|
-
const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
34
|
+
import { nowIso } from "#core/util/time.js";
|
|
51
35
|
|
|
52
36
|
// --------------------- handlers ---------------------------------------------
|
|
53
37
|
|
|
@@ -211,7 +195,7 @@ const HANDLERS = {
|
|
|
211
195
|
|
|
212
196
|
// --------------------- pipeline: pre/post shell commands --------------------
|
|
213
197
|
|
|
214
|
-
|
|
198
|
+
/** Run a single shell command. Returns { exitCode, stdout, stderr }. */
|
|
215
199
|
function runShellCmd(cmd, env = {}, cwd = os.homedir()) {
|
|
216
200
|
return new Promise((resolve) => {
|
|
217
201
|
const child = spawn("sh", ["-c", cmd], {
|
|
@@ -228,13 +212,13 @@ function runShellCmd(cmd, env = {}, cwd = os.homedir()) {
|
|
|
228
212
|
});
|
|
229
213
|
}
|
|
230
214
|
|
|
231
|
-
|
|
215
|
+
/** Inject {{pre_output}} into a prompt string. */
|
|
232
216
|
function injectPreOutput(prompt, preOutput) {
|
|
233
217
|
if (!prompt || typeof prompt !== "string") return prompt;
|
|
234
218
|
return prompt.replace(/\{\{pre_output\}\}/g, preOutput || "");
|
|
235
219
|
}
|
|
236
220
|
|
|
237
|
-
|
|
221
|
+
/** Decide whether to skip the LLM call based on skip_prompt_on + pre results. */
|
|
238
222
|
function shouldSkipPrompt(routine, preExitCode, preStdout) {
|
|
239
223
|
const mode = routine.skip_prompt_on || "signal";
|
|
240
224
|
if (mode === "always") return true;
|
|
@@ -245,10 +229,22 @@ function shouldSkipPrompt(routine, preExitCode, preStdout) {
|
|
|
245
229
|
return false;
|
|
246
230
|
}
|
|
247
231
|
|
|
248
|
-
// --------------------- runtime: run one
|
|
249
|
-
|
|
232
|
+
// --------------------- runtime: run one routine -----------------------------
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Execute a single routine end-to-end (pre_commands → handler → post_commands)
|
|
236
|
+
* and persist last-run state. Pure with respect to process lifecycle — does NOT
|
|
237
|
+
* touch a timer, queue, or scheduler. Pure with respect to network — the
|
|
238
|
+
* super-agent / telegram handlers obviously go out, but the orchestration is
|
|
239
|
+
* sync from the caller's point of view via the returned promise.
|
|
240
|
+
*
|
|
241
|
+
* @param {object} ctx
|
|
242
|
+
* - project: ProjectManager entry (logMessage, path, storagePath)
|
|
243
|
+
* - projects, plugins, registries, globalConfig
|
|
244
|
+
* @param {object} routine The routine record from core/stores/routines.js
|
|
245
|
+
* @returns {object} { status, last_run_at, next_run_at, ...handler-result }
|
|
246
|
+
*/
|
|
250
247
|
export async function runRoutineNow(ctx, routine) {
|
|
251
|
-
// Determine the working directory for shell commands.
|
|
252
248
|
const cwd = ctx.project?.path || os.homedir();
|
|
253
249
|
const storagePath = ctx.project?.storagePath || os.homedir();
|
|
254
250
|
|
|
@@ -263,31 +259,26 @@ export async function runRoutineNow(ctx, routine) {
|
|
|
263
259
|
if (hasPreCmds) {
|
|
264
260
|
const combinedOut = [];
|
|
265
261
|
for (const rawCmd of routine.pre_commands) {
|
|
266
|
-
// Resolve "artifact:<name>" shorthand to its absolute path.
|
|
267
262
|
const cmd = resolveArtifactRef(rawCmd, storagePath);
|
|
268
263
|
const { exitCode, stdout, stderr } = await runShellCmd(cmd, {}, cwd);
|
|
269
264
|
combinedOut.push(stdout);
|
|
270
265
|
if (stderr) combinedOut.push(stderr);
|
|
271
266
|
preExitCode = exitCode;
|
|
272
267
|
if (exitCode !== 0 && (routine.skip_prompt_on === "pre_failure" || routine.skip_prompt_on === "signal")) {
|
|
273
|
-
// Stop running further pre_commands on failure when mode cares about exit code.
|
|
274
268
|
break;
|
|
275
269
|
}
|
|
276
270
|
}
|
|
277
271
|
preStdout = combinedOut.join("");
|
|
278
272
|
|
|
279
|
-
// Write pre output to a temp file so post_commands can reference it via
|
|
280
|
-
// $APX_PRE_OUTPUT_FILE even if the output is large.
|
|
281
273
|
try {
|
|
282
274
|
preOutputFile = path.join(os.tmpdir(), `apx-pre-${routine.name}-${Date.now()}.txt`);
|
|
283
275
|
fs.writeFileSync(preOutputFile, preStdout);
|
|
284
276
|
} catch { preOutputFile = null; }
|
|
285
277
|
}
|
|
286
278
|
|
|
287
|
-
// Env vars injected into post_commands and available in shell pre_commands output.
|
|
288
279
|
const pipelineEnv = {
|
|
289
280
|
APX_PRE_EXIT: String(preExitCode),
|
|
290
|
-
APX_PRE_OUTPUT: preStdout.slice(0, 32_000),
|
|
281
|
+
APX_PRE_OUTPUT: preStdout.slice(0, 32_000),
|
|
291
282
|
APX_PRE_OUTPUT_FILE: preOutputFile || "",
|
|
292
283
|
APX_ROUTINE: routine.name,
|
|
293
284
|
};
|
|
@@ -300,7 +291,6 @@ export async function runRoutineNow(ctx, routine) {
|
|
|
300
291
|
let errMsg = null;
|
|
301
292
|
|
|
302
293
|
if (!skip) {
|
|
303
|
-
// Inject {{pre_output}} into exec_agent and super_agent prompts.
|
|
304
294
|
const enrichedRoutine = (hasPreCmds && preStdout)
|
|
305
295
|
? {
|
|
306
296
|
...routine,
|
|
@@ -344,11 +334,9 @@ export async function runRoutineNow(ctx, routine) {
|
|
|
344
334
|
for (const rawCmd of routine.post_commands) {
|
|
345
335
|
const cmd = resolveArtifactRef(rawCmd, storagePath);
|
|
346
336
|
await runShellCmd(cmd, postEnv, cwd);
|
|
347
|
-
// Post-command failures are logged but don't change routine status.
|
|
348
337
|
}
|
|
349
338
|
}
|
|
350
339
|
|
|
351
|
-
// Cleanup temp file.
|
|
352
340
|
if (preOutputFile) try { fs.unlinkSync(preOutputFile); } catch {}
|
|
353
341
|
|
|
354
342
|
const lastRun = nowIso();
|
|
@@ -374,58 +362,3 @@ export async function runRoutineNow(ctx, routine) {
|
|
|
374
362
|
});
|
|
375
363
|
return { ...result, last_run_at: lastRun, next_run_at: next };
|
|
376
364
|
}
|
|
377
|
-
|
|
378
|
-
export class RoutineScheduler {
|
|
379
|
-
constructor({ projects, plugins, registries, globalConfig, log }) {
|
|
380
|
-
this.projects = projects;
|
|
381
|
-
this.plugins = plugins;
|
|
382
|
-
this.registries = registries;
|
|
383
|
-
this.globalConfig = globalConfig;
|
|
384
|
-
this.log = log || (() => {});
|
|
385
|
-
this._timer = null;
|
|
386
|
-
this._running = false;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
start() {
|
|
390
|
-
if (this._timer) return;
|
|
391
|
-
this._timer = setInterval(
|
|
392
|
-
() => this._tick().catch((e) => this.log(`routines tick error: ${e.message}`)),
|
|
393
|
-
TICK_MS
|
|
394
|
-
);
|
|
395
|
-
this._timer.unref?.();
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
stop() {
|
|
399
|
-
if (this._timer) {
|
|
400
|
-
clearInterval(this._timer);
|
|
401
|
-
this._timer = null;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
async _tick() {
|
|
406
|
-
if (this._running) return;
|
|
407
|
-
this._running = true;
|
|
408
|
-
try {
|
|
409
|
-
const nowStr = nowIso();
|
|
410
|
-
for (const proj of this.projects.list().map((p) => this.projects.get(p.id))) {
|
|
411
|
-
if (!proj) continue;
|
|
412
|
-
const due = getDueRoutines(proj.storagePath, nowStr);
|
|
413
|
-
for (const r of due) {
|
|
414
|
-
this.log(`routine ${r.name} (${r.kind}) firing in project #${proj.id}`);
|
|
415
|
-
await runRoutineNow(
|
|
416
|
-
{
|
|
417
|
-
project: proj,
|
|
418
|
-
projects: this.projects,
|
|
419
|
-
plugins: this.plugins,
|
|
420
|
-
registries: this.registries,
|
|
421
|
-
globalConfig: this.globalConfig,
|
|
422
|
-
},
|
|
423
|
-
r
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
} finally {
|
|
428
|
-
this._running = false;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|