@hybridlabor-api/aos 4.17.0 → 4.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/aos-bus.mjs +8 -4
- package/.claude/hooks/go-gate.mjs +1 -1
- package/.claude/hooks/go-token.mjs +17 -2
- package/.claude/hooks/memb-inject.mjs +61 -37
- package/.codex-plugin/plugin.json +1 -1
- package/.opencode/commands/bdb-aos-plan.md +1 -1
- package/README.md +1 -0
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/bin/aos-acp.mjs +27 -1
- package/bin/aos-doctor.mjs +36 -1
- package/bin/aos-uninstall.mjs +16 -2
- package/bin/go-check.mjs +79 -0
- package/bin/guarded-patterns.json +106 -0
- package/commands/plan.md +1 -1
- package/docs/codenotch.md +44 -0
- package/docs/codex-gate-smoke.md +43 -0
- package/docs/delegation-routing.md +32 -0
- package/docs/go-check.md +60 -0
- package/docs/master-session-acp.md +2 -0
- package/docs/opencode-setup.md +18 -0
- package/installer.js +155 -70
- package/lib/codenotch.js +389 -0
- package/lib/retired-skills.js +101 -0
- package/mcps/mcsc/README.md +1 -1
- package/mcps/mcsc/packages/core/src/adapters/agy.js +3 -1
- package/mcps/mcsc/packages/core/src/adapters/codex.js +2 -1
- package/mcps/mcsc/packages/core/src/adapters/opencode.js +2 -1
- package/mcps/mcsc/packages/core/src/depth.js +16 -0
- package/mcps/mcsc/packages/mcp/server.js +15 -2
- package/package.json +2 -2
- package/plugin-commands.json +1 -2
- package/plugin.json +1 -4
- package/plugins/bdb-aos-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/bdb-aos-codex/skills/plan/SKILL.md +1 -1
- package/scripts/codex-gate-smoke.mjs +73 -0
- package/skills/basic/master-session/SKILL.md +11 -0
- package/skills/global_config/agenttrail/SKILL.md +3 -1
- package/skills/global_config/agenttrail/bin/agenttrail.mjs +255 -117
- package/skills/global_config/agenttrail/bin/ensure.mjs +60 -40
- package/skills/global_config/agenttrail/bin/repoid.mjs +70 -0
- package/skills/global_config/agenttrail/public/index.html +9 -1
- package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +1 -1
- package/skills/global_config/bdb-memb-mcp/SKILL.md +5 -4
- package/skills/global_config/bdb-visual-edit/SKILL.md +28 -32
- package/skills/global_config/bdb-visual-edit/references/vite-react-source-attr.md +2 -2
- package/skills/global_config/bdb-visual-edit/scripts/locate-source.mjs +135 -0
- package/skills/global_config/bdb-visual-edit/scripts/sanitize-element.mjs +30 -2
- package/skills/global_config/mcsc/SKILL.md +9 -1
- package/skills/global_config/plan-arbiter/SKILL.md +1 -1
- package/skills/global_config/plan-canvas/SKILL.md +42 -4
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/README.md +1 -1
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/render.js +2 -2
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/geometry.js +76 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/index.js +596 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/model.js +192 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/toolbar.js +99 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-server.js +282 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotation-schema.js +210 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/route.js +10 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/sdk.js +6 -230
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +45 -4
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/sessions.js +60 -21
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/trail-on-approve.js +103 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +19 -7
- package/skills/global_config/plan-canvas/scripts/plan-canvas.js +118 -20
- package/skills/global_config/subagent-setup/SKILL.md +6 -0
- package/skills/global_config/subagent-setup/scripts/setup-subagents.mjs +20 -1
- package/skills/playbooks/pb-idea-to-launch/SKILL.md +2 -2
- package/skills/playbooks/pb-redesign-app/SKILL.md +3 -3
- package/skills/playbooks/pb-release-aos/SKILL.md +2 -2
- package/skills/playbooks/pb-ship/SKILL.md +2 -2
- package/skills/playbooks/pb-worktrees-land/SKILL.md +2 -2
- package/skills/global_config/bdb-visual-edit/scripts/pick-snippet.js +0 -27
- package/skills/global_config/visual-edit/README.md +0 -96
- package/skills/global_config/visual-edit/SKILL.md +0 -615
- package/skills/global_config/visual-plan/README.md +0 -93
- package/skills/global_config/visual-plan/SKILL.md +0 -544
- package/skills/global_config/visual-plan/references/canvas.md +0 -139
- package/skills/global_config/visual-plan/references/connection.md +0 -51
- package/skills/global_config/visual-plan/references/document-quality.md +0 -186
- package/skills/global_config/visual-plan/references/exemplar.md +0 -62
- package/skills/global_config/visual-plan/references/local-files.md +0 -99
- package/skills/global_config/visual-plan/references/wireframe.md +0 -319
- package/skills/global_config/visual-recap/README.md +0 -103
- package/skills/global_config/visual-recap/SKILL.md +0 -560
- package/skills/global_config/visual-recap/references/connection.md +0 -51
- package/skills/global_config/visual-recap/references/local-files.md +0 -99
- package/skills/global_config/visual-recap/references/wireframe.md +0 -319
|
@@ -68,17 +68,21 @@ export function touchSession(name, pid = process.pid) {
|
|
|
68
68
|
|
|
69
69
|
const alive = (pid) => { try { process.kill(pid, 0); return true; } catch (e) { return e.code === "EPERM"; } };
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
// prune: also delete registrations that are dead, stale or corrupt (registration files only, never inboxes).
|
|
72
|
+
export function listSessions({ prune = false } = {}) {
|
|
72
73
|
const dir = join(homedir(), ".aos", "bus", "sessions");
|
|
73
74
|
const out = [];
|
|
74
75
|
let files = [];
|
|
75
76
|
try { files = readdirSync(dir).filter((f) => f.endsWith(".json")); } catch { /* none */ }
|
|
76
77
|
for (const f of files) {
|
|
78
|
+
const file = join(dir, f);
|
|
79
|
+
let live = false;
|
|
77
80
|
try {
|
|
78
|
-
const file = join(dir, f);
|
|
79
81
|
const r = JSON.parse(readFileSync(file, "utf8"));
|
|
80
|
-
|
|
82
|
+
live = Number.isInteger(r.pid) && alive(r.pid) && Date.now() - lstatSync(file).mtimeMs < STALE_MS;
|
|
83
|
+
if (live) out.push(r);
|
|
81
84
|
} catch { /* corrupt entry */ }
|
|
85
|
+
if (prune && !live) { try { unlinkSync(file); } catch { /* already gone */ } }
|
|
82
86
|
}
|
|
83
87
|
return out;
|
|
84
88
|
}
|
|
@@ -134,7 +138,7 @@ if (isMain()) {
|
|
|
134
138
|
if (cmd === "send" && words.length >= 2) {
|
|
135
139
|
console.log(sendMessage(words[0], words.slice(1).join(" "), opts));
|
|
136
140
|
} else if (cmd === "list") {
|
|
137
|
-
for (const s of listSessions()) console.log(`${s.name}\tpid ${s.pid}\t${s.cwd}`);
|
|
141
|
+
for (const s of listSessions({ prune: true })) console.log(`${s.name}\tpid ${s.pid}\t${s.cwd}`);
|
|
138
142
|
} else {
|
|
139
143
|
console.error("usage: aos-bus send <name> <text...> [--from x] [--wake] | aos-bus list");
|
|
140
144
|
process.exit(2);
|
|
@@ -751,7 +751,7 @@ export function statusText(key, eff, now = Date.now()) {
|
|
|
751
751
|
export const listStateKeys = () => {
|
|
752
752
|
try { return readdirSync(gateDir()).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)); } catch { return []; }
|
|
753
753
|
};
|
|
754
|
-
const TOKEN_TTL_MS = 10 * 60 * 1000;
|
|
754
|
+
export const TOKEN_TTL_MS = 10 * 60 * 1000;
|
|
755
755
|
|
|
756
756
|
function ownSessionName(transcriptPath) {
|
|
757
757
|
let name = "";
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
// master session, record a single-use token that go-gate.mjs in <session-name>
|
|
4
4
|
// accepts. Writes nothing else and never blocks the prompt.
|
|
5
5
|
|
|
6
|
-
import { readFileSync, writeFileSync, mkdirSync, realpathSync } from "node:fs";
|
|
6
|
+
import { readFileSync, writeFileSync, mkdirSync, realpathSync, readdirSync, unlinkSync } from "node:fs";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { slug } from "./go-gate.mjs";
|
|
10
|
+
import { slug, TOKEN_TTL_MS } from "./go-gate.mjs";
|
|
11
11
|
|
|
12
12
|
function sessionName(transcriptPath) {
|
|
13
13
|
let name = "";
|
|
@@ -24,6 +24,20 @@ function sessionName(transcriptPath) {
|
|
|
24
24
|
return name || process.env.AOS_SESSION_NAME || "";
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// Expired or unreadable tokens are never read again by anyone, so a write is the moment to drop them.
|
|
28
|
+
export function pruneExpiredTokens(dir, now = Date.now()) {
|
|
29
|
+
let files = [];
|
|
30
|
+
try { files = readdirSync(dir).filter((f) => f.endsWith(".token")); } catch { return 0; }
|
|
31
|
+
let removed = 0;
|
|
32
|
+
for (const f of files) {
|
|
33
|
+
let age = NaN;
|
|
34
|
+
try { age = now - Date.parse(JSON.parse(readFileSync(join(dir, f), "utf8"))?.issued_at); } catch { /* unreadable: drop */ }
|
|
35
|
+
if (age >= 0 && age < TOKEN_TTL_MS) continue;
|
|
36
|
+
try { unlinkSync(join(dir, f)); removed++; } catch { /* already gone */ }
|
|
37
|
+
}
|
|
38
|
+
return removed;
|
|
39
|
+
}
|
|
40
|
+
|
|
27
41
|
// Returns the token path, or null. Never throws. An aos-acp worker is a worker
|
|
28
42
|
// by definition and must not mint a token for itself.
|
|
29
43
|
export function issueGoToken(prompt, { transcript_path, session_id, message_id } = {}) {
|
|
@@ -38,6 +52,7 @@ export function issueGoToken(prompt, { transcript_path, session_id, message_id }
|
|
|
38
52
|
if (!target || !tok) return null;
|
|
39
53
|
const dir = join(homedir(), ".aos", "go");
|
|
40
54
|
mkdirSync(dir, { recursive: true });
|
|
55
|
+
pruneExpiredTokens(dir);
|
|
41
56
|
const file = join(dir, `${target}.token`);
|
|
42
57
|
writeFileSync(file, JSON.stringify(tok));
|
|
43
58
|
return file;
|
|
@@ -1,32 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// aos-hook-version:
|
|
2
|
+
// aos-hook-version: 8
|
|
3
3
|
/**
|
|
4
4
|
* memB ambient memory hook for Claude Code, Google Antigravity, and OpenAI Codex.
|
|
5
5
|
*
|
|
6
6
|
* Stamped with aos-hook-version for /aos-setup doctor validation.
|
|
7
7
|
* Reads ~/.MemBDB/memb.db directly through node:sqlite and injects relevant
|
|
8
|
-
* project & user memories as ephemeral context
|
|
9
|
-
*
|
|
8
|
+
* project & user memories as ephemeral context, emitting only the key the
|
|
9
|
+
* calling harness reads:
|
|
10
|
+
* - hookSpecificOutput.additionalContext (Claude Code, OpenAI Codex)
|
|
10
11
|
* - injectSteps (Google Antigravity PreInvocation)
|
|
11
|
-
*
|
|
12
|
+
*
|
|
13
|
+
* Reads rows of the configured user (MEMB_USER_ID, fallback $USER) plus the
|
|
14
|
+
* group ids in MEMB_GROUP_IDS (comma list, default bdb_developer). Rows bound
|
|
15
|
+
* to the current project are injected whatever their category (max 5, 180
|
|
16
|
+
* chars each); global rows still need category `godmode`.
|
|
12
17
|
*
|
|
13
18
|
* Event-aware: SessionStart injects the persona file, the global godmode
|
|
14
19
|
* identity rows, and the current project's cards once per session; every
|
|
15
20
|
* other prompt (UserPromptSubmit) recalls only FTS keyword hits so 40
|
|
16
21
|
* identity facts are not re-sent on every message. Harnesses without a
|
|
17
22
|
* session hook (e.g. Antigravity PreInvocation) get the full block plus
|
|
18
|
-
* identity on
|
|
23
|
+
* identity on the first invocation of a conversation (cached per
|
|
24
|
+
* conversationId); later invocations recall FTS hits only.
|
|
19
25
|
*
|
|
20
26
|
* Fails open: any error exits 0 silently.
|
|
21
27
|
*/
|
|
22
28
|
|
|
23
|
-
import { readFileSync, existsSync, realpathSync } from 'node:fs';
|
|
29
|
+
import { readFileSync, existsSync, realpathSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
24
30
|
import os from 'node:os';
|
|
25
31
|
import path from 'node:path';
|
|
26
32
|
import { fileURLToPath } from 'node:url';
|
|
27
33
|
import { execFileSync } from 'node:child_process';
|
|
28
34
|
|
|
29
35
|
const COLLECTION = 'bdb_agent_memory';
|
|
36
|
+
const DEFAULT_GROUP_IDS = 'bdb_developer';
|
|
37
|
+
const PROJECT_ROW_CAP = 5;
|
|
38
|
+
const ROW_CHARS = 180;
|
|
30
39
|
const failOpen = () => process.exit(0);
|
|
31
40
|
|
|
32
41
|
// `deja wip --json` run in the project root, rendered as context lines.
|
|
@@ -51,6 +60,14 @@ export function dejaWip(cwd, run = execFileSync) {
|
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
|
|
63
|
+
// Rows are read for the user plus the configured group ids; nothing is
|
|
64
|
+
// hard-coded beyond the documented default group.
|
|
65
|
+
export function resolveReaderIds({ userId, env = process.env } = {}) {
|
|
66
|
+
const user = env.MEMB_USER_ID || userId || env.USER || env.LOGNAME || '';
|
|
67
|
+
const groups = String(env.MEMB_GROUP_IDS ?? DEFAULT_GROUP_IDS).split(',').map((g) => g.trim());
|
|
68
|
+
return Array.from(new Set([user, ...groups].filter(Boolean)));
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
// Filler words (German + English) that must never become FTS query terms —
|
|
55
72
|
// they match virtually any document and would inject unrelated projects.
|
|
56
73
|
const STOPWORDS = new Set([
|
|
@@ -160,14 +177,7 @@ export async function buildMemoryBlock({ event = '', prompt = '', cwd, userId }
|
|
|
160
177
|
const projectRoot = findProjectRoot(rawCandidateDir, home);
|
|
161
178
|
const candidateProjectIds = resolveProjectIdentifiers(projectRoot, home);
|
|
162
179
|
|
|
163
|
-
|
|
164
|
-
const activeUser =
|
|
165
|
-
process.env.MEMB_USER_ID ||
|
|
166
|
-
userId ||
|
|
167
|
-
process.env.USER ||
|
|
168
|
-
process.env.LOGNAME ||
|
|
169
|
-
'bdb_developer';
|
|
170
|
-
const candidateUsers = Array.from(new Set([activeUser, 'bdb_developer'])).filter(Boolean);
|
|
180
|
+
const candidateUsers = resolveReaderIds({ userId });
|
|
171
181
|
|
|
172
182
|
const contextItems = [];
|
|
173
183
|
|
|
@@ -259,12 +269,10 @@ export async function buildMemoryBlock({ event = '', prompt = '', cwd, userId }
|
|
|
259
269
|
OR json_extract(payload, '$.user_id') IN (${uPlaceholders})
|
|
260
270
|
OR json_extract(payload, '$.metadata.user_id') IN (${uPlaceholders})
|
|
261
271
|
)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
ORDER BY rowid DESC
|
|
267
|
-
LIMIT 5
|
|
272
|
+
ORDER BY (COALESCE(json_extract(payload, '$.category'),
|
|
273
|
+
json_extract(payload, '$.metadata.category')) = 'project_card') DESC,
|
|
274
|
+
rowid DESC
|
|
275
|
+
LIMIT ${PROJECT_ROW_CAP * 4}
|
|
268
276
|
`;
|
|
269
277
|
const params = [
|
|
270
278
|
COLLECTION,
|
|
@@ -276,11 +284,14 @@ export async function buildMemoryBlock({ event = '', prompt = '', cwd, userId }
|
|
|
276
284
|
...candidateUsers,
|
|
277
285
|
];
|
|
278
286
|
const rows = db.prepare(sql).all(...params);
|
|
287
|
+
let taken = 0;
|
|
279
288
|
for (const r of rows) {
|
|
289
|
+
if (taken >= PROJECT_ROW_CAP) break;
|
|
280
290
|
const text = textOf(r.payload);
|
|
281
|
-
if (text.length
|
|
282
|
-
|
|
283
|
-
}
|
|
291
|
+
if (text.length <= 10 || /^\[[^\]|]+\|[^\]|]+\|/.test(text)) continue; // too short / imported file chunk
|
|
292
|
+
if (contextItems.some((e) => e.includes(text.slice(0, 50)))) continue;
|
|
293
|
+
contextItems.push(`- Project [${candidateProjectIds[0]}]: ${text.slice(0, ROW_CHARS)}`);
|
|
294
|
+
taken++;
|
|
284
295
|
}
|
|
285
296
|
} catch { /* project query is best-effort */ }
|
|
286
297
|
}
|
|
@@ -316,7 +327,7 @@ export async function buildMemoryBlock({ event = '', prompt = '', cwd, userId }
|
|
|
316
327
|
const hitProject = projectOf(r.payload);
|
|
317
328
|
if (hitProject && !candidateProjectIds.includes(hitProject)) continue;
|
|
318
329
|
const cat = categoryOf(r.payload);
|
|
319
|
-
if (cat !== 'project_card' && cat !== 'godmode') continue;
|
|
330
|
+
if (!hitProject && cat !== 'project_card' && cat !== 'godmode') continue;
|
|
320
331
|
if (!contextItems.some((e) => e.includes(text.slice(0, 50)))) {
|
|
321
332
|
contextItems.push(`- Domain memory: ${text.slice(0, 180)}`);
|
|
322
333
|
}
|
|
@@ -338,6 +349,19 @@ export async function buildMemoryBlock({ event = '', prompt = '', cwd, userId }
|
|
|
338
349
|
}
|
|
339
350
|
}
|
|
340
351
|
|
|
352
|
+
// Antigravity has no session hook: remember per conversation that identity and
|
|
353
|
+
// project rows were already sent, so later invocations do only the FTS recall
|
|
354
|
+
// and skip `deja wip`. Stamps under ~/.aos/cache are disposable.
|
|
355
|
+
export function agyIdentitySent(conversationId, base = path.join(os.homedir(), '.aos', 'cache')) {
|
|
356
|
+
const id = String(conversationId || '').replace(/[^\w.-]/g, '_').slice(0, 80);
|
|
357
|
+
if (!id) return false;
|
|
358
|
+
const dir = path.join(base, 'memb-agy');
|
|
359
|
+
const stamp = path.join(dir, id);
|
|
360
|
+
if (existsSync(stamp)) return true;
|
|
361
|
+
try { mkdirSync(dir, { recursive: true }); writeFileSync(stamp, ''); } catch {}
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
|
|
341
365
|
// Main logic runs only when the file is executed directly as a hook; importing
|
|
342
366
|
// the module (e.g. from tests) must stay side-effect free.
|
|
343
367
|
async function main() {
|
|
@@ -351,26 +375,26 @@ async function main() {
|
|
|
351
375
|
|
|
352
376
|
// Discover candidate directory across harnesses:
|
|
353
377
|
// Claude / Codex: eventData.cwd; Antigravity: eventData.workspacePaths[0] or eventData.cwd
|
|
378
|
+
const isAgy = !eventData.hook_event_name && (Array.isArray(eventData.workspacePaths) || !!eventData.conversationId);
|
|
379
|
+
const event = isAgy && agyIdentitySent(eventData.conversationId)
|
|
380
|
+
? 'UserPromptSubmit'
|
|
381
|
+
: (eventData.hook_event_name || '');
|
|
354
382
|
const memoryBlock = await buildMemoryBlock({
|
|
355
|
-
event
|
|
383
|
+
event,
|
|
356
384
|
prompt: eventData.prompt || eventData.userPrompt || '',
|
|
357
385
|
cwd: (Array.isArray(eventData.workspacePaths) && eventData.workspacePaths[0]) || eventData.cwd,
|
|
358
386
|
userId: eventData.user_id || eventData.userId,
|
|
359
387
|
});
|
|
360
388
|
|
|
361
389
|
if (memoryBlock) {
|
|
362
|
-
process.stdout.write(JSON.stringify(
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}
|
|
371
|
-
],
|
|
372
|
-
systemMessage: memoryBlock,
|
|
373
|
-
}) + '\n');
|
|
390
|
+
process.stdout.write(JSON.stringify(isAgy
|
|
391
|
+
? { injectSteps: [{ ephemeralMessage: memoryBlock }] }
|
|
392
|
+
: {
|
|
393
|
+
hookSpecificOutput: {
|
|
394
|
+
hookEventName: eventData.hook_event_name || 'UserPromptSubmit',
|
|
395
|
+
additionalContext: memoryBlock,
|
|
396
|
+
},
|
|
397
|
+
}) + '\n');
|
|
374
398
|
}
|
|
375
399
|
process.exit(0);
|
|
376
400
|
} catch {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bdb-aos",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.18.1",
|
|
4
4
|
"description": "Full BDB Agent OS suite for Codex: command entry points ($bdb-aos:setup, $bdb-aos:init, ...) generated from plugin-commands.json.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Tim Rennings / Hybridlabor Global LLC",
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
description: "Plan end to end: draft, render in plan-canvas or Plan Builder, annotate, await approval, then hand off."
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
Run the full planning pipeline: 1) draft the plan with `concise-planning`; 2) always offer the choice between plan-canvas and Plan Builder (`
|
|
5
|
+
Run the full planning pipeline: 1) draft the plan with `concise-planning`; 2) always offer the choice between plan-canvas and Plan Builder (`aos-plan-canvas open <dir> --mode bdb-plan-builder`) for rendering; 3) collect annotations and wait for the human's approval; 4) only after approval continue with `writing-plans` or `/bdb-aos-graph`. Topic: $ARGUMENTS
|
package/README.md
CHANGED
|
@@ -118,6 +118,7 @@ What the installer writes for each target. Paths are the defaults; the installer
|
|
|
118
118
|
| Roo Code / Cline | `~/.roo/skills` | — | — | `.roomodes` (project) |
|
|
119
119
|
| Aider | `~/.aider/bdb-skills` | — | — | — |
|
|
120
120
|
| AOS CLI (`pi`) | reads `~/.agents/skills` | `~/.agents/AGENTS.md` as system prompt | — | no MCP; separate install, Node >= 22.19 — see [packages/aos-cli](packages/aos-cli/README.md) |
|
|
121
|
+
| BDB AO Codenotch (macOS app, Windows installer) | macOS `/Applications` or `~/Applications`; Windows per-user NSIS install (`/S`, no admin) | — | — | installed by default on macOS and Windows (never Linux); opt out with `--no-codenotch` or `AOS_CODENOTCH=0`; failures only warn; DMG from the public releases repo created by Tim, SHA-256 verified, ad-hoc signed with quarantine removed, see [docs/codenotch.md](docs/codenotch.md) |
|
|
121
122
|
|
|
122
123
|
Every install also writes the universal copy to `~/.agents/skills`, which is what the AOS CLI and the `skills` CLI read.
|
|
123
124
|
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -309,7 +309,7 @@ dependency.
|
|
|
309
309
|
|
|
310
310
|
<https://github.com/BuilderIO/skills> — MIT. Copyright (c) 2026 Builder.io.
|
|
311
311
|
|
|
312
|
-
|
|
312
|
+
Eight skills are adapted under `skills/global_config/`: `read-the-damn-docs`, `stay-within-limits`, `quick-recap`, `plan-arbiter`, `factory-human-digest`, `factory-collect`, `factory-lookback`, and `factory-review-prs`. The four Factory skills and `plan-arbiter` have `installer-group` dropped and `category`/`source` added; in `factory-collect`, `factory-lookback` and `factory-review-prs` reply, close, approve, merge and publish actions are reduced to drafts or readiness reports that need the user's GO, and all of them carry an "AOS safety rules" section. `plan-arbiter` drops `agents/openai.yaml` and adds an AOS section on double plans. `stay-within-limits` and `quick-recap` are carried over with `category:` and `source:` added; `stay-within-limits` pins `ccusage` to 20.0.26 instead of `@latest`, and `quick-recap` no longer asks the installer to inject a managed `AGENTS.md` / `CLAUDE.md` block. `read-the-damn-docs` is described below:
|
|
313
313
|
the docs-first trigger list, the source hierarchy, the required workflow, the
|
|
314
314
|
must-trigger examples, and the "if docs are unavailable" rule. AOS additionally
|
|
315
315
|
adds a Verification section, a `category:` key (Builder's own validator forbids
|
|
@@ -318,7 +318,7 @@ guesswork* rule, and a pointer to the installed `firecrawl-search` /
|
|
|
318
318
|
`firecrawl-scrape` tools. Prose only — the upstream `agents/openai.yaml` and
|
|
319
319
|
README were not carried over, and no executable from the repo is referenced.
|
|
320
320
|
|
|
321
|
-
|
|
321
|
+
The other 13 skills in that repo (as of commit 0dba9ef) are **not** vendored. Three of them
|
|
322
322
|
(`an`, `turn-into-app`, `rewind`) plus three others
|
|
323
323
|
are build artefacts synced from a different upstream by
|
|
324
324
|
`scripts/sync-agent-native-skills.mjs`, and the repo's own documentation says
|
package/bin/aos-acp.mjs
CHANGED
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
15
|
import { appendFileSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
|
|
16
16
|
import { homedir } from "node:os";
|
|
17
|
-
import { dirname, join } from "node:path";
|
|
17
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
19
|
import { createInterface } from "node:readline";
|
|
20
20
|
import { GUARDED_PATTERNS, slug, tokenGrantsGo } from "../.claude/hooks/go-gate.mjs";
|
|
21
|
+
import { emitTrail } from "../mcps/mcsc/packages/core/src/trail.js";
|
|
21
22
|
|
|
22
23
|
export const ADAPTERS = {
|
|
23
24
|
claude: { cmd: ["npx", "-y", "@agentclientprotocol/claude-agent-acp"], consume: false },
|
|
@@ -113,6 +114,25 @@ export class AcpClient {
|
|
|
113
114
|
notify(method, params) { this.write({ jsonrpc: "2.0", method, params }); }
|
|
114
115
|
}
|
|
115
116
|
|
|
117
|
+
// Same agenttrail protocol as mcsc's adapters (SessionStart/SessionEnd, PreToolUse/PostToolUse),
|
|
118
|
+
// so the GO board reads ACP workers and mcsc runs from one source.
|
|
119
|
+
const cap = (t) => (t ? t.charAt(0).toUpperCase() + t.slice(1) : "Tool");
|
|
120
|
+
|
|
121
|
+
export function trailToolEvent(u, seen, cwd) {
|
|
122
|
+
const prev = seen.get(u.toolCallId) || {};
|
|
123
|
+
const info = { title: u.title ?? prev.title, kind: u.kind ?? prev.kind };
|
|
124
|
+
seen.set(u.toolCallId, info);
|
|
125
|
+
const done = u.status === "completed" || u.status === "failed";
|
|
126
|
+
if (u.sessionUpdate === "tool_call_update" && !done) return null;
|
|
127
|
+
const tool_input = {};
|
|
128
|
+
const file = u.locations?.[0]?.path ?? prev.file;
|
|
129
|
+
if (typeof file === "string" && file) tool_input.file_path = isAbsolute(file) ? file : resolve(cwd, file);
|
|
130
|
+
info.file = tool_input.file_path;
|
|
131
|
+
const command = commandOf(u);
|
|
132
|
+
if (command) tool_input.command = command;
|
|
133
|
+
return { hook_event_name: done ? "PostToolUse" : "PreToolUse", tool_name: cap(info.kind || info.title), tool_input };
|
|
134
|
+
}
|
|
135
|
+
|
|
116
136
|
function textOf(block) {
|
|
117
137
|
return block && block.type === "text" ? block.text : "";
|
|
118
138
|
}
|
|
@@ -134,6 +154,9 @@ export async function run(opts) {
|
|
|
134
154
|
const consume = opts.consume ?? adapter?.consume ?? true;
|
|
135
155
|
log("start", { adapter: opts.adapter, cmd: cmd.join(" "), cwd: opts.cwd, allow_default: opts.allowDefault, consume, model: opts.model || "adapter default" });
|
|
136
156
|
|
|
157
|
+
const trail = { session_id: `aos-acp-${process.pid}-${Date.now()}`, cwd: opts.cwd, agent: `${opts.adapter}:${opts.name}` };
|
|
158
|
+
const seenTools = new Map();
|
|
159
|
+
emitTrail({ ...trail, hook_event_name: "SessionStart" });
|
|
137
160
|
let sessionId;
|
|
138
161
|
const out = opts.stdout || process.stdout;
|
|
139
162
|
const client = new AcpClient(cmd, {
|
|
@@ -148,6 +171,8 @@ export async function run(opts) {
|
|
|
148
171
|
if (t) { out.write(t); log("text", { text: t }); }
|
|
149
172
|
} else if (u.sessionUpdate === "tool_call" || u.sessionUpdate === "tool_call_update") {
|
|
150
173
|
log(u.sessionUpdate, { id: u.toolCallId, title: u.title, kind: u.kind, status: u.status, command: commandOf(u) || undefined });
|
|
174
|
+
const ev = trailToolEvent(u, seenTools, opts.cwd);
|
|
175
|
+
if (ev) emitTrail({ ...trail, ...ev });
|
|
151
176
|
} else log("update", { kind: u.sessionUpdate });
|
|
152
177
|
},
|
|
153
178
|
onRequest: (method, p) => {
|
|
@@ -182,6 +207,7 @@ export async function run(opts) {
|
|
|
182
207
|
} finally {
|
|
183
208
|
if (timer) clearTimeout(timer);
|
|
184
209
|
client.child.kill();
|
|
210
|
+
await emitTrail({ ...trail, hook_event_name: "SessionEnd" });
|
|
185
211
|
}
|
|
186
212
|
}
|
|
187
213
|
|
package/bin/aos-doctor.mjs
CHANGED
|
@@ -169,6 +169,40 @@ function checkAosCore() {
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
// JSONC to JSON: drops comments (not inside strings) and trailing commas.
|
|
173
|
+
function parseJsonc(text) {
|
|
174
|
+
let out = '';
|
|
175
|
+
for (let i = 0, str = false; i < text.length; i++) {
|
|
176
|
+
const c = text[i];
|
|
177
|
+
if (str) { out += c; if (c === '\\') out += text[++i] ?? ''; else if (c === '"') str = false; continue; }
|
|
178
|
+
if (c === '"') { str = true; out += c; continue; }
|
|
179
|
+
if (c === '/' && text[i + 1] === '/') { while (i < text.length && text[i] !== '\n') i++; out += '\n'; continue; }
|
|
180
|
+
if (c === '/' && text[i + 1] === '*') { i += 2; while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) i++; i++; continue; }
|
|
181
|
+
out += c;
|
|
182
|
+
}
|
|
183
|
+
try { return JSON.parse(out.replace(/,(\s*[}\]])/g, '$1')); } catch { return null; }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Zen gateway limit: a tool name (OpenCode builds it from the MCP server name) is at most 64 characters.
|
|
187
|
+
// Names only, no network: the tool part of the name is not known here.
|
|
188
|
+
const ZEN_TOOL_NAME_MAX = 64;
|
|
189
|
+
|
|
190
|
+
function checkOpencodeMcpNames() {
|
|
191
|
+
const dirs = [process.platform === 'win32' ? path.join(process.env.APPDATA || HOME, 'opencode') : h('.config', 'opencode')];
|
|
192
|
+
const file = firstExisting(dirs.flatMap((d) => ['opencode.jsonc', 'opencode.json'].map((f) => path.join(d, f))));
|
|
193
|
+
if (!file) return;
|
|
194
|
+
const conf = parseJsonc(readFileSync(file, 'utf8'));
|
|
195
|
+
if (!conf) {
|
|
196
|
+
add('harnesses', 'OpenCode MCP names', false, `${tilde(file)} is not valid JSON(C); names not checked`, 'Fix the OpenCode config syntax.', true);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const names = Object.keys(conf.mcp && typeof conf.mcp === 'object' ? conf.mcp : {});
|
|
200
|
+
const tooLong = names.filter((n) => n.length > ZEN_TOOL_NAME_MAX);
|
|
201
|
+
add('harnesses', 'OpenCode MCP names', tooLong.length === 0,
|
|
202
|
+
tooLong.length ? `${tooLong.length} MCP name(s) over ${ZEN_TOOL_NAME_MAX} characters (Zen gateway limit): ${tooLong.join(', ')}` : `${names.length} MCP name(s) in ${tilde(file)}, none over ${ZEN_TOOL_NAME_MAX} characters`,
|
|
203
|
+
`Rename the MCP server(s) in ${tilde(file)} to a shorter name; tool names are built from them and a name over ${ZEN_TOOL_NAME_MAX} characters is rejected by the Zen gateway. Keeping the MCP set lean also helps.`, true);
|
|
204
|
+
}
|
|
205
|
+
|
|
172
206
|
// ---------------------------------------------------------------- 3. Harness Placement & Skills Sync
|
|
173
207
|
function checkHarnesses() {
|
|
174
208
|
const harnesses = [
|
|
@@ -199,12 +233,13 @@ function checkHarnesses() {
|
|
|
199
233
|
add('harnesses', 'OpenCode Plugin', !!opencodePlugin,
|
|
200
234
|
opencodePlugin ? tilde(opencodePlugin) : 'bdb-aos.js not installed in OpenCode plugins',
|
|
201
235
|
'Run the AOS installer to wire OpenCode telemetry plugin.', true);
|
|
236
|
+
checkOpencodeMcpNames();
|
|
202
237
|
}
|
|
203
238
|
|
|
204
239
|
// ---------------------------------------------------------------- 4. Hooks & Security Gates
|
|
205
240
|
function checkHooks() {
|
|
206
241
|
const claudeHooksDir = h('.claude', 'hooks');
|
|
207
|
-
const EXPECTED_VERSION = { 'memb-inject.mjs':
|
|
242
|
+
const EXPECTED_VERSION = { 'memb-inject.mjs': 8 };
|
|
208
243
|
const versionOf = (text) => {
|
|
209
244
|
const m = /^\/\/\s*aos-hook-version:\s*(\d+)/m.exec(text);
|
|
210
245
|
return m ? Number(m[1]) : null;
|
package/bin/aos-uninstall.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import path from 'node:path';
|
|
|
25
25
|
import { createRequire } from 'node:module';
|
|
26
26
|
|
|
27
27
|
const pm = createRequire(import.meta.url)('../lib/plugin-migration.js');
|
|
28
|
+
const cn = createRequire(import.meta.url)('../lib/codenotch.js');
|
|
28
29
|
|
|
29
30
|
const HOME = os.homedir();
|
|
30
31
|
const h = (...p) => path.join(HOME, ...p);
|
|
@@ -103,7 +104,9 @@ function plan() {
|
|
|
103
104
|
const legacy = LEGACY_MARKERS.filter(existsSync);
|
|
104
105
|
const data = PURGE ? DATA_PATHS.filter((d) => existsSync(d.path)) : [];
|
|
105
106
|
|
|
106
|
-
|
|
107
|
+
const codenotch = cn.planCodenotchUninstall({ stateFile: cn.stateFilePath(HOME) });
|
|
108
|
+
|
|
109
|
+
return { manifest, ours, edited, gone, agents, modules, legacy, data, codenotch };
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
function describe(p) {
|
|
@@ -140,6 +143,12 @@ function describe(p) {
|
|
|
140
143
|
for (const l of legacy) console.log(` ${tilde(l)}`);
|
|
141
144
|
}
|
|
142
145
|
|
|
146
|
+
if (p.codenotch) {
|
|
147
|
+
const c = p.codenotch;
|
|
148
|
+
const note = { remove: 'is removed (still the recorded build)', keep: 'is kept (replaced or modified since AOS installed it)', gone: 'is already gone' }[c.action];
|
|
149
|
+
console.log(`\nBDB AO Codenotch ${tilde(c.state.path || c.state.uninstallPath)} ${note}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
143
152
|
console.log('\nBleibt erhalten:');
|
|
144
153
|
if (!PURGE) for (const d of DATA_PATHS.filter((d) => existsSync(d.path))) console.log(` ${tilde(d.path).padEnd(18)} ${d.what}`);
|
|
145
154
|
console.log(' jede Datei ohne Manifest-Eintrag — eigene Skills, fremde Hooks, alles Selbstgeschriebene');
|
|
@@ -169,7 +178,7 @@ function dirSize(p) {
|
|
|
169
178
|
|
|
170
179
|
// ------------------------------------------------------------------ execute
|
|
171
180
|
function execute(p) {
|
|
172
|
-
const { ours, edited, agents, modules, legacy, data } = p;
|
|
181
|
+
const { ours, edited, agents, modules, legacy, data, codenotch } = p;
|
|
173
182
|
const stamp = new Date().toISOString().replace(/[:.]/g, '').slice(0, 15);
|
|
174
183
|
let removed = 0, backed = 0;
|
|
175
184
|
|
|
@@ -242,6 +251,11 @@ function execute(p) {
|
|
|
242
251
|
} catch { console.log(` ${tilde(codexToml)} nicht lesbar — von Hand prüfen`); }
|
|
243
252
|
}
|
|
244
253
|
|
|
254
|
+
if (codenotch) {
|
|
255
|
+
const r = cn.uninstallCodenotch(codenotch, { stateFile: cn.stateFilePath(HOME) });
|
|
256
|
+
console.log(` Codenotch: ${r === 'remove' ? 'removed' : r === 'keep' ? 'kept, not the recorded build' : r === 'error' ? 'uninstaller failed, left in place' : 'already gone'}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
245
259
|
for (const l of legacy) { try { rmSync(l); } catch { /* already gone */ } }
|
|
246
260
|
if (legacy.length) console.log(` ${legacy.length} Installations-Marker entfernt`);
|
|
247
261
|
|
package/bin/go-check.mjs
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// go-check: one fixed answer to "may this worker run this guarded command?" for callers that
|
|
3
|
+
// are not a harness hook (AO). Installed at ~/.aos/bin/go-check.mjs.
|
|
4
|
+
//
|
|
5
|
+
// node go-check.mjs --session <Name> --command "<shell string>" [--consume]
|
|
6
|
+
// node go-check.mjs --write-patterns <file>
|
|
7
|
+
//
|
|
8
|
+
// stdout: {"guarded":bool,"ok":bool,"scope":[...]|null,"reason":"..."}
|
|
9
|
+
// exit 0 allowed (not guarded, or a valid GO token; --consume uses it up), 1 guarded without a
|
|
10
|
+
// valid GO, 2 error (the caller must treat 2 as denied). Only `GO <Name>` tokens count: mode
|
|
11
|
+
// soft grants and mode off belong to the human session and are never read here. Nothing in
|
|
12
|
+
// this file writes a token; the tool is synchronous, offline and reads no stdin.
|
|
13
|
+
|
|
14
|
+
import { existsSync, realpathSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
17
|
+
|
|
18
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
|
|
20
|
+
async function loadGate() {
|
|
21
|
+
for (const f of [join(here, "go-gate.mjs"), join(here, "..", ".claude", "hooks", "go-gate.mjs")]) {
|
|
22
|
+
if (existsSync(f)) return import(pathToFileURL(f).href);
|
|
23
|
+
}
|
|
24
|
+
throw new Error("go-gate.mjs not found next to go-check.mjs or in ../.claude/hooks");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const patternsJson = (gate) => ({
|
|
28
|
+
version: 1,
|
|
29
|
+
source: "go-gate.mjs GUARDED_PATTERNS (JavaScript RegExp syntax, lookaheads included; not RE2)",
|
|
30
|
+
scopes: gate.SCOPES,
|
|
31
|
+
patterns: gate.GUARDED_PATTERNS.map((r) => ({ source: r.source, flags: r.flags })),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export function check(gate, { session, command, consume = false }) {
|
|
35
|
+
if (typeof command !== "string" || !command.trim()) return { code: 2, out: { guarded: null, ok: false, scope: null, reason: "--command is empty" } };
|
|
36
|
+
const storeReason = gate.gateStoreReason(command);
|
|
37
|
+
if (storeReason) return { code: 1, out: { guarded: true, ok: false, scope: null, reason: storeReason } };
|
|
38
|
+
if (!gate.isGuardedCommand(command)) return { code: 0, out: { guarded: false, ok: true, scope: [], reason: "" } };
|
|
39
|
+
const scope = gate.commandScopes(command);
|
|
40
|
+
if (typeof session !== "string" || !gate.slug(session)) {
|
|
41
|
+
return { code: 2, out: { guarded: true, ok: false, scope, reason: "--session is missing or has no usable name" } };
|
|
42
|
+
}
|
|
43
|
+
const r = gate.tokenGrantsGo(session, { consume });
|
|
44
|
+
return r.ok
|
|
45
|
+
? { code: 0, out: { guarded: true, ok: true, scope, reason: consume ? "GO token consumed" : "GO token valid (not consumed)" } }
|
|
46
|
+
: { code: 1, out: { guarded: true, ok: false, scope, reason: r.reason } };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function parseArgs(argv) {
|
|
50
|
+
const o = {};
|
|
51
|
+
for (let i = 0; i < argv.length; i++) {
|
|
52
|
+
const a = argv[i];
|
|
53
|
+
if (a === "--session") o.session = argv[++i];
|
|
54
|
+
else if (a === "--command") o.command = argv[++i];
|
|
55
|
+
else if (a === "--write-patterns") o.writePatterns = argv[++i];
|
|
56
|
+
else if (a === "--consume") o.consume = true;
|
|
57
|
+
else throw new Error(`unknown argument ${a}`);
|
|
58
|
+
}
|
|
59
|
+
return o;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const done = (code, out) => process.stdout.write(JSON.stringify(out) + "\n", () => process.exit(code));
|
|
63
|
+
|
|
64
|
+
const invokedAs = (() => { try { return realpathSync(process.argv[1] || ""); } catch { return ""; } })();
|
|
65
|
+
if (invokedAs === fileURLToPath(import.meta.url)) {
|
|
66
|
+
try {
|
|
67
|
+
const o = parseArgs(process.argv.slice(2));
|
|
68
|
+
const gate = await loadGate();
|
|
69
|
+
if (o.writePatterns) {
|
|
70
|
+
writeFileSync(o.writePatterns, JSON.stringify(patternsJson(gate), null, 2) + "\n");
|
|
71
|
+
done(0, { ok: true, reason: `wrote ${o.writePatterns}` });
|
|
72
|
+
} else {
|
|
73
|
+
const { code, out } = check(gate, o);
|
|
74
|
+
done(code, out);
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
done(2, { guarded: null, ok: false, scope: null, reason: String(e?.message || e).slice(0, 300) });
|
|
78
|
+
}
|
|
79
|
+
}
|