alvin-bot 4.15.2 → 4.16.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/CHANGELOG.md +51 -18
- package/README.md +13 -13
- package/bin/cli.js +124 -0
- package/dist/handlers/platform-message.js +2 -2
- package/dist/paths.js +12 -2
- package/dist/services/alvin-mcp-tools.js +1 -1
- package/dist/services/asset-index.js +5 -11
- package/dist/services/browser-manager.js +19 -6
- package/dist/services/cdp-bootstrap.js +351 -0
- package/dist/services/memory-layers.js +1 -1
- package/dist/services/personality.js +1 -1
- package/dist/services/session.js +1 -1
- package/dist/services/skills.js +4 -7
- package/dist/services/workspaces.js +4 -4
- package/docs/security.md +4 -4
- package/package.json +1 -1
- package/skills/browse/SKILL.md +77 -70
- package/skills/social-fetch/SKILL.md +3 -3
- package/skills/webcheck/SKILL.md +1 -1
- package/test/async-agent-chunk-flow.test.ts +1 -1
- package/test/claude-sdk-tool-use-id.test.ts +1 -1
- package/test/memory-extractor.test.ts +10 -10
- package/test/memory-layers.test.ts +15 -15
- package/test/memory-sdk-injection.test.ts +4 -4
- package/test/memory-stress-restart.test.ts +2 -2
- package/test/multi-session-stress.test.ts +21 -21
- package/test/platform-session-key.test.ts +2 -2
- package/test/slack-test-connection.test.ts +3 -3
- package/test/subagent-delivery-platform-routing.test.ts +2 -2
- package/test/telegram-workspace-command.test.ts +5 -5
- package/test/workspaces.test.ts +32 -32
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDP Bootstrap — spawns Chromium with remote-debugging-port=9222 independently.
|
|
3
|
+
*
|
|
4
|
+
* Avoids two problems that plague naive CDP setups:
|
|
5
|
+
*
|
|
6
|
+
* 1. **LaunchServices hijack** — invoking /Applications/Google Chrome.app while
|
|
7
|
+
* the user's Chrome is running silently redirects the call to the existing
|
|
8
|
+
* instance without applying --remote-debugging-port. Log symptom:
|
|
9
|
+
* "Wird in einer aktuellen Browsersitzung geöffnet." We avoid it by
|
|
10
|
+
* preferring Playwright's "Google Chrome for Testing" binary, which has a
|
|
11
|
+
* distinct bundle ID.
|
|
12
|
+
*
|
|
13
|
+
* 2. **Stale PID files** — a crashed Chromium leaves chrome-cdp.pid pointing at
|
|
14
|
+
* a dead process; subsequent starts conclude "already running" and fail
|
|
15
|
+
* silently. We verify liveness via both `ps` and a CDP /json/version probe.
|
|
16
|
+
*
|
|
17
|
+
* The module is idempotent: `ensureRunning()` is safe to call repeatedly; if
|
|
18
|
+
* CDP is already healthy it returns immediately.
|
|
19
|
+
*/
|
|
20
|
+
import { spawn } from "child_process";
|
|
21
|
+
import fs from "fs";
|
|
22
|
+
import path from "path";
|
|
23
|
+
import os from "os";
|
|
24
|
+
import http from "http";
|
|
25
|
+
import { CDP_PROFILE_DIR, CDP_SCREENSHOTS_DIR, CDP_PID_FILE, CDP_LOG_FILE, } from "../paths.js";
|
|
26
|
+
const CDP_PORT = 9222;
|
|
27
|
+
const CDP_VERSION_URL = `http://127.0.0.1:${CDP_PORT}/json/version`;
|
|
28
|
+
const START_TIMEOUT_MS = 15_000;
|
|
29
|
+
// ── Binary resolution ───────────────────────────────────────────────
|
|
30
|
+
/**
|
|
31
|
+
* Find Playwright's bundled Chromium. Prefers "Google Chrome for Testing"
|
|
32
|
+
* (distinct macOS bundle ID — no LaunchServices conflict with user Chrome),
|
|
33
|
+
* falls back to plain Chromium for older Playwright installs.
|
|
34
|
+
*
|
|
35
|
+
* Returns null if no bundled Chromium is present — callers should then fall
|
|
36
|
+
* back to a user-supplied binary or error out with guidance.
|
|
37
|
+
*/
|
|
38
|
+
export function findPlaywrightChromium() {
|
|
39
|
+
const pwRoot = path.join(os.homedir(), "Library", "Caches", "ms-playwright");
|
|
40
|
+
if (!fs.existsSync(pwRoot)) {
|
|
41
|
+
// Linux cache path
|
|
42
|
+
const linuxPwRoot = path.join(os.homedir(), ".cache", "ms-playwright");
|
|
43
|
+
if (fs.existsSync(linuxPwRoot))
|
|
44
|
+
return resolveFromPwRoot(linuxPwRoot);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return resolveFromPwRoot(pwRoot);
|
|
48
|
+
}
|
|
49
|
+
function resolveFromPwRoot(pwRoot) {
|
|
50
|
+
let dirs;
|
|
51
|
+
try {
|
|
52
|
+
dirs = fs.readdirSync(pwRoot).filter((d) => /^chromium-\d+$/.test(d));
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
if (dirs.length === 0)
|
|
58
|
+
return null;
|
|
59
|
+
// Latest version by numeric suffix
|
|
60
|
+
dirs.sort((a, b) => {
|
|
61
|
+
const na = parseInt(a.replace("chromium-", ""), 10);
|
|
62
|
+
const nb = parseInt(b.replace("chromium-", ""), 10);
|
|
63
|
+
return nb - na;
|
|
64
|
+
});
|
|
65
|
+
// Platform-dependent layout; try all known variants
|
|
66
|
+
const candidates = [];
|
|
67
|
+
for (const dir of dirs) {
|
|
68
|
+
const root = path.join(pwRoot, dir);
|
|
69
|
+
for (const arch of ["chrome-mac-arm64", "chrome-mac", "chrome-linux", "chrome-win"]) {
|
|
70
|
+
for (const app of [
|
|
71
|
+
// macOS app bundles
|
|
72
|
+
"Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing",
|
|
73
|
+
"Chromium.app/Contents/MacOS/Chromium",
|
|
74
|
+
// Linux / Windows raw binaries
|
|
75
|
+
"chrome",
|
|
76
|
+
"chrome.exe",
|
|
77
|
+
]) {
|
|
78
|
+
candidates.push(path.join(root, arch, app));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const c of candidates) {
|
|
83
|
+
try {
|
|
84
|
+
const st = fs.statSync(c);
|
|
85
|
+
if (st.isFile())
|
|
86
|
+
return c;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// not present, keep searching
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolve the browser binary in preference order:
|
|
96
|
+
* 1. Playwright's Chromium (no conflict with user Chrome, preferred)
|
|
97
|
+
* 2. Existing user browser (may trigger LaunchServices hijack — last resort)
|
|
98
|
+
*/
|
|
99
|
+
export function resolveBrowserBinary() {
|
|
100
|
+
const pw = findPlaywrightChromium();
|
|
101
|
+
if (pw)
|
|
102
|
+
return { path: pw, origin: "playwright" };
|
|
103
|
+
// System Chrome fallback (macOS path). On Linux/Windows we return null and
|
|
104
|
+
// let callers surface a clear error.
|
|
105
|
+
const sysChrome = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
|
106
|
+
if (fs.existsSync(sysChrome))
|
|
107
|
+
return { path: sysChrome, origin: "system" };
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
// ── Liveness probes ─────────────────────────────────────────────────
|
|
111
|
+
function pidAlive(pid) {
|
|
112
|
+
try {
|
|
113
|
+
// signal 0 tests existence without actually signaling
|
|
114
|
+
process.kill(pid, 0);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function readPidFile() {
|
|
122
|
+
try {
|
|
123
|
+
const raw = fs.readFileSync(CDP_PID_FILE, "utf8").trim();
|
|
124
|
+
const pid = parseInt(raw, 10);
|
|
125
|
+
return Number.isFinite(pid) ? pid : null;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function cdpReachable(timeoutMs = 2000) {
|
|
132
|
+
return new Promise((resolve) => {
|
|
133
|
+
const req = http.get(CDP_VERSION_URL, (res) => {
|
|
134
|
+
res.resume(); // drain
|
|
135
|
+
resolve(res.statusCode === 200);
|
|
136
|
+
});
|
|
137
|
+
req.on("error", () => resolve(false));
|
|
138
|
+
req.setTimeout(timeoutMs, () => {
|
|
139
|
+
req.destroy();
|
|
140
|
+
resolve(false);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
// ── Process control ─────────────────────────────────────────────────
|
|
145
|
+
let bootstrapLock = null;
|
|
146
|
+
/**
|
|
147
|
+
* Ensure CDP is running on port 9222. Idempotent and safe to call from
|
|
148
|
+
* multiple concurrent code paths — only one spawn happens at a time.
|
|
149
|
+
*/
|
|
150
|
+
export async function ensureRunning(opts = {}) {
|
|
151
|
+
const mode = opts.mode || "headless";
|
|
152
|
+
// Already running? Verify both ends (process + endpoint).
|
|
153
|
+
if (await cdpReachable()) {
|
|
154
|
+
return { running: true, pid: readPidFile() || undefined, endpoint: CDP_VERSION_URL };
|
|
155
|
+
}
|
|
156
|
+
// Serialize concurrent bootstrap attempts
|
|
157
|
+
if (bootstrapLock) {
|
|
158
|
+
await bootstrapLock;
|
|
159
|
+
if (await cdpReachable()) {
|
|
160
|
+
return { running: true, pid: readPidFile() || undefined, endpoint: CDP_VERSION_URL };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
bootstrapLock = (async () => {
|
|
164
|
+
// Stale PID cleanup — a PID file pointing at a dead process blocks nothing
|
|
165
|
+
// but is confusing. Remove it before spawning.
|
|
166
|
+
const stalePid = readPidFile();
|
|
167
|
+
if (stalePid && !pidAlive(stalePid)) {
|
|
168
|
+
try {
|
|
169
|
+
fs.unlinkSync(CDP_PID_FILE);
|
|
170
|
+
}
|
|
171
|
+
catch { }
|
|
172
|
+
}
|
|
173
|
+
const binary = resolveBrowserBinary();
|
|
174
|
+
if (!binary) {
|
|
175
|
+
throw new Error("No Chromium binary found. Install Playwright's Chromium: " +
|
|
176
|
+
"cd ~/.alvin-bot && npx playwright install chromium");
|
|
177
|
+
}
|
|
178
|
+
// Ensure data dirs exist
|
|
179
|
+
for (const dir of [CDP_PROFILE_DIR, CDP_SCREENSHOTS_DIR, path.dirname(CDP_PID_FILE)]) {
|
|
180
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
181
|
+
}
|
|
182
|
+
const args = [
|
|
183
|
+
`--remote-debugging-port=${CDP_PORT}`,
|
|
184
|
+
`--user-data-dir=${CDP_PROFILE_DIR}`,
|
|
185
|
+
"--no-first-run",
|
|
186
|
+
"--no-default-browser-check",
|
|
187
|
+
"--disable-features=ChromeWhatsNewUI,PrivacySandboxSettings4",
|
|
188
|
+
];
|
|
189
|
+
if (mode === "headless") {
|
|
190
|
+
args.push("--headless=new", "--disable-gpu");
|
|
191
|
+
}
|
|
192
|
+
args.push("about:blank");
|
|
193
|
+
const logStream = fs.openSync(CDP_LOG_FILE, "w");
|
|
194
|
+
const child = spawn(binary.path, args, {
|
|
195
|
+
stdio: ["ignore", logStream, logStream],
|
|
196
|
+
detached: true,
|
|
197
|
+
});
|
|
198
|
+
child.unref();
|
|
199
|
+
if (!child.pid) {
|
|
200
|
+
throw new Error("Failed to spawn Chromium (no PID)");
|
|
201
|
+
}
|
|
202
|
+
fs.writeFileSync(CDP_PID_FILE, String(child.pid));
|
|
203
|
+
// Wait until CDP answers
|
|
204
|
+
const deadline = Date.now() + START_TIMEOUT_MS;
|
|
205
|
+
while (Date.now() < deadline) {
|
|
206
|
+
if (await cdpReachable())
|
|
207
|
+
return;
|
|
208
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
209
|
+
}
|
|
210
|
+
// Did not come up — kill and surface a useful error
|
|
211
|
+
try {
|
|
212
|
+
process.kill(child.pid);
|
|
213
|
+
}
|
|
214
|
+
catch { }
|
|
215
|
+
try {
|
|
216
|
+
fs.unlinkSync(CDP_PID_FILE);
|
|
217
|
+
}
|
|
218
|
+
catch { }
|
|
219
|
+
const tail = readLogTail(20);
|
|
220
|
+
throw new Error(`CDP did not come up within ${START_TIMEOUT_MS}ms using ${binary.path}\n` +
|
|
221
|
+
`Log tail:\n${tail}`);
|
|
222
|
+
})();
|
|
223
|
+
try {
|
|
224
|
+
await bootstrapLock;
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
bootstrapLock = null;
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
running: true,
|
|
231
|
+
pid: readPidFile() || undefined,
|
|
232
|
+
binary: resolveBrowserBinary()?.path,
|
|
233
|
+
endpoint: CDP_VERSION_URL,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Stop the bot-managed Chromium. Does NOT touch the user's own Chrome.
|
|
238
|
+
*/
|
|
239
|
+
export async function stop() {
|
|
240
|
+
const pid = readPidFile();
|
|
241
|
+
if (pid && pidAlive(pid)) {
|
|
242
|
+
try {
|
|
243
|
+
process.kill(pid, "SIGTERM");
|
|
244
|
+
}
|
|
245
|
+
catch { }
|
|
246
|
+
// Give it a second to close gracefully, then force-kill
|
|
247
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
248
|
+
if (pidAlive(pid)) {
|
|
249
|
+
try {
|
|
250
|
+
process.kill(pid, "SIGKILL");
|
|
251
|
+
}
|
|
252
|
+
catch { }
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
fs.unlinkSync(CDP_PID_FILE);
|
|
257
|
+
}
|
|
258
|
+
catch { }
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Report current status without starting anything.
|
|
262
|
+
*/
|
|
263
|
+
export async function status() {
|
|
264
|
+
const pid = readPidFile();
|
|
265
|
+
const endpoint = CDP_VERSION_URL;
|
|
266
|
+
const binary = resolveBrowserBinary()?.path;
|
|
267
|
+
if (pid && pidAlive(pid) && (await cdpReachable())) {
|
|
268
|
+
return { running: true, pid, binary, endpoint };
|
|
269
|
+
}
|
|
270
|
+
if (pid && !pidAlive(pid)) {
|
|
271
|
+
return { running: false, endpoint, reason: `stale PID ${pid} — process not running` };
|
|
272
|
+
}
|
|
273
|
+
if (pid && !(await cdpReachable())) {
|
|
274
|
+
return { running: false, pid, endpoint, reason: "PID alive but CDP endpoint unreachable" };
|
|
275
|
+
}
|
|
276
|
+
return { running: false, endpoint, reason: "not started" };
|
|
277
|
+
}
|
|
278
|
+
function readLogTail(lines) {
|
|
279
|
+
try {
|
|
280
|
+
const content = fs.readFileSync(CDP_LOG_FILE, "utf8");
|
|
281
|
+
return content.split("\n").slice(-lines).join("\n");
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return "(no log file)";
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
export async function doctor() {
|
|
288
|
+
const checks = [];
|
|
289
|
+
// 1. Binary
|
|
290
|
+
const binary = resolveBrowserBinary();
|
|
291
|
+
if (binary) {
|
|
292
|
+
checks.push({
|
|
293
|
+
name: "Binary",
|
|
294
|
+
ok: true,
|
|
295
|
+
detail: binary.origin === "playwright"
|
|
296
|
+
? `Playwright Chromium — ${binary.path}`
|
|
297
|
+
: `System Chrome (fallback — risk of LaunchServices conflict) — ${binary.path}`,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
checks.push({
|
|
302
|
+
name: "Binary",
|
|
303
|
+
ok: false,
|
|
304
|
+
detail: "No Chromium found. Run: npx playwright install chromium",
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
// 2. Port / endpoint
|
|
308
|
+
const reachable = await cdpReachable();
|
|
309
|
+
checks.push({
|
|
310
|
+
name: "CDP endpoint",
|
|
311
|
+
ok: reachable,
|
|
312
|
+
detail: reachable ? `${CDP_VERSION_URL} reachable` : `${CDP_VERSION_URL} not reachable`,
|
|
313
|
+
});
|
|
314
|
+
// 3. PID file
|
|
315
|
+
const pid = readPidFile();
|
|
316
|
+
if (pid === null) {
|
|
317
|
+
checks.push({ name: "PID file", ok: true, detail: "none (OK if CDP not running)" });
|
|
318
|
+
}
|
|
319
|
+
else if (pidAlive(pid)) {
|
|
320
|
+
checks.push({ name: "PID file", ok: true, detail: `PID ${pid} alive` });
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
checks.push({ name: "PID file", ok: false, detail: `stale PID ${pid} — delete ${CDP_PID_FILE}` });
|
|
324
|
+
}
|
|
325
|
+
// 4. Profile lock (only relevant on macOS / Linux)
|
|
326
|
+
const lockPath = path.join(CDP_PROFILE_DIR, "SingletonLock");
|
|
327
|
+
if (fs.existsSync(lockPath)) {
|
|
328
|
+
// Chromium creates SingletonLock while running; only flag if there's no
|
|
329
|
+
// live process associated with it.
|
|
330
|
+
const livePid = pid && pidAlive(pid);
|
|
331
|
+
checks.push({
|
|
332
|
+
name: "Profile lock",
|
|
333
|
+
ok: !!livePid,
|
|
334
|
+
detail: livePid
|
|
335
|
+
? "held by live process (OK)"
|
|
336
|
+
: `stale lock — delete ${lockPath}`,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
checks.push({ name: "Profile lock", ok: true, detail: "clean" });
|
|
341
|
+
}
|
|
342
|
+
// 5. Log tail
|
|
343
|
+
if (fs.existsSync(CDP_LOG_FILE)) {
|
|
344
|
+
checks.push({
|
|
345
|
+
name: "Recent log",
|
|
346
|
+
ok: true,
|
|
347
|
+
detail: `last lines (${CDP_LOG_FILE}):\n${readLogTail(5)}`,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return { ok: checks.every((c) => c.ok), checks };
|
|
351
|
+
}
|
|
@@ -77,7 +77,7 @@ function matchProjectsToQuery(projects, query) {
|
|
|
77
77
|
}
|
|
78
78
|
// Also check the first 200 chars of project content — this catches cases
|
|
79
79
|
// where the user mentions a project's headline term that isn't the
|
|
80
|
-
// filename (e.g., "VPS" matching
|
|
80
|
+
// filename (e.g., "VPS" matching my-project.md which mentions "VPS:" upfront).
|
|
81
81
|
const head = p.content.slice(0, 200).toLowerCase();
|
|
82
82
|
const headWords = head.split(/[\s\W]+/).filter(w => w.length >= 4);
|
|
83
83
|
if (headWords.some(w => q.includes(w))) {
|
|
@@ -129,7 +129,7 @@ you explicitly need the sub-agent's result IN THIS SAME TURN (rare).
|
|
|
129
129
|
|
|
130
130
|
**After launching a background agent (either tool), you MUST:**
|
|
131
131
|
1. Tell the user in ONE short sentence what you kicked off.
|
|
132
|
-
Example: "Starting SEO audit for
|
|
132
|
+
Example: "Starting SEO audit for example.com in the background —
|
|
133
133
|
I'll send the report when it's done."
|
|
134
134
|
2. End your turn IMMEDIATELY. Do not continue working. Do not wait.
|
|
135
135
|
3. The bot will deliver the result as a separate message when ready.
|
package/dist/services/session.js
CHANGED
|
@@ -144,7 +144,7 @@ export function trackProviderUsage(key, providerKey, cost, inputTokens, outputTo
|
|
|
144
144
|
session.totalOutputTokens += outputTokens;
|
|
145
145
|
persist();
|
|
146
146
|
// Soft budget warnings — these NEVER block the bot. They exist purely
|
|
147
|
-
// as log signals so the operator
|
|
147
|
+
// as log signals so the operator can notice unusually expensive
|
|
148
148
|
// sessions. Each threshold fires at most once per session (reset on /new).
|
|
149
149
|
const budget = config.maxBudgetUsd;
|
|
150
150
|
if (budget > 0) {
|
package/dist/services/skills.js
CHANGED
|
@@ -228,13 +228,10 @@ export function matchSkills(userMessage, maxResults = 2) {
|
|
|
228
228
|
.map(s => s.skill);
|
|
229
229
|
}
|
|
230
230
|
// ── Skill-Asset Mapping ────────────────────────────────
|
|
231
|
-
/** Default mapping for skills that don't declare assetCategories in frontmatter.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
"cover-letter": ["cover-letters", "cv-templates"],
|
|
236
|
-
"formal-letter": ["legal", "cv-templates"],
|
|
237
|
-
};
|
|
231
|
+
/** Default mapping for skills that don't declare assetCategories in frontmatter.
|
|
232
|
+
* Skills can override this by declaring `assetCategories:` in their SKILL.md
|
|
233
|
+
* frontmatter. This map is only the fallback. */
|
|
234
|
+
const SKILL_ASSET_MAP = {};
|
|
238
235
|
/**
|
|
239
236
|
* Find assets relevant to a skill.
|
|
240
237
|
* Uses frontmatter assetCategories if declared, otherwise falls back to static map.
|
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
* Example:
|
|
14
14
|
*
|
|
15
15
|
* ---
|
|
16
|
-
* purpose:
|
|
17
|
-
* cwd: ~/Projects/
|
|
16
|
+
* purpose: my-project website dev
|
|
17
|
+
* cwd: ~/Projects/my-project
|
|
18
18
|
* emoji: "🏢"
|
|
19
19
|
* color: "#6366f1"
|
|
20
|
-
* channels: ["
|
|
20
|
+
* channels: ["C01EXAMPLE"]
|
|
21
21
|
* ---
|
|
22
|
-
* You are the
|
|
22
|
+
* You are the my-project dev assistant. Stack: React + Express + Drizzle + MySQL.
|
|
23
23
|
* Prefer concise, directly actionable answers about deployment...
|
|
24
24
|
*
|
|
25
25
|
* If no workspaces are configured or no match is found, a built-in "default"
|
package/docs/security.md
CHANGED
|
@@ -127,7 +127,7 @@ By default sub-agents inherit the full tool set of the parent. v4.12.2 adds two
|
|
|
127
127
|
- **`research`** — `readonly` + `WebSearch`, `WebFetch`. Good for research tasks that need the web but shouldn't touch local files.
|
|
128
128
|
- **`full`** (default) — all tools.
|
|
129
129
|
|
|
130
|
-
You cannot (yet) set this from the Telegram `/agent` command — it's only available to plugin code and to
|
|
130
|
+
You cannot (yet) set this from the Telegram `/agent` command — it's only available to plugin code and to maintainer customizations. A future release (Phase 18) will expose it through the UI.
|
|
131
131
|
|
|
132
132
|
### 5. Network hardening
|
|
133
133
|
|
|
@@ -203,10 +203,10 @@ See the README Roadmap → Phase 18 for the full list.
|
|
|
203
203
|
|
|
204
204
|
## Reporting security issues
|
|
205
205
|
|
|
206
|
-
If you find a security vulnerability in Alvin Bot, **please do not open a public GitHub issue**.
|
|
206
|
+
If you find a security vulnerability in Alvin Bot, **please do not open a public GitHub issue**. Report it privately via GitHub Security Advisories:
|
|
207
207
|
|
|
208
|
-
- **
|
|
209
|
-
- **Subject:** `[SECURITY] alvin-bot — <short description>`
|
|
208
|
+
- **Advisory form:** https://github.com/alvbln/Alvin-Bot/security/advisories/new
|
|
209
|
+
- **Subject line:** `[SECURITY] alvin-bot — <short description>`
|
|
210
210
|
|
|
211
211
|
Include:
|
|
212
212
|
- Affected version (e.g. `alvin-bot@4.12.1`)
|