aiden-runtime 4.0.2 → 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.
Files changed (108) hide show
  1. package/README.md +11 -7
  2. package/config/hardware.json +2 -2
  3. package/dist/api/server.js +50 -52
  4. package/dist/cli/v4/aidenCLI.js +421 -5
  5. package/dist/cli/v4/aidenPrompt.js +317 -0
  6. package/dist/cli/v4/box.js +105 -39
  7. package/dist/cli/v4/callbacks.js +39 -6
  8. package/dist/cli/v4/chatSession.js +256 -55
  9. package/dist/cli/v4/citationFooter.js +97 -0
  10. package/dist/cli/v4/commands/channel.js +656 -0
  11. package/dist/cli/v4/commands/clear.js +1 -1
  12. package/dist/cli/v4/commands/compress.js +1 -1
  13. package/dist/cli/v4/commands/cron.js +44 -16
  14. package/dist/cli/v4/commands/fanout.js +236 -0
  15. package/dist/cli/v4/commands/help.js +15 -4
  16. package/dist/cli/v4/commands/history.js +84 -0
  17. package/dist/cli/v4/commands/index.js +16 -1
  18. package/dist/cli/v4/commands/mcp.js +358 -0
  19. package/dist/cli/v4/commands/show.js +43 -0
  20. package/dist/cli/v4/commands/skills.js +169 -4
  21. package/dist/cli/v4/commands/status.js +84 -0
  22. package/dist/cli/v4/commands/subagent.js +78 -0
  23. package/dist/cli/v4/commands/verbose.js +1 -1
  24. package/dist/cli/v4/commands/voice.js +218 -0
  25. package/dist/cli/v4/cronCli.js +103 -0
  26. package/dist/cli/v4/display.js +297 -13
  27. package/dist/cli/v4/doctor.js +41 -0
  28. package/dist/cli/v4/envSources.js +105 -0
  29. package/dist/cli/v4/ghostMatch.js +74 -0
  30. package/dist/cli/v4/historyStore.js +163 -0
  31. package/dist/cli/v4/pasteCompression.js +124 -0
  32. package/dist/cli/v4/pasteIntercept.js +203 -0
  33. package/dist/cli/v4/replyRenderer.js +209 -0
  34. package/dist/cli/v4/resizeGuard.js +92 -0
  35. package/dist/cli/v4/shellInterpolation.js +139 -0
  36. package/dist/cli/v4/skinEngine.js +21 -1
  37. package/dist/cli/v4/streamingPrefix.js +121 -0
  38. package/dist/cli/v4/syntaxHighlight.js +345 -0
  39. package/dist/cli/v4/table.js +216 -0
  40. package/dist/cli/v4/themeDetect.js +81 -0
  41. package/dist/cli/v4/uiBuild.js +74 -0
  42. package/dist/cli/v4/voiceCli.js +113 -0
  43. package/dist/cli/v4/voicePromptApi.js +196 -0
  44. package/dist/core/channels/discord.js +16 -10
  45. package/dist/core/channels/email.js +13 -9
  46. package/dist/core/channels/imessage.js +13 -9
  47. package/dist/core/channels/manager.js +25 -7
  48. package/dist/core/channels/pdf-extract.js +180 -0
  49. package/dist/core/channels/photo-vision.js +157 -0
  50. package/dist/core/channels/signal.js +11 -7
  51. package/dist/core/channels/slack.js +13 -10
  52. package/dist/core/channels/telegram-commands.js +154 -0
  53. package/dist/core/channels/telegram-groups.js +198 -0
  54. package/dist/core/channels/telegram-rate-limit.js +124 -0
  55. package/dist/core/channels/telegram.js +1980 -0
  56. package/dist/core/channels/twilio.js +11 -7
  57. package/dist/core/channels/webhook.js +9 -5
  58. package/dist/core/channels/whatsapp.js +15 -11
  59. package/dist/core/channels/whisper-transcribe.js +163 -0
  60. package/dist/core/cronManager.js +33 -294
  61. package/dist/core/gateway.js +29 -8
  62. package/dist/core/playwrightBridge.js +90 -0
  63. package/dist/core/v4/aidenAgent.js +35 -0
  64. package/dist/core/v4/auxiliaryClient.js +2 -2
  65. package/dist/core/v4/cron/atomicWrite.js +18 -4
  66. package/dist/core/v4/cron/cronExecute.js +300 -0
  67. package/dist/core/v4/cron/cronManager.js +502 -0
  68. package/dist/core/v4/cron/cronState.js +314 -0
  69. package/dist/core/v4/cron/cronTick.js +90 -0
  70. package/dist/core/v4/cron/diagnostics.js +104 -0
  71. package/dist/core/v4/cron/graceWindow.js +79 -0
  72. package/dist/core/v4/logger/factory.js +110 -0
  73. package/dist/core/v4/logger/index.js +22 -0
  74. package/dist/core/v4/logger/logger.js +101 -0
  75. package/dist/core/v4/logger/sinks/fileSink.js +110 -0
  76. package/dist/core/v4/logger/sinks/multiSink.js +43 -0
  77. package/dist/core/v4/logger/sinks/nullSink.js +53 -0
  78. package/dist/core/v4/logger/sinks/stdSink.js +81 -0
  79. package/dist/core/v4/mcp/server/diagnostics.js +40 -0
  80. package/dist/core/v4/mcp/server/skillBridge.js +94 -0
  81. package/dist/core/v4/mcp/server/stdioServer.js +119 -0
  82. package/dist/core/v4/mcp/server/toolBridge.js +168 -0
  83. package/dist/core/v4/platformPaths.js +105 -0
  84. package/dist/core/v4/providerFallback.js +25 -0
  85. package/dist/core/v4/skillLoader.js +21 -5
  86. package/dist/core/v4/skillMining/candidateStore.js +164 -0
  87. package/dist/core/v4/skillMining/extractorPrompt.js +111 -0
  88. package/dist/core/v4/skillMining/proposalBuilder.js +139 -0
  89. package/dist/core/v4/skillMining/skillMiner.js +191 -0
  90. package/dist/core/v4/skillMining/traceFingerprint.js +51 -0
  91. package/dist/core/v4/subagent/budget.js +76 -0
  92. package/dist/core/v4/subagent/diagnostics.js +22 -0
  93. package/dist/core/v4/subagent/fanout.js +216 -0
  94. package/dist/core/v4/subagent/merger.js +148 -0
  95. package/dist/core/v4/subagent/providerRotation.js +54 -0
  96. package/dist/core/v4/voice/audioStream.js +373 -0
  97. package/dist/core/v4/voice/cliVoice.js +393 -0
  98. package/dist/core/v4/voice/diagnostics.js +66 -0
  99. package/dist/core/v4/voice/ttsStream.js +193 -0
  100. package/dist/core/version.js +1 -1
  101. package/dist/core/visionAnalyze.js +291 -90
  102. package/dist/core/voice/audio.js +61 -5
  103. package/dist/core/voice/audioBackend.js +134 -0
  104. package/dist/core/voice/stt.js +61 -6
  105. package/dist/core/voice/tts.js +19 -3
  106. package/dist/tools/v4/index.js +32 -1
  107. package/dist/tools/v4/subagent/subagentFanout.js +166 -0
  108. 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
+ }