aos-harness 0.7.0 → 0.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/README.md +15 -2
- package/package.json +26 -13
- package/src/aos-config.ts +151 -0
- package/src/bridge-server.ts +24 -3
- package/src/commands/init.ts +124 -111
- package/src/commands/run.ts +9 -15
- package/src/env-scanner.ts +356 -0
- package/src/index.ts +11 -1
- package/src/init-applier.ts +42 -0
- package/src/init-config-writer.ts +72 -0
- package/src/init-types.ts +78 -0
- package/src/init-wizard.ts +134 -0
- package/src/prompts.ts +63 -0
- package/src/utils.ts +19 -2
package/README.md
CHANGED
|
@@ -19,13 +19,20 @@ npm i -g aos-harness
|
|
|
19
19
|
|
|
20
20
|
### 2. Install an adapter
|
|
21
21
|
|
|
22
|
+
Install the vendor CLI you want to drive first, then install the matching AOS adapter package. The adapter is the AOS integration layer on top of the vendor CLI:
|
|
23
|
+
|
|
24
|
+
- `claude` + `@aos-harness/claude-code-adapter`
|
|
25
|
+
- `codex` + `@aos-harness/codex-adapter`
|
|
26
|
+
- `gemini` + `@aos-harness/gemini-adapter`
|
|
27
|
+
- `pi` + `@aos-harness/pi-adapter`
|
|
28
|
+
|
|
22
29
|
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.
|
|
23
30
|
|
|
24
31
|
```bash
|
|
25
32
|
npm i -g @aos-harness/claude-code-adapter # Anthropic's Claude Code
|
|
26
33
|
npm i -g @aos-harness/gemini-adapter # Google's Gemini CLI
|
|
27
34
|
npm i -g @aos-harness/codex-adapter # OpenAI's Codex CLI
|
|
28
|
-
npm i -g @aos-harness/pi-adapter # Pi (pi
|
|
35
|
+
npm i -g @aos-harness/pi-adapter # Pi (https://pi.dev)
|
|
29
36
|
```
|
|
30
37
|
|
|
31
38
|
### 3. Initialize and run
|
|
@@ -34,6 +41,12 @@ npm i -g @aos-harness/pi-adapter # Pi (pi-ai) — direct model SDK
|
|
|
34
41
|
# Initialize a project (writes .aos/ and copies core/ into the project)
|
|
35
42
|
aos init
|
|
36
43
|
|
|
44
|
+
# Or scan only in CI / automation
|
|
45
|
+
aos init --non-interactive
|
|
46
|
+
|
|
47
|
+
# Or install missing adapter packages after config generation
|
|
48
|
+
aos init --apply
|
|
49
|
+
|
|
37
50
|
# Run a strategic deliberation
|
|
38
51
|
aos run strategic-council --brief brief.md
|
|
39
52
|
|
|
@@ -58,7 +71,7 @@ aos validate
|
|
|
58
71
|
| 0 | Success |
|
|
59
72
|
| 1 | Uncaught runtime error |
|
|
60
73
|
| 2 | Invalid input (unknown adapter, bad path, bad URL, missing adapter package) |
|
|
61
|
-
| 3 |
|
|
74
|
+
| 3 | Validation failure that requires user action (`aos init --non-interactive --adapter ...` selected adapter not ready, or profile tool-policy widening failure) |
|
|
62
75
|
|
|
63
76
|
## What It Does
|
|
64
77
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aos-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Agentic Orchestration System — assemble AI agents into deliberation and execution teams",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"publishConfig": {
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
7
9
|
"repository": {
|
|
8
10
|
"type": "git",
|
|
9
11
|
"url": "https://github.com/aos-engineer/aos-harness.git"
|
|
@@ -36,24 +38,35 @@
|
|
|
36
38
|
"test": "bun run src/index.ts validate"
|
|
37
39
|
},
|
|
38
40
|
"dependencies": {
|
|
39
|
-
"@aos-harness/adapter-shared": "0.
|
|
40
|
-
"@aos-harness/runtime": "0.
|
|
41
|
+
"@aos-harness/adapter-shared": "0.8.0",
|
|
42
|
+
"@aos-harness/runtime": "0.8.0",
|
|
43
|
+
"@clack/prompts": "^1.2.0",
|
|
41
44
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
42
|
-
"js-yaml": "^4.1.0"
|
|
45
|
+
"js-yaml": "^4.1.0",
|
|
46
|
+
"yaml": "^2.8.3"
|
|
43
47
|
},
|
|
44
48
|
"peerDependencies": {
|
|
45
|
-
"@aos-harness/claude-code-adapter": ">=0.
|
|
46
|
-
"@aos-harness/codex-adapter": ">=0.
|
|
47
|
-
"@aos-harness/gemini-adapter": ">=0.
|
|
48
|
-
"@aos-harness/pi-adapter": ">=0.
|
|
49
|
+
"@aos-harness/claude-code-adapter": ">=0.8.0 <1.0.0",
|
|
50
|
+
"@aos-harness/codex-adapter": ">=0.8.0 <1.0.0",
|
|
51
|
+
"@aos-harness/gemini-adapter": ">=0.8.0 <1.0.0",
|
|
52
|
+
"@aos-harness/pi-adapter": ">=0.8.0 <1.0.0"
|
|
49
53
|
},
|
|
50
54
|
"peerDependenciesMeta": {
|
|
51
|
-
"@aos-harness/claude-code-adapter": {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"@aos-harness/
|
|
55
|
+
"@aos-harness/claude-code-adapter": {
|
|
56
|
+
"optional": true
|
|
57
|
+
},
|
|
58
|
+
"@aos-harness/codex-adapter": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"@aos-harness/gemini-adapter": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"@aos-harness/pi-adapter": {
|
|
65
|
+
"optional": true
|
|
66
|
+
}
|
|
55
67
|
},
|
|
56
68
|
"devDependencies": {
|
|
69
|
+
"@types/bun": "^1.3.12",
|
|
57
70
|
"@types/js-yaml": "^4.0.9",
|
|
58
71
|
"typescript": "^5.4.0"
|
|
59
72
|
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
import { readAdapterConfig } from "./adapter-config";
|
|
5
|
+
import { ADAPTER_ALLOWLIST, type AdapterName, isValidAdapter } from "./utils";
|
|
6
|
+
import type { InitModels } from "./init-types";
|
|
7
|
+
|
|
8
|
+
export interface AosConfigV2 {
|
|
9
|
+
api_version?: string;
|
|
10
|
+
adapters?: {
|
|
11
|
+
enabled?: string[];
|
|
12
|
+
default?: string;
|
|
13
|
+
};
|
|
14
|
+
package_manager?: string;
|
|
15
|
+
models?: Partial<InitModels>;
|
|
16
|
+
editor?: string;
|
|
17
|
+
platform?: {
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
url?: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AosConfigV1 {
|
|
24
|
+
adapter?: string;
|
|
25
|
+
models?: Partial<InitModels>;
|
|
26
|
+
editor?: string;
|
|
27
|
+
platform?: {
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
url?: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type AosConfig = AosConfigV1 & AosConfigV2 & Record<string, unknown>;
|
|
34
|
+
|
|
35
|
+
export interface AdapterResolution {
|
|
36
|
+
adapter: AdapterName;
|
|
37
|
+
source: "flag" | "config-v2" | "config-v1" | "adapter-yaml" | "default";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function readAosConfig(root: string): AosConfig | null {
|
|
41
|
+
const path = join(root, ".aos", "config.yaml");
|
|
42
|
+
if (!existsSync(path)) return null;
|
|
43
|
+
const parsed = yaml.load(readFileSync(path, "utf-8"), { schema: yaml.JSON_SCHEMA }) as AosConfig | null;
|
|
44
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
45
|
+
return parsed;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isAosConfigV2(config: AosConfig | null): boolean {
|
|
49
|
+
return !!config && config.api_version === "aos/config/v2";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function parseAdapterList(value: unknown): AdapterName[] {
|
|
53
|
+
if (typeof value !== "string") return [];
|
|
54
|
+
const seen = new Set<AdapterName>();
|
|
55
|
+
for (const raw of value.split(",")) {
|
|
56
|
+
const candidate = raw.trim();
|
|
57
|
+
if (isValidAdapter(candidate)) seen.add(candidate);
|
|
58
|
+
}
|
|
59
|
+
return [...seen];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getEnabledAdaptersFromConfig(config: AosConfig | null): AdapterName[] {
|
|
63
|
+
if (!config) return [];
|
|
64
|
+
if (isAosConfigV2(config)) {
|
|
65
|
+
const enabled = Array.isArray(config.adapters?.enabled) ? config.adapters?.enabled : [];
|
|
66
|
+
return enabled.filter((value): value is AdapterName => isValidAdapter(value));
|
|
67
|
+
}
|
|
68
|
+
return typeof config.adapter === "string" && isValidAdapter(config.adapter) ? [config.adapter] : [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function getDefaultAdapterFromConfig(config: AosConfig | null): AdapterName | null {
|
|
72
|
+
if (!config) return null;
|
|
73
|
+
if (isAosConfigV2(config)) {
|
|
74
|
+
if (typeof config.adapters?.default === "string" && isValidAdapter(config.adapters.default)) {
|
|
75
|
+
return config.adapters.default;
|
|
76
|
+
}
|
|
77
|
+
const enabled = getEnabledAdaptersFromConfig(config);
|
|
78
|
+
return enabled[0] ?? null;
|
|
79
|
+
}
|
|
80
|
+
return typeof config.adapter === "string" && isValidAdapter(config.adapter) ? config.adapter : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function resolveAdapterSelection(root: string, flagAdapter?: string | boolean): AdapterResolution {
|
|
84
|
+
if (typeof flagAdapter === "string" && isValidAdapter(flagAdapter)) {
|
|
85
|
+
return { adapter: flagAdapter, source: "flag" };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const aosConfig = readAosConfig(root);
|
|
89
|
+
if (aosConfig) {
|
|
90
|
+
const v2Default = isAosConfigV2(aosConfig) ? getDefaultAdapterFromConfig(aosConfig) : null;
|
|
91
|
+
if (v2Default) return { adapter: v2Default, source: "config-v2" };
|
|
92
|
+
|
|
93
|
+
if (typeof aosConfig.adapter === "string" && isValidAdapter(aosConfig.adapter)) {
|
|
94
|
+
return { adapter: aosConfig.adapter, source: "config-v1" };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const adapterConfig = readAdapterConfig(root);
|
|
99
|
+
if (typeof adapterConfig?.platform === "string" && isValidAdapter(adapterConfig.platform)) {
|
|
100
|
+
return { adapter: adapterConfig.platform, source: "adapter-yaml" };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { adapter: "pi", source: "default" };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getSelectedAdaptersForInit(root: string, flagAdapter?: string | boolean): AdapterName[] {
|
|
107
|
+
const explicit = parseAdapterList(flagAdapter);
|
|
108
|
+
if (explicit.length > 0) return explicit;
|
|
109
|
+
|
|
110
|
+
const config = readAosConfig(root);
|
|
111
|
+
const fromConfig = getEnabledAdaptersFromConfig(config);
|
|
112
|
+
if (fromConfig.length > 0) return fromConfig;
|
|
113
|
+
|
|
114
|
+
const adapterConfig = readAdapterConfig(root);
|
|
115
|
+
if (typeof adapterConfig?.platform === "string" && isValidAdapter(adapterConfig.platform)) {
|
|
116
|
+
return [adapterConfig.platform];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function getPlatformUrlFromConfig(root: string): string | null {
|
|
123
|
+
const config = readAosConfig(root);
|
|
124
|
+
if (!config?.platform?.enabled) return null;
|
|
125
|
+
return typeof config.platform.url === "string" ? config.platform.url : null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const DEFAULT_INIT_MODELS: InitModels = {
|
|
129
|
+
economy: "anthropic/claude-haiku-4-5",
|
|
130
|
+
standard: "anthropic/claude-sonnet-4-6",
|
|
131
|
+
premium: "anthropic/claude-opus-4-6",
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export function getInitModels(root: string): InitModels {
|
|
135
|
+
const config = readAosConfig(root);
|
|
136
|
+
const models = config?.models ?? {};
|
|
137
|
+
return {
|
|
138
|
+
economy: typeof models.economy === "string" ? models.economy : DEFAULT_INIT_MODELS.economy,
|
|
139
|
+
standard: typeof models.standard === "string" ? models.standard : DEFAULT_INIT_MODELS.standard,
|
|
140
|
+
premium: typeof models.premium === "string" ? models.premium : DEFAULT_INIT_MODELS.premium,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function getInitEditor(root: string): string {
|
|
145
|
+
const config = readAosConfig(root);
|
|
146
|
+
return typeof config?.editor === "string" ? config.editor : "code";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function listKnownAdapters(): readonly AdapterName[] {
|
|
150
|
+
return ADAPTER_ALLOWLIST;
|
|
151
|
+
}
|
package/src/bridge-server.ts
CHANGED
|
@@ -6,11 +6,28 @@ export interface BridgeHandlers {
|
|
|
6
6
|
end: (params: { closing_message: string }) => Promise<unknown>;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
function parseListenTarget(target: string): { host: string; port: number } | { path: string } {
|
|
10
|
+
if (!target.startsWith("tcp://")) {
|
|
11
|
+
return { path: target };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const url = new URL(target);
|
|
15
|
+
const port = Number.parseInt(url.port, 10);
|
|
16
|
+
if (!url.hostname || Number.isNaN(port)) {
|
|
17
|
+
throw new Error(`Invalid bridge listen target: ${target}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return { host: url.hostname, port };
|
|
21
|
+
}
|
|
22
|
+
|
|
9
23
|
export async function startBridgeServer(
|
|
10
24
|
socketPath: string,
|
|
11
25
|
handlers: BridgeHandlers,
|
|
12
26
|
): Promise<() => Promise<void>> {
|
|
13
|
-
|
|
27
|
+
const listenTarget = parseListenTarget(socketPath);
|
|
28
|
+
const isUnixSocket = "path" in listenTarget;
|
|
29
|
+
|
|
30
|
+
if (isUnixSocket && existsSync(listenTarget.path)) unlinkSync(listenTarget.path);
|
|
14
31
|
|
|
15
32
|
const open = new Set<Socket>();
|
|
16
33
|
|
|
@@ -43,12 +60,16 @@ export async function startBridgeServer(
|
|
|
43
60
|
|
|
44
61
|
await new Promise<void>((resolve, reject) => {
|
|
45
62
|
server.once("error", reject);
|
|
46
|
-
|
|
63
|
+
if ("path" in listenTarget) {
|
|
64
|
+
server.listen(listenTarget.path, () => resolve());
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
server.listen(listenTarget.port, listenTarget.host, () => resolve());
|
|
47
68
|
});
|
|
48
69
|
|
|
49
70
|
return async () => {
|
|
50
71
|
for (const s of open) s.destroy();
|
|
51
72
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
52
|
-
if (existsSync(
|
|
73
|
+
if (isUnixSocket && existsSync(listenTarget.path)) unlinkSync(listenTarget.path);
|
|
53
74
|
};
|
|
54
75
|
}
|
package/src/commands/init.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* aos init — Initialize AOS
|
|
2
|
+
* aos init — Initialize or reconfigure AOS in the current project.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
+
import yaml from "js-yaml";
|
|
8
9
|
import { c, type ParsedArgs } from "../colors";
|
|
9
|
-
import {
|
|
10
|
+
import { getHarnessRoot, getPackageCoreDir, isValidAdapter } from "../utils";
|
|
11
|
+
import { getInitEditor, getInitModels, getSelectedAdaptersForInit, parseAdapterList } from "../aos-config";
|
|
12
|
+
import { scanEnvironment } from "../env-scanner";
|
|
13
|
+
import { mergeConfig, generateMemoryYaml } from "../init-config-writer";
|
|
14
|
+
import { applyActions } from "../init-applier";
|
|
15
|
+
import type { AdapterName } from "../utils";
|
|
16
|
+
import type { ScanReport, WizardResult } from "../init-types";
|
|
17
|
+
import { runWizard } from "../init-wizard";
|
|
10
18
|
|
|
11
19
|
function readCliVersion(): string {
|
|
12
20
|
try {
|
|
@@ -19,39 +27,75 @@ function readCliVersion(): string {
|
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
const HELP = `
|
|
22
|
-
${c.bold("aos init")} — Initialize AOS in the current project
|
|
30
|
+
${c.bold("aos init")} — Initialize or reconfigure AOS in the current project
|
|
23
31
|
|
|
24
32
|
${c.bold("USAGE")}
|
|
25
|
-
aos init [--adapter <adapter>] [--force]
|
|
33
|
+
aos init [--adapter <adapter>] [--force] [--apply] [--non-interactive] [--from-yaml <path>]
|
|
26
34
|
|
|
27
35
|
${c.bold("OPTIONS")}
|
|
28
|
-
--adapter <name>
|
|
29
|
-
--force
|
|
36
|
+
--adapter <name> Adapter to enable or validate: pi, claude-code, gemini, codex
|
|
37
|
+
--force Reinitialize config files and recopy core/ if needed
|
|
38
|
+
--apply Install missing AOS adapter packages after config generation
|
|
39
|
+
--non-interactive Scan-only mode. Writes .aos/scan.json and validates selected adapters if provided
|
|
40
|
+
--from-yaml <path> Read a prebuilt WizardResult YAML/JSON file and write config from it
|
|
30
41
|
|
|
31
42
|
${c.bold("DESCRIPTION")}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
aos init
|
|
37
|
-
aos init --adapter pi
|
|
38
|
-
aos init --adapter claude-code
|
|
43
|
+
Scans vendor CLI readiness, scans AOS adapter-package readiness, writes
|
|
44
|
+
.aos/config.yaml in v2 shape, and can optionally install missing adapter
|
|
45
|
+
packages. In non-interactive mode without a selected adapter, init is pure
|
|
46
|
+
scan/report and does not write config.
|
|
39
47
|
`;
|
|
40
48
|
|
|
41
|
-
function
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
function projectNameFromCwd(cwd: string): string {
|
|
50
|
+
return cwd.split("/").pop()?.toLowerCase().replace(/[^a-z0-9-]/g, "-") ?? "default";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseWizardResultFile(path: string): WizardResult {
|
|
54
|
+
const parsed = yaml.load(readFileSync(path, "utf-8"), { schema: yaml.JSON_SCHEMA }) as Partial<WizardResult> | null;
|
|
55
|
+
if (!parsed || typeof parsed !== "object") {
|
|
56
|
+
throw new Error("Malformed --from-yaml input: expected an object");
|
|
57
|
+
}
|
|
45
58
|
|
|
46
|
-
|
|
59
|
+
const enabledAdapters = Array.isArray(parsed.enabledAdapters)
|
|
60
|
+
? parsed.enabledAdapters.filter((value): value is AdapterName => typeof value === "string" && isValidAdapter(value))
|
|
61
|
+
: [];
|
|
62
|
+
const defaultAdapter = typeof parsed.defaultAdapter === "string" && isValidAdapter(parsed.defaultAdapter)
|
|
63
|
+
? parsed.defaultAdapter
|
|
64
|
+
: null;
|
|
47
65
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
premium: anthropic/claude-opus-4-6
|
|
66
|
+
if (enabledAdapters.length === 0 || !defaultAdapter || !enabledAdapters.includes(defaultAdapter)) {
|
|
67
|
+
throw new Error("Malformed --from-yaml input: enabledAdapters/defaultAdapter are invalid");
|
|
68
|
+
}
|
|
52
69
|
|
|
53
|
-
|
|
54
|
-
|
|
70
|
+
return {
|
|
71
|
+
enabledAdapters,
|
|
72
|
+
defaultAdapter,
|
|
73
|
+
memory: {
|
|
74
|
+
provider: parsed.memory?.provider === "mempalace" ? "mempalace" : "expertise",
|
|
75
|
+
},
|
|
76
|
+
models: parsed.models ?? getInitModels(process.cwd()),
|
|
77
|
+
editor: typeof parsed.editor === "string" ? parsed.editor : getInitEditor(process.cwd()),
|
|
78
|
+
actions: Array.isArray(parsed.actions) ? parsed.actions : [],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function writeScanReport(aosDir: string, scan: ScanReport): string {
|
|
83
|
+
mkdirSync(aosDir, { recursive: true });
|
|
84
|
+
const scanPath = join(aosDir, "scan.json");
|
|
85
|
+
writeFileSync(scanPath, JSON.stringify(scan, null, 2), "utf-8");
|
|
86
|
+
return scanPath;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function printAdapterInstallHints(version: string): void {
|
|
90
|
+
console.log(`${c.bold("Next step: install an adapter")}
|
|
91
|
+
Adapters augment the AI CLI you already use. Install the AOS adapter package that matches it:
|
|
92
|
+
|
|
93
|
+
Claude Code: ${c.cyan(`npm i -g @aos-harness/claude-code-adapter@${version}`)}
|
|
94
|
+
Gemini CLI: ${c.cyan(`npm i -g @aos-harness/gemini-adapter@${version}`)}
|
|
95
|
+
Codex CLI: ${c.cyan(`npm i -g @aos-harness/codex-adapter@${version}`)}
|
|
96
|
+
Pi: ${c.cyan(`npm i -g @aos-harness/pi-adapter@${version}`)}
|
|
97
|
+
|
|
98
|
+
${c.dim("Install one (or more) that matches the vendor CLI you already have.")}`);
|
|
55
99
|
}
|
|
56
100
|
|
|
57
101
|
export async function initCommand(args: ParsedArgs): Promise<void> {
|
|
@@ -60,39 +104,59 @@ export async function initCommand(args: ParsedArgs): Promise<void> {
|
|
|
60
104
|
return;
|
|
61
105
|
}
|
|
62
106
|
|
|
63
|
-
const adapter = (args.flags.adapter as string) || "pi";
|
|
64
|
-
|
|
65
|
-
if (!isValidAdapter(adapter)) {
|
|
66
|
-
console.error(c.red(`Invalid adapter: "${adapter}". Valid adapters: ${ADAPTER_ALLOWLIST.join(", ")}`));
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
107
|
const cwd = process.cwd();
|
|
71
108
|
const aosDir = join(cwd, ".aos");
|
|
72
109
|
const configPath = join(aosDir, "config.yaml");
|
|
73
|
-
|
|
110
|
+
const memoryPath = join(aosDir, "memory.yaml");
|
|
74
111
|
const force = !!args.flags.force;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
112
|
+
const apply = !!args.flags.apply;
|
|
113
|
+
const isInteractive = !!(process.stdin.isTTY && process.stdout.isTTY);
|
|
114
|
+
const fromYamlPath = typeof args.flags["from-yaml"] === "string" ? args.flags["from-yaml"] : null;
|
|
115
|
+
const explicitAdapters = parseAdapterList(args.flags.adapter);
|
|
116
|
+
|
|
117
|
+
if (!isInteractive && !args.flags["non-interactive"] && !fromYamlPath) {
|
|
118
|
+
console.error('Pass --from-yaml=<path> or --non-interactive');
|
|
119
|
+
process.exit(2);
|
|
80
120
|
}
|
|
81
121
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
122
|
+
const scan = await scanEnvironment({ cwd });
|
|
123
|
+
const scanPath = writeScanReport(aosDir, scan);
|
|
124
|
+
|
|
125
|
+
if (args.flags["non-interactive"] && !fromYamlPath) {
|
|
126
|
+
const selected = explicitAdapters.length > 0 ? explicitAdapters : getSelectedAdaptersForInit(cwd, args.flags.adapter);
|
|
127
|
+
if (selected.length === 0) {
|
|
128
|
+
console.log(`${c.green("Scan complete.")} Report: ${c.cyan(scanPath)}`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const notReady = selected.filter((adapter) => scan.adapters[adapter].status !== "ready");
|
|
133
|
+
if (notReady.length > 0) {
|
|
134
|
+
console.error(c.red(`Selected adapters are not ready: ${notReady.join(", ")}`));
|
|
135
|
+
for (const adapter of notReady) {
|
|
136
|
+
console.error(c.dim(` ${adapter}: ${scan.adapters[adapter].statusHint}`));
|
|
137
|
+
}
|
|
138
|
+
process.exit(3);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.log(`${c.green("Selected adapters are ready.")} Report: ${c.cyan(scanPath)}`);
|
|
86
142
|
return;
|
|
87
143
|
}
|
|
88
144
|
|
|
89
|
-
|
|
145
|
+
let wizardResult: WizardResult;
|
|
146
|
+
if (fromYamlPath) {
|
|
147
|
+
wizardResult = parseWizardResultFile(fromYamlPath);
|
|
148
|
+
} else {
|
|
149
|
+
wizardResult = await runWizard(scan, cwd, args.flags.adapter);
|
|
150
|
+
}
|
|
151
|
+
|
|
90
152
|
mkdirSync(aosDir, { recursive: true });
|
|
153
|
+
writeFileSync(configPath, mergeConfig(cwd, wizardResult, scan.packageManager), "utf-8");
|
|
91
154
|
|
|
92
|
-
|
|
93
|
-
|
|
155
|
+
const projectName = projectNameFromCwd(cwd);
|
|
156
|
+
if (!existsSync(memoryPath) || force) {
|
|
157
|
+
writeFileSync(memoryPath, generateMemoryYaml(projectName, wizardResult.memory.provider), "utf-8");
|
|
158
|
+
}
|
|
94
159
|
|
|
95
|
-
// Copy core configs if not already present (or --force)
|
|
96
160
|
const destCore = join(cwd, "core");
|
|
97
161
|
if (!existsSync(destCore) || force) {
|
|
98
162
|
const sourceCore = getPackageCoreDir() ?? join(getHarnessRoot(), "core");
|
|
@@ -101,82 +165,31 @@ export async function initCommand(args: ParsedArgs): Promise<void> {
|
|
|
101
165
|
console.log(c.green(" Copied core configs (agents, profiles, domains, workflows, skills)"));
|
|
102
166
|
} else {
|
|
103
167
|
console.log(c.yellow(" Warning: Could not find core configs to copy."));
|
|
104
|
-
console.log(c.dim(` You may need to manually copy the core/ directory from the AOS Harness repository.`));
|
|
105
168
|
}
|
|
106
169
|
}
|
|
107
170
|
|
|
108
|
-
const projectName = cwd.split("/").pop()?.toLowerCase().replace(/[^a-z0-9-]/g, "-") ?? "default";
|
|
109
|
-
|
|
110
|
-
const memoryYaml = `# AOS Memory Configuration
|
|
111
|
-
# Generated by: aos init
|
|
112
|
-
api_version: aos/memory/v1
|
|
113
|
-
|
|
114
|
-
# Memory provider: "mempalace" (recommended) or "expertise" (built-in fallback)
|
|
115
|
-
provider: expertise
|
|
116
|
-
|
|
117
|
-
# MemPalace settings (used when provider: mempalace)
|
|
118
|
-
# mempalace:
|
|
119
|
-
# palace_path: ~/.mempalace/palace
|
|
120
|
-
# project_wing: ${projectName}
|
|
121
|
-
# wake_layers: [L0, L1]
|
|
122
|
-
# auto_hall: true
|
|
123
|
-
# max_wake_tokens: 1200
|
|
124
|
-
# max_drawer_tokens: 500
|
|
125
|
-
|
|
126
|
-
# Built-in expertise settings (used when provider: expertise)
|
|
127
|
-
expertise:
|
|
128
|
-
max_lines: 200
|
|
129
|
-
scope: per-project
|
|
130
|
-
|
|
131
|
-
# Orchestrator memory behavior
|
|
132
|
-
orchestrator:
|
|
133
|
-
remember_prompt: session_end
|
|
134
|
-
recall_gate: true
|
|
135
|
-
max_recall_per_session: 10
|
|
136
|
-
`;
|
|
137
|
-
|
|
138
|
-
const memoryPath = join(aosDir, "memory.yaml");
|
|
139
|
-
if (!existsSync(memoryPath) || force) {
|
|
140
|
-
writeFileSync(memoryPath, memoryYaml);
|
|
141
|
-
console.log(c.dim(` Memory config: ${memoryPath}`));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
171
|
console.log(`
|
|
145
172
|
${c.green("AOS initialized successfully!")}
|
|
146
173
|
|
|
147
174
|
${c.bold("Configuration")}
|
|
148
175
|
Directory: ${c.cyan(aosDir)}
|
|
149
176
|
Config: ${c.cyan(configPath)}
|
|
150
|
-
Adapter: ${c.cyan(
|
|
151
|
-
|
|
152
|
-
${c.bold("Getting Started")}
|
|
153
|
-
1. Write a brief describing your strategic problem
|
|
154
|
-
${c.dim("See core/briefs/sample-product-decision/brief.md for an example")}
|
|
155
|
-
|
|
156
|
-
2. Run a deliberation session:
|
|
157
|
-
${c.cyan("aos run strategic-council --brief path/to/your/brief.md")}
|
|
158
|
-
|
|
159
|
-
3. Or list available profiles and agents:
|
|
160
|
-
${c.cyan("aos list")}
|
|
161
|
-
|
|
162
|
-
${c.bold("Customization")}
|
|
163
|
-
Create custom agents: ${c.cyan("aos create agent <name>")}
|
|
164
|
-
Create custom profiles: ${c.cyan("aos create profile <name>")}
|
|
165
|
-
Create domain packs: ${c.cyan("aos create domain <name>")}
|
|
166
|
-
Validate everything: ${c.cyan("aos validate")}
|
|
177
|
+
Adapter: ${c.cyan(wizardResult.defaultAdapter)}
|
|
178
|
+
Scan: ${c.cyan(scanPath)}
|
|
167
179
|
`);
|
|
168
180
|
|
|
169
|
-
|
|
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:
|
|
181
|
+
printAdapterInstallHints(readCliVersion());
|
|
174
182
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
183
|
+
if (apply) {
|
|
184
|
+
const result = await applyActions(wizardResult.actions);
|
|
185
|
+
if (!result.ok) {
|
|
186
|
+
console.error(c.red(`Some installs failed: ${result.failures.join(", ")}`));
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
const installable = wizardResult.actions.filter((action) => action.type === "install-adapter");
|
|
191
|
+
if (installable.length > 0) {
|
|
192
|
+
console.log(c.dim(`Run "aos init --apply" to install missing adapter packages with ${scan.packageManager}.`));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
182
195
|
}
|
package/src/commands/run.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { TranscriptEntry } from "@aos-harness/runtime/types";
|
|
|
10
10
|
import { runAdapterSession } from "../adapter-session";
|
|
11
11
|
import { readAdapterConfig } from "../adapter-config";
|
|
12
12
|
import { buildToolPolicy, type ToolPolicy } from "@aos-harness/adapter-shared";
|
|
13
|
+
import { getPlatformUrlFromConfig, resolveAdapterSelection } from "../aos-config";
|
|
13
14
|
|
|
14
15
|
function createEventBuffer(platformUrl: string, sessionId: string) {
|
|
15
16
|
const buffer: TranscriptEntry[] = [];
|
|
@@ -273,8 +274,8 @@ export async function runCommand(args: ParsedArgs): Promise<void> {
|
|
|
273
274
|
let workflowSection = "";
|
|
274
275
|
if (isExecutionProfile && workflowConfig) {
|
|
275
276
|
const stepSummary = workflowConfig.steps
|
|
276
|
-
.map((s
|
|
277
|
-
` ${s.id.padEnd(20)} ${s.name.padEnd(30)} ${s.action}${s.review_gate ? " [gate]" : ""}`
|
|
277
|
+
.map((s) =>
|
|
278
|
+
` ${s.id.padEnd(20)} ${(s.name ?? s.id).padEnd(30)} ${s.action}${s.review_gate ? " [gate]" : ""}`
|
|
278
279
|
)
|
|
279
280
|
.join("\n");
|
|
280
281
|
const gateCount = workflowConfig.gates?.length || 0;
|
|
@@ -348,20 +349,13 @@ ${c.bold(`AOS ${sessionType} Session`)}
|
|
|
348
349
|
Output: ${c.cyan(deliberationDir)}
|
|
349
350
|
`);
|
|
350
351
|
|
|
351
|
-
// Determine adapter
|
|
352
|
+
// Determine adapter with shared precedence so init/run agree:
|
|
353
|
+
// --adapter > config v2 > config v1 > .aos/adapter.yaml > default "pi"
|
|
352
354
|
let platformUrl = (args.flags["platform-url"] as string) || null;
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const yaml = await import("js-yaml");
|
|
357
|
-
const configText = await Bun.file(aosConfigPath).text();
|
|
358
|
-
const config = yaml.load(configText, { schema: yaml.JSON_SCHEMA }) as Record<string, unknown>;
|
|
359
|
-
adapter = (config.adapter as string) || "pi";
|
|
360
|
-
if (!platformUrl && config?.platform && (config.platform as Record<string, unknown>)?.enabled && (config.platform as Record<string, unknown>)?.url) {
|
|
361
|
-
platformUrl = (config.platform as Record<string, unknown>).url as string;
|
|
362
|
-
}
|
|
355
|
+
const { adapter } = resolveAdapterSelection(process.cwd(), args.flags["adapter"]);
|
|
356
|
+
if (!platformUrl) {
|
|
357
|
+
platformUrl = getPlatformUrlFromConfig(process.cwd());
|
|
363
358
|
}
|
|
364
|
-
if (args.flags["adapter"]) adapter = args.flags["adapter"] as string;
|
|
365
359
|
|
|
366
360
|
// Validate platform URL (spec D5). Fires for both --platform-url flag
|
|
367
361
|
// and .aos/config.yaml platform.url after both sources are merged.
|
|
@@ -390,7 +384,7 @@ ${c.bold(`AOS ${sessionType} Session`)}
|
|
|
390
384
|
const adapterEntry = resolvedAdapterDir ? join(resolvedAdapterDir, "src", "index.ts") : null;
|
|
391
385
|
if (!adapterEntry || !existsSync(adapterEntry)) {
|
|
392
386
|
console.error(c.red(`Pi adapter not found.`));
|
|
393
|
-
console.error(c.yellow("Make sure Pi CLI is installed: https://github.com/pi-
|
|
387
|
+
console.error(c.yellow("Make sure Pi CLI is installed: https://pi.dev (source: https://github.com/badlogic/pi-mono)"));
|
|
394
388
|
console.error(c.dim("Install the adapter package:"));
|
|
395
389
|
console.error(c.dim(" npm i -g @aos-harness/pi-adapter"));
|
|
396
390
|
console.error(c.dim(" # or in a project: npm i @aos-harness/pi-adapter"));
|