aiden-runtime 4.1.5 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +250 -847
- package/dist/api/server.js +32 -5
- package/dist/cli/v4/aidenCLI.js +351 -53
- package/dist/cli/v4/callbacks.js +170 -0
- package/dist/cli/v4/chatSession.js +138 -3
- package/dist/cli/v4/commands/_runtimeToggleHelpers.js +92 -0
- package/dist/cli/v4/commands/browserDepth.js +45 -0
- package/dist/cli/v4/commands/cron.js +264 -0
- package/dist/cli/v4/commands/daemon.js +541 -0
- package/dist/cli/v4/commands/daemonStatus.js +253 -0
- package/dist/cli/v4/commands/help.js +7 -0
- package/dist/cli/v4/commands/index.js +20 -1
- package/dist/cli/v4/commands/runs.js +203 -0
- package/dist/cli/v4/commands/sandbox.js +48 -0
- package/dist/cli/v4/commands/suggestions.js +68 -0
- package/dist/cli/v4/commands/tce.js +41 -0
- package/dist/cli/v4/commands/trigger.js +378 -0
- package/dist/cli/v4/commands/update.js +95 -3
- package/dist/cli/v4/daemonAgentBuilder.js +142 -0
- package/dist/cli/v4/defaultSoul.js +1 -1
- package/dist/cli/v4/display/capabilityCard.js +26 -0
- package/dist/cli/v4/display.js +18 -8
- package/dist/cli/v4/replyRenderer.js +31 -23
- package/dist/cli/v4/updateBootPrompt.js +170 -0
- package/dist/core/playwrightBridge.js +129 -0
- package/dist/core/v4/aidenAgent.js +308 -4
- package/dist/core/v4/browserState.js +436 -0
- package/dist/core/v4/checkpoint.js +79 -0
- package/dist/core/v4/daemon/bootstrap.js +604 -0
- package/dist/core/v4/daemon/cleanShutdown.js +154 -0
- package/dist/core/v4/daemon/cron/cronBridge.js +126 -0
- package/dist/core/v4/daemon/cron/cronEmitter.js +173 -0
- package/dist/core/v4/daemon/cron/migration.js +199 -0
- package/dist/core/v4/daemon/cron/misfirePolicy.js +115 -0
- package/dist/core/v4/daemon/daemonConfig.js +90 -0
- package/dist/core/v4/daemon/db/connection.js +106 -0
- package/dist/core/v4/daemon/db/migrations.js +296 -0
- package/dist/core/v4/daemon/db/schema/v1.spec.js +18 -0
- package/dist/core/v4/daemon/dispatcher/agentRunner.js +98 -0
- package/dist/core/v4/daemon/dispatcher/budgetGate.js +127 -0
- package/dist/core/v4/daemon/dispatcher/daemonApproval.js +113 -0
- package/dist/core/v4/daemon/dispatcher/dailyBudgetTracker.js +120 -0
- package/dist/core/v4/daemon/dispatcher/dispatcher.js +389 -0
- package/dist/core/v4/daemon/dispatcher/fireRateLimiter.js +113 -0
- package/dist/core/v4/daemon/dispatcher/index.js +53 -0
- package/dist/core/v4/daemon/dispatcher/promptTemplate.js +95 -0
- package/dist/core/v4/daemon/dispatcher/realAgentRunner.js +356 -0
- package/dist/core/v4/daemon/dispatcher/resolveModel.js +93 -0
- package/dist/core/v4/daemon/dispatcher/sessionId.js +93 -0
- package/dist/core/v4/daemon/drain.js +156 -0
- package/dist/core/v4/daemon/eventLoopLag.js +73 -0
- package/dist/core/v4/daemon/health.js +159 -0
- package/dist/core/v4/daemon/idempotencyStore.js +204 -0
- package/dist/core/v4/daemon/index.js +179 -0
- package/dist/core/v4/daemon/instanceTracker.js +99 -0
- package/dist/core/v4/daemon/resourceRegistry.js +150 -0
- package/dist/core/v4/daemon/restartCode.js +32 -0
- package/dist/core/v4/daemon/restartFailureCounter.js +77 -0
- package/dist/core/v4/daemon/runStore.js +114 -0
- package/dist/core/v4/daemon/runtimeLock.js +167 -0
- package/dist/core/v4/daemon/signals.js +50 -0
- package/dist/core/v4/daemon/supervisor.js +272 -0
- package/dist/core/v4/daemon/triggerBus.js +279 -0
- package/dist/core/v4/daemon/triggers/email/allowlist.js +70 -0
- package/dist/core/v4/daemon/triggers/email/automatedSender.js +78 -0
- package/dist/core/v4/daemon/triggers/email/bodyExtractor.js +0 -0
- package/dist/core/v4/daemon/triggers/email/emailSeenStore.js +99 -0
- package/dist/core/v4/daemon/triggers/email/emailSpec.js +107 -0
- package/dist/core/v4/daemon/triggers/email/imapConnection.js +211 -0
- package/dist/core/v4/daemon/triggers/email/index.js +332 -0
- package/dist/core/v4/daemon/triggers/email/seenUids.js +60 -0
- package/dist/core/v4/daemon/triggers/fileObservationsStore.js +93 -0
- package/dist/core/v4/daemon/triggers/fileWatcher.js +253 -0
- package/dist/core/v4/daemon/triggers/fileWatcherSpec.js +88 -0
- package/dist/core/v4/daemon/triggers/fsIdentity.js +42 -0
- package/dist/core/v4/daemon/triggers/globMatcher.js +100 -0
- package/dist/core/v4/daemon/triggers/reconcile.js +206 -0
- package/dist/core/v4/daemon/triggers/settleStat.js +81 -0
- package/dist/core/v4/daemon/triggers/webhook.js +376 -0
- package/dist/core/v4/daemon/triggers/webhookDeliveriesStore.js +109 -0
- package/dist/core/v4/daemon/triggers/webhookIdempotency.js +72 -0
- package/dist/core/v4/daemon/triggers/webhookRateLimit.js +56 -0
- package/dist/core/v4/daemon/triggers/webhookSpec.js +76 -0
- package/dist/core/v4/daemon/triggers/webhookVerifier.js +128 -0
- package/dist/core/v4/daemon/types.js +15 -0
- package/dist/core/v4/dockerSession.js +461 -0
- package/dist/core/v4/dryRun.js +117 -0
- package/dist/core/v4/failureClassifier.js +779 -0
- package/dist/core/v4/recoveryReport.js +449 -0
- package/dist/core/v4/runtimeToggles.js +187 -0
- package/dist/core/v4/sandboxConfig.js +285 -0
- package/dist/core/v4/sandboxFs.js +316 -0
- package/dist/core/v4/suggestionCatalog.js +41 -0
- package/dist/core/v4/suggestionEngine.js +210 -0
- package/dist/core/v4/toolRegistry.js +18 -0
- package/dist/core/v4/turnState.js +587 -0
- package/dist/core/v4/update/checkUpdate.js +63 -3
- package/dist/core/v4/update/installMethodDetect.js +115 -0
- package/dist/core/v4/update/registryClient.js +121 -0
- package/dist/core/v4/update/skipState.js +75 -0
- package/dist/core/v4/verifier.js +448 -0
- package/dist/core/version.js +1 -1
- package/dist/tools/v4/browser/_observer.js +224 -0
- package/dist/tools/v4/browser/browserBlocker.js +396 -0
- package/dist/tools/v4/browser/browserClick.js +18 -1
- package/dist/tools/v4/browser/browserClose.js +18 -1
- package/dist/tools/v4/browser/browserExtract.js +5 -1
- package/dist/tools/v4/browser/browserFill.js +17 -1
- package/dist/tools/v4/browser/browserGetUrl.js +5 -1
- package/dist/tools/v4/browser/browserNavigate.js +16 -1
- package/dist/tools/v4/browser/browserScreenshot.js +5 -1
- package/dist/tools/v4/browser/browserScroll.js +18 -1
- package/dist/tools/v4/browser/browserType.js +17 -1
- package/dist/tools/v4/browser/captchaCheck.js +5 -1
- package/dist/tools/v4/executeCode.js +1 -0
- package/dist/tools/v4/files/fileCopy.js +56 -2
- package/dist/tools/v4/files/fileDelete.js +38 -1
- package/dist/tools/v4/files/fileList.js +12 -1
- package/dist/tools/v4/files/fileMove.js +59 -2
- package/dist/tools/v4/files/filePatch.js +43 -1
- package/dist/tools/v4/files/fileRead.js +12 -1
- package/dist/tools/v4/files/fileWrite.js +41 -1
- package/dist/tools/v4/index.js +71 -58
- package/dist/tools/v4/memory/memoryAdd.js +14 -0
- package/dist/tools/v4/memory/memoryRemove.js +14 -0
- package/dist/tools/v4/memory/memoryReplace.js +15 -0
- package/dist/tools/v4/memory/sessionSummary.js +12 -0
- package/dist/tools/v4/process/processKill.js +19 -0
- package/dist/tools/v4/process/processList.js +1 -0
- package/dist/tools/v4/process/processLogRead.js +1 -0
- package/dist/tools/v4/process/processSpawn.js +13 -0
- package/dist/tools/v4/process/processWait.js +1 -0
- package/dist/tools/v4/sessions/recallSession.js +1 -0
- package/dist/tools/v4/sessions/sessionList.js +1 -0
- package/dist/tools/v4/sessions/sessionSearch.js +1 -0
- package/dist/tools/v4/skills/lookupToolSchema.js +2 -0
- package/dist/tools/v4/skills/skillManage.js +13 -0
- package/dist/tools/v4/skills/skillView.js +1 -0
- package/dist/tools/v4/skills/skillsList.js +1 -0
- package/dist/tools/v4/subagent/subagentFanout.js +1 -0
- package/dist/tools/v4/system/aidenSelfUpdate.js +16 -0
- package/dist/tools/v4/system/appClose.js +13 -0
- package/dist/tools/v4/system/appInput.js +13 -0
- package/dist/tools/v4/system/appLaunch.js +13 -0
- package/dist/tools/v4/system/clipboardRead.js +1 -0
- package/dist/tools/v4/system/clipboardWrite.js +14 -0
- package/dist/tools/v4/system/mediaKey.js +12 -0
- package/dist/tools/v4/system/mediaSessions.js +1 -0
- package/dist/tools/v4/system/mediaTransport.js +13 -0
- package/dist/tools/v4/system/naturalEvents.js +1 -0
- package/dist/tools/v4/system/nowPlaying.js +1 -0
- package/dist/tools/v4/system/osProcessList.js +1 -0
- package/dist/tools/v4/system/screenshot.js +1 -0
- package/dist/tools/v4/system/systemInfo.js +1 -0
- package/dist/tools/v4/system/volumeSet.js +17 -0
- package/dist/tools/v4/terminal/shellExec.js +81 -9
- package/dist/tools/v4/web/deepResearch.js +1 -0
- package/dist/tools/v4/web/openUrl.js +1 -0
- package/dist/tools/v4/web/webFetch.js +1 -0
- package/dist/tools/v4/web/webPage.js +1 -0
- package/dist/tools/v4/web/webSearch.js +1 -0
- package/dist/tools/v4/web/youtubeSearch.js +1 -0
- package/package.json +7 -1
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveRealPath = resolveRealPath;
|
|
7
|
+
exports._clearRealPathCacheForTests = _clearRealPathCacheForTests;
|
|
8
|
+
exports.inferDefaultRiskTier = inferDefaultRiskTier;
|
|
9
|
+
exports.readSandboxConfig = readSandboxConfig;
|
|
10
|
+
exports.getSandboxConfig = getSandboxConfig;
|
|
11
|
+
exports._resetSandboxConfigForTests = _resetSandboxConfigForTests;
|
|
12
|
+
/**
|
|
13
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
14
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
15
|
+
*
|
|
16
|
+
* Aiden — local-first agent.
|
|
17
|
+
*/
|
|
18
|
+
const runtimeToggles_1 = require("./runtimeToggles");
|
|
19
|
+
/**
|
|
20
|
+
* core/v4/sandboxConfig.ts — v4.4 Phase 1: Execution sandbox configuration.
|
|
21
|
+
*
|
|
22
|
+
* Single source of truth for sandbox enablement + resource policies +
|
|
23
|
+
* filesystem allow/deny lists + Docker hardening flags. Read from
|
|
24
|
+
* environment variables at construction time (matches v4.2's TurnState
|
|
25
|
+
* + v4.3's BrowserState env-driven pattern).
|
|
26
|
+
*
|
|
27
|
+
* Phase 1 ships the config types + reader + default-tier inference;
|
|
28
|
+
* downstream phases consume:
|
|
29
|
+
* - Phase 2 — fsAllowList / fsDenyList used by file_* tools
|
|
30
|
+
* - Phase 3 — defaultBackend / resourceLimits / networkMode /
|
|
31
|
+
* persistent / idleReaperMs used by shell_exec + Docker
|
|
32
|
+
* backend (long-lived container reuse, hardening flags)
|
|
33
|
+
* - Phase 4 — dryRun used by all `mutates: true` tools
|
|
34
|
+
* - Phase 5 — riskTier annotations consumed by FailureClassifier +
|
|
35
|
+
* ApprovalEngine (as a FLOOR; patterns can escalate)
|
|
36
|
+
*
|
|
37
|
+
* v4.4 Phase 6 — default-on transition. AIDEN_SANDBOX is now
|
|
38
|
+
* enabled by default; users opt out with `AIDEN_SANDBOX=0`. The
|
|
39
|
+
* strict `!== '0'` flip mirrors v4.2 Phase 6 (TCE) + v4.3 Phase 6
|
|
40
|
+
* (browser depth) semantics exactly.
|
|
41
|
+
*
|
|
42
|
+
* AIDEN_DRYRUN is orthogonal — independent flag, independent semantics.
|
|
43
|
+
* Phase 6 does NOT flip dry-run; it stays opt-in by design (dry-run
|
|
44
|
+
* is a deliberate "preview-only" mode users opt into, not a default).
|
|
45
|
+
*
|
|
46
|
+
* Pure module — no I/O, no side effects, no Playwright/Docker
|
|
47
|
+
* dependencies. Just env-var reads + path normalization helpers.
|
|
48
|
+
* Easy to unit test by passing a stubbed `env` argument.
|
|
49
|
+
*/
|
|
50
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
51
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
52
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
53
|
+
// ── Defaults ────────────────────────────────────────────────────────────────
|
|
54
|
+
const DEFAULT_RESOURCE_LIMITS = {
|
|
55
|
+
memory: '1g',
|
|
56
|
+
cpus: '2',
|
|
57
|
+
pidsLimit: 256,
|
|
58
|
+
};
|
|
59
|
+
const DEFAULT_IDLE_REAPER_MS = 5 * 60 * 1000; // 5 minutes
|
|
60
|
+
const DEFAULT_IMAGE = 'node:22-alpine';
|
|
61
|
+
/**
|
|
62
|
+
* v4.4 Phase 2 — default write-permitted paths. Real-resolved at
|
|
63
|
+
* config-build time to handle symlinked HOME / cwd. Caller can
|
|
64
|
+
* extend via `AIDEN_SANDBOX_ALLOW=p1:p2:...`.
|
|
65
|
+
*/
|
|
66
|
+
function buildDefaultAllowList() {
|
|
67
|
+
const home = node_os_1.default.homedir();
|
|
68
|
+
const tmp = node_os_1.default.tmpdir();
|
|
69
|
+
const cwd = process.cwd();
|
|
70
|
+
const paths = [
|
|
71
|
+
cwd,
|
|
72
|
+
node_path_1.default.join(home, 'Documents'),
|
|
73
|
+
node_path_1.default.join(home, 'Downloads'),
|
|
74
|
+
node_path_1.default.join(home, 'Desktop'),
|
|
75
|
+
tmp,
|
|
76
|
+
];
|
|
77
|
+
return resolveRealPaths(paths);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* v4.4 Phase 2 — default write-denied paths. Always wins over the
|
|
81
|
+
* allow list. Mirrors the consult-shaped deny-list pattern (sensitive
|
|
82
|
+
* configs, system dirs).
|
|
83
|
+
*/
|
|
84
|
+
function buildDefaultDenyList() {
|
|
85
|
+
const home = node_os_1.default.homedir();
|
|
86
|
+
const paths = [
|
|
87
|
+
node_path_1.default.join(home, '.ssh'),
|
|
88
|
+
node_path_1.default.join(home, '.aws'),
|
|
89
|
+
node_path_1.default.join(home, '.gnupg'),
|
|
90
|
+
node_path_1.default.join(home, '.env'),
|
|
91
|
+
node_path_1.default.join(home, '.netrc'),
|
|
92
|
+
node_path_1.default.join(home, '.pgpass'),
|
|
93
|
+
node_path_1.default.join(home, '.npmrc'),
|
|
94
|
+
node_path_1.default.join(home, '.pypirc'),
|
|
95
|
+
node_path_1.default.join(home, '.bashrc'),
|
|
96
|
+
node_path_1.default.join(home, '.zshrc'),
|
|
97
|
+
node_path_1.default.join(home, '.profile'),
|
|
98
|
+
'/etc',
|
|
99
|
+
'/var',
|
|
100
|
+
'/usr',
|
|
101
|
+
'/boot',
|
|
102
|
+
'/sys',
|
|
103
|
+
'/proc',
|
|
104
|
+
];
|
|
105
|
+
return resolveRealPaths(paths);
|
|
106
|
+
}
|
|
107
|
+
// ── Path normalization ──────────────────────────────────────────────────────
|
|
108
|
+
const _realPathCache = new Map();
|
|
109
|
+
/**
|
|
110
|
+
* Resolve a path to its canonical absolute form. `path.resolve` first
|
|
111
|
+
* (handles relative + `..`); then `fs.realpathSync` to follow symlinks.
|
|
112
|
+
* Symlink resolution defeats the bypass attack where an allowlisted
|
|
113
|
+
* directory contains a symlink to a denied path.
|
|
114
|
+
*
|
|
115
|
+
* Results cached for the lifetime of the module (paths rarely change
|
|
116
|
+
* during a process; the cache hit rate is high on the file-tool path
|
|
117
|
+
* where every call resolves the same handful of allowlist entries).
|
|
118
|
+
*
|
|
119
|
+
* Falls back gracefully when the path doesn't exist (returns the
|
|
120
|
+
* resolved-but-unrealpath form) — caller may be checking a path
|
|
121
|
+
* about to be created.
|
|
122
|
+
*/
|
|
123
|
+
function resolveRealPath(input) {
|
|
124
|
+
if (_realPathCache.has(input))
|
|
125
|
+
return _realPathCache.get(input);
|
|
126
|
+
const resolved = node_path_1.default.resolve(input);
|
|
127
|
+
let real = resolved;
|
|
128
|
+
try {
|
|
129
|
+
real = node_fs_1.default.realpathSync.native ? node_fs_1.default.realpathSync.native(resolved) : node_fs_1.default.realpathSync(resolved);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Path may not exist yet (e.g. file_write target). Use the
|
|
133
|
+
// resolved form; symlink-bypass on a non-existent path isn't
|
|
134
|
+
// a real attack vector.
|
|
135
|
+
}
|
|
136
|
+
_realPathCache.set(input, real);
|
|
137
|
+
return real;
|
|
138
|
+
}
|
|
139
|
+
/** Resolve an array of paths to their canonical forms; deduplicate. */
|
|
140
|
+
function resolveRealPaths(paths) {
|
|
141
|
+
const seen = new Set();
|
|
142
|
+
const out = [];
|
|
143
|
+
for (const p of paths) {
|
|
144
|
+
const real = resolveRealPath(p);
|
|
145
|
+
if (!seen.has(real)) {
|
|
146
|
+
seen.add(real);
|
|
147
|
+
out.push(real);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
/** Public for tests — clears the realpath cache so env-var changes
|
|
153
|
+
* in test isolation pick up fresh resolutions. Production code never
|
|
154
|
+
* calls this. */
|
|
155
|
+
function _clearRealPathCacheForTests() {
|
|
156
|
+
_realPathCache.clear();
|
|
157
|
+
}
|
|
158
|
+
// ── Env-var parsing helpers ─────────────────────────────────────────────────
|
|
159
|
+
function parseList(raw) {
|
|
160
|
+
if (!raw)
|
|
161
|
+
return [];
|
|
162
|
+
return raw.split(':').map((s) => s.trim()).filter((s) => s.length > 0);
|
|
163
|
+
}
|
|
164
|
+
function parseIntSafe(raw, fallback) {
|
|
165
|
+
if (raw === undefined || raw === null || raw === '')
|
|
166
|
+
return fallback;
|
|
167
|
+
const n = Number.parseInt(raw, 10);
|
|
168
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
169
|
+
}
|
|
170
|
+
function parseNetworkMode(raw) {
|
|
171
|
+
if (raw === 'none')
|
|
172
|
+
return 'none';
|
|
173
|
+
return 'bridge'; // bridge for unset / 'bridge' / junk
|
|
174
|
+
}
|
|
175
|
+
// ── Risk-tier inference ─────────────────────────────────────────────────────
|
|
176
|
+
/**
|
|
177
|
+
* Default risk tier for a tool that doesn't carry an explicit
|
|
178
|
+
* `riskTier` annotation. Leverages the existing `mutates: boolean`
|
|
179
|
+
* field — mutating tools default to `caution`, read-only tools
|
|
180
|
+
* default to `safe`. Plugin tools without annotation get a safe
|
|
181
|
+
* default for free.
|
|
182
|
+
*
|
|
183
|
+
* Phase 5 ApprovalEngine integration treats explicit annotations as
|
|
184
|
+
* a FLOOR — DANGEROUS_PATTERNS can escalate but never demote. The
|
|
185
|
+
* inference here is the floor when no annotation exists at all.
|
|
186
|
+
*/
|
|
187
|
+
function inferDefaultRiskTier(mutates) {
|
|
188
|
+
return mutates ? 'caution' : 'safe';
|
|
189
|
+
}
|
|
190
|
+
// ── Reader ──────────────────────────────────────────────────────────────────
|
|
191
|
+
/**
|
|
192
|
+
* Pure factory. Reads env vars + defaults into a frozen-snapshot
|
|
193
|
+
* SandboxConfig. Idempotent for a given env. The CLI calls this
|
|
194
|
+
* once at boot via the singleton factory below; tests pass a custom
|
|
195
|
+
* `env` argument.
|
|
196
|
+
*/
|
|
197
|
+
function readSandboxConfig(env = process.env) {
|
|
198
|
+
// v4.4 Phase 6 — default-on transition. AIDEN_SANDBOX is now
|
|
199
|
+
// enabled by default; users opt out with `AIDEN_SANDBOX=0`.
|
|
200
|
+
//
|
|
201
|
+
// v4.5 Phase 8a — the actual read goes through the runtimeToggles
|
|
202
|
+
// singleton so /sandbox slash-command flips and config.yaml
|
|
203
|
+
// overrides take effect without restart. When the singleton was
|
|
204
|
+
// initialised by the CLI it consults env > config.yaml > default;
|
|
205
|
+
// when not initialised (test bench) it falls back to env > default
|
|
206
|
+
// — the existing semantics.
|
|
207
|
+
// unset / '1' / 'true' / junk → enabled
|
|
208
|
+
// '0' → disabled
|
|
209
|
+
// Mirrors v4.2 Phase 6 (TCE) + v4.3 Phase 6 (browser depth)
|
|
210
|
+
// semantics exactly. The strict `!== '0'` check matches the
|
|
211
|
+
// pattern those phases set: any explicit "off" value disables;
|
|
212
|
+
// everything else (including unset) enables.
|
|
213
|
+
// v4.5 Phase 8a — when env (the readSandboxConfig input) is not the
|
|
214
|
+
// process env (test bench passing a custom env), honour the input
|
|
215
|
+
// directly to keep test isolation. Otherwise route through the
|
|
216
|
+
// runtimeToggles singleton.
|
|
217
|
+
const enabled = env === process.env
|
|
218
|
+
? (0, runtimeToggles_1.getRuntimeToggles)().isEnabled('sandbox')
|
|
219
|
+
: env.AIDEN_SANDBOX !== '0';
|
|
220
|
+
// Allow/deny lists: defaults + user-provided extensions.
|
|
221
|
+
const customAllow = parseList(env.AIDEN_SANDBOX_ALLOW).map(resolveRealPath);
|
|
222
|
+
const customDeny = parseList(env.AIDEN_SANDBOX_DENY).map(resolveRealPath);
|
|
223
|
+
const fsAllowList = Array.from(new Set([...buildDefaultAllowList(), ...customAllow]));
|
|
224
|
+
const fsDenyList = Array.from(new Set([...buildDefaultDenyList(), ...customDeny]));
|
|
225
|
+
// Resource limits — string values pass through Docker as-is.
|
|
226
|
+
const resourceLimits = {
|
|
227
|
+
memory: env.AIDEN_SANDBOX_MEMORY ?? DEFAULT_RESOURCE_LIMITS.memory,
|
|
228
|
+
cpus: env.AIDEN_SANDBOX_CPUS ?? DEFAULT_RESOURCE_LIMITS.cpus,
|
|
229
|
+
pidsLimit: parseIntSafe(env.AIDEN_SANDBOX_PIDS, DEFAULT_RESOURCE_LIMITS.pidsLimit),
|
|
230
|
+
};
|
|
231
|
+
const networkMode = parseNetworkMode(env.AIDEN_SANDBOX_NETWORK);
|
|
232
|
+
const persistent = env.AIDEN_SANDBOX_PERSISTENT !== '0'; // default true
|
|
233
|
+
const idleReaperMs = parseIntSafe(env.AIDEN_SANDBOX_IDLE_MS, DEFAULT_IDLE_REAPER_MS);
|
|
234
|
+
const dryRun = env.AIDEN_DRYRUN === '1';
|
|
235
|
+
const image = typeof env.AIDEN_SANDBOX_IMAGE === 'string' && env.AIDEN_SANDBOX_IMAGE.trim().length > 0
|
|
236
|
+
? env.AIDEN_SANDBOX_IMAGE.trim()
|
|
237
|
+
: DEFAULT_IMAGE;
|
|
238
|
+
// Phase 3 will route to 'docker' when enabled AND docker is
|
|
239
|
+
// available. Phase 1 reports the abstract default — Phase 3's
|
|
240
|
+
// runtime probe decides the actual route.
|
|
241
|
+
const defaultBackend = enabled ? 'docker' : 'local';
|
|
242
|
+
return {
|
|
243
|
+
enabled,
|
|
244
|
+
fsAllowList,
|
|
245
|
+
fsDenyList,
|
|
246
|
+
defaultBackend,
|
|
247
|
+
persistent,
|
|
248
|
+
resourceLimits,
|
|
249
|
+
networkMode,
|
|
250
|
+
idleReaperMs,
|
|
251
|
+
dryRun,
|
|
252
|
+
image,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
// ── Singleton ───────────────────────────────────────────────────────────────
|
|
256
|
+
let _singleton = null;
|
|
257
|
+
let _toggleHookInstalled = false;
|
|
258
|
+
/**
|
|
259
|
+
* Read the singleton sandbox config. Initialized on first call from
|
|
260
|
+
* `process.env` (matches v4.2 TurnState / v4.3 BrowserState lifecycle).
|
|
261
|
+
* Tests construct fresh configs via `readSandboxConfig(env)` directly
|
|
262
|
+
* — the singleton path is for production CLI startup.
|
|
263
|
+
*/
|
|
264
|
+
function getSandboxConfig() {
|
|
265
|
+
if (!_toggleHookInstalled) {
|
|
266
|
+
// v4.5 Phase 8a — invalidate the singleton when /sandbox toggles.
|
|
267
|
+
// Registered lazily on first read so test benches that build
|
|
268
|
+
// their own runtimeToggles + reset between cases don't carry
|
|
269
|
+
// hooks across instances.
|
|
270
|
+
try {
|
|
271
|
+
(0, runtimeToggles_1.getRuntimeToggles)().onChange('sandbox', () => { _singleton = null; });
|
|
272
|
+
_toggleHookInstalled = true;
|
|
273
|
+
}
|
|
274
|
+
catch { /* runtimeToggles import / init race — best-effort */ }
|
|
275
|
+
}
|
|
276
|
+
if (!_singleton)
|
|
277
|
+
_singleton = readSandboxConfig();
|
|
278
|
+
return _singleton;
|
|
279
|
+
}
|
|
280
|
+
/** Reset the singleton for test isolation. Production code never calls this. */
|
|
281
|
+
function _resetSandboxConfigForTests() {
|
|
282
|
+
_singleton = null;
|
|
283
|
+
_toggleHookInstalled = false;
|
|
284
|
+
_clearRealPathCacheForTests();
|
|
285
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
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/sandboxFs.ts — v4.4 Phase 2: filesystem allowlist enforcement.
|
|
10
|
+
*
|
|
11
|
+
* Pure in-process path policy decision module. Consulted by the six
|
|
12
|
+
* file_* tools (file_read, file_list, file_write, file_patch,
|
|
13
|
+
* file_copy, file_move, file_delete) BEFORE any disk I/O, so a
|
|
14
|
+
* decision can be returned to the agent without ever touching the
|
|
15
|
+
* filesystem when the answer is "no".
|
|
16
|
+
*
|
|
17
|
+
* Two-list model (mirrors Phase 1's `SandboxConfig`):
|
|
18
|
+
* - fsDenyList — sensitive paths the user never wants touched
|
|
19
|
+
* (.ssh, .aws, .env, /etc, /var, ...). Wins for
|
|
20
|
+
* BOTH read and write operations. Denylist always
|
|
21
|
+
* takes precedence over allowlist.
|
|
22
|
+
* - fsAllowList — write-permitted roots (cwd, ~/Documents,
|
|
23
|
+
* ~/Downloads, ~/Desktop, os.tmpdir()). Writes /
|
|
24
|
+
* deletes outside these roots are refused. Reads
|
|
25
|
+
* are NOT constrained by the allowlist — reads
|
|
26
|
+
* only have to clear the denylist.
|
|
27
|
+
*
|
|
28
|
+
* Symlink defense:
|
|
29
|
+
* - Realpath the target (or its first existing ancestor for
|
|
30
|
+
* non-existent paths like file_write destinations). Symlink
|
|
31
|
+
* bypass on allowlist roots is the canonical attack vector;
|
|
32
|
+
* this module canonicalizes before checking.
|
|
33
|
+
* - A path that is LEXICALLY under an allowlist root but
|
|
34
|
+
* REALPATH'd outside (via a symlink) yields a distinct
|
|
35
|
+
* `fs.symlink_escape` violation code.
|
|
36
|
+
*
|
|
37
|
+
* Non-existent path handling (Q-P2-3 default):
|
|
38
|
+
* - Walk up to the first existing ancestor, realpath that, then
|
|
39
|
+
* rejoin the trailing segments. This defends against
|
|
40
|
+
* `<allowlist-root>/<symlinked-dir>/new-file.txt` writes.
|
|
41
|
+
*
|
|
42
|
+
* TOCTOU posture (Q-P2-5 default):
|
|
43
|
+
* - Phase 2 is in-process. A racing actor could rename a
|
|
44
|
+
* directory between our policy check and the tool's open()
|
|
45
|
+
* call. Phase 3 (Docker sandbox) closes this gap at the OS
|
|
46
|
+
* layer via container isolation. Phase 2 documents the gap
|
|
47
|
+
* and accepts it for the strict-opt-in `AIDEN_SANDBOX=1`
|
|
48
|
+
* period (default-off until Phase 6).
|
|
49
|
+
*
|
|
50
|
+
* Short-circuit semantics:
|
|
51
|
+
* - When `config.enabled === false` (default through Phase 5),
|
|
52
|
+
* `isPathAllowed` returns `{ allowed: true, resolvedPath: ...,
|
|
53
|
+
* ... }` with no denylist/allowlist evaluation. Zero overhead
|
|
54
|
+
* for users who haven't opted in. Phase 6 flips the gate but
|
|
55
|
+
* the wire-in stays.
|
|
56
|
+
*
|
|
57
|
+
* The returned `PathPolicyDecision` shape is also forward-compatible
|
|
58
|
+
* with Phase 5's `FailureCategory.sandbox_violation` — the
|
|
59
|
+
* `violation.category` field is pre-populated with the constant
|
|
60
|
+
* Phase 5 will register in FailureClassifier.
|
|
61
|
+
*/
|
|
62
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
63
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
64
|
+
};
|
|
65
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
66
|
+
exports.isWithin = isWithin;
|
|
67
|
+
exports.realpathWithFallback = realpathWithFallback;
|
|
68
|
+
exports.isPathAllowed = isPathAllowed;
|
|
69
|
+
exports.violationEnvelope = violationEnvelope;
|
|
70
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
71
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
72
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
73
|
+
const sandboxConfig_1 = require("./sandboxConfig");
|
|
74
|
+
// ── Internal helpers ────────────────────────────────────────────────────────
|
|
75
|
+
/**
|
|
76
|
+
* `tools/v4/utils/paths.ts#expandPath` re-implemented locally so the
|
|
77
|
+
* core module doesn't depend on a tools-layer helper. Keeps the
|
|
78
|
+
* import direction core ← tools (never the reverse).
|
|
79
|
+
*/
|
|
80
|
+
function expandPathInline(input, cwd) {
|
|
81
|
+
const home = node_os_1.default.homedir();
|
|
82
|
+
let p = input;
|
|
83
|
+
if (/^~[\\/]/i.test(p))
|
|
84
|
+
p = home + p.slice(1);
|
|
85
|
+
else if (/^Desktop[\\/]?$/i.test(p))
|
|
86
|
+
p = node_path_1.default.join(home, 'Desktop');
|
|
87
|
+
else if (/^Desktop[\\/]/i.test(p))
|
|
88
|
+
p = node_path_1.default.join(home, 'Desktop', p.slice(8));
|
|
89
|
+
if (node_path_1.default.isAbsolute(p))
|
|
90
|
+
return p;
|
|
91
|
+
if (/^[A-Z]:/i.test(p))
|
|
92
|
+
return p;
|
|
93
|
+
return node_path_1.default.join(cwd, p);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Boundary-aware containment check. `path.relative` avoids the
|
|
97
|
+
* `/home/user-evil` vs `/home/user` false positive that a naive
|
|
98
|
+
* `startsWith` would produce.
|
|
99
|
+
*/
|
|
100
|
+
function isWithin(child, parent) {
|
|
101
|
+
if (!child || !parent)
|
|
102
|
+
return false;
|
|
103
|
+
const rel = node_path_1.default.relative(parent, child);
|
|
104
|
+
return rel === '' || (!rel.startsWith('..') && !node_path_1.default.isAbsolute(rel));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Realpath a path that may not yet exist. Walks up to the first
|
|
108
|
+
* existing ancestor, realpaths that, then rejoins the trailing
|
|
109
|
+
* segments. Defends against `<allowlist>/<symlink>/new-file.txt`
|
|
110
|
+
* writes where the symlink mid-path points outside the allowlist.
|
|
111
|
+
*
|
|
112
|
+
* Idempotent: existing paths are realpath'd directly (single
|
|
113
|
+
* `resolveRealPath` call).
|
|
114
|
+
*/
|
|
115
|
+
function realpathWithFallback(input) {
|
|
116
|
+
// First, resolve the whole thing optimistically — if it exists,
|
|
117
|
+
// realpath handles it directly and we're done.
|
|
118
|
+
const resolved = node_path_1.default.resolve(input);
|
|
119
|
+
try {
|
|
120
|
+
if (node_fs_1.default.existsSync(resolved)) {
|
|
121
|
+
return (0, sandboxConfig_1.resolveRealPath)(resolved);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// existsSync shouldn't throw, but a permissions error on a
|
|
126
|
+
// parent dir could surface here on Windows. Fall through.
|
|
127
|
+
}
|
|
128
|
+
// Path doesn't exist yet — walk up.
|
|
129
|
+
let cur = resolved;
|
|
130
|
+
let tail = '';
|
|
131
|
+
// Guard against infinite loop on malformed paths (path.dirname
|
|
132
|
+
// of a root returns the root itself).
|
|
133
|
+
for (let i = 0; i < 4096; i++) {
|
|
134
|
+
const parent = node_path_1.default.dirname(cur);
|
|
135
|
+
if (parent === cur) {
|
|
136
|
+
// Reached filesystem root without finding any existing
|
|
137
|
+
// ancestor. Return the lexical resolve.
|
|
138
|
+
return resolved;
|
|
139
|
+
}
|
|
140
|
+
let parentExists = false;
|
|
141
|
+
try {
|
|
142
|
+
parentExists = node_fs_1.default.existsSync(parent);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
parentExists = false;
|
|
146
|
+
}
|
|
147
|
+
if (parentExists) {
|
|
148
|
+
const parentReal = (0, sandboxConfig_1.resolveRealPath)(parent);
|
|
149
|
+
tail = tail ? node_path_1.default.join(node_path_1.default.basename(cur), tail) : node_path_1.default.basename(cur);
|
|
150
|
+
return node_path_1.default.join(parentReal, tail);
|
|
151
|
+
}
|
|
152
|
+
tail = tail ? node_path_1.default.join(node_path_1.default.basename(cur), tail) : node_path_1.default.basename(cur);
|
|
153
|
+
cur = parent;
|
|
154
|
+
}
|
|
155
|
+
return resolved;
|
|
156
|
+
}
|
|
157
|
+
function fmtList(list, max = 5) {
|
|
158
|
+
if (list.length === 0)
|
|
159
|
+
return '(none)';
|
|
160
|
+
if (list.length <= max)
|
|
161
|
+
return list.join(', ');
|
|
162
|
+
return list.slice(0, max).join(', ') + `, ... (${list.length - max} more)`;
|
|
163
|
+
}
|
|
164
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
165
|
+
/**
|
|
166
|
+
* Evaluate a path against the active sandbox policy.
|
|
167
|
+
*
|
|
168
|
+
* @param rawPath The original path string from tool args (untrusted).
|
|
169
|
+
* @param op 'read' | 'write' | 'delete' — determines whether the
|
|
170
|
+
* allowlist applies. 'read' = deny-only; 'write' /
|
|
171
|
+
* 'delete' = allowlist required.
|
|
172
|
+
* @param cwd The tool context's working directory, used to resolve
|
|
173
|
+
* relative paths the same way the file tools do.
|
|
174
|
+
* @param config Optional config override (default = singleton from
|
|
175
|
+
* `getSandboxConfig()`). Tests pass a custom config.
|
|
176
|
+
*
|
|
177
|
+
* @returns `{ allowed: true, ... }` when the operation may proceed,
|
|
178
|
+
* or `{ allowed: false, violation: {...}, ... }` with a
|
|
179
|
+
* structured `FsViolation` when it must be refused. Tools
|
|
180
|
+
* should forward the violation into a `sandbox_violation`
|
|
181
|
+
* envelope on the result object alongside `success: false`.
|
|
182
|
+
*
|
|
183
|
+
* Behavior when `config.enabled === false` (Phase 1-5 default):
|
|
184
|
+
* the function still resolves the path (so the caller can use
|
|
185
|
+
* `resolvedPath` uniformly), but returns `allowed: true` without
|
|
186
|
+
* evaluating either list. Zero runtime cost beyond expand+resolve.
|
|
187
|
+
*/
|
|
188
|
+
function isPathAllowed(rawPath, op, cwd, config = (0, sandboxConfig_1.getSandboxConfig)()) {
|
|
189
|
+
const requestedPath = rawPath;
|
|
190
|
+
const expandedPath = expandPathInline(rawPath, cwd);
|
|
191
|
+
// Short-circuit when sandbox is disabled. Still produce a useful
|
|
192
|
+
// resolvedPath so the tool can keep its current single-codepath
|
|
193
|
+
// structure (resolve once, use the resolved path).
|
|
194
|
+
if (!config.enabled) {
|
|
195
|
+
return {
|
|
196
|
+
allowed: true,
|
|
197
|
+
resolvedPath: expandedPath,
|
|
198
|
+
requestedPath,
|
|
199
|
+
expandedPath,
|
|
200
|
+
op,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
// Lexical path-traversal sniff: if `rawPath` was a relative input
|
|
204
|
+
// that escapes `cwd` via `..`, refuse with a clear code BEFORE
|
|
205
|
+
// realpath touches the disk. This is belt-and-suspenders — realpath
|
|
206
|
+
// would catch most cases via the symlink-escape branch — but a
|
|
207
|
+
// structured `fs.path_traversal` reads more cleanly in logs.
|
|
208
|
+
if (!node_path_1.default.isAbsolute(rawPath) && !/^[A-Z]:/i.test(rawPath) && !/^~[\\/]/i.test(rawPath)) {
|
|
209
|
+
const cwdReal = (0, sandboxConfig_1.resolveRealPath)(cwd);
|
|
210
|
+
const expReal = node_path_1.default.resolve(cwd, rawPath);
|
|
211
|
+
if (!isWithin(expReal, cwdReal) && rawPath.includes('..')) {
|
|
212
|
+
// Don't treat this as fatal yet — a relative path can legitimately
|
|
213
|
+
// escape cwd (e.g. `../tmp/x` when cwd is under tmp). We only flag
|
|
214
|
+
// it if it ALSO lands outside both lists, which the standard checks
|
|
215
|
+
// below will catch. Leaving the sniff in as a `path_traversal`
|
|
216
|
+
// upgrade for the escape-with-no-allowlist-hit case.
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const resolvedPath = realpathWithFallback(expandedPath);
|
|
220
|
+
const base = {
|
|
221
|
+
resolvedPath,
|
|
222
|
+
requestedPath,
|
|
223
|
+
expandedPath,
|
|
224
|
+
op,
|
|
225
|
+
};
|
|
226
|
+
// ── Denylist (always wins, both read and write) ───────────────────────
|
|
227
|
+
for (const denied of config.fsDenyList) {
|
|
228
|
+
if (isWithin(resolvedPath, denied) || resolvedPath === denied) {
|
|
229
|
+
return {
|
|
230
|
+
...base,
|
|
231
|
+
allowed: false,
|
|
232
|
+
violation: {
|
|
233
|
+
code: 'fs.sensitive_path',
|
|
234
|
+
matchedPolicy: denied,
|
|
235
|
+
category: 'sandbox_violation',
|
|
236
|
+
retryable: false,
|
|
237
|
+
message: `Sandbox: path "${resolvedPath}" is under the sensitive denylist entry ` +
|
|
238
|
+
`"${denied}". Reads and writes are both refused. ` +
|
|
239
|
+
`(Override by extending AIDEN_SANDBOX_ALLOW is not sufficient — the ` +
|
|
240
|
+
`denylist takes precedence.)`,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// ── Allowlist (write/delete only — reads pass through after denylist) ─
|
|
246
|
+
if (op === 'read') {
|
|
247
|
+
return { ...base, allowed: true };
|
|
248
|
+
}
|
|
249
|
+
// Symlink-escape detection: resolvedPath escaped the allowlist
|
|
250
|
+
// tree, but expandedPath was LEXICALLY under it. That's the
|
|
251
|
+
// classic allowlist-bypass-via-symlink pattern.
|
|
252
|
+
let lexicalUnderAllow = false;
|
|
253
|
+
let realUnderAllow = false;
|
|
254
|
+
let matchedAllow = '';
|
|
255
|
+
for (const allowed of config.fsAllowList) {
|
|
256
|
+
if (isWithin(expandedPath, allowed) || expandedPath === allowed) {
|
|
257
|
+
lexicalUnderAllow = true;
|
|
258
|
+
}
|
|
259
|
+
if (isWithin(resolvedPath, allowed) || resolvedPath === allowed) {
|
|
260
|
+
realUnderAllow = true;
|
|
261
|
+
matchedAllow = allowed;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (realUnderAllow) {
|
|
266
|
+
return { ...base, allowed: true };
|
|
267
|
+
}
|
|
268
|
+
if (lexicalUnderAllow) {
|
|
269
|
+
return {
|
|
270
|
+
...base,
|
|
271
|
+
allowed: false,
|
|
272
|
+
violation: {
|
|
273
|
+
code: 'fs.symlink_escape',
|
|
274
|
+
matchedPolicy: '',
|
|
275
|
+
category: 'sandbox_violation',
|
|
276
|
+
retryable: false,
|
|
277
|
+
message: `Sandbox: path "${expandedPath}" appears to live under an allowlisted ` +
|
|
278
|
+
`root, but its real path "${resolvedPath}" is outside every allowlist ` +
|
|
279
|
+
`entry. A symlink in the path likely points outside the sandbox.`,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
// Plain write-outside-allowlist. Most common refusal.
|
|
284
|
+
return {
|
|
285
|
+
...base,
|
|
286
|
+
allowed: false,
|
|
287
|
+
violation: {
|
|
288
|
+
code: 'fs.write_outside_allowlist',
|
|
289
|
+
matchedPolicy: matchedAllow,
|
|
290
|
+
category: 'sandbox_violation',
|
|
291
|
+
retryable: false,
|
|
292
|
+
message: `Sandbox: ${op} target "${resolvedPath}" is not under any allowlisted ` +
|
|
293
|
+
`directory. Allowed roots: ${fmtList(config.fsAllowList)}. ` +
|
|
294
|
+
`(Extend via AIDEN_SANDBOX_ALLOW=<colon-separated-paths>.)`,
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Convenience: build the structured envelope the file tools attach
|
|
300
|
+
* to their result objects when a policy denial occurs. Centralises
|
|
301
|
+
* the wire format so all six tools serialise the same shape.
|
|
302
|
+
*/
|
|
303
|
+
function violationEnvelope(decision) {
|
|
304
|
+
if (!decision.violation) {
|
|
305
|
+
// Defensive — callers should only call this on denied decisions.
|
|
306
|
+
throw new Error('violationEnvelope called on allowed decision');
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
code: decision.violation.code,
|
|
310
|
+
matched_policy: decision.violation.matchedPolicy,
|
|
311
|
+
requested_path: decision.requestedPath,
|
|
312
|
+
resolved_path: decision.resolvedPath,
|
|
313
|
+
retryable: false,
|
|
314
|
+
category: 'sandbox_violation',
|
|
315
|
+
};
|
|
316
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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/suggestionCatalog.ts — v4.5 Phase 8b.
|
|
10
|
+
*
|
|
11
|
+
* Pre-rendered user-facing copy for each contextual suggestion slot.
|
|
12
|
+
* Centralised here so future tone tuning / i18n can change one file
|
|
13
|
+
* without touching the engine.
|
|
14
|
+
*
|
|
15
|
+
* Style guide (approved tone Q-P8b-5(b)):
|
|
16
|
+
* - One line per tip.
|
|
17
|
+
* - ≤ 90 visible chars (room for indentation + emoji).
|
|
18
|
+
* - "💡 tip:" prefix, lowercase command + brief why.
|
|
19
|
+
* - No trailing punctuation gymnastics.
|
|
20
|
+
* - No second-person preaching ("you should" → drop).
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.SUGGESTION_COPY = void 0;
|
|
24
|
+
exports.suggestionMessageFor = suggestionMessageFor;
|
|
25
|
+
/**
|
|
26
|
+
* Slot → message map. The engine resolves the slot and the
|
|
27
|
+
* catalog renders the matching copy.
|
|
28
|
+
*/
|
|
29
|
+
exports.SUGGESTION_COPY = Object.freeze({
|
|
30
|
+
sandbox: '💡 tip: enable /sandbox on for a safer guardrail against destructive ops.',
|
|
31
|
+
browser_depth: '💡 tip: enable /browser-depth on to capture page state + auto-retry stale refs.',
|
|
32
|
+
daemon_scheduling: '💡 tip: this looks like a recurring task — `aiden cron add` or `aiden trigger add file` can run it on a schedule.',
|
|
33
|
+
tce_recovery: '💡 tip: enable /tce on so Aiden classifies tool failures + auto-recovers.',
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* Look up the rendered tip string for a slot. Pure — caller decides
|
|
37
|
+
* when to display (`display.dim()` is the conventional sink).
|
|
38
|
+
*/
|
|
39
|
+
function suggestionMessageFor(slot) {
|
|
40
|
+
return exports.SUGGESTION_COPY[slot];
|
|
41
|
+
}
|