@botbotgo/agent-harness 0.0.55 → 0.0.57
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 +131 -56
- package/dist/api.d.ts +7 -0
- package/dist/api.js +6 -0
- package/dist/config/agents/direct.yaml +18 -0
- package/dist/config/agents/orchestra.yaml +10 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/resource/resource-impl.js +41 -9
- package/dist/runtime/agent-runtime-adapter.d.ts +8 -0
- package/dist/runtime/agent-runtime-adapter.js +126 -8
- package/dist/runtime/declared-middleware.d.ts +5 -0
- package/dist/runtime/declared-middleware.js +38 -2
- package/dist/runtime/harness.d.ts +7 -0
- package/dist/runtime/harness.js +7 -0
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/inventory.d.ts +10 -5
- package/dist/runtime/inventory.js +50 -12
- package/dist/runtime/skill-requirements.d.ts +27 -0
- package/dist/runtime/skill-requirements.js +112 -0
- package/dist/runtime/support/runtime-env.d.ts +2 -0
- package/dist/runtime/support/runtime-env.js +57 -0
- package/dist/runtime/support/skill-metadata.d.ts +14 -1
- package/dist/runtime/support/skill-metadata.js +59 -7
- package/dist/workspace/validate.js +3 -0
- package/package.json +2 -2
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { augmentExecutablePath } from "./support/runtime-env.js";
|
|
4
|
+
function dedupe(items) {
|
|
5
|
+
return Array.from(new Set(items.filter(Boolean)));
|
|
6
|
+
}
|
|
7
|
+
function pathEntries(pathValue) {
|
|
8
|
+
return augmentExecutablePath(pathValue ?? process.env.PATH ?? "")
|
|
9
|
+
.split(path.delimiter)
|
|
10
|
+
.map((entry) => entry.trim())
|
|
11
|
+
.filter(Boolean);
|
|
12
|
+
}
|
|
13
|
+
function isExecutableInPath(binName, searchPath) {
|
|
14
|
+
for (const entry of pathEntries(searchPath)) {
|
|
15
|
+
if (existsSync(path.join(entry, binName))) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
function assessNamedRequirements(names, evaluator) {
|
|
22
|
+
return dedupe(names ?? []).map((name) => ({
|
|
23
|
+
name,
|
|
24
|
+
status: evaluator(name),
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
function hasConfigPath(config, dottedPath) {
|
|
28
|
+
const parts = dottedPath.split(".").filter(Boolean);
|
|
29
|
+
let cursor = config;
|
|
30
|
+
for (const part of parts) {
|
|
31
|
+
if (typeof cursor !== "object" || cursor === null || Array.isArray(cursor) || !(part in cursor)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
cursor = cursor[part];
|
|
35
|
+
}
|
|
36
|
+
return cursor !== undefined;
|
|
37
|
+
}
|
|
38
|
+
function assessConfigRequirements(names, config) {
|
|
39
|
+
if (!names || names.length === 0) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
if (!config) {
|
|
43
|
+
return dedupe(names).map((name) => ({ name, status: "unknown" }));
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(config)) {
|
|
46
|
+
const set = new Set(config);
|
|
47
|
+
return dedupe(names).map((name) => ({
|
|
48
|
+
name,
|
|
49
|
+
status: set.has(name) ? "satisfied" : "missing",
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
return dedupe(names).map((name) => ({
|
|
53
|
+
name,
|
|
54
|
+
status: hasConfigPath(config, name) ? "satisfied" : "missing",
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
function summarizeRequirementGroups(groups) {
|
|
58
|
+
const flat = groups.flat();
|
|
59
|
+
const missing = flat.some((item) => item.status === "missing");
|
|
60
|
+
const unknown = flat.some((item) => item.status === "unknown");
|
|
61
|
+
return {
|
|
62
|
+
ready: flat.length === 0 ? true : !missing && !unknown,
|
|
63
|
+
missing,
|
|
64
|
+
unknown,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function assessOpenClawRequirements(metadata, options = {}) {
|
|
68
|
+
if (!metadata?.requires) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
const availableBins = options.availableBins ? new Set(options.availableBins) : null;
|
|
72
|
+
const bins = assessNamedRequirements(metadata.requires.bins, (name) => availableBins
|
|
73
|
+
? (availableBins.has(name) ? "satisfied" : "missing")
|
|
74
|
+
: (isExecutableInPath(name, options.path) ? "satisfied" : "missing"));
|
|
75
|
+
const anyBins = assessNamedRequirements(metadata.requires.anyBins, (name) => availableBins
|
|
76
|
+
? (availableBins.has(name) ? "satisfied" : "missing")
|
|
77
|
+
: (isExecutableInPath(name, options.path) ? "satisfied" : "missing"));
|
|
78
|
+
const env = assessNamedRequirements(metadata.requires.env, (name) => options.env && options.env[name] ? "satisfied" : "missing");
|
|
79
|
+
const config = assessConfigRequirements(metadata.requires.config, options.config);
|
|
80
|
+
const primaryEnv = metadata.requires.primaryEnv
|
|
81
|
+
? {
|
|
82
|
+
name: metadata.requires.primaryEnv,
|
|
83
|
+
status: (options.env && options.env[metadata.requires.primaryEnv] ? "satisfied" : "missing"),
|
|
84
|
+
}
|
|
85
|
+
: undefined;
|
|
86
|
+
const anyBinsSummary = anyBins.length === 0
|
|
87
|
+
? []
|
|
88
|
+
: [{
|
|
89
|
+
name: anyBins.map((item) => item.name).join(" | "),
|
|
90
|
+
status: (anyBins.some((item) => item.status === "satisfied") ? "satisfied" : "missing"),
|
|
91
|
+
}];
|
|
92
|
+
const summary = summarizeRequirementGroups([
|
|
93
|
+
bins,
|
|
94
|
+
anyBinsSummary,
|
|
95
|
+
env,
|
|
96
|
+
config,
|
|
97
|
+
...(primaryEnv ? [[primaryEnv]] : []),
|
|
98
|
+
]);
|
|
99
|
+
return {
|
|
100
|
+
...summary,
|
|
101
|
+
bins,
|
|
102
|
+
anyBins,
|
|
103
|
+
env,
|
|
104
|
+
config,
|
|
105
|
+
...(primaryEnv ? { primaryEnv } : {}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export function assessSkillRequirements(metadata, options = {}) {
|
|
109
|
+
return {
|
|
110
|
+
...(metadata.openclaw ? { openclaw: assessOpenClawRequirements(metadata.openclaw, options) } : {}),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare function augmentExecutablePath(pathValue: string | undefined, env?: Record<string, string | undefined> | NodeJS.ProcessEnv): string;
|
|
2
|
+
export declare function createRuntimeEnv(env: Record<string, string> | undefined, baseEnv?: Record<string, string | undefined> | NodeJS.ProcessEnv): Record<string, string>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
4
|
+
const COMMON_EXECUTABLE_DIRS = [
|
|
5
|
+
"/opt/homebrew/bin",
|
|
6
|
+
"/opt/homebrew/sbin",
|
|
7
|
+
"/usr/local/bin",
|
|
8
|
+
"/usr/local/sbin",
|
|
9
|
+
"/usr/bin",
|
|
10
|
+
"/bin",
|
|
11
|
+
"/usr/sbin",
|
|
12
|
+
"/sbin",
|
|
13
|
+
];
|
|
14
|
+
function normalizeEnvRecord(env) {
|
|
15
|
+
return Object.fromEntries(Object.entries(env).filter((entry) => typeof entry[1] === "string"));
|
|
16
|
+
}
|
|
17
|
+
function splitPathEntries(pathValue) {
|
|
18
|
+
return (pathValue ?? "")
|
|
19
|
+
.split(path.delimiter)
|
|
20
|
+
.map((entry) => entry.trim())
|
|
21
|
+
.filter(Boolean);
|
|
22
|
+
}
|
|
23
|
+
function discoverNvmExecutableDirs(home) {
|
|
24
|
+
const versionsRoot = path.join(home, ".nvm", "versions", "node");
|
|
25
|
+
if (!existsSync(versionsRoot)) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
return readdirSync(versionsRoot, { withFileTypes: true })
|
|
29
|
+
.filter((entry) => entry.isDirectory())
|
|
30
|
+
.map((entry) => path.join(versionsRoot, entry.name, "bin"));
|
|
31
|
+
}
|
|
32
|
+
function defaultExecutableDirs(env) {
|
|
33
|
+
const home = env.HOME ?? env.USERPROFILE ?? homedir();
|
|
34
|
+
return [
|
|
35
|
+
...COMMON_EXECUTABLE_DIRS,
|
|
36
|
+
path.join(home, ".pyenv", "shims"),
|
|
37
|
+
path.join(home, ".asdf", "shims"),
|
|
38
|
+
...discoverNvmExecutableDirs(home),
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
export function augmentExecutablePath(pathValue, env = process.env) {
|
|
42
|
+
const deduped = new Set();
|
|
43
|
+
for (const entry of [...splitPathEntries(pathValue), ...defaultExecutableDirs(env)]) {
|
|
44
|
+
deduped.add(entry);
|
|
45
|
+
}
|
|
46
|
+
return Array.from(deduped).join(path.delimiter);
|
|
47
|
+
}
|
|
48
|
+
export function createRuntimeEnv(env, baseEnv = process.env) {
|
|
49
|
+
const base = normalizeEnvRecord(baseEnv);
|
|
50
|
+
const merged = {
|
|
51
|
+
...base,
|
|
52
|
+
...(env ?? {}),
|
|
53
|
+
};
|
|
54
|
+
const combinedPath = [base.PATH, env?.PATH].filter((value) => typeof value === "string").join(path.delimiter);
|
|
55
|
+
merged.PATH = augmentExecutablePath(combinedPath, { ...base, ...(env ?? {}) });
|
|
56
|
+
return merged;
|
|
57
|
+
}
|
|
@@ -3,8 +3,21 @@ export type SkillMetadata = {
|
|
|
3
3
|
description?: string;
|
|
4
4
|
license?: string;
|
|
5
5
|
compatibility?: string;
|
|
6
|
-
metadata?: Record<string,
|
|
6
|
+
metadata?: Record<string, unknown>;
|
|
7
7
|
allowedTools?: string[];
|
|
8
|
+
userInvocable?: boolean;
|
|
9
|
+
openclaw?: OpenClawSkillMetadata;
|
|
10
|
+
};
|
|
11
|
+
export type OpenClawSkillRequirements = {
|
|
12
|
+
bins?: string[];
|
|
13
|
+
anyBins?: string[];
|
|
14
|
+
env?: string[];
|
|
15
|
+
config?: string[];
|
|
16
|
+
primaryEnv?: string;
|
|
17
|
+
};
|
|
18
|
+
export type OpenClawSkillMetadata = {
|
|
19
|
+
emoji?: string;
|
|
20
|
+
requires?: OpenClawSkillRequirements;
|
|
8
21
|
};
|
|
9
22
|
export declare function readSkillMetadata(skillPath: string): SkillMetadata;
|
|
10
23
|
export declare function validateSkillMetadata(skillPath: string): SkillMetadata;
|
|
@@ -11,33 +11,81 @@ function parseFrontmatterSource(document) {
|
|
|
11
11
|
function isRecord(value) {
|
|
12
12
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
13
|
}
|
|
14
|
+
function isJsonLikeValue(value) {
|
|
15
|
+
if (value === null ||
|
|
16
|
+
typeof value === "string" ||
|
|
17
|
+
typeof value === "number" ||
|
|
18
|
+
typeof value === "boolean") {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
return value.every((item) => isJsonLikeValue(item));
|
|
23
|
+
}
|
|
24
|
+
if (isRecord(value)) {
|
|
25
|
+
return Object.values(value).every((item) => isJsonLikeValue(item));
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
14
29
|
function normalizeMetadata(value, strict) {
|
|
15
30
|
if (value === undefined) {
|
|
16
31
|
return undefined;
|
|
17
32
|
}
|
|
18
33
|
if (!isRecord(value)) {
|
|
19
34
|
if (strict) {
|
|
20
|
-
throw new Error("metadata must be a
|
|
35
|
+
throw new Error("metadata must be a JSON-like key-value map");
|
|
21
36
|
}
|
|
22
37
|
return undefined;
|
|
23
38
|
}
|
|
24
39
|
const normalized = Object.entries(value).reduce((acc, [key, item]) => {
|
|
25
|
-
if (
|
|
40
|
+
if (isJsonLikeValue(item)) {
|
|
26
41
|
acc[key] = item;
|
|
27
42
|
}
|
|
28
43
|
return acc;
|
|
29
44
|
}, {});
|
|
30
45
|
if (strict && Object.keys(normalized).length !== Object.keys(value).length) {
|
|
31
|
-
throw new Error("metadata values must
|
|
46
|
+
throw new Error("metadata values must be JSON-like");
|
|
32
47
|
}
|
|
33
48
|
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
34
49
|
}
|
|
50
|
+
function normalizeStringArray(value) {
|
|
51
|
+
if (typeof value === "string") {
|
|
52
|
+
const tokens = value.split(/\s+/).filter(Boolean);
|
|
53
|
+
return tokens.length > 0 ? tokens : undefined;
|
|
54
|
+
}
|
|
55
|
+
if (!Array.isArray(value)) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const tokens = value
|
|
59
|
+
.filter((item) => typeof item === "string")
|
|
60
|
+
.map((item) => item.trim())
|
|
61
|
+
.filter(Boolean);
|
|
62
|
+
return tokens.length > 0 ? tokens : undefined;
|
|
63
|
+
}
|
|
35
64
|
function toAllowedTools(value) {
|
|
36
|
-
|
|
65
|
+
const tools = normalizeStringArray(value);
|
|
66
|
+
return tools && tools.length > 0 ? tools : undefined;
|
|
67
|
+
}
|
|
68
|
+
function parseOpenClawMetadata(value) {
|
|
69
|
+
if (!isRecord(value)) {
|
|
37
70
|
return undefined;
|
|
38
71
|
}
|
|
39
|
-
const
|
|
40
|
-
|
|
72
|
+
const requires = isRecord(value.requires) ? value.requires : undefined;
|
|
73
|
+
const normalizedRequires = {
|
|
74
|
+
...(normalizeStringArray(requires?.bins) ? { bins: normalizeStringArray(requires?.bins) } : {}),
|
|
75
|
+
...(normalizeStringArray(requires?.anyBins) ? { anyBins: normalizeStringArray(requires?.anyBins) } : {}),
|
|
76
|
+
...(normalizeStringArray(requires?.env) ? { env: normalizeStringArray(requires?.env) } : {}),
|
|
77
|
+
...(normalizeStringArray(requires?.config) ? { config: normalizeStringArray(requires?.config) } : {}),
|
|
78
|
+
...(typeof value.primaryEnv === "string" && value.primaryEnv.trim()
|
|
79
|
+
? { primaryEnv: value.primaryEnv.trim() }
|
|
80
|
+
: typeof requires?.primaryEnv === "string" && requires.primaryEnv.trim()
|
|
81
|
+
? { primaryEnv: requires.primaryEnv.trim() }
|
|
82
|
+
: {}),
|
|
83
|
+
};
|
|
84
|
+
const normalized = {
|
|
85
|
+
...(typeof value.emoji === "string" && value.emoji.trim() ? { emoji: value.emoji } : {}),
|
|
86
|
+
...(Object.keys(normalizedRequires).length > 0 ? { requires: normalizedRequires } : {}),
|
|
87
|
+
};
|
|
88
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
41
89
|
}
|
|
42
90
|
function parseFrontmatter(document, strict = false) {
|
|
43
91
|
const source = parseFrontmatterSource(document);
|
|
@@ -55,6 +103,8 @@ function parseFrontmatter(document, strict = false) {
|
|
|
55
103
|
compatibility: typeof parsed.compatibility === "string" ? parsed.compatibility : undefined,
|
|
56
104
|
metadata: normalizeMetadata(parsed.metadata, strict),
|
|
57
105
|
allowedTools: toAllowedTools(parsed["allowed-tools"]),
|
|
106
|
+
userInvocable: typeof parsed["user-invocable"] === "boolean" ? parsed["user-invocable"] : undefined,
|
|
107
|
+
openclaw: parseOpenClawMetadata(isRecord(parsed.metadata) ? parsed.metadata.openclaw : undefined),
|
|
58
108
|
};
|
|
59
109
|
}
|
|
60
110
|
function isLegacyBuiltinSkillName(name) {
|
|
@@ -86,7 +136,7 @@ function validateMetadata(metadata, skillPath) {
|
|
|
86
136
|
return;
|
|
87
137
|
}
|
|
88
138
|
if (Object.keys(metadata).length === 0) {
|
|
89
|
-
throw new Error(`Skill ${skillPath} metadata must contain
|
|
139
|
+
throw new Error(`Skill ${skillPath} metadata must contain JSON-like key-value pairs when provided`);
|
|
90
140
|
}
|
|
91
141
|
}
|
|
92
142
|
function parseSkillMetadataFromDocument(document, skillPath, strict) {
|
|
@@ -104,6 +154,8 @@ function parseSkillMetadataFromDocument(document, skillPath, strict) {
|
|
|
104
154
|
compatibility: parsed.compatibility,
|
|
105
155
|
metadata: parsed.metadata,
|
|
106
156
|
allowedTools: parsed.allowedTools,
|
|
157
|
+
userInvocable: parsed.userInvocable,
|
|
158
|
+
openclaw: parsed.openclaw,
|
|
107
159
|
};
|
|
108
160
|
if (strict) {
|
|
109
161
|
validateSkillName(metadata.name, skillPath);
|
|
@@ -41,6 +41,9 @@ function validateMiddlewareConfig(agent) {
|
|
|
41
41
|
if (kind === "humanInTheLoop" && typeof typed.interruptOn !== "object") {
|
|
42
42
|
throw new Error(`Agent ${agent.id} humanInTheLoop middleware requires interruptOn`);
|
|
43
43
|
}
|
|
44
|
+
if (kind === "filesystem" && agent.executionMode === "deepagent") {
|
|
45
|
+
throw new Error(`Agent ${agent.id} cannot use filesystem middleware with DeepAgents; configure filesystem behavior through the deepagent backend instead`);
|
|
46
|
+
}
|
|
44
47
|
if (kind === "pii" && typeof typed.piiType !== "string") {
|
|
45
48
|
throw new Error(`Agent ${agent.id} pii middleware requires piiType`);
|
|
46
49
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botbotgo/agent-harness",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.57",
|
|
4
4
|
"description": "Workspace runtime for multi-agent applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@10.9.2",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && cp -R config dist/",
|
|
52
52
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
53
|
-
"test": "vitest run test/public-api.test.ts test/resource-optional-provider.test.ts test/resource-isolation.test.ts test/stock-research-app-load-harness.test.ts test/stock-research-app-run.test.ts test/stock-research-app-config.test.ts test/release-workflow.test.ts test/release-version.test.ts test/gitignore.test.ts test/package-lock.test.ts test/readme.test.ts test/product-boundary-docs.test.ts test/runtime-adapter-regressions.test.ts test/runtime-capabilities.test.ts test/runtime-recovery.test.ts test/tool-extension-gaps.test.ts test/checkpoint-maintenance.test.ts test/llamaindex-dependency-compat.test.ts test/skill-standard.test.ts test/routing-config.test.ts test/workspace-compat-regressions.test.ts test/upstream-compat-regressions.test.ts test/yaml-format.test.ts",
|
|
53
|
+
"test": "vitest run test/hello-file.test.ts test/public-api.test.ts test/resource-optional-provider.test.ts test/resource-isolation.test.ts test/stock-research-app-load-harness.test.ts test/stock-research-app-run.test.ts test/stock-research-app-config.test.ts test/release-workflow.test.ts test/release-version.test.ts test/gitignore.test.ts test/package-lock.test.ts test/readme.test.ts test/product-boundary-docs.test.ts test/docs-site.test.ts test/runtime-adapter-regressions.test.ts test/runtime-capabilities.test.ts test/runtime-recovery.test.ts test/tool-extension-gaps.test.ts test/checkpoint-maintenance.test.ts test/llamaindex-dependency-compat.test.ts test/skill-standard.test.ts test/routing-config.test.ts test/workspace-compat-regressions.test.ts test/upstream-compat-regressions.test.ts test/yaml-format.test.ts",
|
|
54
54
|
"test:real-providers": "vitest run test/real-provider-harness.test.ts",
|
|
55
55
|
"release:prepare": "npm version patch --no-git-tag-version && node ./scripts/sync-example-version.mjs",
|
|
56
56
|
"release:pack": "npm pack --dry-run",
|