aiden-runtime 4.0.1 → 4.1.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/README.md +11 -7
- package/config/hardware.json +2 -2
- package/dist/api/server.js +50 -52
- package/dist/cli/v4/aidenCLI.js +513 -14
- package/dist/cli/v4/aidenPrompt.js +317 -0
- package/dist/cli/v4/box.js +105 -39
- package/dist/cli/v4/callbacks.js +39 -6
- package/dist/cli/v4/chatSession.js +269 -52
- package/dist/cli/v4/citationFooter.js +97 -0
- package/dist/cli/v4/commands/channel.js +656 -0
- package/dist/cli/v4/commands/clear.js +1 -1
- package/dist/cli/v4/commands/compress.js +1 -1
- package/dist/cli/v4/commands/cron.js +44 -16
- package/dist/cli/v4/commands/fanout.js +236 -0
- package/dist/cli/v4/commands/help.js +15 -4
- package/dist/cli/v4/commands/history.js +84 -0
- package/dist/cli/v4/commands/index.js +19 -1
- package/dist/cli/v4/commands/mcp.js +358 -0
- package/dist/cli/v4/commands/setup.js +34 -0
- package/dist/cli/v4/commands/show.js +43 -0
- package/dist/cli/v4/commands/skills.js +169 -4
- package/dist/cli/v4/commands/status.js +84 -0
- package/dist/cli/v4/commands/subagent.js +78 -0
- package/dist/cli/v4/commands/verbose.js +1 -1
- package/dist/cli/v4/commands/voice.js +218 -0
- package/dist/cli/v4/cronCli.js +103 -0
- package/dist/cli/v4/display.js +300 -14
- package/dist/cli/v4/doctor.js +41 -0
- package/dist/cli/v4/envSources.js +105 -0
- package/dist/cli/v4/ghostMatch.js +74 -0
- package/dist/cli/v4/historyStore.js +163 -0
- package/dist/cli/v4/pasteCompression.js +124 -0
- package/dist/cli/v4/pasteIntercept.js +203 -0
- package/dist/cli/v4/replyRenderer.js +209 -0
- package/dist/cli/v4/resizeGuard.js +92 -0
- package/dist/cli/v4/setupWizard.js +466 -232
- package/dist/cli/v4/shellInterpolation.js +139 -0
- package/dist/cli/v4/skinEngine.js +21 -1
- package/dist/cli/v4/streamingPrefix.js +121 -0
- package/dist/cli/v4/syntaxHighlight.js +345 -0
- package/dist/cli/v4/table.js +216 -0
- package/dist/cli/v4/themeDetect.js +81 -0
- package/dist/cli/v4/uiBuild.js +74 -0
- package/dist/cli/v4/voiceCli.js +113 -0
- package/dist/cli/v4/voicePromptApi.js +196 -0
- package/dist/core/channels/discord.js +16 -10
- package/dist/core/channels/email.js +13 -9
- package/dist/core/channels/imessage.js +13 -9
- package/dist/core/channels/manager.js +25 -7
- package/dist/core/channels/pdf-extract.js +180 -0
- package/dist/core/channels/photo-vision.js +157 -0
- package/dist/core/channels/signal.js +11 -7
- package/dist/core/channels/slack.js +13 -10
- package/dist/core/channels/telegram-commands.js +154 -0
- package/dist/core/channels/telegram-groups.js +198 -0
- package/dist/core/channels/telegram-rate-limit.js +124 -0
- package/dist/core/channels/telegram.js +1980 -0
- package/dist/core/channels/twilio.js +11 -7
- package/dist/core/channels/webhook.js +9 -5
- package/dist/core/channels/whatsapp.js +15 -11
- package/dist/core/channels/whisper-transcribe.js +163 -0
- package/dist/core/cronManager.js +33 -294
- package/dist/core/gateway.js +29 -8
- package/dist/core/playwrightBridge.js +90 -0
- package/dist/core/v4/aidenAgent.js +35 -0
- package/dist/core/v4/auxiliaryClient.js +2 -2
- package/dist/core/v4/cron/atomicWrite.js +18 -4
- package/dist/core/v4/cron/cronExecute.js +300 -0
- package/dist/core/v4/cron/cronManager.js +502 -0
- package/dist/core/v4/cron/cronState.js +314 -0
- package/dist/core/v4/cron/cronTick.js +90 -0
- package/dist/core/v4/cron/diagnostics.js +104 -0
- package/dist/core/v4/cron/graceWindow.js +79 -0
- package/dist/core/v4/firstRun/providerDetection.js +287 -0
- package/dist/core/v4/logger/factory.js +110 -0
- package/dist/core/v4/logger/index.js +22 -0
- package/dist/core/v4/logger/logger.js +101 -0
- package/dist/core/v4/logger/sinks/fileSink.js +110 -0
- package/dist/core/v4/logger/sinks/multiSink.js +43 -0
- package/dist/core/v4/logger/sinks/nullSink.js +53 -0
- package/dist/core/v4/logger/sinks/stdSink.js +81 -0
- package/dist/core/v4/mcp/server/diagnostics.js +40 -0
- package/dist/core/v4/mcp/server/skillBridge.js +94 -0
- package/dist/core/v4/mcp/server/stdioServer.js +119 -0
- package/dist/core/v4/mcp/server/toolBridge.js +168 -0
- package/dist/core/v4/platformPaths.js +105 -0
- package/dist/core/v4/providerFallback.js +25 -0
- package/dist/core/v4/skillLoader.js +21 -5
- package/dist/core/v4/skillMining/candidateStore.js +164 -0
- package/dist/core/v4/skillMining/extractorPrompt.js +111 -0
- package/dist/core/v4/skillMining/proposalBuilder.js +139 -0
- package/dist/core/v4/skillMining/skillMiner.js +191 -0
- package/dist/core/v4/skillMining/traceFingerprint.js +51 -0
- package/dist/core/v4/subagent/budget.js +76 -0
- package/dist/core/v4/subagent/diagnostics.js +22 -0
- package/dist/core/v4/subagent/fanout.js +216 -0
- package/dist/core/v4/subagent/merger.js +148 -0
- package/dist/core/v4/subagent/providerRotation.js +54 -0
- package/dist/core/v4/voice/audioStream.js +373 -0
- package/dist/core/v4/voice/cliVoice.js +393 -0
- package/dist/core/v4/voice/diagnostics.js +66 -0
- package/dist/core/v4/voice/ttsStream.js +193 -0
- package/dist/core/version.js +1 -1
- package/dist/core/visionAnalyze.js +291 -90
- package/dist/core/voice/audio.js +61 -5
- package/dist/core/voice/audioBackend.js +134 -0
- package/dist/core/voice/stt.js +61 -6
- package/dist/core/voice/tts.js +19 -3
- package/dist/providers/v4/nullAdapter.js +58 -0
- package/dist/tools/v4/index.js +32 -1
- package/dist/tools/v4/subagent/subagentFanout.js +166 -0
- package/package.json +11 -2
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
4
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
5
|
+
*
|
|
6
|
+
* Aiden — local-first agent.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* cli/v4/commands/mcp.ts — Phase v4.1-mcp
|
|
10
|
+
*
|
|
11
|
+
* `aiden mcp <action>` subcommand. Three actions:
|
|
12
|
+
*
|
|
13
|
+
* serve — spawn the MCP stdio server. Blocks until parent closes
|
|
14
|
+
* stdio (this is the canonical Claude Desktop /
|
|
15
|
+
* Cursor / Claude Code lifecycle).
|
|
16
|
+
* status — print build fingerprint, exposed tool/skill counts, and
|
|
17
|
+
* current env config. Quick sanity check before pointing
|
|
18
|
+
* a client at the binary.
|
|
19
|
+
* tools — list every exposed tool name + category. Useful when the
|
|
20
|
+
* user wants to know what their allowlist currently maps
|
|
21
|
+
* to before they save the client config.
|
|
22
|
+
*
|
|
23
|
+
* `serve` runs in a deliberately stripped-down runtime: tools,
|
|
24
|
+
* skill loader, sessions, memory, processes — but NO provider /
|
|
25
|
+
* adapter / agent loop. The MCP protocol IS the agent loop here; the
|
|
26
|
+
* spawning client owns the model. This keeps `aiden mcp serve`
|
|
27
|
+
* startable on a freshly-installed Aiden with zero provider keys.
|
|
28
|
+
*
|
|
29
|
+
* Phase-9 approval engine is intentionally NOT wired. The bridge
|
|
30
|
+
* env-gate (`AIDEN_MCP_ALLOW_DESTRUCTIVE`) is the consent layer when
|
|
31
|
+
* there's no human at the REPL. The bridge filter blocks mutating
|
|
32
|
+
* tools by default; opting in means the user accepted server-side
|
|
33
|
+
* execution risk at config time.
|
|
34
|
+
*/
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AIDEN_MCP_BUILD = void 0;
|
|
37
|
+
exports.runMcpSubcommand = runMcpSubcommand;
|
|
38
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
39
|
+
const paths_1 = require("../../../core/v4/paths");
|
|
40
|
+
const toolRegistry_1 = require("../../../core/v4/toolRegistry");
|
|
41
|
+
const skillLoader_1 = require("../../../core/v4/skillLoader");
|
|
42
|
+
const sessionStore_1 = require("../../../core/v4/sessionStore");
|
|
43
|
+
const sessionManager_1 = require("../../../core/v4/sessionManager");
|
|
44
|
+
const memoryManager_1 = require("../../../core/v4/memoryManager");
|
|
45
|
+
const processRegistry_1 = require("../../../core/v4/processRegistry");
|
|
46
|
+
const config_1 = require("../../../core/v4/config");
|
|
47
|
+
const providerFallback_1 = require("../../../core/v4/providerFallback");
|
|
48
|
+
const chatCompletionsAdapter_1 = require("../../../providers/v4/chatCompletionsAdapter");
|
|
49
|
+
const aidenAgent_1 = require("../../../core/v4/aidenAgent");
|
|
50
|
+
const factory_1 = require("../../../core/v4/logger/factory");
|
|
51
|
+
const index_1 = require("../../../tools/v4/index");
|
|
52
|
+
const envSources_1 = require("../envSources");
|
|
53
|
+
const credentialResolver_1 = require("../../../providers/v4/credentialResolver");
|
|
54
|
+
const runtimeResolver_1 = require("../../../providers/v4/runtimeResolver");
|
|
55
|
+
const stdioServer_1 = require("../../../core/v4/mcp/server/stdioServer");
|
|
56
|
+
Object.defineProperty(exports, "AIDEN_MCP_BUILD", { enumerable: true, get: function () { return stdioServer_1.AIDEN_MCP_BUILD; } });
|
|
57
|
+
const diagnostics_1 = require("../../../core/v4/mcp/server/diagnostics");
|
|
58
|
+
const toolBridge_1 = require("../../../core/v4/mcp/server/toolBridge");
|
|
59
|
+
/** Build the slim runtime an MCP server needs. Tools + skills + the
|
|
60
|
+
* subsystems they consume — no provider, no agent. */
|
|
61
|
+
async function buildMcpRuntime(opts = {}) {
|
|
62
|
+
const paths = opts.pathsOverride ?? (0, paths_1.resolveAidenPaths)();
|
|
63
|
+
await (0, paths_1.ensureAidenDirsExist)(paths);
|
|
64
|
+
// ── Phase v4.1-mcp.2 — eager .env load ───────────────────────
|
|
65
|
+
// Stdio MCP clients (Claude Desktop, Cursor) spawn `aiden mcp
|
|
66
|
+
// serve` with an EMPTY env block by default. Without an explicit
|
|
67
|
+
// `env: {...}` per-server entry in their config, our spawned
|
|
68
|
+
// process has no GROQ_API_KEY etc., and any provider-using tool
|
|
69
|
+
// (subagent_fanout, web_search, fetch_url, …) fails. Load the
|
|
70
|
+
// well-known .env locations BEFORE the registry is built so tool
|
|
71
|
+
// factories that read env at registration time see live values.
|
|
72
|
+
// Fill-only — process.env wins, file values fill gaps.
|
|
73
|
+
const envReport = (0, envSources_1.loadMcpEnvSources)({
|
|
74
|
+
aidenHomeEnv: paths.envFile,
|
|
75
|
+
});
|
|
76
|
+
// mcp-stdio mode: file sink + stderr only. Crucial: this MUST happen
|
|
77
|
+
// before any module emits via console.* — but we don't use console in
|
|
78
|
+
// this module, and mcp-stdio mode + the no-stdout-sink invariant in
|
|
79
|
+
// factory.ts guarantee the protocol channel stays clean.
|
|
80
|
+
const { logger } = (0, factory_1.createBootLogger)({ mode: 'mcp-stdio', logsDir: paths.logsDir });
|
|
81
|
+
// Log the env-load report. NEVER log values — only paths + key
|
|
82
|
+
// NAMES. The mcp-stdio logger writes to file + stderr (zero stdout
|
|
83
|
+
// sinks per v4.1-mcp), so spawning clients see this in their MCP
|
|
84
|
+
// log stream and grep can confirm keys loaded.
|
|
85
|
+
for (const a of envReport.attempts) {
|
|
86
|
+
logger.info(`mcp env: ${a.exists ? 'loaded' : 'skipped (missing)'} ${a.path}`, {
|
|
87
|
+
scope: 'mcp',
|
|
88
|
+
path: a.path,
|
|
89
|
+
exists: a.exists,
|
|
90
|
+
applied: a.appliedKeys.length,
|
|
91
|
+
keyNames: a.appliedKeys,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const registry = new toolRegistry_1.ToolRegistry();
|
|
95
|
+
(0, index_1.registerAllTools)(registry);
|
|
96
|
+
const skillLoader = new skillLoader_1.SkillLoader(paths);
|
|
97
|
+
await skillLoader.loadAll().catch(() => undefined);
|
|
98
|
+
const store = new sessionStore_1.SessionStore(paths.sessionsDb);
|
|
99
|
+
const sessions = new sessionManager_1.SessionManager(store);
|
|
100
|
+
const memory = new memoryManager_1.MemoryManager(paths);
|
|
101
|
+
const processes = new processRegistry_1.ProcessRegistry();
|
|
102
|
+
const toolContext = {
|
|
103
|
+
cwd: process.cwd(),
|
|
104
|
+
paths,
|
|
105
|
+
sessions,
|
|
106
|
+
memory,
|
|
107
|
+
processes,
|
|
108
|
+
skillLoader,
|
|
109
|
+
// approvalEngine / ssrfProtection / tirithScanner / memoryGuard
|
|
110
|
+
// intentionally omitted — see header comment.
|
|
111
|
+
};
|
|
112
|
+
// ── Phase v4.1-subagent.2 — wire real subagent_fanout factory ────
|
|
113
|
+
//
|
|
114
|
+
// Without this, the stub registered by `registerAllTools` (from
|
|
115
|
+
// `registerReadOnlyTools`) returns "no providers configured" on
|
|
116
|
+
// every MCP-side call because its `resolveProviders` is `() => []`.
|
|
117
|
+
// The CLI path replaces the stub inside `buildAgentRuntime`; the
|
|
118
|
+
// MCP path is a different runtime build, so it needs its own
|
|
119
|
+
// replacement here.
|
|
120
|
+
//
|
|
121
|
+
// Provider resolution mirrors `buildAgentRuntime` but stripped:
|
|
122
|
+
// 1. Read config.yaml when present; fall back to groq /
|
|
123
|
+
// llama-3.3-70b-versatile (the same default the CLI uses).
|
|
124
|
+
// 2. RuntimeResolver constructs the active adapter.
|
|
125
|
+
// 3. If providerId is groq/together (chat_completions with
|
|
126
|
+
// multi-slot fallback), wrap in FallbackAdapter so subagent
|
|
127
|
+
// rotation gets a real list of provider options.
|
|
128
|
+
//
|
|
129
|
+
// When credentials are missing, leave the stub in place — the
|
|
130
|
+
// status command's "provider keys" block tells the user what to
|
|
131
|
+
// fix. We never throw out of buildMcpRuntime; an unwired stub is
|
|
132
|
+
// strictly better UX than a crashed MCP server.
|
|
133
|
+
await wireSubagentFanout({
|
|
134
|
+
registry,
|
|
135
|
+
paths,
|
|
136
|
+
sessionManager: sessions,
|
|
137
|
+
memoryManager: memory,
|
|
138
|
+
skillLoader,
|
|
139
|
+
logger: logger.child('subagent'),
|
|
140
|
+
});
|
|
141
|
+
return { paths, registry, skillLoader, toolContext, logger };
|
|
142
|
+
}
|
|
143
|
+
/** Mirror of `buildAgentFallbackSlots` (cli/v4/aidenCLI.ts) inlined
|
|
144
|
+
* here to avoid a load-time module cycle between aidenCLI and mcp.ts.
|
|
145
|
+
* Wraps the resolver-resolved primary adapter as slot 0 and appends
|
|
146
|
+
* the env-var-derived defaults so multi-key Groq fanouts and
|
|
147
|
+
* Together failover work without a config-yaml round-trip. */
|
|
148
|
+
function buildMcpFallbackSlots(primary, primaryProviderId, primaryModelId) {
|
|
149
|
+
const defaults = (0, providerFallback_1.buildDefaultSlots)({
|
|
150
|
+
adapterFactory: (cfg) => new chatCompletionsAdapter_1.ChatCompletionsAdapter({
|
|
151
|
+
baseUrl: cfg.baseUrl,
|
|
152
|
+
apiKey: cfg.apiKey,
|
|
153
|
+
model: cfg.model,
|
|
154
|
+
providerName: cfg.providerName,
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
157
|
+
const primarySlot = {
|
|
158
|
+
id: 'primary',
|
|
159
|
+
providerId: primaryProviderId,
|
|
160
|
+
modelId: primaryModelId,
|
|
161
|
+
keyPresent: true,
|
|
162
|
+
keyTail: null,
|
|
163
|
+
build: () => primary,
|
|
164
|
+
};
|
|
165
|
+
return [primarySlot, ...defaults];
|
|
166
|
+
}
|
|
167
|
+
/** Resolve adapter + wire `subagent_fanout` into the MCP registry.
|
|
168
|
+
* Soft-fails (logs + leaves stub) when credentials are missing. */
|
|
169
|
+
async function wireSubagentFanout(opts) {
|
|
170
|
+
const config = new config_1.ConfigManager(opts.paths);
|
|
171
|
+
try {
|
|
172
|
+
await config.load();
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// ENOENT → defaults. Other parse errors are logged but non-fatal.
|
|
176
|
+
opts.logger.warn('config.yaml load failed; using defaults', { scope: 'mcp' });
|
|
177
|
+
}
|
|
178
|
+
const providerId = config.getValue('model.provider', 'groq');
|
|
179
|
+
const modelId = config.getValue('model.modelId', 'llama-3.3-70b-versatile');
|
|
180
|
+
const credentialResolver = new credentialResolver_1.CredentialResolver(opts.paths.authJson);
|
|
181
|
+
const resolver = new runtimeResolver_1.RuntimeResolver(credentialResolver);
|
|
182
|
+
let adapter;
|
|
183
|
+
try {
|
|
184
|
+
adapter = await resolver.resolve({ providerId, modelId, config, paths: opts.paths });
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
188
|
+
opts.logger.warn('subagent_fanout NOT wired — provider resolution failed (run `aiden setup` or set keys in .env)', { scope: 'mcp', providerId, modelId, error: msg });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// Wrap in FallbackAdapter when the active provider is one of the
|
|
192
|
+
// chat-completions families with multi-slot fallback support.
|
|
193
|
+
// Same pattern aidenCLI.ts uses (line ~562); slot construction is
|
|
194
|
+
// inlined to avoid a module cycle between aidenCLI and mcp.ts.
|
|
195
|
+
let wrapped = adapter;
|
|
196
|
+
if (adapter.apiMode === 'chat_completions'
|
|
197
|
+
&& (providerId === 'groq' || providerId === 'together')) {
|
|
198
|
+
const slots = buildMcpFallbackSlots(adapter, providerId, modelId);
|
|
199
|
+
const reachable = slots.filter((s) => s.keyPresent);
|
|
200
|
+
if (reachable.length >= 2) {
|
|
201
|
+
wrapped = new providerFallback_1.FallbackAdapter({
|
|
202
|
+
apiMode: 'chat_completions',
|
|
203
|
+
slots,
|
|
204
|
+
onRateLimit: (slotId) => opts.logger.info(`slot ${slotId} rate-limited`, { scope: 'mcp' }),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const finalAdapter = wrapped;
|
|
209
|
+
opts.registry.register((0, index_1.makeSubagentFanoutTool)({
|
|
210
|
+
logger: opts.logger,
|
|
211
|
+
resolveActiveModel: () => ({ providerId, modelId }),
|
|
212
|
+
aggregatorAdapter: finalAdapter,
|
|
213
|
+
resolveProviders: () => {
|
|
214
|
+
if (finalAdapter instanceof providerFallback_1.FallbackAdapter) {
|
|
215
|
+
const diag = finalAdapter.getDiagnostics();
|
|
216
|
+
const live = diag.slots.filter((s) => s.keyPresent);
|
|
217
|
+
if (live.length > 0) {
|
|
218
|
+
return live.map((s) => ({
|
|
219
|
+
providerId: s.providerId,
|
|
220
|
+
modelId: s.modelId,
|
|
221
|
+
label: s.id,
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return [{ providerId, modelId }];
|
|
226
|
+
},
|
|
227
|
+
runChild: async (childOpts) => {
|
|
228
|
+
const childCtx = {
|
|
229
|
+
cwd: process.cwd(),
|
|
230
|
+
paths: opts.paths,
|
|
231
|
+
sessions: opts.sessionManager,
|
|
232
|
+
memory: opts.memoryManager,
|
|
233
|
+
skillLoader: opts.skillLoader,
|
|
234
|
+
// approvalEngine intentionally undefined — N children
|
|
235
|
+
// contending for one stdin REPL would deadlock under MCP.
|
|
236
|
+
};
|
|
237
|
+
// Filter the child tool surface — read-only by default; opt-in
|
|
238
|
+
// via AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE=1 (mirrors MCP env from
|
|
239
|
+
// v4.1-mcp). Recursive fanout disallowed (depth=1).
|
|
240
|
+
const allowDestructive = process.env.AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE === '1' ||
|
|
241
|
+
process.env.AIDEN_SUBAGENT_ALLOW_DESTRUCTIVE === 'true';
|
|
242
|
+
const childToolNames = [];
|
|
243
|
+
for (const name of opts.registry.list()) {
|
|
244
|
+
const h = opts.registry.get(name);
|
|
245
|
+
if (!h)
|
|
246
|
+
continue;
|
|
247
|
+
if (h.mutates && !allowDestructive)
|
|
248
|
+
continue;
|
|
249
|
+
if (name === 'subagent_fanout')
|
|
250
|
+
continue;
|
|
251
|
+
childToolNames.push(name);
|
|
252
|
+
}
|
|
253
|
+
const childExecutor = opts.registry.buildExecutor(childCtx);
|
|
254
|
+
const childTools = childToolNames
|
|
255
|
+
.map((n) => opts.registry.get(n)?.schema)
|
|
256
|
+
.filter((s) => !!s);
|
|
257
|
+
// Per-child cloned FallbackAdapter — own rate-limit state.
|
|
258
|
+
const childProvider = finalAdapter instanceof providerFallback_1.FallbackAdapter
|
|
259
|
+
? finalAdapter.clone()
|
|
260
|
+
: finalAdapter;
|
|
261
|
+
const child = new aidenAgent_1.AidenAgent({
|
|
262
|
+
provider: childProvider,
|
|
263
|
+
tools: childTools,
|
|
264
|
+
toolExecutor: childExecutor,
|
|
265
|
+
maxTurns: childOpts.maxIterations,
|
|
266
|
+
providerId: childOpts.provider.providerId,
|
|
267
|
+
modelId: childOpts.provider.modelId,
|
|
268
|
+
// No promptBuilder — children get a brief system prompt
|
|
269
|
+
// (same lesson as v4.1-subagent.1: full SOUL.md makes
|
|
270
|
+
// trivial fanouts spend 30s+ on verbose self-introductions).
|
|
271
|
+
});
|
|
272
|
+
if (childOpts.signal.aborted) {
|
|
273
|
+
throw new Error('aborted before dispatch');
|
|
274
|
+
}
|
|
275
|
+
const roleLine = childOpts.role ? `Role: ${childOpts.role}. ` : '';
|
|
276
|
+
const childSystemPrompt = `You are one of N parallel subagents. ${roleLine}` +
|
|
277
|
+
`Answer the user's request concisely. Use available tools when ` +
|
|
278
|
+
`the answer requires real-world information you don't have memorized.`;
|
|
279
|
+
const history = [
|
|
280
|
+
{ role: 'system', content: childSystemPrompt },
|
|
281
|
+
{ role: 'user', content: childOpts.prompt },
|
|
282
|
+
];
|
|
283
|
+
const result = await child.runConversation(history);
|
|
284
|
+
return result.finalContent;
|
|
285
|
+
},
|
|
286
|
+
}));
|
|
287
|
+
opts.logger.info('subagent_fanout: wired (replaces stub) [mcp serve]', {
|
|
288
|
+
providerId,
|
|
289
|
+
modelId,
|
|
290
|
+
fallback: finalAdapter instanceof providerFallback_1.FallbackAdapter ? 'FallbackAdapter' : 'direct',
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
async function runMcpSubcommand(action, opts = {}) {
|
|
294
|
+
const writeOut = opts.writeOut ?? ((t) => process.stdout.write(t));
|
|
295
|
+
const writeErr = opts.writeErr ?? ((t) => process.stderr.write(t));
|
|
296
|
+
switch (action) {
|
|
297
|
+
case 'serve': {
|
|
298
|
+
const { registry, skillLoader, toolContext, logger } = await buildMcpRuntime(opts);
|
|
299
|
+
await (0, stdioServer_1.startStdioMcpServer)({
|
|
300
|
+
registry,
|
|
301
|
+
skillLoader,
|
|
302
|
+
toolContext,
|
|
303
|
+
logger,
|
|
304
|
+
});
|
|
305
|
+
// Block forever — parent closes stdio when the client disconnects,
|
|
306
|
+
// which tears down the SDK transport and unwinds the process.
|
|
307
|
+
await new Promise(() => undefined);
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
310
|
+
case 'status': {
|
|
311
|
+
const { registry, skillLoader } = await buildMcpRuntime(opts);
|
|
312
|
+
const diag = await (0, diagnostics_1.collectMcpDiagnostics)(registry, skillLoader);
|
|
313
|
+
writeOut(`Aiden MCP server\n`);
|
|
314
|
+
writeOut(` build: ${diag.build}\n`);
|
|
315
|
+
writeOut(` tools (total): ${diag.toolsTotal}\n`);
|
|
316
|
+
writeOut(` tools (exposed): ${diag.toolsExposed}\n`);
|
|
317
|
+
writeOut(` skills: ${diag.skillsTotal}\n`);
|
|
318
|
+
writeOut(` allowDestructive: ${diag.env.allowDestructive ? 'yes' : 'no'}\n`);
|
|
319
|
+
writeOut(` allowlist: ${diag.env.allowlist
|
|
320
|
+
? diag.env.allowlist.join(', ') || '(empty)'
|
|
321
|
+
: '(unset — all)'}\n`);
|
|
322
|
+
// Phase v4.1-mcp.2 — provider key presence + source. NEVER log
|
|
323
|
+
// values; only the source tag (preset / aiden-env / unset).
|
|
324
|
+
writeOut(` provider keys:\n`);
|
|
325
|
+
const keys = (0, envSources_1.describeProviderKeys)();
|
|
326
|
+
const present = keys.filter((k) => k.present).length;
|
|
327
|
+
writeOut(` detected: ${present}/${keys.length}\n`);
|
|
328
|
+
for (const k of keys) {
|
|
329
|
+
const tag = k.present ? '✓' : '✗';
|
|
330
|
+
// Lower-case the key for display: "GROQ_API_KEY" → "groq".
|
|
331
|
+
const label = k.key.replace(/_API_KEY$/, '').toLowerCase();
|
|
332
|
+
const src = k.present
|
|
333
|
+
? (k.source === 'aiden-env' ? '(.env)' : '(preset)')
|
|
334
|
+
: '(unset)';
|
|
335
|
+
writeOut(` ${tag} ${label.padEnd(12)} ${src}\n`);
|
|
336
|
+
}
|
|
337
|
+
return 0;
|
|
338
|
+
}
|
|
339
|
+
case 'tools': {
|
|
340
|
+
const { registry } = await buildMcpRuntime(opts);
|
|
341
|
+
const env = (0, toolBridge_1.readToolBridgeEnv)();
|
|
342
|
+
const list = (0, toolBridge_1.buildToolsList)(registry, env);
|
|
343
|
+
writeOut(`Aiden MCP — exposed tools (${list.length})\n`);
|
|
344
|
+
for (const tool of list) {
|
|
345
|
+
const handler = registry.get(tool.name);
|
|
346
|
+
const cat = handler?.category ?? '?';
|
|
347
|
+
const set = handler?.toolset ?? '-';
|
|
348
|
+
writeOut(` ${tool.name.padEnd(28)} ${cat.padEnd(8)} [${set}]\n`);
|
|
349
|
+
}
|
|
350
|
+
return 0;
|
|
351
|
+
}
|
|
352
|
+
default: {
|
|
353
|
+
writeErr(`Unknown 'aiden mcp' action: ${action}\n`);
|
|
354
|
+
writeErr(`Actions: serve | status | tools\n`);
|
|
355
|
+
return 1;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setup = void 0;
|
|
4
|
+
const setupWizard_1 = require("../setupWizard");
|
|
5
|
+
exports.setup = {
|
|
6
|
+
name: 'setup',
|
|
7
|
+
description: 'Re-run the setup wizard (configure provider + API key).',
|
|
8
|
+
category: 'system',
|
|
9
|
+
icon: '⚙',
|
|
10
|
+
handler: async (ctx) => {
|
|
11
|
+
if (!ctx.paths) {
|
|
12
|
+
ctx.display.printError('Cannot run wizard from this context — no paths available.', 'This is a wiring bug; please report.');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const result = await (0, setupWizard_1.runSetupWizard)({
|
|
16
|
+
paths: ctx.paths,
|
|
17
|
+
display: ctx.display,
|
|
18
|
+
force: true,
|
|
19
|
+
});
|
|
20
|
+
if (result.status === 'configured' && result.ran) {
|
|
21
|
+
ctx.display.write('\nProvider configured. ' +
|
|
22
|
+
'Restart Aiden (`/quit` then re-run `aiden`) to pick up the new provider.\n\n');
|
|
23
|
+
}
|
|
24
|
+
else if (result.status === 'skipped') {
|
|
25
|
+
ctx.display.write('\nStill in explore mode. Run /setup again whenever you\'re ready.\n\n');
|
|
26
|
+
}
|
|
27
|
+
else if (result.status === 'exited') {
|
|
28
|
+
// Wizard explicitly chose to exit — but we're inside a REPL,
|
|
29
|
+
// so just report and stay in the session.
|
|
30
|
+
ctx.display.dim('Wizard exited; continuing existing session.');
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
4
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
5
|
+
*
|
|
6
|
+
* Aiden — local-first agent.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* cli/v4/commands/show.ts — Tier-3.1 (v4.1-tier3.1)
|
|
10
|
+
*
|
|
11
|
+
* `/show <id>` — print a previously compressed paste. Compressed
|
|
12
|
+
* pastes are echoed in the REPL as `[paste #<id>: <N> lines, <KB>]`
|
|
13
|
+
* and stored on disk; this command reverses that for the user
|
|
14
|
+
* (NOT the agent — the agent receives the full original text at
|
|
15
|
+
* paste time).
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.show = void 0;
|
|
19
|
+
const pasteCompression_1 = require("../pasteCompression");
|
|
20
|
+
exports.show = {
|
|
21
|
+
name: 'show',
|
|
22
|
+
description: 'Print the original content of a compressed paste (/show <id>).',
|
|
23
|
+
category: 'system',
|
|
24
|
+
icon: '>',
|
|
25
|
+
handler: async (ctx) => {
|
|
26
|
+
const id = (ctx.args[0] ?? '').trim();
|
|
27
|
+
if (!id) {
|
|
28
|
+
ctx.display.warn('Usage: /show <id> (id from the [paste #<id>: ...] echo)');
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
const original = await (0, pasteCompression_1.expandPaste)(id);
|
|
32
|
+
if (original == null) {
|
|
33
|
+
ctx.display.warn(`No paste with id ${id} found.`);
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
ctx.display.write('\n');
|
|
37
|
+
ctx.display.write(original);
|
|
38
|
+
if (!original.endsWith('\n'))
|
|
39
|
+
ctx.display.write('\n');
|
|
40
|
+
ctx.display.write('\n');
|
|
41
|
+
return {};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.skills = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
9
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
10
|
+
*
|
|
11
|
+
* Aiden — local-first agent.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* cli/v4/commands/skills.ts — Phase 14b
|
|
15
|
+
*
|
|
16
|
+
* `/skills [list|view <name>|install <id>]` — minimal CLI surface to
|
|
17
|
+
* Phase 10's SkillLoader + Phase 14a's SkillsHub. Default subcommand: list.
|
|
18
|
+
*/
|
|
19
|
+
const node_fs_1 = require("node:fs");
|
|
20
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
21
|
+
const table_1 = require("../table");
|
|
22
|
+
const candidateStore_1 = require("../../../core/v4/skillMining/candidateStore");
|
|
23
|
+
const paths_1 = require("../../../core/v4/paths");
|
|
24
|
+
const skillSpec_1 = require("../../../core/v4/skillSpec");
|
|
4
25
|
exports.skills = {
|
|
5
26
|
name: 'skills',
|
|
6
27
|
description: 'List, view, or install skills.',
|
|
@@ -19,9 +40,12 @@ exports.skills = {
|
|
|
19
40
|
return {};
|
|
20
41
|
}
|
|
21
42
|
ctx.display.info(`Installed skills (${skills.length}):`);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
43
|
+
ctx.display.write((0, table_1.renderTable)(skills.map((s) => ({ name: s.name, description: s.description ?? '' })), [
|
|
44
|
+
{ key: 'name', header: 'Name', align: 'left', minWidth: 16 },
|
|
45
|
+
// Tier-3.1b: drop the legacy `truncate: 60` cap so the
|
|
46
|
+
// description column flexes to fill available width.
|
|
47
|
+
{ key: 'description', header: 'Description', align: 'left', flex: true },
|
|
48
|
+
]));
|
|
25
49
|
return {};
|
|
26
50
|
}
|
|
27
51
|
if (sub === 'view') {
|
|
@@ -69,7 +93,148 @@ exports.skills = {
|
|
|
69
93
|
}
|
|
70
94
|
return {};
|
|
71
95
|
}
|
|
72
|
-
|
|
96
|
+
// ── Phase v4.1-skill-mining ──────────────────────────────────
|
|
97
|
+
// /skills review — list pending mined candidates
|
|
98
|
+
// /skills view-candidate <id> — preview a candidate's SKILL.md
|
|
99
|
+
// /skills accept <id> — promote candidate to live skill
|
|
100
|
+
// /skills reject <id> [reason]— record rejection (dedup-aware)
|
|
101
|
+
// /skills propose — explanation of the mining hook
|
|
102
|
+
const store = new candidateStore_1.CandidateStore();
|
|
103
|
+
if (sub === 'review') {
|
|
104
|
+
const candidates = await store.list();
|
|
105
|
+
if (candidates.length === 0) {
|
|
106
|
+
ctx.display.dim('(no pending candidates — mined skills appear here after a successful 3+ tool turn)');
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
ctx.display.info(`Pending mined candidates (${candidates.length}):`);
|
|
110
|
+
ctx.display.write((0, table_1.renderTable)(candidates.map((c) => {
|
|
111
|
+
// Pull the name + 1-line description from the candidate's
|
|
112
|
+
// own SKILL.md so the table reflects what the user will
|
|
113
|
+
// accept verbatim.
|
|
114
|
+
let name = '(unparsed)';
|
|
115
|
+
let description = '';
|
|
116
|
+
try {
|
|
117
|
+
const parsed = (0, skillSpec_1.parseSkillContent)(c.skillContent);
|
|
118
|
+
name = parsed.frontmatter.name ?? name;
|
|
119
|
+
description = parsed.frontmatter.description ?? '';
|
|
120
|
+
}
|
|
121
|
+
catch { /* fall through to defaults */ }
|
|
122
|
+
return {
|
|
123
|
+
id: c.id.slice(0, 8),
|
|
124
|
+
name,
|
|
125
|
+
confidence: c.candidateConfidence.toFixed(2),
|
|
126
|
+
session: c.sourceSessionId.slice(0, 8),
|
|
127
|
+
created: c.createdAt.slice(0, 19).replace('T', ' '),
|
|
128
|
+
description,
|
|
129
|
+
};
|
|
130
|
+
}), [
|
|
131
|
+
{ key: 'id', header: 'ID', align: 'left' },
|
|
132
|
+
{ key: 'name', header: 'Name', align: 'left' },
|
|
133
|
+
{ key: 'confidence', header: 'Conf', align: 'right' },
|
|
134
|
+
{ key: 'session', header: 'Session', align: 'left' },
|
|
135
|
+
{ key: 'created', header: 'Created', align: 'left' },
|
|
136
|
+
{ key: 'description', header: 'Description', align: 'left', flex: true },
|
|
137
|
+
]));
|
|
138
|
+
ctx.display.dim('Use `/skills view-candidate <id-prefix>` to preview, `/skills accept <id>` to promote, `/skills reject <id> [reason]` to dismiss.');
|
|
139
|
+
return {};
|
|
140
|
+
}
|
|
141
|
+
if (sub === 'view-candidate') {
|
|
142
|
+
const idPrefix = ctx.args[1];
|
|
143
|
+
if (!idPrefix) {
|
|
144
|
+
ctx.display.printError('Usage: /skills view-candidate <id>');
|
|
145
|
+
return {};
|
|
146
|
+
}
|
|
147
|
+
const all = await store.list();
|
|
148
|
+
const match = all.find((c) => c.id.startsWith(idPrefix));
|
|
149
|
+
if (!match) {
|
|
150
|
+
ctx.display.printError(`No candidate matches id prefix '${idPrefix}'.`);
|
|
151
|
+
return {};
|
|
152
|
+
}
|
|
153
|
+
ctx.display.info(`Candidate ${match.id} (confidence ${match.candidateConfidence.toFixed(2)}):`);
|
|
154
|
+
ctx.display.write('\n');
|
|
155
|
+
ctx.display.write(match.skillContent);
|
|
156
|
+
ctx.display.write('\n');
|
|
157
|
+
return {};
|
|
158
|
+
}
|
|
159
|
+
if (sub === 'accept') {
|
|
160
|
+
const idPrefix = ctx.args[1];
|
|
161
|
+
if (!idPrefix) {
|
|
162
|
+
ctx.display.printError('Usage: /skills accept <id>');
|
|
163
|
+
return {};
|
|
164
|
+
}
|
|
165
|
+
const all = await store.list();
|
|
166
|
+
const match = all.find((c) => c.id.startsWith(idPrefix));
|
|
167
|
+
if (!match) {
|
|
168
|
+
ctx.display.printError(`No candidate matches id prefix '${idPrefix}'.`);
|
|
169
|
+
return {};
|
|
170
|
+
}
|
|
171
|
+
let parsed;
|
|
172
|
+
try {
|
|
173
|
+
parsed = (0, skillSpec_1.parseSkillContent)(match.skillContent);
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
ctx.display.printError(`Candidate did not round-trip through the parser: ${err.message}`);
|
|
177
|
+
return {};
|
|
178
|
+
}
|
|
179
|
+
const skillName = parsed.frontmatter.name;
|
|
180
|
+
if (!skillName) {
|
|
181
|
+
ctx.display.printError('Candidate missing required `name` frontmatter field.');
|
|
182
|
+
return {};
|
|
183
|
+
}
|
|
184
|
+
const paths = (0, paths_1.resolveAidenPaths)();
|
|
185
|
+
const targetDir = node_path_1.default.join(paths.skillsDir, skillName);
|
|
186
|
+
if (!(0, node_fs_1.existsSync)(paths.skillsDir))
|
|
187
|
+
(0, node_fs_1.mkdirSync)(paths.skillsDir, { recursive: true });
|
|
188
|
+
if (!(0, node_fs_1.existsSync)(targetDir))
|
|
189
|
+
(0, node_fs_1.mkdirSync)(targetDir, { recursive: true });
|
|
190
|
+
const targetFile = node_path_1.default.join(targetDir, 'SKILL.md');
|
|
191
|
+
try {
|
|
192
|
+
await node_fs_1.promises.writeFile(targetFile, match.skillContent, 'utf8');
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
ctx.display.printError(`Failed to write skill: ${err.message}`);
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
await store.remove(match.id);
|
|
199
|
+
// Invalidate the loader cache so the new skill is visible
|
|
200
|
+
// on the next /skills list call without a session restart.
|
|
201
|
+
try {
|
|
202
|
+
ctx.skillLoader?.invalidate?.();
|
|
203
|
+
}
|
|
204
|
+
catch { /* best-effort */ }
|
|
205
|
+
ctx.display.success(`Promoted '${skillName}' to ${targetFile}.`);
|
|
206
|
+
return {};
|
|
207
|
+
}
|
|
208
|
+
if (sub === 'reject') {
|
|
209
|
+
const idPrefix = ctx.args[1];
|
|
210
|
+
if (!idPrefix) {
|
|
211
|
+
ctx.display.printError('Usage: /skills reject <id> [reason]');
|
|
212
|
+
return {};
|
|
213
|
+
}
|
|
214
|
+
const reason = ctx.args.slice(2).join(' ').trim() || undefined;
|
|
215
|
+
const all = await store.list();
|
|
216
|
+
const match = all.find((c) => c.id.startsWith(idPrefix));
|
|
217
|
+
if (!match) {
|
|
218
|
+
ctx.display.printError(`No candidate matches id prefix '${idPrefix}'.`);
|
|
219
|
+
return {};
|
|
220
|
+
}
|
|
221
|
+
await store.recordRejection(match.fingerprint, reason);
|
|
222
|
+
await store.remove(match.id);
|
|
223
|
+
ctx.display.success(`Rejected candidate '${match.id.slice(0, 8)}'.` +
|
|
224
|
+
(reason ? ` Reason recorded: "${reason}"` : ''));
|
|
225
|
+
return {};
|
|
226
|
+
}
|
|
227
|
+
if (sub === 'propose') {
|
|
228
|
+
// Manual mining fires automatically post-turn via aidenAgent;
|
|
229
|
+
// this subcommand surfaces what's currently pending so the
|
|
230
|
+
// user understands the hook without needing to remember
|
|
231
|
+
// /skills review.
|
|
232
|
+
const candidates = await store.list();
|
|
233
|
+
ctx.display.info(`Skill mining is active — successful 3+ tool turns auto-stage candidates.`);
|
|
234
|
+
ctx.display.dim(`Pending: ${candidates.length}. Run /skills review to inspect.`);
|
|
235
|
+
return {};
|
|
236
|
+
}
|
|
237
|
+
ctx.display.printError(`Unknown subcommand: ${sub}`, 'Try: /skills list | view <name> | install <id> | review | view-candidate <id> | accept <id> | reject <id> [reason] | propose');
|
|
73
238
|
return {};
|
|
74
239
|
},
|
|
75
240
|
};
|