aiden-runtime 4.1.4 → 4.1.5

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.
@@ -0,0 +1,257 @@
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/loopTrace.ts — Phase v4.1.5+ Path A.
10
+ *
11
+ * Env-var-gated per-turn audit log for diagnosing tool-call loops
12
+ * (the user-reported "30+ skill_view calls in 0ms each" failure mode
13
+ * from v4.1.5 visual smoke). Default OFF — adds zero overhead when
14
+ * `AIDEN_DEBUG_LOOP !== '1'`.
15
+ *
16
+ * When enabled, captures:
17
+ * - Full tool-call sequence (name, args, timing) for the turn
18
+ * - Assembled system prompt at turn start
19
+ * - MEMORY.md + USER.md content hashes (sha256, first 12 hex)
20
+ * - Recent skills list (last 10 `skill_view` calls)
21
+ * - Conversation history snapshot (last 5 turns)
22
+ *
23
+ * Auto-writes to `<paths.logsDir>/loop-trace-{ISO-timestamp}.json` at
24
+ * turn end IF the turn triggered loop detection (10+ tool calls OR
25
+ * 5+ consecutive same-name). Quiet otherwise — non-loop turns don't
26
+ * spam the logs directory.
27
+ *
28
+ * A/B harness reproduction lesson: the original 30+ loop the user
29
+ * observed could not be reproduced with a fresh `[system, user]`
30
+ * history (see `scripts/smoke-prompt-bias-ab.ts` results). The loop
31
+ * is either stochastic gpt-5.5 behaviour, history-poisoning from
32
+ * prior turns, or MEMORY/USER context-specific. This logger captures
33
+ * the EXACT context next time the loop happens in live use so we
34
+ * can A/B against the real failure case.
35
+ *
36
+ * Pure module — no Display dependency, no event-emitter, no side
37
+ * effects beyond the gated file write. Safe to import from anywhere.
38
+ */
39
+ var __importDefault = (this && this.__importDefault) || function (mod) {
40
+ return (mod && mod.__esModule) ? mod : { "default": mod };
41
+ };
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.LoopTracer = void 0;
44
+ const node_fs_1 = require("node:fs");
45
+ const node_path_1 = __importDefault(require("node:path"));
46
+ const node_crypto_1 = __importDefault(require("node:crypto"));
47
+ // ── Tracer ──────────────────────────────────────────────────────────────────
48
+ const ARGS_CAP = 200;
49
+ const HISTORY_TAIL_DEPTH = 5;
50
+ const RECENT_SKILLS_MAX = 10;
51
+ /**
52
+ * Tracks tool calls for one turn. Construct fresh per turn (the
53
+ * counters and recent-skills list are turn-scoped). Idempotent
54
+ * `finalize()` — multiple calls produce one file at most.
55
+ */
56
+ class LoopTracer {
57
+ constructor(rawOpts) {
58
+ this.systemPrompt = '';
59
+ this.history = [];
60
+ this.toolStart = new Map();
61
+ this.sequence = [];
62
+ this.lastName = null;
63
+ this.consecSame = 0;
64
+ this.maxConsec = 0;
65
+ this.warnFired = false;
66
+ this.finalized = false;
67
+ this.recentSkills = [];
68
+ this.enabled = rawOpts.enabled ?? (process.env.AIDEN_DEBUG_LOOP === '1');
69
+ this.opts = {
70
+ paths: rawOpts.paths,
71
+ providerId: rawOpts.providerId,
72
+ modelId: rawOpts.modelId,
73
+ enabled: this.enabled,
74
+ toolCountThreshold: rawOpts.toolCountThreshold ?? 10,
75
+ consecSameThreshold: rawOpts.consecSameThreshold ?? 5,
76
+ warnConsecThreshold: rawOpts.warnConsecThreshold ?? 8,
77
+ onLoopWarning: rawOpts.onLoopWarning ?? (() => { }),
78
+ };
79
+ }
80
+ isEnabled() { return this.enabled; }
81
+ /**
82
+ * Set the assembled system prompt for this turn. Called once at turn
83
+ * start (before any tool calls). Stored verbatim; no truncation.
84
+ */
85
+ setSystemPrompt(prompt) {
86
+ if (!this.enabled)
87
+ return;
88
+ this.systemPrompt = prompt;
89
+ }
90
+ /**
91
+ * Set the turn's conversation history snapshot. The tracer keeps
92
+ * only the last `HISTORY_TAIL_DEPTH` messages on finalize.
93
+ */
94
+ setHistory(messages) {
95
+ if (!this.enabled)
96
+ return;
97
+ this.history = messages;
98
+ }
99
+ /**
100
+ * Record the start of a tool call. Pair with `endTool(id, name)` to
101
+ * capture the duration. Caller must pass a stable `id` (the tool
102
+ * call request id) so before/after pairs find each other.
103
+ */
104
+ startTool(id, _name) {
105
+ if (!this.enabled)
106
+ return;
107
+ this.toolStart.set(id, Date.now());
108
+ }
109
+ /**
110
+ * Record the end of a tool call. Computes duration from the matching
111
+ * `startTool` call. Updates consec-same counter; fires the live
112
+ * warning when threshold crosses.
113
+ */
114
+ endTool(id, name, args) {
115
+ if (!this.enabled)
116
+ return;
117
+ const start = this.toolStart.get(id);
118
+ this.toolStart.delete(id);
119
+ const durationMs = start === undefined ? 0 : (Date.now() - start);
120
+ let argsPreview;
121
+ try {
122
+ argsPreview = JSON.stringify(args ?? {});
123
+ }
124
+ catch {
125
+ argsPreview = String(args);
126
+ }
127
+ if (argsPreview.length > ARGS_CAP)
128
+ argsPreview = `${argsPreview.slice(0, ARGS_CAP - 1)}…`;
129
+ this.sequence.push({
130
+ name, argsPreview, durationMs,
131
+ ts: new Date().toISOString(),
132
+ });
133
+ // Consec-same accounting.
134
+ if (name === this.lastName) {
135
+ this.consecSame += 1;
136
+ }
137
+ else {
138
+ this.consecSame = 1;
139
+ this.lastName = name;
140
+ }
141
+ if (this.consecSame > this.maxConsec)
142
+ this.maxConsec = this.consecSame;
143
+ // Skill-view tracking (separate from sequence for quick reference).
144
+ if (name === 'skill_view' || name === 'lookup_tool_schema') {
145
+ const tk = args;
146
+ const target = tk?.name ?? tk?.toolName ?? '(unknown)';
147
+ this.recentSkills.push(target);
148
+ if (this.recentSkills.length > RECENT_SKILLS_MAX) {
149
+ this.recentSkills = this.recentSkills.slice(-RECENT_SKILLS_MAX);
150
+ }
151
+ }
152
+ // Live warning at the loud threshold.
153
+ if (!this.warnFired && this.consecSame >= this.opts.warnConsecThreshold) {
154
+ this.warnFired = true;
155
+ try {
156
+ this.opts.onLoopWarning(`[loop] same tool '${name}' called ${this.consecSame}× — Ctrl+C to interrupt`);
157
+ }
158
+ catch { /* defensive */ }
159
+ }
160
+ }
161
+ /**
162
+ * Compute whether a snapshot would be written if `finalize()` ran
163
+ * now. Useful for tests + the live warning path. Pure read.
164
+ */
165
+ shouldEmit() {
166
+ if (!this.enabled)
167
+ return false;
168
+ return (this.sequence.length >= this.opts.toolCountThreshold ||
169
+ this.maxConsec >= this.opts.consecSameThreshold);
170
+ }
171
+ /**
172
+ * Write the trace to disk if thresholds tripped. Idempotent —
173
+ * subsequent calls are no-ops. Returns the snapshot path or `null`
174
+ * if nothing was written.
175
+ */
176
+ async finalize() {
177
+ if (this.finalized)
178
+ return null;
179
+ this.finalized = true;
180
+ if (!this.enabled)
181
+ return null;
182
+ if (!this.shouldEmit())
183
+ return null;
184
+ const snapshot = await this.buildSnapshot();
185
+ const ts = snapshot.capturedAt.replace(/[:.]/g, '-');
186
+ const filename = `loop-trace-${ts}.json`;
187
+ const filePath = node_path_1.default.join(this.opts.paths.logsDir, filename);
188
+ try {
189
+ await node_fs_1.promises.mkdir(this.opts.paths.logsDir, { recursive: true });
190
+ await node_fs_1.promises.writeFile(filePath, JSON.stringify(snapshot, null, 2), 'utf8');
191
+ return filePath;
192
+ }
193
+ catch {
194
+ // Diagnostic logging must never break the turn. Silently swallow
195
+ // write failures — power user with AIDEN_DEBUG_LOOP=1 will notice
196
+ // missing files and can re-run with stricter perms if needed.
197
+ return null;
198
+ }
199
+ }
200
+ /** Test-accessor: synchronous read of the current consec-same state. */
201
+ getMaxConsecutive() { return this.maxConsec; }
202
+ /** Test-accessor: tool call count so far. */
203
+ getToolCount() { return this.sequence.length; }
204
+ // ── Internals ────────────────────────────────────────────────────────────
205
+ async buildSnapshot() {
206
+ const reason = this.maxConsec >= this.opts.consecSameThreshold
207
+ ? (this.sequence.length >= this.opts.toolCountThreshold
208
+ ? 'consecutive_same'
209
+ : 'consecutive_same')
210
+ : 'tool_count';
211
+ const memoryMdHash = await hashFileFirst12(this.opts.paths.memoryMd);
212
+ const userMdHash = await hashFileFirst12(this.opts.paths.userMd);
213
+ const historyTail = this.history.slice(-HISTORY_TAIL_DEPTH).map((m) => ({
214
+ role: m.role,
215
+ contentPreview: trunc(typeof m.content === 'string' ? m.content : JSON.stringify(m.content), 300),
216
+ }));
217
+ return {
218
+ schemaVersion: 1,
219
+ capturedAt: new Date().toISOString(),
220
+ reason,
221
+ toolCallCount: this.sequence.length,
222
+ maxConsecSame: this.maxConsec,
223
+ consecSameName: this.maxConsec >= 2 ? this.lastName : null,
224
+ toolSequence: this.sequence,
225
+ systemPrompt: this.systemPrompt,
226
+ memoryMdHash,
227
+ userMdHash,
228
+ recentSkills: this.recentSkills,
229
+ historyTail,
230
+ envHints: {
231
+ provider: this.opts.providerId,
232
+ model: this.opts.modelId,
233
+ },
234
+ };
235
+ }
236
+ }
237
+ exports.LoopTracer = LoopTracer;
238
+ // ── Helpers ─────────────────────────────────────────────────────────────────
239
+ /**
240
+ * Read a file and return the sha256 hash, first 12 hex chars.
241
+ * Returns `null` if the file is missing or unreadable. Pure-async.
242
+ * 12 hex is enough collision resistance for context-fingerprinting
243
+ * without bloating the trace file with full 64-char hashes.
244
+ */
245
+ async function hashFileFirst12(filePath) {
246
+ try {
247
+ const content = await node_fs_1.promises.readFile(filePath, 'utf8');
248
+ const hash = node_crypto_1.default.createHash('sha256').update(content).digest('hex');
249
+ return hash.slice(0, 12);
250
+ }
251
+ catch {
252
+ return null;
253
+ }
254
+ }
255
+ function trunc(s, n) {
256
+ return s.length <= n ? s : `${s.slice(0, n - 1)}…`;
257
+ }
@@ -2,4 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
4
  // AUTO-GENERATED by scripts/inject-version.js — do not edit by hand
5
- exports.VERSION = '4.1.4';
5
+ exports.VERSION = '4.1.5';
@@ -7,6 +7,46 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.reliableWebSearch = reliableWebSearch;
8
8
  exports.deepResearch = deepResearch;
9
9
  exports.checkSearxNG = checkSearxNG;
10
+ // core/webSearch.ts — Reliable web search with 4-method fallback chain
11
+ //
12
+ // Priority order:
13
+ // 1. SearxNG (self-hosted, unlimited, Docker on port 8888)
14
+ // 2. Brave Search API (if BRAVE_SEARCH_API_KEY env var set)
15
+ // 3. DuckDuckGo Instant Answer API + HTML scrape
16
+ // 4. Wikipedia (always available, good for factual queries)
17
+ //
18
+ // Usage:
19
+ // import { reliableWebSearch, deepResearch } from './webSearch'
20
+ // const result = await reliableWebSearch('query')
21
+ // ── Debug logging (v4.1.5 Issue O) ────────────────────────────
22
+ //
23
+ // All `[webSearch]` / `[deepResearch]` chatter goes through these two
24
+ // helpers, both gated on `process.env.AIDEN_DEBUG_WEB === '1'`. The
25
+ // v4 REPL ran with these blasting unconditionally to stdout/stderr,
26
+ // surfacing 20+ lines of fallback-chain diagnostics between the user
27
+ // prompt and Aiden's reply on any web-search turn — overwhelming the
28
+ // signal users actually wanted (the tool-trail row).
29
+ //
30
+ // Power users debugging a flaky search backend export the env var:
31
+ // AIDEN_DEBUG_WEB=1 aiden
32
+ // Same pattern as `AIDEN_NO_REFORMAT`, `AIDEN_UI_ICONS`. Default off.
33
+ //
34
+ // `core/webSearch.ts` is shared with the legacy v3 path which has no
35
+ // Display dependency, so we cannot route through `display.dim()` /
36
+ // the v4 verbose-mode config. An env var is the lowest-friction
37
+ // transport that works in both paths.
38
+ function debugLog(...args) {
39
+ if (process.env.AIDEN_DEBUG_WEB === '1') {
40
+ // eslint-disable-next-line no-console
41
+ console.log(...args);
42
+ }
43
+ }
44
+ function debugWarn(...args) {
45
+ if (process.env.AIDEN_DEBUG_WEB === '1') {
46
+ // eslint-disable-next-line no-console
47
+ console.warn(...args);
48
+ }
49
+ }
10
50
  // ── Constants ─────────────────────────────────────────────────
11
51
  const SEARXNG_URL = process.env.SEARXNG_URL || 'http://localhost:8888';
12
52
  const BRAVE_API_KEY = process.env.BRAVE_SEARCH_API_KEY || '';
@@ -21,7 +61,7 @@ async function searchViaSearxNG(query) {
21
61
  signal: AbortSignal.timeout(SEARCH_TIMEOUT),
22
62
  });
23
63
  if (!res.ok) {
24
- console.warn(`[webSearch] SearxNG returned ${res.status}`);
64
+ debugWarn(`[webSearch] SearxNG returned ${res.status}`);
25
65
  return null;
26
66
  }
27
67
  const data = await res.json();
@@ -35,11 +75,11 @@ async function searchViaSearxNG(query) {
35
75
  return null;
36
76
  const lines = results.map(r => `**${r.title}**\n${r.snippet}\n${r.url}`);
37
77
  const output = `[SearxNG Results for "${query}"]\n\n${lines.join('\n\n')}`;
38
- console.log(`[webSearch] SearxNG: ${results.length} results`);
78
+ debugLog(`[webSearch] SearxNG: ${results.length} results`);
39
79
  return { success: true, output, method: 'searxng', results };
40
80
  }
41
81
  catch (e) {
42
- console.warn(`[webSearch] SearxNG failed: ${e.message}`);
82
+ debugWarn(`[webSearch] SearxNG failed: ${e.message}`);
43
83
  return null;
44
84
  }
45
85
  }
@@ -58,7 +98,7 @@ async function searchViaBrave(query) {
58
98
  signal: AbortSignal.timeout(SEARCH_TIMEOUT),
59
99
  });
60
100
  if (!res.ok) {
61
- console.warn(`[webSearch] Brave API returned ${res.status}`);
101
+ debugWarn(`[webSearch] Brave API returned ${res.status}`);
62
102
  return null;
63
103
  }
64
104
  const data = await res.json();
@@ -73,11 +113,11 @@ async function searchViaBrave(query) {
73
113
  }));
74
114
  const lines = results.map(r => `**${r.title}**\n${r.snippet}\n${r.url}`);
75
115
  const output = `[Brave Search Results for "${query}"]\n\n${lines.join('\n\n')}`;
76
- console.log(`[webSearch] Brave: ${results.length} results`);
116
+ debugLog(`[webSearch] Brave: ${results.length} results`);
77
117
  return { success: true, output, method: 'brave', results };
78
118
  }
79
119
  catch (e) {
80
- console.warn(`[webSearch] Brave failed: ${e.message}`);
120
+ debugWarn(`[webSearch] Brave failed: ${e.message}`);
81
121
  return null;
82
122
  }
83
123
  }
@@ -108,7 +148,7 @@ async function searchViaDDG(query) {
108
148
  }
109
149
  }
110
150
  catch (e) {
111
- console.warn(`[webSearch] DDG Instant failed: ${e.message}`);
151
+ debugWarn(`[webSearch] DDG Instant failed: ${e.message}`);
112
152
  }
113
153
  // DDG HTML scrape — get snippet text + page content
114
154
  try {
@@ -164,12 +204,12 @@ async function searchViaDDG(query) {
164
204
  parts.push(...validPages);
165
205
  }
166
206
  catch (e) {
167
- console.warn(`[webSearch] DDG HTML scrape failed: ${e.message}`);
207
+ debugWarn(`[webSearch] DDG HTML scrape failed: ${e.message}`);
168
208
  }
169
209
  if (parts.length === 0)
170
210
  return null;
171
211
  const output = `[DuckDuckGo Results for "${query}"]\n\n${parts.join('\n\n')}`;
172
- console.log(`[webSearch] DDG: ${parts.length} sections`);
212
+ debugLog(`[webSearch] DDG: ${parts.length} sections`);
173
213
  return { success: true, output, method: 'ddg' };
174
214
  }
175
215
  // ── METHOD 4: Wikipedia ───────────────────────────────────────
@@ -193,11 +233,11 @@ async function searchViaWikipedia(query) {
193
233
  .filter(s => s.length > 20);
194
234
  const extra = snippets.length > 0 ? `\n\nRelated: ${snippets.join(' | ')}` : '';
195
235
  const output = `[Wikipedia: ${wiki.title}]\n${wiki.extract.slice(0, 1500)}${extra}`;
196
- console.log(`[webSearch] Wikipedia: ${wiki.extract.length} chars for "${wiki.title}"`);
236
+ debugLog(`[webSearch] Wikipedia: ${wiki.extract.length} chars for "${wiki.title}"`);
197
237
  return { success: true, output, method: 'wikipedia' };
198
238
  }
199
239
  catch (e) {
200
- console.warn(`[webSearch] Wikipedia failed: ${e.message}`);
240
+ debugWarn(`[webSearch] Wikipedia failed: ${e.message}`);
201
241
  return null;
202
242
  }
203
243
  }
@@ -229,11 +269,11 @@ async function fetchWeather(query) {
229
269
  out += ` ${day.date}: High ${day.maxtempC}°C / Low ${day.mintempC}°C${mid ? ' — ' + mid : ''}\n`;
230
270
  }
231
271
  }
232
- console.log(`[webSearch] Weather: retrieved for "${city}"`);
272
+ debugLog(`[webSearch] Weather: retrieved for "${city}"`);
233
273
  return { success: true, output: out.trim(), method: 'wttr.in' };
234
274
  }
235
275
  catch (e) {
236
- console.warn(`[webSearch] Weather failed: ${e.message}`);
276
+ debugWarn(`[webSearch] Weather failed: ${e.message}`);
237
277
  return null;
238
278
  }
239
279
  }
@@ -241,7 +281,7 @@ async function fetchWeather(query) {
241
281
  async function reliableWebSearch(query) {
242
282
  if (!query?.trim())
243
283
  return { success: false, output: '', error: 'No query provided' };
244
- console.log(`[webSearch] Query: "${query}"`);
284
+ debugLog(`[webSearch] Query: "${query}"`);
245
285
  // Weather shortcut
246
286
  if (/weather|temperature|forecast|rain|snow|sunny|cloudy|humidity|wind/i.test(query)) {
247
287
  const weather = await fetchWeather(query);
@@ -251,28 +291,28 @@ async function reliableWebSearch(query) {
251
291
  // Method 1 — SearxNG
252
292
  const searxResult = await searchViaSearxNG(query);
253
293
  if (searxResult) {
254
- console.log(`[webSearch] ✓ SearxNG succeeded`);
294
+ debugLog(`[webSearch] ✓ SearxNG succeeded`);
255
295
  return { success: true, output: searxResult.output.slice(0, 10000) };
256
296
  }
257
297
  // Method 2 — Brave
258
298
  const braveResult = await searchViaBrave(query);
259
299
  if (braveResult) {
260
- console.log(`[webSearch] ✓ Brave succeeded`);
300
+ debugLog(`[webSearch] ✓ Brave succeeded`);
261
301
  return { success: true, output: braveResult.output.slice(0, 10000) };
262
302
  }
263
303
  // Method 3 — DDG
264
304
  const ddgResult = await searchViaDDG(query);
265
305
  if (ddgResult) {
266
- console.log(`[webSearch] ✓ DDG succeeded`);
306
+ debugLog(`[webSearch] ✓ DDG succeeded`);
267
307
  return { success: true, output: ddgResult.output.slice(0, 10000) };
268
308
  }
269
309
  // Method 4 — Wikipedia
270
310
  const wikiResult = await searchViaWikipedia(query);
271
311
  if (wikiResult) {
272
- console.log(`[webSearch] ✓ Wikipedia fallback`);
312
+ debugLog(`[webSearch] ✓ Wikipedia fallback`);
273
313
  return { success: true, output: wikiResult.output };
274
314
  }
275
- console.warn(`[webSearch] All methods failed for: "${query}"`);
315
+ debugWarn(`[webSearch] All methods failed for: "${query}"`);
276
316
  return {
277
317
  success: false,
278
318
  output: '',
@@ -283,24 +323,24 @@ async function reliableWebSearch(query) {
283
323
  async function deepResearch(topic) {
284
324
  if (!topic?.trim())
285
325
  return { success: false, output: '', error: 'No topic provided' };
286
- console.log(`[deepResearch] Topic: "${topic}"`);
326
+ debugLog(`[deepResearch] Topic: "${topic}"`);
287
327
  const parts = [];
288
328
  // Pass 1: Broad
289
- console.log(`[deepResearch] Pass 1: broad`);
329
+ debugLog(`[deepResearch] Pass 1: broad`);
290
330
  const broad = await reliableWebSearch(topic);
291
331
  if (broad.success && broad.output.length > 100) {
292
332
  parts.push(`=== PASS 1: BROAD RESEARCH ===\n${broad.output}`);
293
333
  }
294
334
  // Pass 2: Latest 2026
295
335
  const latestQ = `${topic} 2026 latest`;
296
- console.log(`[deepResearch] Pass 2: latest — "${latestQ}"`);
336
+ debugLog(`[deepResearch] Pass 2: latest — "${latestQ}"`);
297
337
  const latest = await reliableWebSearch(latestQ);
298
338
  if (latest.success && latest.output.length > 100) {
299
339
  parts.push(`=== PASS 2: LATEST (2026) ===\n${latest.output}`);
300
340
  }
301
341
  // Pass 3: Comparison / review
302
342
  const compareQ = `best top ${topic} comparison review`;
303
- console.log(`[deepResearch] Pass 3: comparison — "${compareQ}"`);
343
+ debugLog(`[deepResearch] Pass 3: comparison — "${compareQ}"`);
304
344
  const compare = await reliableWebSearch(compareQ);
305
345
  if (compare.success && compare.output.length > 100) {
306
346
  parts.push(`=== PASS 3: COMPARISON & REVIEWS ===\n${compare.output}`);
@@ -309,7 +349,7 @@ async function deepResearch(topic) {
309
349
  return { success: false, output: '', error: `No research results found for: ${topic}` };
310
350
  }
311
351
  const combined = parts.join('\n\n');
312
- console.log(`[deepResearch] Complete: ${combined.length} chars across ${parts.length} passes`);
352
+ debugLog(`[deepResearch] Complete: ${combined.length} chars across ${parts.length} passes`);
313
353
  return { success: true, output: combined.slice(0, 15000) };
314
354
  }
315
355
  // ── SearxNG health check ──────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-runtime",
3
- "version": "4.1.4",
3
+ "version": "4.1.5",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -0,0 +1,8 @@
1
+ {
2
+ "version": 1,
3
+ "granted": [
4
+ "browser",
5
+ "subprocess",
6
+ "network"
7
+ ]
8
+ }