compact-agent 1.21.0 → 1.23.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/dist/api.d.ts +11 -0
- package/dist/api.js +65 -15
- package/dist/api.js.map +1 -1
- package/dist/config.js +8 -1
- package/dist/config.js.map +1 -1
- package/dist/index.js +114 -0
- package/dist/index.js.map +1 -1
- package/dist/key-rotation.d.ts +54 -0
- package/dist/key-rotation.js +117 -0
- package/dist/key-rotation.js.map +1 -0
- package/dist/sandbox.d.ts +37 -0
- package/dist/sandbox.js +192 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/tools/bash.js +27 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API key rotation pool — for users with multiple OpenRouter (or any
|
|
3
|
+
* compatible) accounts who want to round-robin / failover across them
|
|
4
|
+
* when one hits a rate limit or runs out of free credits.
|
|
5
|
+
*
|
|
6
|
+
* Pool composition: config.apiKey + config.apiKeys (deduplicated, order
|
|
7
|
+
* preserved). The single `apiKey` field always becomes pool[0] so users
|
|
8
|
+
* with only the legacy config see no change in behavior.
|
|
9
|
+
*
|
|
10
|
+
* Rotation strategy: on 401 / 429 / quota errors, the failing key is
|
|
11
|
+
* marked "cool" for `COOL_DOWN_MS`, the next healthy key takes over.
|
|
12
|
+
* If all keys are cool, the request fails normally — the user will see
|
|
13
|
+
* the last error message. Health state lives in module memory; restart
|
|
14
|
+
* = fresh start.
|
|
15
|
+
*
|
|
16
|
+
* Why module-level vs persisted: rate-limit windows are usually 60s-5m
|
|
17
|
+
* and reset naturally. Persisting cool-down across processes would
|
|
18
|
+
* make the agent worse at recovering from transient blips.
|
|
19
|
+
*/
|
|
20
|
+
const COOL_DOWN_MS = 60_000; // 1 min — typical free-tier RPM window
|
|
21
|
+
const QUOTA_COOL_DOWN_MS = 60 * 60_000; // 1 hour — daily/monthly quota errors
|
|
22
|
+
let pool = [];
|
|
23
|
+
let cursor = 0; // round-robin pointer for which key to try first
|
|
24
|
+
/**
|
|
25
|
+
* Build the pool from a config snapshot. Idempotent; if the same keys
|
|
26
|
+
* are passed in the same order, the existing state is preserved
|
|
27
|
+
* (so cool-downs persist across rebuilds in the same process).
|
|
28
|
+
*/
|
|
29
|
+
export function setPool(primary, extras = []) {
|
|
30
|
+
// Dedupe in order: primary first, then extras
|
|
31
|
+
const seen = new Set();
|
|
32
|
+
const all = [];
|
|
33
|
+
for (const k of [primary, ...extras]) {
|
|
34
|
+
if (k && !seen.has(k)) {
|
|
35
|
+
seen.add(k);
|
|
36
|
+
all.push(k);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Preserve state for keys that are still in the pool
|
|
40
|
+
const existing = new Map(pool.map((s) => [s.key, s]));
|
|
41
|
+
pool = all.map((k) => existing.get(k) || {
|
|
42
|
+
key: k, coolUntil: 0, successes: 0, failures: 0,
|
|
43
|
+
});
|
|
44
|
+
if (cursor >= pool.length)
|
|
45
|
+
cursor = 0;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Pick the next healthy key to use. Round-robin from the current cursor.
|
|
49
|
+
* Returns null if the pool is empty or all keys are cool.
|
|
50
|
+
*/
|
|
51
|
+
export function pickKey() {
|
|
52
|
+
if (pool.length === 0)
|
|
53
|
+
return null;
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
// Try each key once, starting from cursor
|
|
56
|
+
for (let i = 0; i < pool.length; i++) {
|
|
57
|
+
const idx = (cursor + i) % pool.length;
|
|
58
|
+
if (pool[idx].coolUntil <= now) {
|
|
59
|
+
cursor = (idx + 1) % pool.length; // next call rotates one further
|
|
60
|
+
return pool[idx].key;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Mark a key as failed. The classifier maps the error to either a
|
|
67
|
+
* short cooldown (rate-limit) or a long one (quota / auth). Used by
|
|
68
|
+
* api.ts's retry logic to skip dead keys without re-trying them.
|
|
69
|
+
*/
|
|
70
|
+
export function reportFailure(key, err) {
|
|
71
|
+
const state = pool.find((s) => s.key === key);
|
|
72
|
+
if (!state)
|
|
73
|
+
return;
|
|
74
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
75
|
+
const lower = msg.toLowerCase();
|
|
76
|
+
let cooldown = COOL_DOWN_MS;
|
|
77
|
+
let reason = msg.slice(0, 80);
|
|
78
|
+
if (/quota|insufficient|credit|payment|billing/.test(lower)) {
|
|
79
|
+
cooldown = QUOTA_COOL_DOWN_MS;
|
|
80
|
+
reason = 'quota/credit exhausted';
|
|
81
|
+
}
|
|
82
|
+
else if (/auth|invalid.*key|forbidden|401|403/.test(lower)) {
|
|
83
|
+
cooldown = QUOTA_COOL_DOWN_MS;
|
|
84
|
+
reason = 'auth rejected (bad/revoked key)';
|
|
85
|
+
}
|
|
86
|
+
else if (/rate.?limit|429|too.many/.test(lower)) {
|
|
87
|
+
cooldown = COOL_DOWN_MS;
|
|
88
|
+
reason = 'rate limited';
|
|
89
|
+
}
|
|
90
|
+
state.coolUntil = Date.now() + cooldown;
|
|
91
|
+
state.lastReason = reason;
|
|
92
|
+
state.failures++;
|
|
93
|
+
}
|
|
94
|
+
/** Mark a key as having succeeded — clears any cool-down + records stat. */
|
|
95
|
+
export function reportSuccess(key) {
|
|
96
|
+
const state = pool.find((s) => s.key === key);
|
|
97
|
+
if (!state)
|
|
98
|
+
return;
|
|
99
|
+
state.coolUntil = 0;
|
|
100
|
+
state.lastReason = undefined;
|
|
101
|
+
state.successes++;
|
|
102
|
+
}
|
|
103
|
+
export function listStatus() {
|
|
104
|
+
const now = Date.now();
|
|
105
|
+
return pool.map((s, i) => ({
|
|
106
|
+
index: i,
|
|
107
|
+
tail: `…${s.key.slice(-4)}`,
|
|
108
|
+
healthy: s.coolUntil <= now,
|
|
109
|
+
coolDownRemainingSec: s.coolUntil > now ? Math.ceil((s.coolUntil - now) / 1000) : undefined,
|
|
110
|
+
successes: s.successes,
|
|
111
|
+
failures: s.failures,
|
|
112
|
+
lastReason: s.lastReason,
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
/** Currently-active pool size (post-dedup). */
|
|
116
|
+
export function poolSize() { return pool.length; }
|
|
117
|
+
//# sourceMappingURL=key-rotation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-rotation.js","sourceRoot":"","sources":["../src/key-rotation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type SandboxBackend = 'seatbelt' | 'bwrap' | 'none';
|
|
2
|
+
export type SandboxLevel = 'off' | 'standard' | 'strict';
|
|
3
|
+
interface BackendDetect {
|
|
4
|
+
backend: SandboxBackend;
|
|
5
|
+
available: boolean;
|
|
6
|
+
reason?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Detect which sandbox backend (if any) is available on this machine.
|
|
10
|
+
* Cached after the first call; pass force=true to re-probe.
|
|
11
|
+
*/
|
|
12
|
+
export declare function detectBackend(force?: boolean): BackendDetect;
|
|
13
|
+
/**
|
|
14
|
+
* Wrap a shell command for the active backend at the requested level.
|
|
15
|
+
* Returns the wrapped command string and a label for logging. If the
|
|
16
|
+
* backend isn't available or level is 'off', returns the command
|
|
17
|
+
* unchanged (callers don't need to special-case).
|
|
18
|
+
*/
|
|
19
|
+
export declare function wrapCommand(cmd: string, opts: {
|
|
20
|
+
level: SandboxLevel;
|
|
21
|
+
cwd: string;
|
|
22
|
+
}): {
|
|
23
|
+
cmd: string;
|
|
24
|
+
label: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Status snapshot for the /sandbox slash command. Tells the user what's
|
|
28
|
+
* supported on this machine + what's currently active.
|
|
29
|
+
*/
|
|
30
|
+
export interface SandboxStatus {
|
|
31
|
+
backend: SandboxBackend;
|
|
32
|
+
available: boolean;
|
|
33
|
+
reason?: string;
|
|
34
|
+
platform: NodeJS.Platform;
|
|
35
|
+
}
|
|
36
|
+
export declare function status(): SandboxStatus;
|
|
37
|
+
export {};
|
package/dist/sandbox.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OS-native sandbox wrapper for the bash tool.
|
|
3
|
+
*
|
|
4
|
+
* Defense-in-depth on top of the execpolicy DSL from 1.19.0. Where
|
|
5
|
+
* execpolicy gates INTENT before the command runs (block "rm -rf /",
|
|
6
|
+
* prompt for "sudo"), this layer adds RUNTIME ISOLATION: the command
|
|
7
|
+
* runs under an OS-native sandbox that limits filesystem write access,
|
|
8
|
+
* network reach, and process privileges even if the intent gate
|
|
9
|
+
* approved or the model is trying to evade detection.
|
|
10
|
+
*
|
|
11
|
+
* Backends:
|
|
12
|
+
* macOS → sandbox-exec (Seatbelt). Shipped with macOS since 10.5.
|
|
13
|
+
* Policy described in scheme-like .sb syntax.
|
|
14
|
+
* Linux → bubblewrap (bwrap). Usually preinstalled (Flatpak runtime)
|
|
15
|
+
* or one apt/yum/dnf install away. Combines user
|
|
16
|
+
* namespaces, mount namespaces, seccomp.
|
|
17
|
+
* Windows → no-op for now. Job Objects + restricted tokens + AppContainer
|
|
18
|
+
* are doable but a much larger project. Documented in /sandbox
|
|
19
|
+
* output so the user knows.
|
|
20
|
+
*
|
|
21
|
+
* Policy levels (config: sandbox.level):
|
|
22
|
+
* off — wrap nothing; behave as before
|
|
23
|
+
* standard — read everywhere, write only to cwd + /tmp, no network
|
|
24
|
+
* (still gives access to the project + scratch space)
|
|
25
|
+
* strict — read cwd only, write only to cwd, no network, no /tmp
|
|
26
|
+
*
|
|
27
|
+
* Default: 'off'. Users opt in via /sandbox standard (or strict) — we
|
|
28
|
+
* don't want to surprise anyone whose workflow needs network or writes
|
|
29
|
+
* outside cwd.
|
|
30
|
+
*
|
|
31
|
+
* Failure mode: if the sandbox tool isn't installed or the wrap fails
|
|
32
|
+
* to construct, we LOG and fall through to the un-sandboxed command.
|
|
33
|
+
* Better to run + work than fail closed silently for a missing tool.
|
|
34
|
+
*/
|
|
35
|
+
import { existsSync } from 'node:fs';
|
|
36
|
+
import { execSync } from 'node:child_process';
|
|
37
|
+
let cachedDetection = null;
|
|
38
|
+
/**
|
|
39
|
+
* Detect which sandbox backend (if any) is available on this machine.
|
|
40
|
+
* Cached after the first call; pass force=true to re-probe.
|
|
41
|
+
*/
|
|
42
|
+
export function detectBackend(force = false) {
|
|
43
|
+
if (cachedDetection && !force)
|
|
44
|
+
return cachedDetection;
|
|
45
|
+
if (process.platform === 'darwin') {
|
|
46
|
+
// sandbox-exec is at /usr/bin/sandbox-exec on every macOS install
|
|
47
|
+
// since 10.5. Deprecated by Apple but still functional in 2026.
|
|
48
|
+
const path = '/usr/bin/sandbox-exec';
|
|
49
|
+
cachedDetection = existsSync(path)
|
|
50
|
+
? { backend: 'seatbelt', available: true }
|
|
51
|
+
: { backend: 'seatbelt', available: false, reason: 'sandbox-exec not found at /usr/bin/' };
|
|
52
|
+
}
|
|
53
|
+
else if (process.platform === 'linux') {
|
|
54
|
+
// bwrap could be anywhere on PATH. Use `which` since it's fast.
|
|
55
|
+
try {
|
|
56
|
+
execSync('which bwrap', { stdio: 'ignore', timeout: 1000 });
|
|
57
|
+
cachedDetection = { backend: 'bwrap', available: true };
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
cachedDetection = { backend: 'bwrap', available: false, reason: 'bwrap (bubblewrap) not on PATH. Install with: apt install bubblewrap | dnf install bubblewrap | brew install bubblewrap' };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
cachedDetection = { backend: 'none', available: false, reason: `${process.platform} doesn't have a supported sandbox backend yet (Windows Job Objects planned)` };
|
|
65
|
+
}
|
|
66
|
+
return cachedDetection;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Construct a Seatbelt (.sb) policy for the given level and cwd.
|
|
70
|
+
*
|
|
71
|
+
* Seatbelt operates on a deny-by-default model: we declare allowed
|
|
72
|
+
* operations, anything else is denied. The base policy here is
|
|
73
|
+
* deliberately permissive for system reads (so node, git, etc. can
|
|
74
|
+
* find their libraries) while gating writes + network.
|
|
75
|
+
*
|
|
76
|
+
* Quirks:
|
|
77
|
+
* - file-write* requires the absolute resolved path (no shell expansion)
|
|
78
|
+
* - network-outbound to localhost is allowed regardless of level so
|
|
79
|
+
* dev servers + IPC keep working
|
|
80
|
+
*/
|
|
81
|
+
function buildSeatbeltPolicy(level, cwd) {
|
|
82
|
+
// Common: deny all by default; allow process spawning + signal +
|
|
83
|
+
// sysctl reads + IPC (otherwise nothing runs).
|
|
84
|
+
const common = [
|
|
85
|
+
'(version 1)',
|
|
86
|
+
'(deny default)',
|
|
87
|
+
'(allow process-exec*)',
|
|
88
|
+
'(allow process-fork)',
|
|
89
|
+
'(allow signal (target self))',
|
|
90
|
+
'(allow sysctl-read)',
|
|
91
|
+
'(allow mach-lookup)',
|
|
92
|
+
'(allow ipc-posix-shm*)',
|
|
93
|
+
'(allow file-read*)', // permissive system-wide read for tool deps
|
|
94
|
+
'(allow file-read-metadata)',
|
|
95
|
+
'(allow file-issue-extension)',
|
|
96
|
+
];
|
|
97
|
+
const networkLocal = ['(allow network-outbound (local ip "*:*"))'];
|
|
98
|
+
const networkAll = ['(allow network*)'];
|
|
99
|
+
const writeCwd = [`(allow file-write* (subpath "${cwd}"))`];
|
|
100
|
+
const writeTmp = ['(allow file-write* (subpath "/tmp"))', '(allow file-write* (subpath "/private/tmp"))'];
|
|
101
|
+
if (level === 'strict') {
|
|
102
|
+
return [...common, ...networkLocal, ...writeCwd].join('\n');
|
|
103
|
+
}
|
|
104
|
+
// standard: cwd + /tmp writable, full network. Network restriction
|
|
105
|
+
// breaks too many real workflows (npm install, pip, curl docs); we
|
|
106
|
+
// keep that on the execpolicy intent gate instead.
|
|
107
|
+
return [...common, ...networkAll, ...writeCwd, ...writeTmp].join('\n');
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Construct a bwrap argument list. bwrap is invoked as
|
|
111
|
+
* bwrap [args...] <command>
|
|
112
|
+
* so we return the prefix array; the caller concatenates the user
|
|
113
|
+
* command afterward (typically as `sh -c "..."`).
|
|
114
|
+
*/
|
|
115
|
+
function buildBwrapArgs(level, cwd) {
|
|
116
|
+
// Common: a fresh user namespace, /proc, /dev, /tmp, share host's
|
|
117
|
+
// /usr + /etc as read-only so binaries + configs work, share network
|
|
118
|
+
// namespace by default (we restrict via execpolicy / level).
|
|
119
|
+
const common = [
|
|
120
|
+
'--unshare-user-try', // fall back gracefully if not allowed
|
|
121
|
+
'--unshare-uts',
|
|
122
|
+
'--unshare-pid',
|
|
123
|
+
'--unshare-ipc',
|
|
124
|
+
'--die-with-parent',
|
|
125
|
+
'--proc', '/proc',
|
|
126
|
+
'--dev', '/dev',
|
|
127
|
+
'--ro-bind', '/usr', '/usr',
|
|
128
|
+
'--ro-bind', '/etc', '/etc',
|
|
129
|
+
'--ro-bind', '/lib', '/lib',
|
|
130
|
+
'--ro-bind-try', '/lib64', '/lib64',
|
|
131
|
+
'--ro-bind-try', '/bin', '/bin',
|
|
132
|
+
'--ro-bind-try', '/sbin', '/sbin',
|
|
133
|
+
// HOME read-only (config files, .ssh) — strict drops this
|
|
134
|
+
...(level === 'strict' ? [] : ['--ro-bind-try', process.env.HOME || '/home', process.env.HOME || '/home']),
|
|
135
|
+
// cwd bind-mounted read-write — this is the project the agent edits
|
|
136
|
+
'--bind', cwd, cwd,
|
|
137
|
+
'--chdir', cwd,
|
|
138
|
+
// /tmp writable in standard, omitted in strict
|
|
139
|
+
...(level === 'strict' ? [] : ['--tmpfs', '/tmp']),
|
|
140
|
+
];
|
|
141
|
+
// Network: standard allows, strict disallows
|
|
142
|
+
const network = level === 'strict' ? ['--unshare-net'] : [];
|
|
143
|
+
return [...common, ...network];
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Wrap a shell command for the active backend at the requested level.
|
|
147
|
+
* Returns the wrapped command string and a label for logging. If the
|
|
148
|
+
* backend isn't available or level is 'off', returns the command
|
|
149
|
+
* unchanged (callers don't need to special-case).
|
|
150
|
+
*/
|
|
151
|
+
export function wrapCommand(cmd, opts) {
|
|
152
|
+
if (opts.level === 'off')
|
|
153
|
+
return { cmd, label: 'no-sandbox' };
|
|
154
|
+
const det = detectBackend();
|
|
155
|
+
if (!det.available) {
|
|
156
|
+
// Caller logs the warning; we just pass through.
|
|
157
|
+
return { cmd, label: `unsandboxed (${det.reason || 'no backend'})` };
|
|
158
|
+
}
|
|
159
|
+
if (det.backend === 'seatbelt') {
|
|
160
|
+
const policy = buildSeatbeltPolicy(opts.level, opts.cwd);
|
|
161
|
+
// sandbox-exec -p '<policy>' /bin/sh -c '<command>'
|
|
162
|
+
// Single-quote the policy + escape internal quotes
|
|
163
|
+
const policyEscaped = policy.replace(/'/g, "'\"'\"'");
|
|
164
|
+
const cmdEscaped = cmd.replace(/'/g, "'\"'\"'");
|
|
165
|
+
return {
|
|
166
|
+
cmd: `/usr/bin/sandbox-exec -p '${policyEscaped}' /bin/sh -c '${cmdEscaped}'`,
|
|
167
|
+
label: `seatbelt (${opts.level})`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (det.backend === 'bwrap') {
|
|
171
|
+
const args = buildBwrapArgs(opts.level, opts.cwd);
|
|
172
|
+
// bwrap doesn't accept commands as a single string — use exec form
|
|
173
|
+
// wrapped through sh -c. Shell escaping handled by the outer exec.
|
|
174
|
+
const cmdEscaped = cmd.replace(/'/g, "'\"'\"'");
|
|
175
|
+
const argString = args.map((a) => `'${a.replace(/'/g, "'\"'\"'")}'`).join(' ');
|
|
176
|
+
return {
|
|
177
|
+
cmd: `bwrap ${argString} /bin/sh -c '${cmdEscaped}'`,
|
|
178
|
+
label: `bwrap (${opts.level})`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return { cmd, label: 'unsandboxed (unknown backend)' };
|
|
182
|
+
}
|
|
183
|
+
export function status() {
|
|
184
|
+
const det = detectBackend();
|
|
185
|
+
return {
|
|
186
|
+
backend: det.backend,
|
|
187
|
+
available: det.available,
|
|
188
|
+
reason: det.reason,
|
|
189
|
+
platform: process.platform,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=sandbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/bash.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { exec } from 'node:child_process';
|
|
2
|
+
import { loadConfig } from '../config.js';
|
|
3
|
+
import { wrapCommand, detectBackend } from '../sandbox.js';
|
|
2
4
|
/**
|
|
3
5
|
* Shell tool. Kept named `bash` for tool-call API compatibility, but on
|
|
4
6
|
* Windows we route through cmd.exe by default — the platform-native shell.
|
|
@@ -55,9 +57,33 @@ export const BashTool = {
|
|
|
55
57
|
isReadOnly: false,
|
|
56
58
|
isDestructive: true,
|
|
57
59
|
async call(input, cwd) {
|
|
58
|
-
const
|
|
60
|
+
const rawCommand = input.command;
|
|
59
61
|
const timeout = input.timeout || 120_000;
|
|
60
62
|
const shell = pickShell();
|
|
63
|
+
// Sandbox wrap (no-op when level=off or backend unavailable).
|
|
64
|
+
// Loading config per-call keeps the bash tool stateless and means
|
|
65
|
+
// /sandbox level changes apply on the very next command.
|
|
66
|
+
const cfg = loadConfig();
|
|
67
|
+
const level = cfg.sandbox?.level || 'off';
|
|
68
|
+
let command = rawCommand;
|
|
69
|
+
let sandboxLabel = '';
|
|
70
|
+
if (level !== 'off') {
|
|
71
|
+
const det = detectBackend();
|
|
72
|
+
if (det.available) {
|
|
73
|
+
const wrapped = wrapCommand(rawCommand, { level, cwd });
|
|
74
|
+
command = wrapped.cmd;
|
|
75
|
+
sandboxLabel = wrapped.label;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Sandbox requested but unavailable on this platform — print
|
|
79
|
+
// once-per-call diagnostic. Doesn't block; falls through to
|
|
80
|
+
// unsandboxed exec because failing closed silently is worse.
|
|
81
|
+
process.stderr.write(` [sandbox] requested ${level} but ${det.reason}\n`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (sandboxLabel) {
|
|
85
|
+
process.stderr.write(` [sandbox] ${sandboxLabel}\n`);
|
|
86
|
+
}
|
|
61
87
|
return new Promise((resolve) => {
|
|
62
88
|
exec(command, {
|
|
63
89
|
cwd,
|
package/dist/tools/bash.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface Message {
|
|
|
9
9
|
}
|
|
10
10
|
export interface CrowcoderConfig {
|
|
11
11
|
apiKey: string;
|
|
12
|
+
apiKeys?: string[];
|
|
12
13
|
baseURL: string;
|
|
13
14
|
model: string;
|
|
14
15
|
fallbackModel?: string;
|
|
@@ -23,6 +24,11 @@ export interface CrowcoderConfig {
|
|
|
23
24
|
showThinking?: boolean;
|
|
24
25
|
voice?: VoiceConfig;
|
|
25
26
|
memory?: MemoryConfig;
|
|
27
|
+
sandbox?: SandboxConfig;
|
|
28
|
+
}
|
|
29
|
+
export interface SandboxConfig {
|
|
30
|
+
/** off (no wrap) | standard (cwd + /tmp writable, net allowed) | strict (cwd-only) */
|
|
31
|
+
level?: 'off' | 'standard' | 'strict';
|
|
26
32
|
}
|
|
27
33
|
export interface MemoryConfig {
|
|
28
34
|
enabled?: boolean;
|
package/dist/types.js
CHANGED
|
@@ -47,6 +47,17 @@ export const PROVIDERS = {
|
|
|
47
47
|
defaultModel: 'glm-4-plus',
|
|
48
48
|
requiresKey: true,
|
|
49
49
|
},
|
|
50
|
+
nvidia: {
|
|
51
|
+
name: 'NVIDIA NIM',
|
|
52
|
+
baseURL: 'https://integrate.api.nvidia.com/v1',
|
|
53
|
+
// meta/llama-3.1-70b-instruct is the most-reliable widely-available
|
|
54
|
+
// default on build.nvidia.com's free tier as of 2026. Users can switch
|
|
55
|
+
// to nvidia/nemotron-4-340b-instruct, qwen/qwen-2.5-coder-32b-instruct,
|
|
56
|
+
// deepseek-ai/deepseek-coder-6.7b-instruct, or any of the ~100 models
|
|
57
|
+
// exposed at /v1/chat/completions via /model.
|
|
58
|
+
defaultModel: 'meta/llama-3.1-70b-instruct',
|
|
59
|
+
requiresKey: true,
|
|
60
|
+
},
|
|
50
61
|
custom: {
|
|
51
62
|
name: 'Custom',
|
|
52
63
|
baseURL: '',
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAiHA,MAAM,CAAC,MAAM,SAAS,GAAmC;IACvD,SAAS,EAAE;QACT,IAAI,EAAE,oBAAoB;QAC1B,OAAO,EAAE,+BAA+B;QACxC,YAAY,EAAE,0BAA0B;QACxC,WAAW,EAAE,IAAI;KAClB;IACD,MAAM,EAAE;QACN,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,2BAA2B;QACpC,YAAY,EAAE,QAAQ;QACtB,WAAW,EAAE,IAAI;KAClB;IACD,UAAU,EAAE;QACV,IAAI,EAAE,wBAAwB;QAC9B,OAAO,EAAE,8BAA8B;QACvC,YAAY,EAAE,2BAA2B;QACzC,WAAW,EAAE,IAAI;KAClB;IACD,MAAM,EAAE;QACN,IAAI,EAAE,iBAAiB;QACvB,OAAO,EAAE,0DAA0D;QACnE,YAAY,EAAE,kBAAkB;QAChC,WAAW,EAAE,IAAI;KAClB;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,6BAA6B;QACtC,YAAY,EAAE,eAAe;QAC7B,WAAW,EAAE,IAAI;KAClB;IACD,MAAM,EAAE;QACN,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,2BAA2B;QACpC,YAAY,EAAE,sBAAsB;QACpC,WAAW,EAAE,KAAK;KACnB;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,0BAA0B;QACnC,YAAY,EAAE,cAAc;QAC5B,WAAW,EAAE,KAAK;KACnB;IACD,GAAG,EAAE;QACH,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,sCAAsC;QAC/C,YAAY,EAAE,YAAY;QAC1B,WAAW,EAAE,IAAI;KAClB;IACD,MAAM,EAAE;QACN,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,qCAAqC;QAC9C,oEAAoE;QACpE,uEAAuE;QACvE,wEAAwE;QACxE,sEAAsE;QACtE,8CAA8C;QAC9C,YAAY,EAAE,6BAA6B;QAC3C,WAAW,EAAE,IAAI;KAClB;IACD,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,EAAE;QACX,YAAY,EAAE,EAAE;QAChB,WAAW,EAAE,IAAI;KAClB;CACF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "compact-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.0",
|
|
4
4
|
"description": "A dense, feature-rich AI coding agent for the terminal. Built-in voice dictation (Whisper) + TTS readout (ElevenLabs) + screen-reader mode for blind / low-vision users. 80+ slash commands, 9 modes including Hermes self-improving loop, multi-agent orchestration, bundled everything-claude-code skills library, learning system, and observable LLM transport. Works with OpenRouter, OpenAI, Anthropic-compatible, Ollama, LM Studio, DeepSeek, or any OpenAI-compatible API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|