@hybridlabor-api/aos 4.7.1 → 4.8.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/.claude/workflows/startcycle-dispatch.mjs +1 -1
- package/bin/aos-doctor.mjs +23 -3
- package/bin/aos-store.mjs +5 -1
- package/installer.js +163 -70
- package/package.json +2 -1
- package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +8 -2
- package/skills/global_config/ask-tim/SKILL.md +1 -1
- package/docs/handover/agenttrail-for-ao.md +0 -58
- package/docs/handover/deja-for-ao.md +0 -51
|
@@ -823,7 +823,7 @@ if (mandatorySkillNames.length > 0) {
|
|
|
823
823
|
let message = `--skill named skill(s) that could not be found on this machine: ${missing.join(', ')}. `;
|
|
824
824
|
if (storeMatches.length > 0) {
|
|
825
825
|
message += `Found in the AOS / ECC Store: ${storeMatches.join(', ')}. ` +
|
|
826
|
-
`Run: aos store install ${storeMatches.join(' ')} ` +
|
|
826
|
+
`Run: aos store install ${storeMatches.join(' ')} --net ` +
|
|
827
827
|
'Then re-run your startcycle command. ';
|
|
828
828
|
} else if (near.length > 0) {
|
|
829
829
|
message += `Did you mean: ${near.join(', ')}? `;
|
package/bin/aos-doctor.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// node bin/aos-doctor.mjs [--json] [--net]
|
|
7
7
|
|
|
8
8
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
9
|
-
import { execFileSync
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
10
|
import { connect } from 'node:net';
|
|
11
11
|
import os from 'node:os';
|
|
12
12
|
import path from 'node:path';
|
|
@@ -49,7 +49,7 @@ const add = (area, name, ok, detail, fix, warningOnly = false) => {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
const which = (bin) => {
|
|
52
|
-
const probe = IS_WIN ? 'where' : 'which';
|
|
52
|
+
const probe = IS_WIN ? 'where.exe' : 'which';
|
|
53
53
|
try {
|
|
54
54
|
return tilde(execFileSync(probe, [bin], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim().split(/\r?\n/)[0]);
|
|
55
55
|
} catch {
|
|
@@ -107,6 +107,26 @@ function checkAosCore() {
|
|
|
107
107
|
manifest ? `v${manifest.version || '4.x'} · tier ${manifest.tier || 'full'} · modules: ${(manifest.installedModules || []).join(', ') || 'default'}` : `${manifestPath} missing or unreadable`,
|
|
108
108
|
'Run the AOS installer: npx @hybridlabor-api/aos@latest');
|
|
109
109
|
|
|
110
|
+
// Network version check gated by --net
|
|
111
|
+
if (NET) {
|
|
112
|
+
try {
|
|
113
|
+
const npmBin = IS_WIN ? 'npm.cmd' : 'npm';
|
|
114
|
+
const latest = execFileSync(npmBin, ['view', '@hybridlabor-api/aos', 'version'], {
|
|
115
|
+
encoding: 'utf8',
|
|
116
|
+
timeout: 8000,
|
|
117
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
118
|
+
shell: IS_WIN
|
|
119
|
+
}).trim();
|
|
120
|
+
const currentVer = manifest?.version || '4.7.1';
|
|
121
|
+
add('aos-core', 'Version vs npm', currentVer === latest, `local v${currentVer} · npm v${latest}`,
|
|
122
|
+
'Run: npx @hybridlabor-api/aos@latest');
|
|
123
|
+
} catch {
|
|
124
|
+
add('aos-core', 'Version vs npm', false, 'npm view failed (offline or network timeout)', 'Re-run with network or omit --net', true);
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
add('aos-core', 'Version vs npm', true, 'Skipped (offline mode — pass --net to check npm)', '', true);
|
|
128
|
+
}
|
|
129
|
+
|
|
110
130
|
// Check store index
|
|
111
131
|
const storeIndexCandidates = [
|
|
112
132
|
path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'lib', 'ecc-store-index.json'),
|
|
@@ -284,7 +304,7 @@ async function checkDaemonsAndModules() {
|
|
|
284
304
|
if (IS_MAC && aoBin) {
|
|
285
305
|
let signed = false;
|
|
286
306
|
try {
|
|
287
|
-
|
|
307
|
+
execFileSync('codesign', ['-v', aoBin], { stdio: 'ignore' });
|
|
288
308
|
signed = true;
|
|
289
309
|
} catch {}
|
|
290
310
|
add('security', 'AO Binary Signature (AMFI)', signed,
|
package/bin/aos-store.mjs
CHANGED
|
@@ -12,6 +12,7 @@ const index = JSON.parse(readFileSync(INDEX_PATH, 'utf8'));
|
|
|
12
12
|
const args = process.argv.slice(2);
|
|
13
13
|
const dryRun = args.includes('--dry-run');
|
|
14
14
|
const project = args.includes('--project');
|
|
15
|
+
const net = args.includes('--net');
|
|
15
16
|
const positional = args.filter((arg) => !arg.startsWith('--'));
|
|
16
17
|
const option = (name) => args.find((arg) => arg === name || arg.startsWith(`${name}=`))?.split('=').slice(1).join('=');
|
|
17
18
|
|
|
@@ -121,6 +122,9 @@ async function install(name) {
|
|
|
121
122
|
for (const target of targets) console.log(`[dry-run] write ${target}`);
|
|
122
123
|
return;
|
|
123
124
|
}
|
|
125
|
+
if (!net) {
|
|
126
|
+
throw new Error(`Downloading '${name}' requires internet access to fetch from upstream GitHub. Pass --net to confirm: aos store install ${name} --net`);
|
|
127
|
+
}
|
|
124
128
|
const content = await download(item.upstream_path);
|
|
125
129
|
const digest = createHash('sha256').update(content).digest('hex');
|
|
126
130
|
if (digest !== item.sha256) throw new Error(`SHA-256 mismatch for ${name}: expected ${item.sha256}, got ${digest}`);
|
|
@@ -135,7 +139,7 @@ function usage() {
|
|
|
135
139
|
console.log('Usage:');
|
|
136
140
|
console.log(' aos store list [--type=skills|agents]');
|
|
137
141
|
console.log(' aos store search <query>');
|
|
138
|
-
console.log(' aos store install <name> [--project] [--dry-run]');
|
|
142
|
+
console.log(' aos store install <name> [--project] [--dry-run] [--net]');
|
|
139
143
|
}
|
|
140
144
|
|
|
141
145
|
async function main() {
|
package/installer.js
CHANGED
|
@@ -41,7 +41,7 @@ function verifyDaemonListening(port, name, timeoutMs = 4000) {
|
|
|
41
41
|
tryConnect();
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
|
-
const { execSync, spawn, spawnSync, exec } = require('child_process');
|
|
44
|
+
const { execSync, spawn, spawnSync, exec, execFileSync } = require('child_process');
|
|
45
45
|
const clack = require('@clack/prompts');
|
|
46
46
|
|
|
47
47
|
const {
|
|
@@ -233,8 +233,12 @@ const conditionalMcpConfigKeys = [
|
|
|
233
233
|
|
|
234
234
|
function hasExecutable(binary) {
|
|
235
235
|
try {
|
|
236
|
-
|
|
237
|
-
|
|
236
|
+
if (process.platform === 'win32') {
|
|
237
|
+
execFileSync('where.exe', [binary], { stdio: 'ignore' });
|
|
238
|
+
} else {
|
|
239
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(binary)) return false;
|
|
240
|
+
execSync(`command -v ${binary}`, { stdio: 'ignore' });
|
|
241
|
+
}
|
|
238
242
|
return true;
|
|
239
243
|
} catch (e) {
|
|
240
244
|
return false;
|
|
@@ -250,7 +254,7 @@ function findWindowsGoBin() {
|
|
|
250
254
|
if (process.platform !== 'win32') return null;
|
|
251
255
|
const candidates = [];
|
|
252
256
|
try {
|
|
253
|
-
const found =
|
|
257
|
+
const found = execFileSync('where.exe', ['go'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
|
|
254
258
|
.split(/\r?\n/).map(s => s.trim()).find(Boolean);
|
|
255
259
|
if (found) candidates.push(path.dirname(found));
|
|
256
260
|
} catch (_) { /* PATH can be stale immediately after installation */ }
|
|
@@ -1705,7 +1709,8 @@ function checkModuleUpdate(pkgName, targetDir) {
|
|
|
1705
1709
|
let remoteVer = null;
|
|
1706
1710
|
let checkFailed = false;
|
|
1707
1711
|
try {
|
|
1708
|
-
|
|
1712
|
+
const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
1713
|
+
remoteVer = execFileSync(npmBin, ['view', pkgName, 'version'], { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8', timeout: 4000, shell: process.platform === 'win32' }).trim();
|
|
1709
1714
|
} catch (e) {
|
|
1710
1715
|
// Offline, a proxy, or a misconfigured registry. Swallowing this
|
|
1711
1716
|
// made every module report "up to date" forever on such a machine,
|
|
@@ -2046,13 +2051,23 @@ async function installSynapse() {
|
|
|
2046
2051
|
}
|
|
2047
2052
|
|
|
2048
2053
|
const isWin = process.platform === 'win32';
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2054
|
+
let binaryPath;
|
|
2055
|
+
if (isWin) {
|
|
2056
|
+
if (fs.existsSync(path.join(synapseDir, 'bin', 'synapse.exe'))) {
|
|
2057
|
+
binaryPath = path.join(synapseDir, 'bin', 'synapse.exe');
|
|
2058
|
+
} else if (fs.existsSync(path.join(synapseDir, 'bin', 'synapse-windows-amd64.exe'))) {
|
|
2059
|
+
binaryPath = path.join(synapseDir, 'bin', 'synapse-windows-amd64.exe');
|
|
2060
|
+
} else {
|
|
2061
|
+
binaryPath = path.join(synapseDir, 'bin', 'synapse.js');
|
|
2062
|
+
}
|
|
2063
|
+
} else {
|
|
2064
|
+
binaryPath = path.join(synapseDir, 'bin', 'synapse');
|
|
2065
|
+
if (!fs.existsSync(binaryPath)) {
|
|
2066
|
+
if (process.platform === 'darwin' && fs.existsSync(path.join(synapseDir, 'bin', 'synapse-darwin-arm64'))) {
|
|
2067
|
+
binaryPath = path.join(synapseDir, 'bin', 'synapse-darwin-arm64');
|
|
2068
|
+
} else if (process.platform === 'linux' && fs.existsSync(path.join(synapseDir, 'bin', 'synapse-linux-amd64'))) {
|
|
2069
|
+
binaryPath = path.join(synapseDir, 'bin', 'synapse-linux-amd64');
|
|
2070
|
+
}
|
|
2056
2071
|
}
|
|
2057
2072
|
}
|
|
2058
2073
|
|
|
@@ -2192,8 +2207,11 @@ async function installOpenWikiVisualizer() {
|
|
|
2192
2207
|
// bin dir, so resolve the absolute binary path now instead of relying on PATH at boot.
|
|
2193
2208
|
let openwikiBin = 'openwiki';
|
|
2194
2209
|
try {
|
|
2195
|
-
|
|
2196
|
-
|
|
2210
|
+
if (process.platform === 'win32') {
|
|
2211
|
+
openwikiBin = execFileSync('where.exe', ['openwiki'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).split(/\r?\n/)[0].trim() || 'openwiki';
|
|
2212
|
+
} else {
|
|
2213
|
+
openwikiBin = execSync('command -v openwiki', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).split(/\r?\n/)[0].trim() || 'openwiki';
|
|
2214
|
+
}
|
|
2197
2215
|
} catch (e) { logDebug(e, 'openwiki path lookup'); }
|
|
2198
2216
|
|
|
2199
2217
|
// The openwiki CLI is a Node script (#!/usr/bin/env node shebang). Under
|
|
@@ -2202,8 +2220,11 @@ async function installOpenWikiVisualizer() {
|
|
|
2202
2220
|
// the interpreter itself is baked in as an absolute path.
|
|
2203
2221
|
let nodeBin = 'node';
|
|
2204
2222
|
try {
|
|
2205
|
-
|
|
2206
|
-
|
|
2223
|
+
if (process.platform === 'win32') {
|
|
2224
|
+
nodeBin = execFileSync('where.exe', ['node'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).split(/\r?\n/)[0].trim() || 'node';
|
|
2225
|
+
} else {
|
|
2226
|
+
nodeBin = execSync('command -v node', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).split(/\r?\n/)[0].trim() || 'node';
|
|
2227
|
+
}
|
|
2207
2228
|
} catch (e) { logDebug(e, 'node path lookup'); }
|
|
2208
2229
|
|
|
2209
2230
|
// Which wiki :4321 serves is per-machine setup configuration, not a
|
|
@@ -2396,79 +2417,148 @@ async function installDevToolInstaller() {
|
|
|
2396
2417
|
}
|
|
2397
2418
|
}
|
|
2398
2419
|
|
|
2399
|
-
// AO
|
|
2400
|
-
// carries Windows and Linux sources but nothing built for them, and its CLI
|
|
2401
|
-
// shim picks the first candidate path that EXISTS without checking whether it
|
|
2402
|
-
// can run — so on any other platform it hands spawn a Mach-O file and fails
|
|
2403
|
-
// with an exec-format error instead of the "go build" hint it means to give.
|
|
2404
|
-
// Installing it there would be installing something known not to work.
|
|
2420
|
+
// AO is fully supported across macOS (arm64, amd64), Linux (amd64, arm64), and Windows (amd64).
|
|
2405
2421
|
function aoSupportedHere() {
|
|
2406
|
-
return
|
|
2422
|
+
return ['darwin', 'linux', 'win32'].includes(process.platform);
|
|
2407
2423
|
}
|
|
2408
2424
|
|
|
2409
2425
|
async function installOSAgentWorkspace() {
|
|
2410
2426
|
if (!aoSupportedHere()) {
|
|
2411
|
-
log.warn(`AO
|
|
2412
|
-
log.warn('Build it from source instead: github.com/hybridlabor-api/bdb-agent-orchestrator');
|
|
2427
|
+
log.warn(`AO is not supported on ${process.platform}/${process.arch}.`);
|
|
2413
2428
|
return;
|
|
2414
2429
|
}
|
|
2415
2430
|
|
|
2416
|
-
const
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2431
|
+
const isWin = process.platform === 'win32';
|
|
2432
|
+
const isMac = process.platform === 'darwin';
|
|
2433
|
+
const isLinux = process.platform === 'linux';
|
|
2434
|
+
|
|
2435
|
+
// Discover AO directory: check local development checkouts first, then moduleBasePath
|
|
2436
|
+
const candidateDirs = [
|
|
2437
|
+
path.join(homeDir, 'dev', 'agents', 'bdb-agent-orchestrator'),
|
|
2438
|
+
path.join(homeDir, 'dev', 'bdb-dev', 'bdb-agent-orchestrator'),
|
|
2439
|
+
path.join(moduleBasePath(), 'bdb-agent-orchestrator')
|
|
2440
|
+
];
|
|
2441
|
+
const osAgentDir = candidateDirs.find(d => fs.existsSync(d)) || candidateDirs[candidateDirs.length - 1];
|
|
2442
|
+
const isLocalGitRepo = fs.existsSync(path.join(osAgentDir, '.git'));
|
|
2443
|
+
|
|
2444
|
+
if (isLocalGitRepo) {
|
|
2445
|
+
log.step(`Using local development workspace for AO: ${osAgentDir}`);
|
|
2446
|
+
} else {
|
|
2447
|
+
if (!downloadOrUpdateModule('@hybridlabor-api/bdb-agent-orchestrator', osAgentDir, 'BDB Agent Orchestrator')) {
|
|
2448
|
+
log.warn('Skipping AO setup: the module could not be downloaded.');
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2420
2451
|
}
|
|
2421
2452
|
if (DRY_RUN) {
|
|
2422
|
-
log.step('[dry-run] would link
|
|
2453
|
+
log.step('[dry-run] would link ao binary and run `ao service install`');
|
|
2423
2454
|
return;
|
|
2424
2455
|
}
|
|
2425
2456
|
|
|
2426
|
-
const localBinDir =
|
|
2427
|
-
|
|
2428
|
-
|
|
2457
|
+
const localBinDir = isWin
|
|
2458
|
+
? path.join(process.env.LOCALAPPDATA || homeDir, 'Programs', 'ao')
|
|
2459
|
+
: path.join(homeDir, '.local', 'bin');
|
|
2460
|
+
const binTarget = path.join(localBinDir, isWin ? 'ao.exe' : 'ao');
|
|
2461
|
+
|
|
2462
|
+
// Platform-specific binary lookup
|
|
2463
|
+
const candidateBinaries = [];
|
|
2464
|
+
if (isWin) {
|
|
2465
|
+
candidateBinaries.push(
|
|
2466
|
+
path.join(osAgentDir, 'backend', 'bin', 'ao-windows-amd64.exe'),
|
|
2467
|
+
path.join(osAgentDir, 'backend', 'bin', 'ao.exe'),
|
|
2468
|
+
path.join(osAgentDir, 'backend', 'ao.exe')
|
|
2469
|
+
);
|
|
2470
|
+
} else if (isMac) {
|
|
2471
|
+
if (process.arch === 'arm64') {
|
|
2472
|
+
candidateBinaries.push(path.join(osAgentDir, 'backend', 'bin', 'ao-darwin-arm64'));
|
|
2473
|
+
} else {
|
|
2474
|
+
candidateBinaries.push(path.join(osAgentDir, 'backend', 'bin', 'ao-darwin-amd64'));
|
|
2475
|
+
}
|
|
2476
|
+
candidateBinaries.push(
|
|
2477
|
+
path.join(osAgentDir, 'backend', 'ao-daemon'),
|
|
2478
|
+
path.join(osAgentDir, 'backend', 'bin', 'ao'),
|
|
2479
|
+
path.join(osAgentDir, 'backend', 'ao')
|
|
2480
|
+
);
|
|
2481
|
+
} else if (isLinux) {
|
|
2482
|
+
if (process.arch === 'arm64') {
|
|
2483
|
+
candidateBinaries.push(path.join(osAgentDir, 'backend', 'bin', 'ao-linux-arm64'));
|
|
2484
|
+
} else {
|
|
2485
|
+
candidateBinaries.push(path.join(osAgentDir, 'backend', 'bin', 'ao-linux-amd64'));
|
|
2486
|
+
}
|
|
2487
|
+
candidateBinaries.push(
|
|
2488
|
+
path.join(osAgentDir, 'backend', 'ao-daemon'),
|
|
2489
|
+
path.join(osAgentDir, 'backend', 'bin', 'ao'),
|
|
2490
|
+
path.join(osAgentDir, 'backend', 'ao')
|
|
2491
|
+
);
|
|
2492
|
+
}
|
|
2493
|
+
let daemonBin = candidateBinaries.find(p => fs.existsSync(p));
|
|
2429
2494
|
|
|
2430
|
-
if
|
|
2431
|
-
|
|
2495
|
+
// Fallback: compile from Go source if Go is installed and binary is not pre-built
|
|
2496
|
+
if (!daemonBin && hasExecutable('go') && fs.existsSync(path.join(osAgentDir, 'backend', 'cmd', 'ao'))) {
|
|
2497
|
+
const s = spinner();
|
|
2498
|
+
s.start('Compiling AO daemon binary from Go source...');
|
|
2499
|
+
try {
|
|
2500
|
+
const buildTarget = path.join(osAgentDir, 'backend', 'bin', isWin ? 'ao.exe' : 'ao');
|
|
2501
|
+
fs.mkdirSync(path.dirname(buildTarget), { recursive: true });
|
|
2502
|
+
execFileSync('go', ['build', '-ldflags=-s -w', '-o', buildTarget, './cmd/ao'], {
|
|
2503
|
+
cwd: path.join(osAgentDir, 'backend'),
|
|
2504
|
+
stdio: 'ignore'
|
|
2505
|
+
});
|
|
2506
|
+
daemonBin = buildTarget;
|
|
2507
|
+
s.stop('Compiled AO daemon binary successfully.');
|
|
2508
|
+
} catch (e) {
|
|
2509
|
+
s.stop(`Failed to compile AO from source: ${e.message}`);
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
// Fallback: if already installed and functional, keep it
|
|
2514
|
+
if (!daemonBin && fs.existsSync(binTarget)) {
|
|
2515
|
+
try {
|
|
2516
|
+
execFileSync(binTarget, ['--version'], { stdio: 'ignore' });
|
|
2517
|
+
log.step(`Keeping existing working AO binary at ${binTarget}`);
|
|
2518
|
+
daemonBin = binTarget;
|
|
2519
|
+
} catch (_) {}
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
if (!daemonBin) {
|
|
2523
|
+
log.warn(`AO binary missing in ${osAgentDir} — package layout changed or Go build needed.`);
|
|
2432
2524
|
return;
|
|
2433
2525
|
}
|
|
2434
2526
|
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
}
|
|
2527
|
+
if (daemonBin !== binTarget) {
|
|
2528
|
+
installStep('place the ao binary', () => {
|
|
2529
|
+
fs.mkdirSync(localBinDir, { recursive: true });
|
|
2530
|
+
fs.copyFileSync(daemonBin, binTarget);
|
|
2531
|
+
if (!isWin) {
|
|
2532
|
+
fs.chmodSync(binTarget, 0o755);
|
|
2533
|
+
}
|
|
2534
|
+
log.step(`Installed ao to ${binTarget}`);
|
|
2535
|
+
}, 'AO cannot be started without its binary.');
|
|
2536
|
+
}
|
|
2445
2537
|
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
} catch {
|
|
2449
|
-
log.warn('The ao binary is not code-signed — AMFI will kill it at launch.');
|
|
2538
|
+
// macOS only: code-sign ad-hoc to prevent AMFI SIGKILL
|
|
2539
|
+
if (isMac) {
|
|
2450
2540
|
try {
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2541
|
+
execFileSync('codesign', ['-v', binTarget], { stdio: 'ignore' });
|
|
2542
|
+
} catch {
|
|
2543
|
+
log.warn('The ao binary is not code-signed — AMFI will kill it at launch.');
|
|
2544
|
+
try {
|
|
2545
|
+
execFileSync('codesign', ['-s', '-', '-f', binTarget], { stdio: 'ignore' });
|
|
2546
|
+
log.step('Signed it ad-hoc.');
|
|
2547
|
+
} catch (e) { log.warn(`Could not sign it: ${e.message}`); }
|
|
2548
|
+
}
|
|
2454
2549
|
}
|
|
2455
2550
|
|
|
2456
2551
|
// AO installs its own service, and does it for macOS, Windows and Linux.
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
// service definition changes upstream.
|
|
2464
|
-
const legacyPlist = path.join(homeDir, 'Library', 'LaunchAgents', 'com.bdb.agent-workspace.plist');
|
|
2465
|
-
if (fs.existsSync(legacyPlist)) {
|
|
2466
|
-
try { execSync(`launchctl unload "${legacyPlist}" 2>/dev/null`, { stdio: 'ignore' }); } catch (e) { logDebug(e, 'unload legacy ao agent'); }
|
|
2467
|
-
try { fs.unlinkSync(legacyPlist); log.step('Removed the old com.bdb.agent-workspace service.'); } catch (e) { logDebug(e, 'remove legacy plist'); }
|
|
2552
|
+
if (isMac) {
|
|
2553
|
+
const legacyPlist = path.join(homeDir, 'Library', 'LaunchAgents', 'com.bdb.agent-workspace.plist');
|
|
2554
|
+
if (fs.existsSync(legacyPlist)) {
|
|
2555
|
+
try { execFileSync('launchctl', ['unload', legacyPlist], { stdio: 'ignore' }); } catch (e) { logDebug(e, 'unload legacy ao agent'); }
|
|
2556
|
+
try { fs.unlinkSync(legacyPlist); log.step('Removed the old com.bdb.agent-workspace service.'); } catch (e) { logDebug(e, 'remove legacy plist'); }
|
|
2557
|
+
}
|
|
2468
2558
|
}
|
|
2469
2559
|
|
|
2470
2560
|
installStep('register the AO service', () => {
|
|
2471
|
-
|
|
2561
|
+
execFileSync(binTarget, ['service', 'install'], { stdio: 'ignore' });
|
|
2472
2562
|
}, 'Start it by hand with: ao service install');
|
|
2473
2563
|
|
|
2474
2564
|
if (await verifyDaemonListening(3101, 'AO Orchestrator', 8000)) {
|
|
@@ -2524,7 +2614,7 @@ function verifyEcosystemInstallation() {
|
|
|
2524
2614
|
{ name: '1. bdb-synapse', pkg: '@hybridlabor-api/bdb-synapse', paths: [path.join(moduleBasePath(), 'bdb-synapse')] },
|
|
2525
2615
|
{ name: '2. memB', pkg: '@hybridlabor-api/memb', paths: [path.join(moduleBasePath(), 'memB'), path.join(geminiDir, 'config', 'mcps', 'memb-mcp')] },
|
|
2526
2616
|
{ name: '3. heimdall-token-saver', pkg: '@hybridlabor-api/heimdall-token-saver', paths: [path.join(moduleBasePath(), 'heimdall-token-saver'), path.join(srcDir, 'vendor', 'token-saver')] },
|
|
2527
|
-
{ name: '4. AO Agent Orchestrator', pkg: '@hybridlabor-api/bdb-agent-orchestrator', paths: [path.join(moduleBasePath(), 'bdb-agent-orchestrator')] },
|
|
2617
|
+
{ name: '4. AO Agent Orchestrator', pkg: '@hybridlabor-api/bdb-agent-orchestrator', paths: [path.join(homeDir, 'dev', 'agents', 'bdb-agent-orchestrator'), path.join(moduleBasePath(), 'bdb-agent-orchestrator')] },
|
|
2528
2618
|
{ name: '5. bdb-dev-creator-extension', pkg: '@hybridlabor-api/bdb-dev-creator-extension', paths: [path.join(moduleBasePath(), 'bdb-dev-creator-extension')] },
|
|
2529
2619
|
{ name: '6. bdb-hardware-pcb', pkg: '@hybridlabor-api/bdb-hardware-pcb', paths: [path.join(moduleBasePath(), 'bdb-hardware-pcb')] },
|
|
2530
2620
|
{ name: '7. bdb-os-remote', pkg: '@hybridlabor-api/bdb-os-remote', paths: [path.join(moduleBasePath(), 'bdb-os-remote')] },
|
|
@@ -2548,7 +2638,8 @@ function verifyEcosystemInstallation() {
|
|
|
2548
2638
|
let newerVersion = null;
|
|
2549
2639
|
let newerTag = null;
|
|
2550
2640
|
try {
|
|
2551
|
-
const
|
|
2641
|
+
const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
2642
|
+
const distTagsJson = execFileSync(npmBin, ['view', mod.pkg, 'dist-tags', '--json'], { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8', timeout: 4000, shell: process.platform === 'win32' }).trim();
|
|
2552
2643
|
const distTags = JSON.parse(distTagsJson);
|
|
2553
2644
|
for (const [tag, ver] of Object.entries(distTags)) {
|
|
2554
2645
|
if (isNewerVersion(localVer, ver)) {
|
|
@@ -2942,8 +3033,11 @@ async function installMcpsForTarget(paths, ctx) {
|
|
|
2942
3033
|
|
|
2943
3034
|
let uvPath = 'uv';
|
|
2944
3035
|
try {
|
|
2945
|
-
|
|
2946
|
-
|
|
3036
|
+
if (process.platform === 'win32') {
|
|
3037
|
+
uvPath = execFileSync('where.exe', ['uv'], { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' }).trim().split(/\r?\n/)[0];
|
|
3038
|
+
} else {
|
|
3039
|
+
uvPath = execSync('command -v uv', { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' }).trim().split(/\r?\n/)[0];
|
|
3040
|
+
}
|
|
2947
3041
|
} catch (e) {
|
|
2948
3042
|
if (fs.existsSync(path.join(homeDir, '.local', 'bin', 'uv'))) uvPath = path.join(homeDir, '.local', 'bin', 'uv');
|
|
2949
3043
|
else if (fs.existsSync(path.join(homeDir, '.cargo', 'bin', 'uv'))) uvPath = path.join(homeDir, '.cargo', 'bin', 'uv');
|
|
@@ -4782,8 +4876,7 @@ async function runQuickUpdate(installState) {
|
|
|
4782
4876
|
if (subId === 'synapse') await installSynapse();
|
|
4783
4877
|
else if (subId === 'memb') await installMemB(false);
|
|
4784
4878
|
else if (subId === 'remote') await installOSRemoteGateway();
|
|
4785
|
-
|
|
4786
|
-
// existing installs -- see promptOptionalModules() for why.
|
|
4879
|
+
else if (subId === 'ao' && aoSupportedHere()) await installOSAgentWorkspace();
|
|
4787
4880
|
else if (subId === 'creator') await installCreatorExtension();
|
|
4788
4881
|
else if (subId === 'hardware') await installHardwarePcb();
|
|
4789
4882
|
else if (subId === 'installer') await installDevToolInstaller();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hybridlabor-api/aos",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.0",
|
|
4
4
|
"description": "AOS — A Curated AI AGENT OS. Optimized agent skills and add-ons like memB, OpenWiki, Heimdall Token Saver, and Godmode architectures.",
|
|
5
5
|
"main": "installer.js",
|
|
6
6
|
"engines": {
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
".codex-plugin/",
|
|
53
53
|
".opencode/",
|
|
54
54
|
"docs/",
|
|
55
|
+
"!docs/handover/**",
|
|
55
56
|
"assets/",
|
|
56
57
|
"skills/",
|
|
57
58
|
"mcps/",
|
|
@@ -52,7 +52,7 @@ const tilde = (p) => (p || '').replace(HOME, '~');
|
|
|
52
52
|
// `which` does not exist on native Windows; cmd ships `where`, which prints one
|
|
53
53
|
// path per line.
|
|
54
54
|
const which = (bin) => {
|
|
55
|
-
const probe = process.platform === 'win32' ? 'where' : 'which';
|
|
55
|
+
const probe = process.platform === 'win32' ? 'where.exe' : 'which';
|
|
56
56
|
try { return tilde(execFileSync(probe, [bin], { encoding: 'utf8' }).trim().split(/\r?\n/)[0]); } catch { return null; }
|
|
57
57
|
};
|
|
58
58
|
const dirCount = (p) => { try { return readdirSync(p, { withFileTypes: true }).filter(d => d.isDirectory()).length; } catch { return 0; } };
|
|
@@ -100,7 +100,13 @@ function checkAos() {
|
|
|
100
100
|
|
|
101
101
|
if (manifest && NET) {
|
|
102
102
|
try {
|
|
103
|
-
const
|
|
103
|
+
const npmBin = IS_WIN ? 'npm.cmd' : 'npm';
|
|
104
|
+
const latest = execFileSync(npmBin, ['view', '@hybridlabor-api/aos', 'version'], {
|
|
105
|
+
encoding: 'utf8',
|
|
106
|
+
timeout: 8000,
|
|
107
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
108
|
+
shell: IS_WIN
|
|
109
|
+
}).trim();
|
|
104
110
|
add('aos', 'version vs npm', manifest.version === latest, `local v${manifest.version} · npm v${latest}`,
|
|
105
111
|
'npx -y @hybridlabor-api/aos@latest');
|
|
106
112
|
} catch {
|
|
@@ -46,7 +46,7 @@ Reach for `test-driven-development` or `tdd-workflow` on their own when you want
|
|
|
46
46
|
When no native AOS skill fits, search the markdown-only ECC store before starting a pipeline:
|
|
47
47
|
|
|
48
48
|
- `aos store search <query>` finds available fallback skills and agents.
|
|
49
|
-
- `aos store install <name
|
|
49
|
+
- `aos store install <name> --net` installs a verified item after explicit user action.
|
|
50
50
|
- `aos store list --type=skills` and `aos store list --type=agents` show the catalogue.
|
|
51
51
|
- The core remains the trusted AOS skill set; the store is a fallback, not a reason to bypass the startcycle validation.
|
|
52
52
|
- A missing `--skill` is reported by the dispatcher with the exact install command. It never downloads during a run.
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# agenttrail for AO — waiting questions & GO/Nein handover
|
|
2
|
-
|
|
3
|
-
AO (the Go daemon shipped as `@hybridlabor-api/bdb-agent-orchestrator`, binary at `~/.local/bin/ao`) can mirror what the agenttrail live map shows: an agent that waits on the human appears as a question with GO / Nein, and AO surfaces "Needs you" in its own UI — then types the human's decision into the waiting session (approved_plan_go.md:25, :57). agenttrail is the live-map daemon vendored into AOS at `skills/global_config/agenttrail/bin/agenttrail.mjs`, run as `aos-trail`; one daemon per repo, bound to 127.0.0.1 only. This document lists the HTTP surfaces AO may call and the things it must never do. The ask/answer endpoints are the AOS patch defined in production_artifacts/00_execution_plan.md ({#ask-engine}, lines 8-13) and production_artifacts/approved_plan_go.md; `/whoami` and `/model` already exist in the vendored daemon (agenttrail.mjs:583-584, :674-675).
|
|
4
|
-
|
|
5
|
-
## What AO calls
|
|
6
|
-
|
|
7
|
-
### 1. Find the repo's daemon — `GET /whoami`
|
|
8
|
-
|
|
9
|
-
Probe `http://127.0.0.1:5330` through `5344` and match `repoPath` against the repo AO is working in — the same pattern the daemon itself uses for boot dedup (agenttrail.mjs:733-741):
|
|
10
|
-
|
|
11
|
-
```sh
|
|
12
|
-
curl -s http://127.0.0.1:$p/whoami
|
|
13
|
-
# {"project":"aos","port":5330,"repoPath":"/Users/tim/dev/bdb-dev/aos-wt-go"}
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
Stop at the first match. If no daemon answers there is nothing to mirror — do not spawn a daemon from AO; the asker's CLI already prints `start: aos-trail . --plan …` on stderr (00_execution_plan.md:5). The `AGENTTRAIL_PORT` env var pins a single port and wins over the probe range (agenttrail.mjs:91).
|
|
17
|
-
|
|
18
|
-
### 2. Read open questions — `GET /model`, `GET /ask/<id>`, `GET /events`
|
|
19
|
-
|
|
20
|
-
- `GET /model` carries `asks`: one object per question `{id, question, askedBy, askedAt, expiryAt, status: 'pending' | 'go' | 'deny', decidedAt, by, expired}`. Show every ask, oldest first (`askedAt` ascending — approved_plan_go.md:36).
|
|
21
|
-
- `GET /ask/<id>` returns one ask plus the last 10 decisions — the "wer · wann · was" log (00_execution_plan.md:13).
|
|
22
|
-
- `GET /events` is the SSE stream; activity ticks carry `asks` too (00_execution_plan.md:10), so AO can mirror live instead of polling.
|
|
23
|
-
|
|
24
|
-
A pending question past `expiryAt` flips to `status: 'deny'` with `expired: true` — render it greyed out (abgelaufen), never as a live GO/Nein pair (00_execution_plan.md:52).
|
|
25
|
-
|
|
26
|
-
### 3. Answer — `POST /answer`
|
|
27
|
-
|
|
28
|
-
```sh
|
|
29
|
-
curl -s -X POST http://127.0.0.1:$port/answer \
|
|
30
|
-
-H 'Content-Type: application/json' \
|
|
31
|
-
--data '{"id":"<ask-id>","decision":"go"}' # or "deny"
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
- The `content-type` header must be exactly `application/json` — a missing or different header (`text/plain`, JSON with a charset suffix) is rejected (00_execution_plan.md:13).
|
|
35
|
-
- The body `{id, decision}` is capped at 4 KB.
|
|
36
|
-
- `origin`: browser callers always send it, and it must equal the daemon's own `http://localhost:<port>` or `http://127.0.0.1:<port>`; AO is a local non-browser caller and may omit the header entirely (00_execution_plan.md:51 — the curl above does). If AO sends an `origin`, it must be the daemon's own.
|
|
37
|
-
- A valid answer sets the ask's status + `decidedAt` + `by: 'you'` and appends `{question, decision, by, at}` to the timestamped decision log; timeout denials land there too, as `by: 'timeout'` (00_execution_plan.md:13).
|
|
38
|
-
- The daemon binds 127.0.0.1 only (agenttrail.mjs:700) and sets no `Access-Control-*` header and no `OPTIONS` handler anywhere in its route chain (agenttrail.mjs:572-689) — that absence is the CSRF protection for the map's browser buttons. AO must not ask for CORS to be relaxed.
|
|
39
|
-
|
|
40
|
-
That is the whole surface: AO does not call the daemon's other mutating endpoints (`/spawn`, `/setup`, `/setup-board`, `/hook`) and never writes into the repo — the daemon itself never touches the repo; state lives under `~/.agenttrail` (agenttrail.mjs:307).
|
|
41
|
-
|
|
42
|
-
## What the answer means
|
|
43
|
-
|
|
44
|
-
`aos-trail ask "<question>"` blocks until the question is answered or expires and exits 0 on go, 1 on deny/timeout (00_execution_plan.md:5). The default deadline is 30 minutes, overridable per question via the asker's `--timeout <10s|30m|2h|N>`; expiry auto-denies and is logged like a human decision (00_execution_plan.md:52). Questions and the decision log persist across daemon restarts in `~/.agenttrail/<repo-hash>.json` (agenttrail.mjs:308).
|
|
45
|
-
|
|
46
|
-
## Mirroring in AO's UI
|
|
47
|
-
|
|
48
|
-
- Pending ask → a pinned "Needs you" card with the question, the asking agent (`askedBy`), elapsed time, and one GO and one Nein button.
|
|
49
|
-
- AO answers `POST /answer` only after the human clicked or said the decision for that exact ask id, and only then may it type that decision into the waiting session (approved_plan_go.md:57 — AO already recognizes "Needs You GO" and can write to the terminal, :25).
|
|
50
|
-
- Re-check `GET /ask/<id>` immediately before typing: the human may have answered on the map instead, or the question may have expired meanwhile.
|
|
51
|
-
|
|
52
|
-
## Hard rules
|
|
53
|
-
|
|
54
|
-
- The AO button never authorizes `git push`, `npm publish`, `npm version` or recursive `rm` (approved_plan_go.md:30). Those four stay under go-gate's sole authority (`.claude/hooks/go-gate.mjs:43-49`), which opens only for the literal, human-typed `GO` as the last message in the session transcript (go-gate.mjs:10-13, transcript-only check in main() :116-151). The ask/answer flow only resolves explicit `aos-trail ask` questions and never writes to any session transcript — the map and AO remain "an observer and an answer relay, never a permission system" (00_execution_plan.md:50, approved_plan_go.md:49).
|
|
55
|
-
- Never answer without the human. No batching, no defaults, no "the agent seemed sure" — one answer per explicit human decision, per question id.
|
|
56
|
-
- Never auto-retry a timed-out question. Expiry → deny is final (`by: 'timeout'` in the log); only the human asking again starts a new question (approved_plan_go.md:57; the same "no silent retries / a fresh GO per action" rule the gate runs on, go-gate.mjs:13).
|
|
57
|
-
- Never read `~/.agenttrail/<repo-hash>.json` or anything else inside `~/.agenttrail/` directly (agenttrail.mjs:307-308). Those files are internal state with no format contract — use the HTTP surfaces above.
|
|
58
|
-
- Keep the CSRF contract intact: exact `Content-Type: application/json`, own-or-absent `origin`, and never relay an answer through web content the agent loaded.
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# deja for AO — session-memory handover
|
|
2
|
-
|
|
3
|
-
AO (the Go daemon shipped as `@hybridlabor-api/bdb-agent-orchestrator`, binary at `~/.local/bin/ao`) can ask deja — the local session-memory engine installed together with memB by the AOS installer — for project history. This document lists the surfaces AO may call and the two things it must not do. Every command below is verified against deja-vu 0.21.1.
|
|
4
|
-
|
|
5
|
-
## What AO calls at session start
|
|
6
|
-
|
|
7
|
-
Run in the project directory:
|
|
8
|
-
|
|
9
|
-
```sh
|
|
10
|
-
deja wip --json
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
The answer is what the last session in that directory was doing (deja-vu docs/json-output.md:1192-1223):
|
|
14
|
-
|
|
15
|
-
```json
|
|
16
|
-
{
|
|
17
|
-
"schema_version": 2,
|
|
18
|
-
"session": "ses_fb76fff…",
|
|
19
|
-
"harness": "opencode",
|
|
20
|
-
"asked": "the orders worker is exhausting its database connections under load",
|
|
21
|
-
"decision": "bound every orders-worker query with a context",
|
|
22
|
-
"files": ["internal/worker/orders.go"],
|
|
23
|
-
"command": "go test ./internal/worker/...",
|
|
24
|
-
"command_failed": true,
|
|
25
|
-
"lines": ["working on: …", "settled: …"]
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
- `schema_version: 2` is the only guaranteed field. Every other field is optional and omitted when the transcript does not carry it — treat all of them as nullable.
|
|
30
|
-
- `lines` is the inject-verbatim form: one string per fact, in display order. A caller that wants to show the digest reads `lines` and does not re-assemble the fields.
|
|
31
|
-
|
|
32
|
-
## Untrusted historical data
|
|
33
|
-
|
|
34
|
-
Everything deja returns is transcript-derived and may carry text an attacker influenced: a directive copied from a web page persists in the index and replays into later sessions. deja-vu wraps its own agent-facing recall in `<deja-recall>` markers with an untrusted-data preamble (deja-vu docs/SECURITY-MODEL.md:235-245). AO must label or wrap the content the same way before it reaches a model, and instructions inside it must not be followed.
|
|
35
|
-
|
|
36
|
-
## Other surfaces AO may use
|
|
37
|
-
|
|
38
|
-
- `deja fix "<error>" --json` — what this machine ran after that error. Rows carry `command` or `edit`; `candidate: true` marks ran-next-but-unconfirmed evidence, not a guaranteed fix (deja-vu docs/json-output.md:763).
|
|
39
|
-
- `deja last --json` — recent session metadata, never message bodies (deja-vu docs/json-output.md:200).
|
|
40
|
-
- `deja handoff [id-prefix]` — the handoff digest for a session (deja-vu CLI reference, docs/guide/commands.html).
|
|
41
|
-
- `deja blame <path> --json` — which sessions discussed a file; a top-level JSON array, no envelope (deja-vu docs/json-output.md:1107).
|
|
42
|
-
- `deja doctor --json --offline` — index and store health; `--offline` keeps the version check off the network (deja-vu docs/json-output.md:356).
|
|
43
|
-
|
|
44
|
-
## Stability contract
|
|
45
|
-
|
|
46
|
-
Envelope-shaped answers carry `schema_version: 2`. Within a version, changes are additive only: existing field names, types and meanings stay the same. Bumping the version signals a breaking change, so branch on `schema_version` before parsing the rest of the envelope (deja-vu docs/json-output.md:7-28). `deja blame --json` returns a bare array and carries no version; its element shapes are stable.
|
|
47
|
-
|
|
48
|
-
## Hard rules
|
|
49
|
-
|
|
50
|
-
- Never read `~/.cache/deja/index.db` or anything else inside the index directory (`manifest.gob`, `sessions.gob`, `records.bin`, the buckets, the sidecars) directly. The format has moved forty-odd times and is explicitly not a contract (deja-vu docs/INTEGRATING.md:102-106). Use the CLI surfaces above.
|
|
51
|
-
- AO must NOT run `deja install --auto` and must not wire session-start auto-recall. The approved AOS plan rules both out (production_artifacts/approved_plan_deja.md:61-66): a second writer into agent configs, and old history injected automatically at every session start, are exactly the injection vectors AOS installed deja to avoid. AO calls the `deja` CLI itself, on demand, in the project directory.
|