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.
Files changed (113) hide show
  1. package/README.md +19 -11
  2. package/config/hardware.json +2 -2
  3. package/dist/api/server.js +50 -52
  4. package/dist/cli/v4/aidenCLI.js +424 -7
  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 +102 -1
  28. package/dist/cli/v4/doctorLiveness.js +329 -0
  29. package/dist/cli/v4/envSources.js +105 -0
  30. package/dist/cli/v4/ghostMatch.js +74 -0
  31. package/dist/cli/v4/historyStore.js +163 -0
  32. package/dist/cli/v4/pasteCompression.js +124 -0
  33. package/dist/cli/v4/pasteIntercept.js +203 -0
  34. package/dist/cli/v4/replyRenderer.js +209 -0
  35. package/dist/cli/v4/resizeGuard.js +92 -0
  36. package/dist/cli/v4/shellInterpolation.js +139 -0
  37. package/dist/cli/v4/skinEngine.js +21 -1
  38. package/dist/cli/v4/streamingPrefix.js +121 -0
  39. package/dist/cli/v4/syntaxHighlight.js +345 -0
  40. package/dist/cli/v4/table.js +216 -0
  41. package/dist/cli/v4/themeDetect.js +81 -0
  42. package/dist/cli/v4/uiBuild.js +74 -0
  43. package/dist/cli/v4/voiceCli.js +113 -0
  44. package/dist/cli/v4/voicePromptApi.js +196 -0
  45. package/dist/core/channels/discord.js +16 -10
  46. package/dist/core/channels/email.js +13 -9
  47. package/dist/core/channels/imessage.js +13 -9
  48. package/dist/core/channels/manager.js +25 -7
  49. package/dist/core/channels/pdf-extract.js +180 -0
  50. package/dist/core/channels/photo-vision.js +157 -0
  51. package/dist/core/channels/signal.js +11 -7
  52. package/dist/core/channels/slack.js +13 -10
  53. package/dist/core/channels/telegram-commands.js +154 -0
  54. package/dist/core/channels/telegram-groups.js +198 -0
  55. package/dist/core/channels/telegram-rate-limit.js +124 -0
  56. package/dist/core/channels/telegram.js +1980 -0
  57. package/dist/core/channels/twilio.js +11 -7
  58. package/dist/core/channels/webhook.js +9 -5
  59. package/dist/core/channels/whatsapp.js +15 -11
  60. package/dist/core/channels/whisper-transcribe.js +163 -0
  61. package/dist/core/cronManager.js +33 -294
  62. package/dist/core/gateway.js +29 -8
  63. package/dist/core/playwrightBridge.js +90 -0
  64. package/dist/core/v4/aidenAgent.js +35 -0
  65. package/dist/core/v4/auxiliaryClient.js +2 -2
  66. package/dist/core/v4/cron/atomicWrite.js +18 -4
  67. package/dist/core/v4/cron/cronExecute.js +300 -0
  68. package/dist/core/v4/cron/cronManager.js +502 -0
  69. package/dist/core/v4/cron/cronState.js +314 -0
  70. package/dist/core/v4/cron/cronTick.js +90 -0
  71. package/dist/core/v4/cron/diagnostics.js +104 -0
  72. package/dist/core/v4/cron/graceWindow.js +79 -0
  73. package/dist/core/v4/logger/factory.js +110 -0
  74. package/dist/core/v4/logger/index.js +22 -0
  75. package/dist/core/v4/logger/logger.js +101 -0
  76. package/dist/core/v4/logger/sinks/fileSink.js +110 -0
  77. package/dist/core/v4/logger/sinks/multiSink.js +43 -0
  78. package/dist/core/v4/logger/sinks/nullSink.js +53 -0
  79. package/dist/core/v4/logger/sinks/stdSink.js +81 -0
  80. package/dist/core/v4/mcp/server/diagnostics.js +40 -0
  81. package/dist/core/v4/mcp/server/skillBridge.js +94 -0
  82. package/dist/core/v4/mcp/server/stdioServer.js +119 -0
  83. package/dist/core/v4/mcp/server/toolBridge.js +168 -0
  84. package/dist/core/v4/platformPaths.js +105 -0
  85. package/dist/core/v4/providerFallback.js +25 -0
  86. package/dist/core/v4/skillLoader.js +21 -5
  87. package/dist/core/v4/skillMining/candidateStore.js +164 -0
  88. package/dist/core/v4/skillMining/extractorPrompt.js +118 -0
  89. package/dist/core/v4/skillMining/proposalBuilder.js +140 -0
  90. package/dist/core/v4/skillMining/skillMiner.js +191 -0
  91. package/dist/core/v4/skillMining/traceFingerprint.js +51 -0
  92. package/dist/core/v4/subagent/budget.js +76 -0
  93. package/dist/core/v4/subagent/diagnostics.js +22 -0
  94. package/dist/core/v4/subagent/fanout.js +216 -0
  95. package/dist/core/v4/subagent/merger.js +148 -0
  96. package/dist/core/v4/subagent/providerRotation.js +54 -0
  97. package/dist/core/v4/voice/audioStream.js +373 -0
  98. package/dist/core/v4/voice/cliVoice.js +393 -0
  99. package/dist/core/v4/voice/diagnostics.js +66 -0
  100. package/dist/core/v4/voice/ttsStream.js +193 -0
  101. package/dist/core/version.js +1 -1
  102. package/dist/core/visionAnalyze.js +291 -90
  103. package/dist/core/voice/audio.js +61 -5
  104. package/dist/core/voice/audioBackend.js +134 -0
  105. package/dist/core/voice/stt.js +61 -6
  106. package/dist/core/voice/tts.js +19 -3
  107. package/dist/moat/dangerousPatterns.js +1 -1
  108. package/dist/providers/v4/codexResponsesAdapter.js +7 -2
  109. package/dist/providers/v4/errors.js +51 -1
  110. package/dist/providers/v4/ollamaPromptToolsAdapter.js +9 -2
  111. package/dist/tools/v4/index.js +32 -1
  112. package/dist/tools/v4/subagent/subagentFanout.js +190 -0
  113. package/package.json +11 -2
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ // ============================================================
3
+ // DevOS — Autonomous AI Execution System
4
+ // Copyright (c) 2026 Shiva Deore. All rights reserved.
5
+ // ============================================================
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.TelegramGroupStore = void 0;
11
+ // core/channels/telegram-groups.ts — Phase v4.1-2.
12
+ //
13
+ // Persistent per-group state for the Telegram channel:
14
+ // - paused — admin /pause stops the bot from replying
15
+ // - allowedUsers — opt-in restriction set by /allowusers
16
+ // - title — group display name (cached for /channel
17
+ // telegram groups list — Telegram's getChat
18
+ // costs an HTTP call per query)
19
+ // - lastMessageAt — wall-clock of the last seen inbound msg
20
+ // - lastAdminAction — when an admin last touched the state
21
+ // - firstSeenAt — when the bot first observed this group
22
+ //
23
+ // State lives at `<aidenRoot>/state/telegram-groups.json`. Atomic
24
+ // writes (tmp → rename) keep the file consistent across process
25
+ // crashes. Loaded once at adapter start; mutations debounce flushes
26
+ // at 1 s so a burst of admin commands doesn't hammer the disk.
27
+ //
28
+ // All diagnostics route through the v4.1-1.3a Logger contract.
29
+ // No console.* anywhere in this module.
30
+ const node_fs_1 = require("node:fs");
31
+ const node_fs_2 = require("node:fs");
32
+ const node_path_1 = __importDefault(require("node:path"));
33
+ const logger_1 = require("../v4/logger");
34
+ /**
35
+ * In-memory + disk-backed store of per-group state. Read paths are
36
+ * always synchronous reads of the in-memory map; mutations schedule
37
+ * a debounced flush so a burst of admin commands collapses to one
38
+ * write.
39
+ */
40
+ class TelegramGroupStore {
41
+ constructor(opts) {
42
+ this.groups = new Map();
43
+ this.flushTimer = null;
44
+ this.loaded = false;
45
+ this.stateDir = node_path_1.default.join(opts.paths.root, 'state');
46
+ this.statePath = node_path_1.default.join(this.stateDir, 'telegram-groups.json');
47
+ this.log = opts.logger ?? (0, logger_1.noopLogger)();
48
+ this.flushDebounceMs = opts.flushDebounceMs ?? 1000;
49
+ }
50
+ /**
51
+ * Synchronously load on first call. Subsequent calls are no-ops.
52
+ * Failure to read is treated as "fresh state" — better than crashing
53
+ * the adapter on a malformed file.
54
+ */
55
+ async load() {
56
+ if (this.loaded)
57
+ return;
58
+ this.loaded = true;
59
+ if (!(0, node_fs_2.existsSync)(this.statePath))
60
+ return;
61
+ try {
62
+ const raw = await node_fs_1.promises.readFile(this.statePath, 'utf8');
63
+ const parsed = JSON.parse(raw);
64
+ if (parsed?.version !== 1 || !parsed.groups)
65
+ return;
66
+ for (const [id, g] of Object.entries(parsed.groups)) {
67
+ if (g && typeof g === 'object' && 'groupId' in g) {
68
+ this.groups.set(id, normalizeOnLoad(g));
69
+ }
70
+ }
71
+ this.log.info(`loaded ${this.groups.size} group(s)`);
72
+ }
73
+ catch (err) {
74
+ this.log.warn(`could not load state: ${err?.message ?? err}`);
75
+ }
76
+ }
77
+ /** True when this group is allowed to interact with the bot. */
78
+ isPaused(groupId) {
79
+ return this.groups.get(groupId)?.paused === true;
80
+ }
81
+ /**
82
+ * When an allowed-users list is set on a group, only those users may
83
+ * converse. Empty list (the default) → everyone in the group is OK.
84
+ * Returns true when the user is allowed.
85
+ */
86
+ userIsAllowed(groupId, userId) {
87
+ const g = this.groups.get(groupId);
88
+ if (!g || g.allowedUsers.length === 0)
89
+ return true;
90
+ return g.allowedUsers.includes(userId);
91
+ }
92
+ /** Public accessor for /channel telegram groups list. */
93
+ list() {
94
+ return Array.from(this.groups.values()).sort((a, b) => (b.lastMessageAt ?? 0) - (a.lastMessageAt ?? 0));
95
+ }
96
+ get(groupId) {
97
+ return this.groups.get(groupId);
98
+ }
99
+ /** Record an inbound observation — bumps lastMessageAt + caches title. */
100
+ observeMessage(groupId, opts) {
101
+ const existing = this.groups.get(groupId);
102
+ const now = Date.now();
103
+ if (existing) {
104
+ existing.lastMessageAt = now;
105
+ if (opts.title && existing.title !== opts.title)
106
+ existing.title = opts.title;
107
+ }
108
+ else {
109
+ this.groups.set(groupId, {
110
+ groupId,
111
+ title: opts.title,
112
+ paused: false,
113
+ allowedUsers: [],
114
+ firstSeenAt: now,
115
+ lastMessageAt: now,
116
+ });
117
+ }
118
+ this.scheduleFlush();
119
+ }
120
+ setPaused(groupId, paused, actor) {
121
+ const g = this.ensureGroup(groupId);
122
+ g.paused = paused;
123
+ g.lastAdminAction = { actor, cmd: paused ? 'pause' : 'resume', at: Date.now() };
124
+ this.scheduleFlush();
125
+ }
126
+ setAllowedUsers(groupId, userIds, actor) {
127
+ const g = this.ensureGroup(groupId);
128
+ g.allowedUsers = [...new Set(userIds.map(s => s.trim()).filter(Boolean))];
129
+ g.lastAdminAction = { actor, cmd: 'allowusers', at: Date.now() };
130
+ this.scheduleFlush();
131
+ }
132
+ recordAdminAction(groupId, cmd, actor) {
133
+ const g = this.ensureGroup(groupId);
134
+ g.lastAdminAction = { actor, cmd, at: Date.now() };
135
+ this.scheduleFlush();
136
+ }
137
+ /** Force-flush + clear debounce timer (called on adapter teardown). */
138
+ async flushNow() {
139
+ if (this.flushTimer) {
140
+ clearTimeout(this.flushTimer);
141
+ this.flushTimer = null;
142
+ }
143
+ await this.writeFile();
144
+ }
145
+ // ── Internals ─────────────────────────────────────────────────
146
+ ensureGroup(groupId) {
147
+ let g = this.groups.get(groupId);
148
+ if (!g) {
149
+ g = {
150
+ groupId,
151
+ paused: false,
152
+ allowedUsers: [],
153
+ firstSeenAt: Date.now(),
154
+ };
155
+ this.groups.set(groupId, g);
156
+ }
157
+ return g;
158
+ }
159
+ scheduleFlush() {
160
+ if (this.flushTimer)
161
+ return;
162
+ this.flushTimer = setTimeout(() => {
163
+ this.flushTimer = null;
164
+ this.writeFile().catch((err) => this.log.warn(`flush failed: ${err?.message ?? err}`));
165
+ }, this.flushDebounceMs);
166
+ if (typeof this.flushTimer.unref === 'function')
167
+ this.flushTimer.unref();
168
+ }
169
+ async writeFile() {
170
+ const payload = {
171
+ version: 1,
172
+ groups: Object.fromEntries(this.groups),
173
+ };
174
+ await node_fs_1.promises.mkdir(this.stateDir, { recursive: true });
175
+ const tmp = `${this.statePath}.${process.pid}.tmp`;
176
+ await node_fs_1.promises.writeFile(tmp, JSON.stringify(payload, null, 2), 'utf8');
177
+ await node_fs_1.promises.rename(tmp, this.statePath);
178
+ }
179
+ }
180
+ exports.TelegramGroupStore = TelegramGroupStore;
181
+ /**
182
+ * Defensive load-time normaliser — older state files may be missing
183
+ * fields we've since added; fall back to safe defaults instead of
184
+ * propagating undefined into the rest of the adapter.
185
+ */
186
+ function normalizeOnLoad(raw) {
187
+ return {
188
+ groupId: String(raw.groupId),
189
+ title: typeof raw.title === 'string' ? raw.title : undefined,
190
+ paused: raw.paused === true,
191
+ allowedUsers: Array.isArray(raw.allowedUsers) ? raw.allowedUsers.map(String) : [],
192
+ firstSeenAt: typeof raw.firstSeenAt === 'number' ? raw.firstSeenAt : Date.now(),
193
+ lastMessageAt: typeof raw.lastMessageAt === 'number' ? raw.lastMessageAt : undefined,
194
+ lastAdminAction: raw.lastAdminAction && typeof raw.lastAdminAction === 'object'
195
+ ? raw.lastAdminAction
196
+ : undefined,
197
+ };
198
+ }
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ // ============================================================
3
+ // DevOS — Autonomous AI Execution System
4
+ // Copyright (c) 2026 Shiva Deore. All rights reserved.
5
+ // ============================================================
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.TelegramRateLimiter = void 0;
8
+ // core/channels/telegram-rate-limit.ts — Phase v4.1-2.
9
+ //
10
+ // Sliding-window per-user rate limiter for the Telegram channel.
11
+ //
12
+ // Design:
13
+ // - In-memory only — resets on aiden restart. The threat model is
14
+ // "stop a single chatter from burning the bot owner's quota in
15
+ // one sitting"; a restart-bypass that costs the abuser an entire
16
+ // restart-window's worth of messages is acceptable.
17
+ // - Single user-id keyspace across all chats — a spammer can't dodge
18
+ // by hopping between groups.
19
+ // - 1-minute sliding window, default 5 messages. Both knobs are
20
+ // configurable via env (`TELEGRAM_USER_RATE_LIMIT`,
21
+ // `TELEGRAM_USER_RATE_WINDOW_MS`) so an op who hosts the bot for a
22
+ // bigger community can bump them without code changes.
23
+ // - `shouldThrottle(userId)` is the only consumer-facing method —
24
+ // records the access *and* reports whether the caller should drop
25
+ // the message. One lookup, one mutation, one decision.
26
+ // - A coalescing sweeper trims stale buckets every 5 minutes so an
27
+ // adversary can't blow the heap by hammering with fresh user ids.
28
+ //
29
+ // `TelegramRateLimiter` accepts an injected logger from the unified
30
+ // `Logger` contract (Phase v4.1-1.3a) — diagnostics file-only, REPL
31
+ // stays sacred. No console.* anywhere in this module.
32
+ const logger_1 = require("../v4/logger");
33
+ const DEFAULT_LIMIT = 5;
34
+ const DEFAULT_WINDOW_MS = 60000;
35
+ const SWEEP_INTERVAL_MS = 5 * 60 * 1000; // prune buckets idle > sweep+window
36
+ class TelegramRateLimiter {
37
+ constructor(opts = {}) {
38
+ this.buckets = new Map();
39
+ this.sweepTimer = null;
40
+ this.limit = readPositiveInt(process.env.TELEGRAM_USER_RATE_LIMIT, opts.limit ?? DEFAULT_LIMIT);
41
+ this.windowMs = readPositiveInt(process.env.TELEGRAM_USER_RATE_WINDOW_MS, opts.windowMs ?? DEFAULT_WINDOW_MS);
42
+ this.now = opts.now ?? Date.now;
43
+ this.log = opts.logger ?? (0, logger_1.noopLogger)();
44
+ }
45
+ /**
46
+ * Record an attempted message + return true when the caller should
47
+ * drop it. The bucket is updated on every call (even ones that get
48
+ * throttled), so a sustained over-limit user stays throttled until
49
+ * the oldest message in their window ages out.
50
+ */
51
+ shouldThrottle(userId) {
52
+ if (!userId)
53
+ return false;
54
+ const cutoff = this.now() - this.windowMs;
55
+ const bucket = this.buckets.get(userId) ?? [];
56
+ // Drop expired entries from the front (oldest-first list).
57
+ while (bucket.length > 0 && bucket[0] <= cutoff)
58
+ bucket.shift();
59
+ if (bucket.length >= this.limit) {
60
+ // Don't append on throttle so the window can age out cleanly —
61
+ // appending here would reset the user's clock every time they
62
+ // try to spam, locking them in indefinitely.
63
+ this.buckets.set(userId, bucket);
64
+ this.log.warn(`rate-limited`, { userId, count: bucket.length, limit: this.limit });
65
+ return true;
66
+ }
67
+ bucket.push(this.now());
68
+ this.buckets.set(userId, bucket);
69
+ this.scheduleSweep();
70
+ return false;
71
+ }
72
+ /** Test / diagnostic accessor — current bucket size for a user. */
73
+ getCount(userId) {
74
+ const bucket = this.buckets.get(userId);
75
+ if (!bucket)
76
+ return 0;
77
+ const cutoff = this.now() - this.windowMs;
78
+ return bucket.filter((t) => t > cutoff).length;
79
+ }
80
+ /** Stop the sweep timer (called on adapter teardown). */
81
+ dispose() {
82
+ if (this.sweepTimer) {
83
+ clearInterval(this.sweepTimer);
84
+ this.sweepTimer = null;
85
+ }
86
+ }
87
+ // ── Internal: prune fully-expired buckets so memory doesn't drift. ──
88
+ scheduleSweep() {
89
+ if (this.sweepTimer)
90
+ return;
91
+ this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);
92
+ // Don't keep the process alive just for the sweeper.
93
+ if (typeof this.sweepTimer.unref === 'function')
94
+ this.sweepTimer.unref();
95
+ }
96
+ sweep() {
97
+ const cutoff = this.now() - this.windowMs;
98
+ let pruned = 0;
99
+ for (const [userId, bucket] of this.buckets) {
100
+ const filtered = bucket.filter((t) => t > cutoff);
101
+ if (filtered.length === 0) {
102
+ this.buckets.delete(userId);
103
+ pruned += 1;
104
+ }
105
+ else {
106
+ this.buckets.set(userId, filtered);
107
+ }
108
+ }
109
+ if (pruned > 0)
110
+ this.log.debug(`swept ${pruned} stale buckets`);
111
+ }
112
+ }
113
+ exports.TelegramRateLimiter = TelegramRateLimiter;
114
+ /**
115
+ * Parse a positive integer env var with a fallback. Negative / NaN /
116
+ * empty values fall back to the default — better than crashing the
117
+ * adapter because someone fat-fingered an env var.
118
+ */
119
+ function readPositiveInt(envValue, fallback) {
120
+ if (typeof envValue !== 'string' || envValue.trim() === '')
121
+ return fallback;
122
+ const n = Number.parseInt(envValue, 10);
123
+ return Number.isFinite(n) && n > 0 ? n : fallback;
124
+ }