aos-harness 0.5.2 → 0.7.0-rc.2
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/README.md +25 -6
- package/package.json +17 -5
- package/src/adapter-config.ts +1 -1
- package/src/adapter-session.ts +84 -42
- package/src/colors.ts +10 -1
- package/src/commands/create.ts +5 -0
- package/src/commands/init.ts +31 -8
- package/src/commands/replay.ts +2 -0
- package/src/commands/run.ts +76 -9
- package/src/index.ts +0 -0
- package/src/utils.ts +135 -10
- package/adapters/claude-code/README.md +0 -74
- package/adapters/claude-code/package.json +0 -26
- package/adapters/claude-code/src/agent-runtime.ts +0 -210
- package/adapters/claude-code/src/index.ts +0 -2
- package/adapters/claude-code/tsconfig.json +0 -19
- package/adapters/codex/README.md +0 -14
- package/adapters/codex/package.json +0 -26
- package/adapters/codex/src/agent-runtime.ts +0 -219
- package/adapters/codex/src/index.ts +0 -2
- package/adapters/codex/tsconfig.json +0 -20
- package/adapters/gemini/README.md +0 -39
- package/adapters/gemini/package.json +0 -26
- package/adapters/gemini/src/agent-runtime.ts +0 -245
- package/adapters/gemini/src/index.ts +0 -2
- package/adapters/gemini/tsconfig.json +0 -19
- package/adapters/pi/README.md +0 -53
- package/adapters/pi/arbiter-scratchpad.md +0 -21
- package/adapters/pi/package.json +0 -36
- package/adapters/pi/src/agent-runtime.ts +0 -183
- package/adapters/pi/src/event-bus.ts +0 -41
- package/adapters/pi/src/index.ts +0 -885
- package/adapters/pi/src/ui.ts +0 -242
- package/adapters/pi/tsconfig.json +0 -18
- package/adapters/shared/README.md +0 -15
- package/adapters/shared/package.json +0 -30
- package/adapters/shared/src/agent-discovery.ts +0 -71
- package/adapters/shared/src/base-agent-runtime.ts +0 -331
- package/adapters/shared/src/base-event-bus.ts +0 -133
- package/adapters/shared/src/base-workflow.ts +0 -392
- package/adapters/shared/src/compose.ts +0 -76
- package/adapters/shared/src/index.ts +0 -12
- package/adapters/shared/src/terminal-ui.ts +0 -140
- package/adapters/shared/src/types.ts +0 -43
- package/adapters/shared/tsconfig.json +0 -18
package/README.md
CHANGED
|
@@ -2,26 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
**Agentic Orchestration System** — Assemble specialized AI agents into deliberation and execution teams.
|
|
4
4
|
|
|
5
|
+
> **Breaking change in 0.6.0:** `aos-harness` no longer bundles adapter code. You must install the adapter(s) for the AI CLI(s) you want to use as separate packages. If you upgrade from 0.5.x and run `aos run` without the matching `@aos-harness/<name>-adapter` installed, the CLI will print an install hint and exit. See [CHANGELOG](../CHANGELOG.md#060) for the full migration note.
|
|
6
|
+
|
|
5
7
|
## Prerequisites
|
|
6
8
|
|
|
7
9
|
- [Bun](https://bun.sh) 1.0+
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## Getting Started
|
|
12
|
+
|
|
13
|
+
### 1. Install the CLI
|
|
10
14
|
|
|
11
15
|
```bash
|
|
12
|
-
|
|
16
|
+
npm i -g aos-harness
|
|
17
|
+
# or: bun add -g aos-harness
|
|
13
18
|
```
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
### 2. Install an adapter
|
|
21
|
+
|
|
22
|
+
Pick the AI CLI you'll drive agents with and install the matching adapter. You can install more than one. Versions are lockstep — pin the adapter to the same version as the CLI.
|
|
16
23
|
|
|
17
24
|
```bash
|
|
18
|
-
|
|
25
|
+
npm i -g @aos-harness/claude-code-adapter # Anthropic's Claude Code
|
|
26
|
+
npm i -g @aos-harness/gemini-adapter # Google's Gemini CLI
|
|
27
|
+
npm i -g @aos-harness/codex-adapter # OpenAI's Codex CLI
|
|
28
|
+
npm i -g @aos-harness/pi-adapter # Pi (pi-ai) — direct model SDK
|
|
19
29
|
```
|
|
20
30
|
|
|
21
|
-
|
|
31
|
+
### 3. Initialize and run
|
|
22
32
|
|
|
23
33
|
```bash
|
|
24
|
-
# Initialize a project
|
|
34
|
+
# Initialize a project (writes .aos/ and copies core/ into the project)
|
|
25
35
|
aos init
|
|
26
36
|
|
|
27
37
|
# Run a strategic deliberation
|
|
@@ -41,6 +51,15 @@ aos create profile my-review
|
|
|
41
51
|
aos validate
|
|
42
52
|
```
|
|
43
53
|
|
|
54
|
+
## Exit Codes
|
|
55
|
+
|
|
56
|
+
| Code | Meaning |
|
|
57
|
+
|---|---|
|
|
58
|
+
| 0 | Success |
|
|
59
|
+
| 1 | Uncaught runtime error |
|
|
60
|
+
| 2 | Invalid input (unknown adapter, bad path, bad URL, missing adapter package) |
|
|
61
|
+
| 3 | Profile tool-policy error (malformed `tools:` block, flag cannot widen profile) |
|
|
62
|
+
|
|
44
63
|
## What It Does
|
|
45
64
|
|
|
46
65
|
AOS Harness orchestrates multiple AI agents with distinct cognitive biases into structured deliberation and execution sessions:
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aos-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0-rc.2",
|
|
4
4
|
"description": "Agentic Orchestration System — assemble AI agents into deliberation and execution teams",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"publishConfig": { "access": "public" },
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
8
9
|
"url": "https://github.com/aos-engineer/aos-harness.git"
|
|
@@ -23,12 +24,11 @@
|
|
|
23
24
|
"aos": "./src/index.ts"
|
|
24
25
|
},
|
|
25
26
|
"engines": {
|
|
26
|
-
"bun": ">=1.
|
|
27
|
+
"bun": ">=1.3.11"
|
|
27
28
|
},
|
|
28
29
|
"files": [
|
|
29
30
|
"src/",
|
|
30
31
|
"core/",
|
|
31
|
-
"adapters/",
|
|
32
32
|
"README.md"
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
@@ -36,11 +36,23 @@
|
|
|
36
36
|
"test": "bun run src/index.ts validate"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@aos-harness/adapter-shared": "0.
|
|
40
|
-
"@aos-harness/runtime": "0.
|
|
39
|
+
"@aos-harness/adapter-shared": "0.7.0-rc.2",
|
|
40
|
+
"@aos-harness/runtime": "0.7.0-rc.2",
|
|
41
41
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
42
42
|
"js-yaml": "^4.1.0"
|
|
43
43
|
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@aos-harness/claude-code-adapter": ">=0.6.0 <1.0.0",
|
|
46
|
+
"@aos-harness/codex-adapter": ">=0.6.0 <1.0.0",
|
|
47
|
+
"@aos-harness/gemini-adapter": ">=0.6.0 <1.0.0",
|
|
48
|
+
"@aos-harness/pi-adapter": ">=0.6.0 <1.0.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependenciesMeta": {
|
|
51
|
+
"@aos-harness/claude-code-adapter": { "optional": true },
|
|
52
|
+
"@aos-harness/codex-adapter": { "optional": true },
|
|
53
|
+
"@aos-harness/gemini-adapter": { "optional": true },
|
|
54
|
+
"@aos-harness/pi-adapter": { "optional": true }
|
|
55
|
+
},
|
|
44
56
|
"devDependencies": {
|
|
45
57
|
"@types/js-yaml": "^4.0.9",
|
|
46
58
|
"typescript": "^5.4.0"
|
package/src/adapter-config.ts
CHANGED
|
@@ -12,5 +12,5 @@ export interface AdapterConfig {
|
|
|
12
12
|
export function readAdapterConfig(root: string): AdapterConfig | null {
|
|
13
13
|
const p = join(root, ".aos", "adapter.yaml");
|
|
14
14
|
if (!existsSync(p)) return null;
|
|
15
|
-
return yaml.load(readFileSync(p, "utf-8")) as AdapterConfig;
|
|
15
|
+
return yaml.load(readFileSync(p, "utf-8"), { schema: yaml.JSON_SCHEMA }) as AdapterConfig;
|
|
16
16
|
}
|
package/src/adapter-session.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { join, dirname } from "node:path";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
import { tmpdir } from "node:os";
|
|
18
|
-
import {
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
19
|
import readline from "node:readline";
|
|
20
20
|
import {
|
|
21
21
|
BaseEventBus,
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
composeAdapter,
|
|
25
25
|
discoverAgents,
|
|
26
26
|
createFlatAgentsDir,
|
|
27
|
+
type ToolPolicy,
|
|
27
28
|
} from "@aos-harness/adapter-shared";
|
|
28
29
|
import { AOSEngine } from "@aos-harness/runtime";
|
|
29
30
|
import { loadAgent } from "@aos-harness/runtime/config-loader";
|
|
@@ -43,6 +44,18 @@ export interface AdapterSessionConfig {
|
|
|
43
44
|
workflowConfig: any | null;
|
|
44
45
|
workflowsDir: string;
|
|
45
46
|
modelOverrides?: Partial<Record<string, string>>;
|
|
47
|
+
/**
|
|
48
|
+
* Tool policy resolved by the CLI from the profile's `tools:` block narrowed
|
|
49
|
+
* (optionally) by the `--allow-code-execution` flag. Passed straight into
|
|
50
|
+
* BaseWorkflow to gate tool access during the session (spec D3).
|
|
51
|
+
*/
|
|
52
|
+
toolPolicy?: ToolPolicy;
|
|
53
|
+
/**
|
|
54
|
+
* Path where BaseWorkflow should append tool-decision audit events
|
|
55
|
+
* (one JSON object per line). Defaults to `<deliberationDir>/transcript.jsonl`
|
|
56
|
+
* when omitted.
|
|
57
|
+
*/
|
|
58
|
+
transcriptPath?: string;
|
|
46
59
|
}
|
|
47
60
|
|
|
48
61
|
const ADAPTER_MAP: Record<string, { package: string; className: string }> = {
|
|
@@ -60,8 +73,8 @@ const ADAPTER_MAP: Record<string, { package: string; className: string }> = {
|
|
|
60
73
|
},
|
|
61
74
|
};
|
|
62
75
|
|
|
63
|
-
// CLI version read once at module load, used in the
|
|
64
|
-
//
|
|
76
|
+
// CLI version read once at module load, used in the missing-adapter
|
|
77
|
+
// install hint and the version-mismatch warning.
|
|
65
78
|
function readCliVersion(): string {
|
|
66
79
|
try {
|
|
67
80
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -73,39 +86,64 @@ function readCliVersion(): string {
|
|
|
73
86
|
}
|
|
74
87
|
const CLI_VERSION = readCliVersion();
|
|
75
88
|
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
|
|
89
|
+
// Classify an error from a dynamic import() as "package not installed".
|
|
90
|
+
// Bun 1.3.11 throws ResolveMessage with code=ERR_MODULE_NOT_FOUND. Older
|
|
91
|
+
// Bun/Node and edge cases are caught by constructor-name and message-regex
|
|
92
|
+
// fallbacks. Anything that doesn't match these patterns is a real error
|
|
93
|
+
// (syntax error, missing transitive dep, etc.) and must surface with its
|
|
94
|
+
// original stack — never swallowed as "not installed".
|
|
95
|
+
function isModuleNotFound(err: any): boolean {
|
|
96
|
+
if (err?.code === "ERR_MODULE_NOT_FOUND") return true;
|
|
97
|
+
if (err?.code === "MODULE_NOT_FOUND") return true;
|
|
98
|
+
if (err?.constructor?.name === "ResolveMessage") return true;
|
|
99
|
+
const msg = typeof err?.message === "string" ? err.message : "";
|
|
100
|
+
return /Cannot find (module|package)/i.test(msg);
|
|
101
|
+
}
|
|
79
102
|
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
103
|
+
function printMissingAdapterError(pkg: string): void {
|
|
104
|
+
const useColor = !!process.stderr.isTTY;
|
|
105
|
+
const red = useColor ? "\x1b[31m" : "";
|
|
106
|
+
const bold = useColor ? "\x1b[1m" : "";
|
|
107
|
+
const reset = useColor ? "\x1b[0m" : "";
|
|
108
|
+
console.error(
|
|
109
|
+
`\n${red}${bold}✗ Adapter not installed: ${pkg}${reset}\n\n` +
|
|
110
|
+
`Install it:\n` +
|
|
111
|
+
` npm i -g ${pkg} # if aos-harness is installed globally\n` +
|
|
112
|
+
` npm i ${pkg} # if aos-harness is a project dependency\n\n` +
|
|
113
|
+
`(or use bun / pnpm / yarn equivalents)\n\n` +
|
|
114
|
+
`CLI version: aos-harness@${CLI_VERSION}. Pin the adapter to the same version.\n`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
85
117
|
|
|
118
|
+
// Compare CLI and adapter versions. Under pre-1.0 lockstep, any minor or
|
|
119
|
+
// major drift is a warning — patch drift is silent (expected during
|
|
120
|
+
// quick-turnaround publishes).
|
|
121
|
+
function versionMismatchSeverity(cliVer: string, adapterVer: string): "none" | "warn" {
|
|
122
|
+
const [cliMaj, cliMin] = cliVer.split(".").map(Number);
|
|
123
|
+
const [adaMaj, adaMin] = adapterVer.split(".").map(Number);
|
|
124
|
+
if (Number.isNaN(cliMaj) || Number.isNaN(adaMaj)) return "none";
|
|
125
|
+
if (cliMaj !== adaMaj) return "warn";
|
|
126
|
+
if (cliMin !== adaMin) return "warn";
|
|
127
|
+
return "none";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const mismatchWarnedPackages = new Set<string>();
|
|
131
|
+
|
|
132
|
+
function maybeWarnVersionMismatch(pkg: string, adapterVer: string): void {
|
|
133
|
+
if (mismatchWarnedPackages.has(pkg)) return;
|
|
134
|
+
if (versionMismatchSeverity(CLI_VERSION, adapterVer) !== "warn") return;
|
|
135
|
+
mismatchWarnedPackages.add(pkg);
|
|
86
136
|
const useColor = !!process.stderr.isTTY;
|
|
87
137
|
const y = useColor ? "\x1b[33m" : "";
|
|
88
|
-
const b = useColor ? "\x1b[1m" : "";
|
|
89
138
|
const r = useColor ? "\x1b[0m" : "";
|
|
90
|
-
|
|
91
139
|
console.error(
|
|
92
|
-
`\n${y}
|
|
93
|
-
`
|
|
94
|
-
`
|
|
95
|
-
` npm i -g ${pkg}@${CLI_VERSION}\n` +
|
|
96
|
-
` # or in a project: npm i ${pkg}@${CLI_VERSION}\n\n` +
|
|
97
|
-
` This warning appears once per project. Delete .aos/migration-warned-0.6 to re-enable.\n`,
|
|
140
|
+
`\n${y}⚠ Version mismatch: aos-harness@${CLI_VERSION} and ${pkg}@${adapterVer}${r}\n` +
|
|
141
|
+
` Adapters are published lockstep with the CLI. Install matching versions:\n` +
|
|
142
|
+
` npm i -g ${pkg}@${CLI_VERSION}\n`,
|
|
98
143
|
);
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
mkdirSync(dirname(flagPath), { recursive: true });
|
|
102
|
-
writeFileSync(flagPath, new Date().toISOString() + "\n");
|
|
103
|
-
} catch {
|
|
104
|
-
// non-fatal — if we can't write the flag, the warning just repeats next run.
|
|
105
|
-
}
|
|
106
144
|
}
|
|
107
145
|
|
|
108
|
-
async function loadAdapterRuntime(platform: string
|
|
146
|
+
async function loadAdapterRuntime(platform: string): Promise<any> {
|
|
109
147
|
const entry = ADAPTER_MAP[platform];
|
|
110
148
|
if (!entry) throw new Error(`Unknown adapter: ${platform}`);
|
|
111
149
|
|
|
@@ -120,21 +158,21 @@ async function loadAdapterRuntime(platform: string, projectRoot: string): Promis
|
|
|
120
158
|
}
|
|
121
159
|
}
|
|
122
160
|
|
|
161
|
+
let mod: any;
|
|
123
162
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const fallback = join(here, "..", "..", "adapters", platform, "src", "index.ts");
|
|
132
|
-
const mod = await import(fallback);
|
|
133
|
-
const version = await readAdapterVersion(`file://${fallback}`);
|
|
134
|
-
console.error(`[adapter] loaded ${entry.package}@${version} (bundled: ${fallback})`);
|
|
135
|
-
maybeWarnAdapterDeprecation(entry.package, projectRoot);
|
|
136
|
-
return mod[entry.className];
|
|
163
|
+
mod = await import(entry.package);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (isModuleNotFound(err)) {
|
|
166
|
+
printMissingAdapterError(entry.package);
|
|
167
|
+
process.exit(2);
|
|
168
|
+
}
|
|
169
|
+
throw err; // real load error — surface it
|
|
137
170
|
}
|
|
171
|
+
const resolved = (import.meta as any).resolve?.(entry.package) ?? entry.package;
|
|
172
|
+
const version = await readAdapterVersion(resolved);
|
|
173
|
+
console.error(`[adapter] loaded ${entry.package}@${version}`);
|
|
174
|
+
maybeWarnVersionMismatch(entry.package, version);
|
|
175
|
+
return mod[entry.className];
|
|
138
176
|
}
|
|
139
177
|
|
|
140
178
|
function toolNamesForPlatform(platform: string): { delegate: string; end: string } {
|
|
@@ -150,13 +188,17 @@ export async function runAdapterSession(config: AdapterSessionConfig): Promise<v
|
|
|
150
188
|
};
|
|
151
189
|
|
|
152
190
|
log("loading adapter runtime");
|
|
153
|
-
const RuntimeClass = await loadAdapterRuntime(config.platform
|
|
191
|
+
const RuntimeClass = await loadAdapterRuntime(config.platform);
|
|
154
192
|
|
|
155
193
|
// ── Layer composition ──────────────────────────────────────
|
|
156
194
|
const eventBus = new BaseEventBus();
|
|
157
195
|
const agentRuntime = new RuntimeClass(eventBus, config.modelOverrides);
|
|
158
196
|
const ui = new TerminalUI();
|
|
159
|
-
const workflow = new BaseWorkflow(agentRuntime, config.root
|
|
197
|
+
const workflow = new BaseWorkflow(agentRuntime, config.root, {
|
|
198
|
+
toolPolicy: config.toolPolicy,
|
|
199
|
+
transcriptPath:
|
|
200
|
+
config.transcriptPath ?? join(config.deliberationDir, "transcript.jsonl"),
|
|
201
|
+
});
|
|
160
202
|
const adapter = composeAdapter(agentRuntime, eventBus, ui, workflow);
|
|
161
203
|
log("layers composed");
|
|
162
204
|
|
package/src/colors.ts
CHANGED
|
@@ -28,7 +28,16 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
28
28
|
while (i < args.length) {
|
|
29
29
|
const arg = args[i];
|
|
30
30
|
if (arg.startsWith("--")) {
|
|
31
|
-
const
|
|
31
|
+
const body = arg.slice(2);
|
|
32
|
+
// Support --key=value (inline) as well as --key value (separate arg).
|
|
33
|
+
const eq = body.indexOf("=");
|
|
34
|
+
if (eq !== -1) {
|
|
35
|
+
const key = body.slice(0, eq);
|
|
36
|
+
flags[key] = body.slice(eq + 1);
|
|
37
|
+
i += 1;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const key = body;
|
|
32
41
|
const next = args[i + 1];
|
|
33
42
|
if (next && !next.startsWith("--")) {
|
|
34
43
|
flags[key] = next;
|
package/src/commands/create.ts
CHANGED
|
@@ -377,6 +377,11 @@ export async function createCommand(args: ParsedArgs): Promise<void> {
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
const id = toKebabCase(name);
|
|
380
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(id)) {
|
|
381
|
+
console.error(c.red(`Invalid name "${name}": must kebab-case to /^[a-z0-9][a-z0-9-]*$/`));
|
|
382
|
+
console.error(c.dim(`Allowed characters: a-z, 0-9, hyphen. Must start with a letter or digit.`));
|
|
383
|
+
process.exit(2);
|
|
384
|
+
}
|
|
380
385
|
const displayName = id
|
|
381
386
|
.split("-")
|
|
382
387
|
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
package/src/commands/init.ts
CHANGED
|
@@ -2,10 +2,21 @@
|
|
|
2
2
|
* aos init — Initialize AOS configuration in the current project.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { cpSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
6
|
-
import { join } from "node:path";
|
|
5
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
7
8
|
import { c, type ParsedArgs } from "../colors";
|
|
8
|
-
import { getHarnessRoot, getPackageCoreDir } from "../utils";
|
|
9
|
+
import { ADAPTER_ALLOWLIST, getHarnessRoot, getPackageCoreDir, isValidAdapter } from "../utils";
|
|
10
|
+
|
|
11
|
+
function readCliVersion(): string {
|
|
12
|
+
try {
|
|
13
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const raw = readFileSync(join(here, "..", "..", "package.json"), "utf-8");
|
|
15
|
+
return (JSON.parse(raw) as { version: string }).version ?? "unknown";
|
|
16
|
+
} catch {
|
|
17
|
+
return "unknown";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
9
20
|
|
|
10
21
|
const HELP = `
|
|
11
22
|
${c.bold("aos init")} — Initialize AOS in the current project
|
|
@@ -14,7 +25,7 @@ ${c.bold("USAGE")}
|
|
|
14
25
|
aos init [--adapter <adapter>] [--force]
|
|
15
26
|
|
|
16
27
|
${c.bold("OPTIONS")}
|
|
17
|
-
--adapter <name> Adapter to use: pi (default), claude-code, gemini
|
|
28
|
+
--adapter <name> Adapter to use: pi (default), claude-code, gemini, codex
|
|
18
29
|
--force Reinitialize even if AOS is already set up (overwrites existing core configs)
|
|
19
30
|
|
|
20
31
|
${c.bold("DESCRIPTION")}
|
|
@@ -27,8 +38,6 @@ ${c.bold("EXAMPLES")}
|
|
|
27
38
|
aos init --adapter claude-code
|
|
28
39
|
`;
|
|
29
40
|
|
|
30
|
-
const VALID_ADAPTERS = ["pi", "claude-code", "gemini"];
|
|
31
|
-
|
|
32
41
|
function generateConfig(adapter: string): string {
|
|
33
42
|
return `# AOS Harness Configuration
|
|
34
43
|
# Generated by: aos init
|
|
@@ -53,8 +62,8 @@ export async function initCommand(args: ParsedArgs): Promise<void> {
|
|
|
53
62
|
|
|
54
63
|
const adapter = (args.flags.adapter as string) || "pi";
|
|
55
64
|
|
|
56
|
-
if (!
|
|
57
|
-
console.error(c.red(`Invalid adapter: "${adapter}". Valid adapters: ${
|
|
65
|
+
if (!isValidAdapter(adapter)) {
|
|
66
|
+
console.error(c.red(`Invalid adapter: "${adapter}". Valid adapters: ${ADAPTER_ALLOWLIST.join(", ")}`));
|
|
58
67
|
process.exit(1);
|
|
59
68
|
}
|
|
60
69
|
|
|
@@ -156,4 +165,18 @@ ${c.bold("Customization")}
|
|
|
156
165
|
Create domain packs: ${c.cyan("aos create domain <name>")}
|
|
157
166
|
Validate everything: ${c.cyan("aos validate")}
|
|
158
167
|
`);
|
|
168
|
+
|
|
169
|
+
// Adapter-install guidance. Starting in 0.6.0 the CLI no longer bundles
|
|
170
|
+
// adapter source — users must install the adapter(s) they want to use.
|
|
171
|
+
const v = readCliVersion();
|
|
172
|
+
console.log(`${c.bold("Next step: install an adapter")}
|
|
173
|
+
Adapters live as separate npm packages. Install the one(s) you'll use:
|
|
174
|
+
|
|
175
|
+
Claude Code: ${c.cyan(`npm i -g @aos-harness/claude-code-adapter@${v}`)}
|
|
176
|
+
Gemini CLI: ${c.cyan(`npm i -g @aos-harness/gemini-adapter@${v}`)}
|
|
177
|
+
Codex CLI: ${c.cyan(`npm i -g @aos-harness/codex-adapter@${v}`)}
|
|
178
|
+
Pi (pi-ai): ${c.cyan(`npm i -g @aos-harness/pi-adapter@${v}`)}
|
|
179
|
+
|
|
180
|
+
${c.dim("Pick one (or more). Then run `aos run`.")}
|
|
181
|
+
`);
|
|
159
182
|
}
|
package/src/commands/replay.ts
CHANGED
|
@@ -169,6 +169,8 @@ export async function replayCommand(args: ParsedArgs): Promise<void> {
|
|
|
169
169
|
process.exit(1);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
// Direct CLI arg — not passed through confinedResolve (spec D4: user is trusted
|
|
173
|
+
// at the CLI boundary). If this ever becomes config-driven, use confinedResolve.
|
|
172
174
|
const resolved = filePath.startsWith("/") ? filePath : resolve(process.cwd(), filePath);
|
|
173
175
|
if (!existsSync(resolved)) {
|
|
174
176
|
console.error(c.red(`Transcript file not found: ${resolved}`));
|
package/src/commands/run.ts
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
import { existsSync, readdirSync, mkdirSync } from "node:fs";
|
|
6
6
|
import { join, resolve, basename } from "node:path";
|
|
7
7
|
import { c, type ParsedArgs } from "../colors";
|
|
8
|
-
import { getHarnessRoot, discoverDirs, promptSelect, getAdapterDir } from "../utils";
|
|
8
|
+
import { getHarnessRoot, discoverDirs, promptSelect, getAdapterDir, ADAPTER_ALLOWLIST, isValidAdapter, validatePlatformUrl, parseAllowCodeExecutionFlag } from "../utils";
|
|
9
9
|
import type { TranscriptEntry } from "@aos-harness/runtime/types";
|
|
10
10
|
import { runAdapterSession } from "../adapter-session";
|
|
11
11
|
import { readAdapterConfig } from "../adapter-config";
|
|
12
|
+
import { buildToolPolicy, type ToolPolicy } from "@aos-harness/adapter-shared";
|
|
12
13
|
|
|
13
14
|
function createEventBuffer(platformUrl: string, sessionId: string) {
|
|
14
15
|
const buffer: TranscriptEntry[] = [];
|
|
@@ -59,6 +60,10 @@ ${c.bold("OPTIONS")}
|
|
|
59
60
|
--dry-run Validate config and print simulation summary without launching
|
|
60
61
|
--workflow-dir <path> Directory containing workflow YAML files (default: core/workflows/)
|
|
61
62
|
--platform-url <url> Platform API URL for live observability (e.g. http://localhost:3001)
|
|
63
|
+
--allow-code-execution[=<langs>|none]
|
|
64
|
+
Narrow (never widen) the profile's code-execution allowlist
|
|
65
|
+
for this session. Pass \`none\` to force-deny; pass a comma
|
|
66
|
+
list like \`python,bash\` to intersect with the profile.
|
|
62
67
|
|
|
63
68
|
${c.bold("DESCRIPTION")}
|
|
64
69
|
Launches a deliberation or execution session using the specified profile.
|
|
@@ -85,6 +90,17 @@ export async function runCommand(args: ParsedArgs): Promise<void> {
|
|
|
85
90
|
return;
|
|
86
91
|
}
|
|
87
92
|
|
|
93
|
+
// Validate --adapter flag early (spec D2) before any project resolution so
|
|
94
|
+
// unknown names are rejected regardless of workspace state.
|
|
95
|
+
if (args.flags["adapter"]) {
|
|
96
|
+
const flagAdapter = args.flags["adapter"] as string;
|
|
97
|
+
if (!isValidAdapter(flagAdapter)) {
|
|
98
|
+
console.error(c.red(`Unknown adapter: ${flagAdapter}`));
|
|
99
|
+
console.error(c.dim(`Allowed: ${ADAPTER_ALLOWLIST.join(", ")}`));
|
|
100
|
+
process.exit(2);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
88
104
|
const root = getHarnessRoot();
|
|
89
105
|
const coreDir = join(root, "core");
|
|
90
106
|
|
|
@@ -166,7 +182,33 @@ export async function runCommand(args: ParsedArgs): Promise<void> {
|
|
|
166
182
|
|
|
167
183
|
// ── Validate brief against profile ───────────────────────────
|
|
168
184
|
const { loadProfile, loadWorkflow, validateBrief } = await import("@aos-harness/runtime/config-loader");
|
|
169
|
-
|
|
185
|
+
let profile: ReturnType<typeof loadProfile>;
|
|
186
|
+
try {
|
|
187
|
+
profile = loadProfile(profileDir);
|
|
188
|
+
} catch (err: any) {
|
|
189
|
+
// Malformed `tools:` block (and any other profile-schema failure) comes
|
|
190
|
+
// through as a ConfigError. Surface as exit 3 per spec D6 so CI can
|
|
191
|
+
// distinguish policy/config errors from runtime errors (exit 1) and
|
|
192
|
+
// invalid input (exit 2).
|
|
193
|
+
if (err?.name === "ConfigError") {
|
|
194
|
+
console.error(c.red(err.message));
|
|
195
|
+
process.exit(3);
|
|
196
|
+
}
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── Resolve tool policy (spec D3.2) ─────────────────────────
|
|
201
|
+
// Profile's `tools:` block is the ceiling; the CLI flag can narrow but
|
|
202
|
+
// never widen. Any widening attempt from buildToolPolicy is exit 3.
|
|
203
|
+
const allowCodeExec = parseAllowCodeExecutionFlag(args.flags["allow-code-execution"]);
|
|
204
|
+
let toolPolicy: ToolPolicy;
|
|
205
|
+
try {
|
|
206
|
+
toolPolicy = buildToolPolicy(profile.tools!, { allowCodeExecution: allowCodeExec });
|
|
207
|
+
} catch (err: any) {
|
|
208
|
+
console.error(c.red(err.message));
|
|
209
|
+
process.exit(3);
|
|
210
|
+
}
|
|
211
|
+
|
|
170
212
|
const validation = validateBrief(briefPath, profile.input.required_sections);
|
|
171
213
|
|
|
172
214
|
// ── Detect execution profile (has workflow field) ──────────
|
|
@@ -321,19 +363,38 @@ ${c.bold(`AOS ${sessionType} Session`)}
|
|
|
321
363
|
}
|
|
322
364
|
if (args.flags["adapter"]) adapter = args.flags["adapter"] as string;
|
|
323
365
|
|
|
324
|
-
|
|
325
|
-
//
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
366
|
+
// Validate platform URL (spec D5). Fires for both --platform-url flag
|
|
367
|
+
// and .aos/config.yaml platform.url after both sources are merged.
|
|
368
|
+
if (platformUrl) {
|
|
369
|
+
try {
|
|
370
|
+
validatePlatformUrl(platformUrl);
|
|
371
|
+
} catch (err: any) {
|
|
372
|
+
console.error(c.red(`Invalid platform.url: ${err.message}`));
|
|
373
|
+
process.exit(2);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!isValidAdapter(adapter)) {
|
|
378
|
+
console.error(c.red(`Unknown adapter: ${adapter}`));
|
|
379
|
+
console.error(c.dim(`Allowed: ${ADAPTER_ALLOWLIST.join(", ")}`));
|
|
380
|
+
process.exit(2);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const adapterName = adapter;
|
|
384
|
+
// Resolve from monorepo dev layout (CLI's own import.meta.dir) or installed
|
|
385
|
+
// @aos-harness/<name>-adapter. Project-local override is intentionally absent
|
|
386
|
+
// (spec D1 — workspace-trust hardening).
|
|
387
|
+
const resolvedAdapterDir = getAdapterDir(adapterName);
|
|
329
388
|
|
|
330
389
|
if (adapter === "pi") {
|
|
331
390
|
const adapterEntry = resolvedAdapterDir ? join(resolvedAdapterDir, "src", "index.ts") : null;
|
|
332
391
|
if (!adapterEntry || !existsSync(adapterEntry)) {
|
|
333
392
|
console.error(c.red(`Pi adapter not found.`));
|
|
334
393
|
console.error(c.yellow("Make sure Pi CLI is installed: https://github.com/pi-agi/pi"));
|
|
335
|
-
console.error(c.dim("
|
|
336
|
-
|
|
394
|
+
console.error(c.dim("Install the adapter package:"));
|
|
395
|
+
console.error(c.dim(" npm i -g @aos-harness/pi-adapter"));
|
|
396
|
+
console.error(c.dim(" # or in a project: npm i @aos-harness/pi-adapter"));
|
|
397
|
+
process.exit(2);
|
|
337
398
|
}
|
|
338
399
|
|
|
339
400
|
console.log(c.dim(`Launching Pi adapter...`));
|
|
@@ -362,6 +423,11 @@ ${c.bold(`AOS ${sessionType} Session`)}
|
|
|
362
423
|
env.AOS_WORKFLOW_ID = workflowConfig.id;
|
|
363
424
|
env.AOS_WORKFLOWS_DIR = workflowsDir;
|
|
364
425
|
}
|
|
426
|
+
// Pass the resolved ToolPolicy to the Pi adapter as JSON. The Pi adapter
|
|
427
|
+
// runs in a separate process; this env var is the contract. Pi-side
|
|
428
|
+
// consumption (gating executeCode + emitting tool-denied transcript
|
|
429
|
+
// events) is a future follow-up — see adapter-trust-model-plan.md.
|
|
430
|
+
env.AOS_TOOL_POLICY_JSON = JSON.stringify(toolPolicy);
|
|
365
431
|
|
|
366
432
|
const proc = Bun.spawn(["pi", "-e", adapterEntry], {
|
|
367
433
|
cwd: root,
|
|
@@ -390,6 +456,7 @@ ${c.bold(`AOS ${sessionType} Session`)}
|
|
|
390
456
|
workflowConfig: isExecutionProfile ? workflowConfig : null,
|
|
391
457
|
workflowsDir,
|
|
392
458
|
modelOverrides: adapterConfig?.model_overrides,
|
|
459
|
+
toolPolicy,
|
|
393
460
|
});
|
|
394
461
|
}
|
|
395
462
|
}
|
package/src/index.ts
CHANGED
|
File without changes
|