aos-harness 0.8.3 → 0.8.5
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/core/profiles/strategic-council/profile.yaml +1 -1
- package/package.json +7 -7
- package/src/adapter-session.ts +53 -4
- package/src/commands/run.ts +120 -18
- package/src/env-scanner.ts +22 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aos-harness",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"description": "Agentic Orchestration System — assemble AI agents into deliberation and execution teams",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -38,18 +38,18 @@
|
|
|
38
38
|
"test": "bun run src/index.ts validate"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@aos-harness/adapter-shared": "0.8.
|
|
42
|
-
"@aos-harness/runtime": "0.8.
|
|
41
|
+
"@aos-harness/adapter-shared": "0.8.5",
|
|
42
|
+
"@aos-harness/runtime": "0.8.5",
|
|
43
43
|
"@clack/prompts": "^1.2.0",
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
45
45
|
"js-yaml": "^4.1.0",
|
|
46
46
|
"yaml": "^2.8.3"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@aos-harness/claude-code-adapter": ">=0.8.
|
|
50
|
-
"@aos-harness/codex-adapter": ">=0.8.
|
|
51
|
-
"@aos-harness/gemini-adapter": ">=0.8.
|
|
52
|
-
"@aos-harness/pi-adapter": ">=0.8.
|
|
49
|
+
"@aos-harness/claude-code-adapter": ">=0.8.5 <1.0.0",
|
|
50
|
+
"@aos-harness/codex-adapter": ">=0.8.5 <1.0.0",
|
|
51
|
+
"@aos-harness/gemini-adapter": ">=0.8.5 <1.0.0",
|
|
52
|
+
"@aos-harness/pi-adapter": ">=0.8.5 <1.0.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependenciesMeta": {
|
|
55
55
|
"@aos-harness/claude-code-adapter": {
|
package/src/adapter-session.ts
CHANGED
|
@@ -65,6 +65,37 @@ export interface AdapterSessionConfig {
|
|
|
65
65
|
* also batched to `${platformUrl}/api/sessions/:id/events`.
|
|
66
66
|
*/
|
|
67
67
|
platformUrl?: string;
|
|
68
|
+
agentTimeoutMs?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function createStreamingPrinter() {
|
|
72
|
+
let printedLength = 0;
|
|
73
|
+
let printedAny = false;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
push(partial: string) {
|
|
77
|
+
if (partial.length <= printedLength) return;
|
|
78
|
+
const delta = partial.slice(printedLength);
|
|
79
|
+
printedLength = partial.length;
|
|
80
|
+
if (!delta) return;
|
|
81
|
+
printedAny = true;
|
|
82
|
+
process.stdout.write(delta);
|
|
83
|
+
},
|
|
84
|
+
flushFinal(text: string): boolean {
|
|
85
|
+
if (!printedAny) return false;
|
|
86
|
+
if (text.length > printedLength) {
|
|
87
|
+
process.stdout.write(text.slice(printedLength));
|
|
88
|
+
printedLength = text.length;
|
|
89
|
+
}
|
|
90
|
+
if (!text.endsWith("\n")) {
|
|
91
|
+
process.stdout.write("\n");
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
},
|
|
95
|
+
hasOutput() {
|
|
96
|
+
return printedAny;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
68
99
|
}
|
|
69
100
|
|
|
70
101
|
const ADAPTER_MAP: Record<string, { package: string; className: string }> = {
|
|
@@ -479,16 +510,34 @@ export async function runAdapterSession(config: AdapterSessionConfig): Promise<v
|
|
|
479
510
|
`Tool calls will stream as rounds complete. ` +
|
|
480
511
|
`Bridge socket: ${sockPath}\n`,
|
|
481
512
|
);
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
513
|
+
const printer = createStreamingPrinter();
|
|
514
|
+
const startedAt = Date.now();
|
|
515
|
+
const heartbeat = setInterval(() => {
|
|
516
|
+
if (printer.hasOutput()) return;
|
|
517
|
+
const elapsedSeconds = Math.round((Date.now() - startedAt) / 1000);
|
|
518
|
+
console.error(
|
|
519
|
+
`[arbiter] waiting for ${config.platform} response... ${elapsedSeconds}s elapsed`,
|
|
520
|
+
);
|
|
521
|
+
}, 15000);
|
|
522
|
+
let response;
|
|
523
|
+
try {
|
|
524
|
+
response = await adapter.sendMessage(arbiterHandle, kickoff, {
|
|
525
|
+
extraArgs: mcpArgs,
|
|
526
|
+
onStream: (partial) => printer.push(partial),
|
|
527
|
+
timeoutMs: config.agentTimeoutMs,
|
|
528
|
+
});
|
|
529
|
+
} finally {
|
|
530
|
+
clearInterval(heartbeat);
|
|
531
|
+
}
|
|
485
532
|
if ((response as any).status !== "success") {
|
|
486
533
|
console.error(
|
|
487
534
|
`\n[arbiter] call failed: status=${(response as any).status} ` +
|
|
488
535
|
`error=${(response as any).error ?? "(none)"}`,
|
|
489
536
|
);
|
|
490
537
|
}
|
|
491
|
-
|
|
538
|
+
if (!printer.flushFinal(response.text)) {
|
|
539
|
+
console.log("\n" + response.text);
|
|
540
|
+
}
|
|
492
541
|
} finally {
|
|
493
542
|
rl.close();
|
|
494
543
|
await closeBridge();
|
package/src/commands/run.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* aos run — Launch a deliberation or execution session.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { existsSync, readdirSync, mkdirSync } from "node:fs";
|
|
5
|
+
import { existsSync, readdirSync, mkdirSync, readFileSync } from "node:fs";
|
|
6
6
|
import { join, resolve, basename } from "node:path";
|
|
7
7
|
import { c, type ParsedArgs } from "../colors";
|
|
8
8
|
import { getHarnessRoot, discoverDirs, promptSelect, getAdapterDir, ADAPTER_ALLOWLIST, isValidAdapter, validatePlatformUrl, parseAllowCodeExecutionFlag } from "../utils";
|
|
@@ -10,6 +10,7 @@ import { runAdapterSession } from "../adapter-session";
|
|
|
10
10
|
import { buildToolPolicy, type ToolPolicy } from "@aos-harness/adapter-shared";
|
|
11
11
|
import { getPlatformUrlFromConfig, getRuntimeAdapterModelConfig, resolveAdapterSelection } from "../aos-config";
|
|
12
12
|
import { backfillAdapterDefaults } from "../config-migration";
|
|
13
|
+
import { ADAPTER_METADATA, scanEnvironment } from "../env-scanner";
|
|
13
14
|
|
|
14
15
|
const HELP = `
|
|
15
16
|
${c.bold("aos run")} — Run a deliberation or execution session
|
|
@@ -49,6 +50,63 @@ ${c.bold("EXAMPLES")}
|
|
|
49
50
|
aos run # interactive profile selection
|
|
50
51
|
`;
|
|
51
52
|
|
|
53
|
+
function readCliVersion(): string {
|
|
54
|
+
try {
|
|
55
|
+
const raw = readFileSync(join(import.meta.dir, "..", "..", "package.json"), "utf-8");
|
|
56
|
+
return (JSON.parse(raw) as { version?: string }).version ?? "unknown";
|
|
57
|
+
} catch {
|
|
58
|
+
return "unknown";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function versionMismatchSeverity(cliVer: string, adapterVer?: string): "none" | "warn" {
|
|
63
|
+
if (!adapterVer) return "none";
|
|
64
|
+
const [cliMaj, cliMin] = cliVer.split(".").map(Number);
|
|
65
|
+
const [adaMaj, adaMin] = adapterVer.split(".").map(Number);
|
|
66
|
+
if (Number.isNaN(cliMaj) || Number.isNaN(adaMaj)) return "none";
|
|
67
|
+
if (cliMaj !== adaMaj) return "warn";
|
|
68
|
+
if (cliMin !== adaMin) return "warn";
|
|
69
|
+
return "none";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function probeClaudeExternalApiKey(): Promise<{ ok: boolean; hint?: string }> {
|
|
73
|
+
const proc = Bun.spawn(["claude", "--print", "Reply with OK."], {
|
|
74
|
+
stdin: "ignore",
|
|
75
|
+
stdout: "pipe",
|
|
76
|
+
stderr: "pipe",
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
let timedOut = false;
|
|
80
|
+
const timeout = setTimeout(() => {
|
|
81
|
+
timedOut = true;
|
|
82
|
+
proc.kill();
|
|
83
|
+
}, 15000);
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
87
|
+
new Response(proc.stdout).text(),
|
|
88
|
+
new Response(proc.stderr).text(),
|
|
89
|
+
proc.exited,
|
|
90
|
+
]);
|
|
91
|
+
const combined = `${stdout}\n${stderr}`.trim();
|
|
92
|
+
if (timedOut) {
|
|
93
|
+
return { ok: true, hint: "Claude auth probe timed out; continuing without blocking launch." };
|
|
94
|
+
}
|
|
95
|
+
if (exitCode === 0) {
|
|
96
|
+
return { ok: true };
|
|
97
|
+
}
|
|
98
|
+
if (/invalid api key|fix external api key|api key/i.test(combined)) {
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
hint: "Claude Code external API-key auth failed. Unset or refresh ANTHROPIC_API_KEY, or switch back to `claude login` auth before running AOS.",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return { ok: true, hint: combined ? `Claude auth probe returned: ${combined.split("\n")[0]}` : undefined };
|
|
105
|
+
} finally {
|
|
106
|
+
clearTimeout(timeout);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
52
110
|
export async function runCommand(args: ParsedArgs): Promise<void> {
|
|
53
111
|
if (args.flags.help) {
|
|
54
112
|
console.log(HELP);
|
|
@@ -224,7 +282,6 @@ export async function runCommand(args: ParsedArgs): Promise<void> {
|
|
|
224
282
|
|
|
225
283
|
// ── Dry-run mode ─────────────────────────────────────────────
|
|
226
284
|
if (args.flags["dry-run"]) {
|
|
227
|
-
const { readFileSync } = await import("node:fs");
|
|
228
285
|
const briefContent = readFileSync(briefPath, "utf-8");
|
|
229
286
|
const briefSections = briefContent.match(/^##\s+.+/gm) || [];
|
|
230
287
|
|
|
@@ -301,22 +358,6 @@ ${c.green("All configuration validated successfully. Ready to launch.")}
|
|
|
301
358
|
process.exit(0);
|
|
302
359
|
}
|
|
303
360
|
|
|
304
|
-
// ── Set up deliberation directory for artifact storage ──────
|
|
305
|
-
const sessionId = `${new Date().toISOString().slice(0, 10)}-${profileName}-${Date.now().toString(36)}`;
|
|
306
|
-
const deliberationDir = join(root, ".aos", "sessions", sessionId);
|
|
307
|
-
mkdirSync(deliberationDir, { recursive: true });
|
|
308
|
-
|
|
309
|
-
// ── Launch adapter ───────────────────────────────────────────
|
|
310
|
-
const sessionType = isExecutionProfile ? "Execution" : "Deliberation";
|
|
311
|
-
console.log(`
|
|
312
|
-
${c.bold(`AOS ${sessionType} Session`)}
|
|
313
|
-
Profile: ${c.cyan(profileName!)}
|
|
314
|
-
Type: ${isExecutionProfile ? c.magenta("execution") : c.cyan("deliberation")}${isExecutionProfile && workflowConfig ? `\n Workflow: ${c.magenta(workflowConfig.id)} (${workflowConfig.steps.length} steps)` : ""}
|
|
315
|
-
Domain: ${c.cyan(domainName || "none")}
|
|
316
|
-
Brief: ${c.cyan(briefPath)}
|
|
317
|
-
Output: ${c.cyan(deliberationDir)}
|
|
318
|
-
`);
|
|
319
|
-
|
|
320
361
|
// Determine adapter with shared precedence so init/run agree:
|
|
321
362
|
// --adapter > config v2 > config v1 > .aos/adapter.yaml > default "pi"
|
|
322
363
|
let platformUrl = (args.flags["platform-url"] as string) || null;
|
|
@@ -342,12 +383,69 @@ ${c.bold(`AOS ${sessionType} Session`)}
|
|
|
342
383
|
process.exit(2);
|
|
343
384
|
}
|
|
344
385
|
|
|
386
|
+
if (adapter !== "pi") {
|
|
387
|
+
const scan = await scanEnvironment({ cwd: process.cwd() });
|
|
388
|
+
const readiness = scan.adapters[adapter];
|
|
389
|
+
const meta = ADAPTER_METADATA[adapter];
|
|
390
|
+
|
|
391
|
+
if (readiness.status !== "ready") {
|
|
392
|
+
console.error(c.red(`${meta.label} is not ready for \`aos run\`.`));
|
|
393
|
+
console.error(c.red(` ${readiness.statusHint}`));
|
|
394
|
+
if (scan.notes.length > 0) {
|
|
395
|
+
for (const note of scan.notes.filter((entry) => entry.startsWith(`${adapter}:`))) {
|
|
396
|
+
console.error(c.dim(` ${note}`));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
process.exit(2);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const cliVersion = readCliVersion();
|
|
403
|
+
if (versionMismatchSeverity(cliVersion, readiness.aosAdapter.version) === "warn") {
|
|
404
|
+
const installHint = readiness.aosAdapter.store === "bun"
|
|
405
|
+
? `bun add -g ${meta.packageName}@${cliVersion}`
|
|
406
|
+
: `npm i -g ${meta.packageName}@${cliVersion}`;
|
|
407
|
+
console.error(c.yellow(`⚠ Version mismatch before launch: aos-harness@${cliVersion} and ${meta.packageName}@${readiness.aosAdapter.version}`));
|
|
408
|
+
console.error(c.dim(` Install matching versions: ${installHint}`));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (readiness.statusHint.includes("ANTHROPIC_API_KEY")) {
|
|
412
|
+
console.error(c.yellow(`⚠ ${readiness.statusHint}`));
|
|
413
|
+
if (adapter === "claude-code") {
|
|
414
|
+
const authProbe = await probeClaudeExternalApiKey();
|
|
415
|
+
if (!authProbe.ok) {
|
|
416
|
+
console.error(c.red(`Claude Code auth preflight failed.`));
|
|
417
|
+
console.error(c.red(` ${authProbe.hint}`));
|
|
418
|
+
process.exit(2);
|
|
419
|
+
}
|
|
420
|
+
if (authProbe.hint) {
|
|
421
|
+
console.error(c.dim(` ${authProbe.hint}`));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
345
427
|
const adapterName = adapter;
|
|
346
428
|
// Resolve from monorepo dev layout (CLI's own import.meta.dir) or installed
|
|
347
429
|
// @aos-harness/<name>-adapter. Project-local override is intentionally absent
|
|
348
430
|
// (spec D1 — workspace-trust hardening).
|
|
349
431
|
const resolvedAdapterDir = getAdapterDir(adapterName);
|
|
350
432
|
|
|
433
|
+
// ── Set up deliberation directory for artifact storage ──────
|
|
434
|
+
const sessionId = `${new Date().toISOString().slice(0, 10)}-${profileName}-${Date.now().toString(36)}`;
|
|
435
|
+
const deliberationDir = join(root, ".aos", "sessions", sessionId);
|
|
436
|
+
mkdirSync(deliberationDir, { recursive: true });
|
|
437
|
+
|
|
438
|
+
// ── Launch adapter ───────────────────────────────────────────
|
|
439
|
+
const sessionType = isExecutionProfile ? "Execution" : "Deliberation";
|
|
440
|
+
console.log(`
|
|
441
|
+
${c.bold(`AOS ${sessionType} Session`)}
|
|
442
|
+
Profile: ${c.cyan(profileName!)}
|
|
443
|
+
Type: ${isExecutionProfile ? c.magenta("execution") : c.cyan("deliberation")}${isExecutionProfile && workflowConfig ? `\n Workflow: ${c.magenta(workflowConfig.id)} (${workflowConfig.steps.length} steps)` : ""}
|
|
444
|
+
Domain: ${c.cyan(domainName || "none")}
|
|
445
|
+
Brief: ${c.cyan(briefPath)}
|
|
446
|
+
Output: ${c.cyan(deliberationDir)}
|
|
447
|
+
`);
|
|
448
|
+
|
|
351
449
|
if (adapter === "pi") {
|
|
352
450
|
const adapterEntry = resolvedAdapterDir ? join(resolvedAdapterDir, "src", "index.ts") : null;
|
|
353
451
|
if (!adapterEntry || !existsSync(adapterEntry)) {
|
|
@@ -421,6 +519,10 @@ ${c.bold(`AOS ${sessionType} Session`)}
|
|
|
421
519
|
useVendorDefaultModel: runtimeModelConfig.useVendorDefaultModel,
|
|
422
520
|
toolPolicy,
|
|
423
521
|
platformUrl: platformUrl ?? undefined,
|
|
522
|
+
agentTimeoutMs:
|
|
523
|
+
typeof profile.error_handling?.agent_timeout_seconds === "number"
|
|
524
|
+
? profile.error_handling.agent_timeout_seconds * 1000
|
|
525
|
+
: undefined,
|
|
424
526
|
});
|
|
425
527
|
}
|
|
426
528
|
}
|
package/src/env-scanner.ts
CHANGED
|
@@ -210,26 +210,42 @@ function scanAdapterPackage(
|
|
|
210
210
|
const bunPath = bunGlobalDir ? join(bunGlobalDir, meta.packageName) : null;
|
|
211
211
|
const npmPath = npmGlobalDir ? join(npmGlobalDir, meta.packageName) : null;
|
|
212
212
|
const projectLocalPath = join(cwd, "node_modules", meta.packageName);
|
|
213
|
+
const resolvedFrom = resolveAdapterDir(adapter) ?? undefined;
|
|
214
|
+
|
|
215
|
+
if (resolvedFrom) {
|
|
216
|
+
let store: AosAdapterReadiness["store"] = "unknown";
|
|
217
|
+
if (resolvedFrom.includes("/adapters/")) {
|
|
218
|
+
store = "workspace";
|
|
219
|
+
} else if (bunPath && resolvedFrom.startsWith(bunPath)) {
|
|
220
|
+
store = "bun";
|
|
221
|
+
} else if (npmPath && resolvedFrom.startsWith(npmPath)) {
|
|
222
|
+
store = "npm";
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
installed: true,
|
|
227
|
+
version: readPackageVersion(resolvedFrom),
|
|
228
|
+
store,
|
|
229
|
+
loadable: true,
|
|
230
|
+
resolvedFrom,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
213
233
|
|
|
214
234
|
if (bunPath && existsSync(bunPath)) {
|
|
215
|
-
const resolvedFrom = resolveAdapterDir(adapter) ?? undefined;
|
|
216
235
|
return {
|
|
217
236
|
installed: true,
|
|
218
237
|
version: readPackageVersion(bunPath),
|
|
219
238
|
store: "bun",
|
|
220
|
-
loadable:
|
|
221
|
-
resolvedFrom,
|
|
239
|
+
loadable: false,
|
|
222
240
|
};
|
|
223
241
|
}
|
|
224
242
|
|
|
225
243
|
if (npmPath && existsSync(npmPath)) {
|
|
226
|
-
const resolvedFrom = resolveAdapterDir(adapter) ?? undefined;
|
|
227
244
|
return {
|
|
228
245
|
installed: true,
|
|
229
246
|
version: readPackageVersion(npmPath),
|
|
230
247
|
store: "npm",
|
|
231
|
-
loadable:
|
|
232
|
-
resolvedFrom,
|
|
248
|
+
loadable: false,
|
|
233
249
|
};
|
|
234
250
|
}
|
|
235
251
|
|
|
@@ -242,17 +258,6 @@ function scanAdapterPackage(
|
|
|
242
258
|
};
|
|
243
259
|
}
|
|
244
260
|
|
|
245
|
-
const resolvedFrom = resolveAdapterDir(adapter) ?? undefined;
|
|
246
|
-
if (resolvedFrom) {
|
|
247
|
-
return {
|
|
248
|
-
installed: true,
|
|
249
|
-
version: readPackageVersion(resolvedFrom),
|
|
250
|
-
store: resolvedFrom.includes("/adapters/") ? "workspace" : "unknown",
|
|
251
|
-
loadable: true,
|
|
252
|
-
resolvedFrom,
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
261
|
return {
|
|
257
262
|
installed: false,
|
|
258
263
|
store: "unknown",
|