aiden-runtime 4.0.2 → 4.1.1
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 +19 -11
- package/config/hardware.json +2 -2
- package/dist/api/server.js +50 -52
- package/dist/cli/v4/aidenCLI.js +424 -7
- 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 +256 -55
- 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 +16 -1
- package/dist/cli/v4/commands/mcp.js +358 -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 +297 -13
- package/dist/cli/v4/doctor.js +102 -1
- package/dist/cli/v4/doctorLiveness.js +329 -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/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/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 +118 -0
- package/dist/core/v4/skillMining/proposalBuilder.js +140 -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/moat/dangerousPatterns.js +1 -1
- package/dist/providers/v4/codexResponsesAdapter.js +7 -2
- package/dist/providers/v4/errors.js +51 -1
- package/dist/providers/v4/ollamaPromptToolsAdapter.js +9 -2
- package/dist/tools/v4/index.js +32 -1
- package/dist/tools/v4/subagent/subagentFanout.js +190 -0
- package/package.json +11 -2
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
* core/v4/logger/factory.ts — Phase v4.1-1.3a
|
|
10
|
+
*
|
|
11
|
+
* Build a root `Logger` for the running process based on which mode
|
|
12
|
+
* Aiden is in. Each mode has different invariants:
|
|
13
|
+
*
|
|
14
|
+
* - cli-interactive — REPL is sacred. Zero stdout sinks. Errors go
|
|
15
|
+
* to stderr (visible to the user without
|
|
16
|
+
* touching the chat prompt). Everything to file.
|
|
17
|
+
* - cli-headless — `aiden setup`, `aiden doctor`, scripts. No
|
|
18
|
+
* REPL to protect. Warnings/errors go to stderr.
|
|
19
|
+
* Everything to file. Stdout stays free for the
|
|
20
|
+
* command's own output (so users can pipe).
|
|
21
|
+
* - serve — daemon. Logs go to stdout as NDJSON for systemd
|
|
22
|
+
* / docker / log aggregators. File mirror keeps
|
|
23
|
+
* a local trace.
|
|
24
|
+
* - test — vitest etc. NullSink only. Pass `withMemory:
|
|
25
|
+
* true` to swap in a MemorySink for assertions.
|
|
26
|
+
*
|
|
27
|
+
* Modules NEVER pick their own sinks — they receive a Logger and call
|
|
28
|
+
* `.info()` etc. The factory is the only place mode-routing decisions
|
|
29
|
+
* live.
|
|
30
|
+
*/
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.createBootLogger = createBootLogger;
|
|
33
|
+
exports.noopLogger = noopLogger;
|
|
34
|
+
exports.markReplActive = markReplActive;
|
|
35
|
+
exports.markReplInactive = markReplInactive;
|
|
36
|
+
exports.isReplActive = isReplActive;
|
|
37
|
+
const logger_1 = require("./logger");
|
|
38
|
+
const fileSink_1 = require("./sinks/fileSink");
|
|
39
|
+
const stdSink_1 = require("./sinks/stdSink");
|
|
40
|
+
const nullSink_1 = require("./sinks/nullSink");
|
|
41
|
+
function createBootLogger(opts) {
|
|
42
|
+
switch (opts.mode) {
|
|
43
|
+
case 'cli-interactive': {
|
|
44
|
+
// REPL invariant: zero stdout writes. Stderr is allowed for
|
|
45
|
+
// warn/error so a real failure isn't completely silent.
|
|
46
|
+
const sinks = [];
|
|
47
|
+
if (opts.logsDir)
|
|
48
|
+
sinks.push(new fileSink_1.FileSink({ dir: opts.logsDir, name: 'aiden' }));
|
|
49
|
+
sinks.push(new stdSink_1.StderrSink({ minLevel: 'warn' }));
|
|
50
|
+
return { logger: new logger_1.CoreLogger({ sinks }) };
|
|
51
|
+
}
|
|
52
|
+
case 'cli-headless': {
|
|
53
|
+
const sinks = [];
|
|
54
|
+
if (opts.logsDir)
|
|
55
|
+
sinks.push(new fileSink_1.FileSink({ dir: opts.logsDir, name: 'aiden' }));
|
|
56
|
+
sinks.push(new stdSink_1.StderrSink({ minLevel: 'warn' }));
|
|
57
|
+
return { logger: new logger_1.CoreLogger({ sinks }) };
|
|
58
|
+
}
|
|
59
|
+
case 'serve': {
|
|
60
|
+
// Daemon — stdout NDJSON for log aggregators, mirror to file for
|
|
61
|
+
// local-on-disk debugging.
|
|
62
|
+
const sinks = [new stdSink_1.StdoutJsonSink()];
|
|
63
|
+
if (opts.logsDir)
|
|
64
|
+
sinks.push(new fileSink_1.FileSink({ dir: opts.logsDir, name: 'aiden' }));
|
|
65
|
+
return { logger: new logger_1.CoreLogger({ sinks }) };
|
|
66
|
+
}
|
|
67
|
+
case 'test': {
|
|
68
|
+
if (opts.withMemory) {
|
|
69
|
+
const memory = new nullSink_1.MemorySink();
|
|
70
|
+
return { logger: new logger_1.CoreLogger({ sinks: [memory] }), memory };
|
|
71
|
+
}
|
|
72
|
+
return { logger: new logger_1.CoreLogger({ sinks: [new nullSink_1.NullSink()] }) };
|
|
73
|
+
}
|
|
74
|
+
case 'mcp-stdio': {
|
|
75
|
+
// Phase v4.1-mcp invariant: stdout carries the JSON-RPC protocol
|
|
76
|
+
// frames — any byte written to stdout outside the MCP transport
|
|
77
|
+
// corrupts the wire. So this mode wires ZERO stdout sinks. Errors
|
|
78
|
+
// and warnings go to stderr (visible to the spawning client's log
|
|
79
|
+
// stream); everything else lands in the file sink for postmortems.
|
|
80
|
+
const sinks = [];
|
|
81
|
+
if (opts.logsDir)
|
|
82
|
+
sinks.push(new fileSink_1.FileSink({ dir: opts.logsDir, name: 'aiden-mcp' }));
|
|
83
|
+
sinks.push(new stdSink_1.StderrSink({ minLevel: 'warn' }));
|
|
84
|
+
return { logger: new logger_1.CoreLogger({ sinks }) };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* No-op singleton — what `attachLogger()` setters fall back to when no
|
|
90
|
+
* caller has wired in a real one yet. Avoids null-checks at every emit
|
|
91
|
+
* site. Lazy-built so tests that import this module don't allocate
|
|
92
|
+
* sinks they'll never touch.
|
|
93
|
+
*/
|
|
94
|
+
let _noop = null;
|
|
95
|
+
function noopLogger() {
|
|
96
|
+
if (!_noop)
|
|
97
|
+
_noop = new logger_1.CoreLogger({ sinks: [new nullSink_1.NullSink()] });
|
|
98
|
+
return _noop;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Phase v4.1-1.3a — process-wide flag tripped once the chat prompt is
|
|
102
|
+
* up. The repl-sacred invariant in `cli-interactive` mode comes from
|
|
103
|
+
* the factory not wiring any stdout sink, but a defense-in-depth layer:
|
|
104
|
+
* if any future code path manages to grab stdout directly, this flag
|
|
105
|
+
* lets us assert in tests + audit.
|
|
106
|
+
*/
|
|
107
|
+
let _replActive = false;
|
|
108
|
+
function markReplActive() { _replActive = true; }
|
|
109
|
+
function markReplInactive() { _replActive = false; }
|
|
110
|
+
function isReplActive() { return _replActive; }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// core/v4/logger/index.ts — Phase v4.1-1.3a barrel export.
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.MultiSink = exports.MemorySink = exports.NullSink = exports.StdoutJsonSink = exports.StderrSink = exports.FileSink = exports.isReplActive = exports.markReplInactive = exports.markReplActive = exports.noopLogger = exports.createBootLogger = exports.CoreLogger = void 0;
|
|
5
|
+
var logger_1 = require("./logger");
|
|
6
|
+
Object.defineProperty(exports, "CoreLogger", { enumerable: true, get: function () { return logger_1.CoreLogger; } });
|
|
7
|
+
var factory_1 = require("./factory");
|
|
8
|
+
Object.defineProperty(exports, "createBootLogger", { enumerable: true, get: function () { return factory_1.createBootLogger; } });
|
|
9
|
+
Object.defineProperty(exports, "noopLogger", { enumerable: true, get: function () { return factory_1.noopLogger; } });
|
|
10
|
+
Object.defineProperty(exports, "markReplActive", { enumerable: true, get: function () { return factory_1.markReplActive; } });
|
|
11
|
+
Object.defineProperty(exports, "markReplInactive", { enumerable: true, get: function () { return factory_1.markReplInactive; } });
|
|
12
|
+
Object.defineProperty(exports, "isReplActive", { enumerable: true, get: function () { return factory_1.isReplActive; } });
|
|
13
|
+
var fileSink_1 = require("./sinks/fileSink");
|
|
14
|
+
Object.defineProperty(exports, "FileSink", { enumerable: true, get: function () { return fileSink_1.FileSink; } });
|
|
15
|
+
var stdSink_1 = require("./sinks/stdSink");
|
|
16
|
+
Object.defineProperty(exports, "StderrSink", { enumerable: true, get: function () { return stdSink_1.StderrSink; } });
|
|
17
|
+
Object.defineProperty(exports, "StdoutJsonSink", { enumerable: true, get: function () { return stdSink_1.StdoutJsonSink; } });
|
|
18
|
+
var nullSink_1 = require("./sinks/nullSink");
|
|
19
|
+
Object.defineProperty(exports, "NullSink", { enumerable: true, get: function () { return nullSink_1.NullSink; } });
|
|
20
|
+
Object.defineProperty(exports, "MemorySink", { enumerable: true, get: function () { return nullSink_1.MemorySink; } });
|
|
21
|
+
var multiSink_1 = require("./sinks/multiSink");
|
|
22
|
+
Object.defineProperty(exports, "MultiSink", { enumerable: true, get: function () { return multiSink_1.MultiSink; } });
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
* core/v4/logger/logger.ts — Phase v4.1-1.3a
|
|
10
|
+
*
|
|
11
|
+
* The Logger contract. Every module that emits diagnostics goes through
|
|
12
|
+
* this — never `console.*` directly. The CLI's REPL is sacred: in
|
|
13
|
+
* `cli-interactive` mode the factory wires zero stdout sinks, so a
|
|
14
|
+
* misbehaving module CANNOT corrupt the chat prompt.
|
|
15
|
+
*
|
|
16
|
+
* Three pieces:
|
|
17
|
+
* - `Logger` — what consumers call (debug / info / warn / error
|
|
18
|
+
* + child(scope) for nested namespaces).
|
|
19
|
+
* - `LoggerSink` — where lines actually go (file, stderr, null, …).
|
|
20
|
+
* - `Logger` impl — fans every line out to all attached sinks.
|
|
21
|
+
*
|
|
22
|
+
* Sinks are the routing surface; the factory in `./factory.ts` picks
|
|
23
|
+
* the right combination per AidenMode. Adding a new module never
|
|
24
|
+
* touches sink logic — modules just call `logger.info('...')` and
|
|
25
|
+
* the factory decides where it goes.
|
|
26
|
+
*/
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.CoreLogger = exports.LOG_LEVEL_ORDER = void 0;
|
|
29
|
+
/** Stable numeric ordering for level filtering. */
|
|
30
|
+
exports.LOG_LEVEL_ORDER = {
|
|
31
|
+
debug: 10,
|
|
32
|
+
info: 20,
|
|
33
|
+
warn: 30,
|
|
34
|
+
error: 40,
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Default `Logger` implementation. Holds a list of sinks and the
|
|
38
|
+
* current scope; child loggers share the same sink list (so updating
|
|
39
|
+
* the level / detaching at the root affects everything).
|
|
40
|
+
*/
|
|
41
|
+
class CoreLogger {
|
|
42
|
+
/**
|
|
43
|
+
* Construct a root logger. Use `child(segment)` for sub-loggers.
|
|
44
|
+
* `sinks` may be empty — useful for tests; writes silently drop.
|
|
45
|
+
*/
|
|
46
|
+
constructor(opts) {
|
|
47
|
+
this.scope = opts.scope ?? '';
|
|
48
|
+
this.sinks = opts.sinks;
|
|
49
|
+
this.level = opts.level ?? 'debug';
|
|
50
|
+
this.sinksOwner = { sinks: this.sinks, level: this.level };
|
|
51
|
+
}
|
|
52
|
+
/** Internal — used by `child()` to share state with the root. */
|
|
53
|
+
static childOf(parent, segment) {
|
|
54
|
+
const c = Object.create(CoreLogger.prototype);
|
|
55
|
+
const nextScope = parent.scope ? `${parent.scope}.${segment}` : segment;
|
|
56
|
+
Object.assign(c, {
|
|
57
|
+
scope: nextScope,
|
|
58
|
+
sinks: parent.sinksOwner.sinks,
|
|
59
|
+
level: parent.sinksOwner.level,
|
|
60
|
+
sinksOwner: parent.sinksOwner,
|
|
61
|
+
});
|
|
62
|
+
return c;
|
|
63
|
+
}
|
|
64
|
+
child(segment) {
|
|
65
|
+
return CoreLogger.childOf(this, segment);
|
|
66
|
+
}
|
|
67
|
+
setLevel(level) {
|
|
68
|
+
this.sinksOwner.level = level;
|
|
69
|
+
this.level = level;
|
|
70
|
+
}
|
|
71
|
+
getLevel() {
|
|
72
|
+
return this.sinksOwner.level;
|
|
73
|
+
}
|
|
74
|
+
detachAll() {
|
|
75
|
+
this.sinksOwner.sinks.length = 0;
|
|
76
|
+
}
|
|
77
|
+
debug(msg, ctx) { this.write('debug', msg, ctx); }
|
|
78
|
+
info(msg, ctx) { this.write('info', msg, ctx); }
|
|
79
|
+
warn(msg, ctx) { this.write('warn', msg, ctx); }
|
|
80
|
+
error(msg, ctx) { this.write('error', msg, ctx); }
|
|
81
|
+
write(level, msg, ctx) {
|
|
82
|
+
if (exports.LOG_LEVEL_ORDER[level] < exports.LOG_LEVEL_ORDER[this.sinksOwner.level])
|
|
83
|
+
return;
|
|
84
|
+
const record = {
|
|
85
|
+
ts: new Date(),
|
|
86
|
+
level,
|
|
87
|
+
scope: this.scope,
|
|
88
|
+
msg,
|
|
89
|
+
ctx,
|
|
90
|
+
};
|
|
91
|
+
// Sinks must not throw — the helpers in ./sinks/* all wrap their
|
|
92
|
+
// I/O in try/catch. Be defensive anyway.
|
|
93
|
+
for (const s of this.sinksOwner.sinks) {
|
|
94
|
+
try {
|
|
95
|
+
s.write(record);
|
|
96
|
+
}
|
|
97
|
+
catch { /* logging must not break callers */ }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.CoreLogger = CoreLogger;
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
* core/v4/logger/sinks/fileSink.ts — Phase v4.1-1.3a
|
|
10
|
+
*
|
|
11
|
+
* Append log records to a file under `<aidenRoot>/logs/<name>.log`.
|
|
12
|
+
*
|
|
13
|
+
* One file per stream. Synchronous append (`appendFileSync`) so a line
|
|
14
|
+
* is durable even if the process exits mid-emission — same trade-off
|
|
15
|
+
* `core/v4/aidenLogger.ts` makes; small writes (< 100 / s during boot,
|
|
16
|
+
* occasional after) keep the cost negligible.
|
|
17
|
+
*
|
|
18
|
+
* Coarse rotation: when the file passes `MAX_BYTES`, rename to
|
|
19
|
+
* `<name>.log.1` (overwriting any previous rotation). One rotation is
|
|
20
|
+
* enough for diagnostics — older history isn't useful for debugging
|
|
21
|
+
* the current session and we'd rather not stash megabytes.
|
|
22
|
+
*/
|
|
23
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
24
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
25
|
+
};
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.FileSink = void 0;
|
|
28
|
+
const node_fs_1 = require("node:fs");
|
|
29
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
30
|
+
/** Rotate at 5 MB — comfortable for a long debugging session, never huge. */
|
|
31
|
+
const MAX_BYTES = 5 * 1024 * 1024;
|
|
32
|
+
class FileSink {
|
|
33
|
+
constructor(opts) {
|
|
34
|
+
this.dirReady = false;
|
|
35
|
+
this.dir = opts.dir;
|
|
36
|
+
this.filePath = node_path_1.default.join(opts.dir, `${opts.name}.log`);
|
|
37
|
+
}
|
|
38
|
+
write(record) {
|
|
39
|
+
if (!this.ensureDir())
|
|
40
|
+
return;
|
|
41
|
+
this.maybeRotate();
|
|
42
|
+
const line = this.format(record);
|
|
43
|
+
try {
|
|
44
|
+
(0, node_fs_1.appendFileSync)(this.filePath, line, 'utf8');
|
|
45
|
+
}
|
|
46
|
+
catch { /* disk full / permission denied — drop */ }
|
|
47
|
+
}
|
|
48
|
+
/** Make `<dir>` once. Repeated calls are cheap (cache hit). */
|
|
49
|
+
ensureDir() {
|
|
50
|
+
if (this.dirReady)
|
|
51
|
+
return true;
|
|
52
|
+
try {
|
|
53
|
+
(0, node_fs_1.mkdirSync)(this.dir, { recursive: true });
|
|
54
|
+
this.dirReady = true;
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* If the file is over MAX_BYTES, rename to `<name>.log.1` (overwriting
|
|
63
|
+
* the prior rotation if any). Best-effort — rotation failure isn't
|
|
64
|
+
* worth blocking the next write for.
|
|
65
|
+
*/
|
|
66
|
+
maybeRotate() {
|
|
67
|
+
let size = 0;
|
|
68
|
+
try {
|
|
69
|
+
size = (0, node_fs_1.statSync)(this.filePath).size;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (size <= MAX_BYTES)
|
|
75
|
+
return;
|
|
76
|
+
try {
|
|
77
|
+
(0, node_fs_1.renameSync)(this.filePath, `${this.filePath}.1`);
|
|
78
|
+
}
|
|
79
|
+
catch { /* ignore */ }
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Pretty single-line format — easy to grep, easy to tail. Structured
|
|
83
|
+
* fields are appended JSON-style after the message. Sinks that want
|
|
84
|
+
* NDJSON live elsewhere (e.g. `serve` mode would use a future
|
|
85
|
+
* JsonStdoutSink).
|
|
86
|
+
*
|
|
87
|
+
* 2026-05-08T01:32:44.681Z [info] [channels.telegram] Connected as @bot
|
|
88
|
+
* 2026-05-08T01:32:50.001Z [warn] [channels.telegram] Polling 409 {"streak":1}
|
|
89
|
+
*/
|
|
90
|
+
format(r) {
|
|
91
|
+
const scope = r.scope ? ` [${r.scope}]` : '';
|
|
92
|
+
const ctx = r.ctx && Object.keys(r.ctx).length > 0
|
|
93
|
+
? ' ' + safeJson(r.ctx)
|
|
94
|
+
: '';
|
|
95
|
+
return `${r.ts.toISOString()} [${r.level}]${scope} ${r.msg}${ctx}\n`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.FileSink = FileSink;
|
|
99
|
+
/**
|
|
100
|
+
* Defensive JSON.stringify — never throws; circular refs collapse to a
|
|
101
|
+
* placeholder so a misbehaving caller can't kill the log line.
|
|
102
|
+
*/
|
|
103
|
+
function safeJson(obj) {
|
|
104
|
+
try {
|
|
105
|
+
return JSON.stringify(obj);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return '"[unserializable ctx]"';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -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
|
+
* core/v4/logger/sinks/multiSink.ts — Phase v4.1-1.3a
|
|
10
|
+
*
|
|
11
|
+
* `Logger` already fans out to every attached sink, so MultiSink is
|
|
12
|
+
* thin sugar for the cases where a sink itself wants to delegate to
|
|
13
|
+
* several others (e.g. wrap a stderr-warn-only filter and a file
|
|
14
|
+
* everything filter behind a single object the caller treats as one
|
|
15
|
+
* sink). In 3a it's used by tests; production logger compositions go
|
|
16
|
+
* through `factory.createBootLogger` which picks sinks per mode.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.MultiSink = void 0;
|
|
20
|
+
class MultiSink {
|
|
21
|
+
constructor(children) {
|
|
22
|
+
this.children = children;
|
|
23
|
+
}
|
|
24
|
+
write(r) {
|
|
25
|
+
for (const c of this.children) {
|
|
26
|
+
try {
|
|
27
|
+
c.write(r);
|
|
28
|
+
}
|
|
29
|
+
catch { /* one sink's failure must not poison the others */ }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async close() {
|
|
33
|
+
for (const c of this.children) {
|
|
34
|
+
if (typeof c.close === 'function') {
|
|
35
|
+
try {
|
|
36
|
+
await c.close();
|
|
37
|
+
}
|
|
38
|
+
catch { /* ignore */ }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.MultiSink = MultiSink;
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
* core/v4/logger/sinks/nullSink.ts — Phase v4.1-1.3a
|
|
10
|
+
*
|
|
11
|
+
* Discard every record. The default for `'test'` mode so vitest output
|
|
12
|
+
* stays clean even when the code under test emits warnings — and the
|
|
13
|
+
* fallback when a module is constructed without a logger (the
|
|
14
|
+
* `noopLogger` factory in `../factory.ts` uses this).
|
|
15
|
+
*
|
|
16
|
+
* `MemorySink` is also here: it captures records into an in-process
|
|
17
|
+
* array so tests can assert on log output without any I/O. Two surfaces
|
|
18
|
+
* in one file because they share the "nothing leaves this process"
|
|
19
|
+
* property.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.MemorySink = exports.NullSink = void 0;
|
|
23
|
+
/** Drops every record. */
|
|
24
|
+
class NullSink {
|
|
25
|
+
write(_record) { }
|
|
26
|
+
}
|
|
27
|
+
exports.NullSink = NullSink;
|
|
28
|
+
/**
|
|
29
|
+
* Captures every record into `records`. Tests use this:
|
|
30
|
+
*
|
|
31
|
+
* const sink = new MemorySink();
|
|
32
|
+
* const log = new CoreLogger({ sinks: [sink] });
|
|
33
|
+
* log.info('hello');
|
|
34
|
+
* expect(sink.records[0].msg).toBe('hello');
|
|
35
|
+
*
|
|
36
|
+
* `clear()` resets between assertions; `findScope()` is a small
|
|
37
|
+
* convenience for "did the channels.telegram scope log anything?".
|
|
38
|
+
*/
|
|
39
|
+
class MemorySink {
|
|
40
|
+
constructor() {
|
|
41
|
+
this.records = [];
|
|
42
|
+
}
|
|
43
|
+
write(r) {
|
|
44
|
+
this.records.push(r);
|
|
45
|
+
}
|
|
46
|
+
clear() {
|
|
47
|
+
this.records.length = 0;
|
|
48
|
+
}
|
|
49
|
+
findScope(scope) {
|
|
50
|
+
return this.records.filter((r) => r.scope === scope || r.scope.startsWith(`${scope}.`));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.MemorySink = MemorySink;
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
* core/v4/logger/sinks/stdSink.ts — Phase v4.1-1.3a
|
|
10
|
+
*
|
|
11
|
+
* Stdout / stderr sinks. Two flavours:
|
|
12
|
+
*
|
|
13
|
+
* - StderrSink — pretty single-line format; used in cli-headless
|
|
14
|
+
* mode for warnings + errors so the user sees them
|
|
15
|
+
* without polluting stdout (which scripts may pipe).
|
|
16
|
+
* - StdoutJsonSink — NDJSON one-record-per-line; used in `serve` mode
|
|
17
|
+
* so log aggregators (systemd-journald, docker logs,
|
|
18
|
+
* Loki, etc.) get structured fields.
|
|
19
|
+
*
|
|
20
|
+
* No StdoutPrettySink in 3a — `cli-interactive` deliberately wires no
|
|
21
|
+
* stdout sink at all (REPL is sacred), and `cli-headless` uses the
|
|
22
|
+
* stderr flavour so stdout stays free for tool output piping. If a
|
|
23
|
+
* future mode needs colourful stdout (e.g. `aiden status` one-shot),
|
|
24
|
+
* add it then.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.StdoutJsonSink = exports.StderrSink = void 0;
|
|
28
|
+
const LEVEL_THRESHOLD = {
|
|
29
|
+
debug: 10, info: 20, warn: 30, error: 40,
|
|
30
|
+
};
|
|
31
|
+
class StderrSink {
|
|
32
|
+
constructor(opts = {}) {
|
|
33
|
+
this.minLevel = LEVEL_THRESHOLD[opts.minLevel ?? 'warn'];
|
|
34
|
+
}
|
|
35
|
+
write(r) {
|
|
36
|
+
if (LEVEL_THRESHOLD[r.level] < this.minLevel)
|
|
37
|
+
return;
|
|
38
|
+
const scope = r.scope ? ` [${r.scope}]` : '';
|
|
39
|
+
try {
|
|
40
|
+
process.stderr.write(`${r.ts.toISOString()} [${r.level}]${scope} ${r.msg}\n`);
|
|
41
|
+
}
|
|
42
|
+
catch { /* dropped */ }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.StderrSink = StderrSink;
|
|
46
|
+
/**
|
|
47
|
+
* NDJSON stdout for `serve` mode. One record per line, structured
|
|
48
|
+
* fields preserved. Aggregators love this; humans don't. Headless
|
|
49
|
+
* scripts that want pretty output use the file sink instead and tail
|
|
50
|
+
* the log file.
|
|
51
|
+
*/
|
|
52
|
+
class StdoutJsonSink {
|
|
53
|
+
constructor(opts = {}) {
|
|
54
|
+
this.minLevel = LEVEL_THRESHOLD[opts.minLevel ?? 'debug'];
|
|
55
|
+
}
|
|
56
|
+
write(r) {
|
|
57
|
+
if (LEVEL_THRESHOLD[r.level] < this.minLevel)
|
|
58
|
+
return;
|
|
59
|
+
const payload = {
|
|
60
|
+
ts: r.ts.toISOString(),
|
|
61
|
+
level: r.level,
|
|
62
|
+
scope: r.scope || undefined,
|
|
63
|
+
msg: r.msg,
|
|
64
|
+
};
|
|
65
|
+
if (r.ctx)
|
|
66
|
+
Object.assign(payload, r.ctx);
|
|
67
|
+
try {
|
|
68
|
+
process.stdout.write(safeJson(payload) + '\n');
|
|
69
|
+
}
|
|
70
|
+
catch { /* dropped */ }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.StdoutJsonSink = StdoutJsonSink;
|
|
74
|
+
function safeJson(obj) {
|
|
75
|
+
try {
|
|
76
|
+
return JSON.stringify(obj);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return '{"err":"unserializable"}';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
* core/v4/mcp/server/diagnostics.ts — Phase v4.1-mcp
|
|
10
|
+
*
|
|
11
|
+
* Build fingerprint + counts surfaced by the `aiden mcp status` CLI
|
|
12
|
+
* subcommand and the launch log line. The fingerprint follows the same
|
|
13
|
+
* convention the Telegram adapter set in v4.1-3.2 — a constant string
|
|
14
|
+
* the user can grep for to verify the build that's actually running
|
|
15
|
+
* matches the phase they expected.
|
|
16
|
+
*
|
|
17
|
+
* Bump on every shipped phase. Format: `v4.1-mcp[+suffix]`.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.AIDEN_MCP_BUILD = void 0;
|
|
21
|
+
exports.collectMcpDiagnostics = collectMcpDiagnostics;
|
|
22
|
+
const toolBridge_1 = require("./toolBridge");
|
|
23
|
+
/** Build fingerprint — bump per phase. Surfaced in `aiden mcp status`
|
|
24
|
+
* and the stderr launch line so it's grep-able from the spawning
|
|
25
|
+
* client's log stream. */
|
|
26
|
+
exports.AIDEN_MCP_BUILD = 'v4.1-mcp.2';
|
|
27
|
+
async function collectMcpDiagnostics(registry, loader, env = (0, toolBridge_1.readToolBridgeEnv)()) {
|
|
28
|
+
const exposed = (0, toolBridge_1.exposedToolNames)(registry, env);
|
|
29
|
+
const skills = await loader.list();
|
|
30
|
+
return {
|
|
31
|
+
build: exports.AIDEN_MCP_BUILD,
|
|
32
|
+
toolsTotal: registry.list().length,
|
|
33
|
+
toolsExposed: exposed.length,
|
|
34
|
+
skillsTotal: skills.length,
|
|
35
|
+
env: {
|
|
36
|
+
allowDestructive: env.allowDestructive,
|
|
37
|
+
allowlist: env.allowlist ? [...env.allowlist].sort() : null,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
* core/v4/mcp/server/skillBridge.ts — Phase v4.1-mcp
|
|
10
|
+
*
|
|
11
|
+
* Expose Aiden's loaded skills as MCP resources. Skills are inert
|
|
12
|
+
* markdown playbooks (`SKILL.md` + frontmatter), perfect candidates for
|
|
13
|
+
* MCP's read-only resource surface.
|
|
14
|
+
*
|
|
15
|
+
* URI scheme — `aiden-skill://<name>`. We deliberately namespaced this
|
|
16
|
+
* (rather than the bare `skill://`) so when a client connects to multiple
|
|
17
|
+
* agent MCP servers, resource URIs do not collide. The `name` segment is
|
|
18
|
+
* the SKILL.md frontmatter `name` field, which is already the lookup key
|
|
19
|
+
* used by the rest of the runtime (`SkillLoader.load(name)`).
|
|
20
|
+
*
|
|
21
|
+
* The bridge is intentionally read-only:
|
|
22
|
+
* - `resources/list` enumerates every loaded skill
|
|
23
|
+
* - `resources/read` returns the raw markdown (frontmatter + body)
|
|
24
|
+
*
|
|
25
|
+
* Mutations live behind the `skill_manage` tool; an MCP client that
|
|
26
|
+
* needs to install or modify a skill must go through that tool path so
|
|
27
|
+
* the same trust-level checks the REPL uses also apply remotely.
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.skillUri = skillUri;
|
|
31
|
+
exports.parseSkillUri = parseSkillUri;
|
|
32
|
+
exports.summaryToResource = summaryToResource;
|
|
33
|
+
exports.buildResourcesList = buildResourcesList;
|
|
34
|
+
exports.readSkillResource = readSkillResource;
|
|
35
|
+
const node_fs_1 = require("node:fs");
|
|
36
|
+
const URI_SCHEME = 'aiden-skill';
|
|
37
|
+
const URI_PREFIX = `${URI_SCHEME}://`;
|
|
38
|
+
function skillUri(name) {
|
|
39
|
+
return `${URI_PREFIX}${encodeURIComponent(name)}`;
|
|
40
|
+
}
|
|
41
|
+
/** Pull the `<name>` segment back out of an `aiden-skill://<name>` URI.
|
|
42
|
+
* Returns null on malformed input — the read handler reports `isError`
|
|
43
|
+
* in that case rather than crashing the protocol. */
|
|
44
|
+
function parseSkillUri(uri) {
|
|
45
|
+
if (!uri.startsWith(URI_PREFIX))
|
|
46
|
+
return null;
|
|
47
|
+
const raw = uri.slice(URI_PREFIX.length);
|
|
48
|
+
if (!raw)
|
|
49
|
+
return null;
|
|
50
|
+
try {
|
|
51
|
+
return decodeURIComponent(raw);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function summaryToResource(s) {
|
|
58
|
+
return {
|
|
59
|
+
uri: skillUri(s.name),
|
|
60
|
+
name: s.name,
|
|
61
|
+
description: s.description,
|
|
62
|
+
mimeType: 'text/markdown',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Build the resources array advertised on `resources/list`. Walks the
|
|
67
|
+
* SkillLoader cache (already warmed at boot in the runtime) so this is
|
|
68
|
+
* just an in-memory map.
|
|
69
|
+
*/
|
|
70
|
+
async function buildResourcesList(loader) {
|
|
71
|
+
const list = await loader.list();
|
|
72
|
+
return list.map(summaryToResource);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* `resources/read` handler. Returns the raw SKILL.md content for the
|
|
76
|
+
* named skill. Throws when the URI is malformed or the skill is unknown
|
|
77
|
+
* — the stdio-server layer maps those to JSON-RPC errors.
|
|
78
|
+
*/
|
|
79
|
+
async function readSkillResource(loader, uri) {
|
|
80
|
+
const name = parseSkillUri(uri);
|
|
81
|
+
if (name === null) {
|
|
82
|
+
throw new Error(`Malformed resource URI (expected ${URI_PREFIX}<name>): ${uri}`);
|
|
83
|
+
}
|
|
84
|
+
const skill = await loader.load(name);
|
|
85
|
+
if (!skill) {
|
|
86
|
+
throw new Error(`Skill not found: ${name}`);
|
|
87
|
+
}
|
|
88
|
+
// SKILL.md content lives on disk. The loader exposes `filePath`; read
|
|
89
|
+
// the raw bytes so the client gets frontmatter + body verbatim. (We
|
|
90
|
+
// could reconstruct from `frontmatter` + `content`, but raw avoids
|
|
91
|
+
// round-trip drift if the loader ever rewrites YAML on parse.)
|
|
92
|
+
const text = await node_fs_1.promises.readFile(skill.filePath, 'utf-8');
|
|
93
|
+
return { uri, mimeType: 'text/markdown', text };
|
|
94
|
+
}
|