@botbotgo/agent-harness 0.0.188 → 0.0.190
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/LICENSE +22 -0
- package/README.md +24 -0
- package/README.zh.md +24 -0
- package/dist/cli.js +64 -6
- package/dist/config/runtime/workspace.yaml +6 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/runtime/harness/system/policy-engine.js +53 -4
- package/package.json +2 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The botbotgo Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
CHANGED
|
@@ -31,6 +31,11 @@
|
|
|
31
31
|
(<code>docs/development/</code>, English / 中文)
|
|
32
32
|
</p>
|
|
33
33
|
|
|
34
|
+
<p align="center">
|
|
35
|
+
<a href="./LICENSE">License</a> · <a href="./CONTRIBUTING.md">Contributing</a> ·
|
|
36
|
+
<a href="./SECURITY.md">Security</a> · <a href="./CODE_OF_CONDUCT.md">Code of Conduct</a>
|
|
37
|
+
</p>
|
|
38
|
+
|
|
34
39
|
<p align="center">
|
|
35
40
|
<em
|
|
36
41
|
>We specialize in AI solutions. If you have a product idea you want to ship,
|
|
@@ -217,6 +222,25 @@ Real products need a runtime that can answer harder questions:
|
|
|
217
222
|
- It lets YAML own assembly and operating policy while code keeps a small, stable surface
|
|
218
223
|
- It goes deep on runtime concerns that upstream libraries do not fully productize
|
|
219
224
|
|
|
225
|
+
## What To Sell First
|
|
226
|
+
|
|
227
|
+
The product story should stay scenario-shaped instead of drifting back to a generic "multi-agent runtime" pitch.
|
|
228
|
+
|
|
229
|
+
- Enterprise internal agent runtime: approvals, restart-safe recovery, operator evidence, and policy-owned MCP access.
|
|
230
|
+
- Code modernization runtime: long-running coding flows, approval checkpoints, resumable runs, and exported evidence packages.
|
|
231
|
+
- Protocol bridge runtime: ACP, A2A, AG-UI, and runtime MCP on one stable control plane instead of bespoke per-surface glue.
|
|
232
|
+
|
|
233
|
+
Typical runtime governance defaults now look like:
|
|
234
|
+
|
|
235
|
+
```yaml
|
|
236
|
+
governance:
|
|
237
|
+
remoteMcp:
|
|
238
|
+
denyTrustTiers: ["untrusted"]
|
|
239
|
+
denyTenantScopes: ["cross-tenant"]
|
|
240
|
+
denyPromptInjectionRisks: ["high"]
|
|
241
|
+
requireApprovalTransports: ["websocket"]
|
|
242
|
+
```
|
|
243
|
+
|
|
220
244
|
## When To Use It
|
|
221
245
|
|
|
222
246
|
Use `agent-harness` when:
|
package/README.zh.md
CHANGED
|
@@ -31,6 +31,11 @@
|
|
|
31
31
|
(多页面静态文档位于 <code>docs/development/</code>,支持 English / 中文)
|
|
32
32
|
</p>
|
|
33
33
|
|
|
34
|
+
<p align="center">
|
|
35
|
+
<a href="./LICENSE">许可证</a> · <a href="./CONTRIBUTING.md">贡献指南</a> ·
|
|
36
|
+
<a href="./SECURITY.md">安全说明</a> · <a href="./CODE_OF_CONDUCT.md">行为准则</a>
|
|
37
|
+
</p>
|
|
38
|
+
|
|
34
39
|
<p align="center">
|
|
35
40
|
<em
|
|
36
41
|
>我们专注于 AI 解决方案。若有希望落地的产品想法,欢迎来信
|
|
@@ -214,6 +219,25 @@ AI 让 agent 逻辑、工具调用和工作流代码更容易生成,真正更
|
|
|
214
219
|
- 复杂装配与运行策略交给 YAML,代码面保持小而稳
|
|
215
220
|
- 在上游库未充分产品化的运行时问题上做深做透
|
|
216
221
|
|
|
222
|
+
## 先卖哪三类场景
|
|
223
|
+
|
|
224
|
+
产品叙事应该保持场景化,不要再漂回“通用 multi-agent runtime”。
|
|
225
|
+
|
|
226
|
+
- 企业内部 agent 运行时:审批、重启恢复、operator 证据链,以及由策略持有的 MCP 访问控制。
|
|
227
|
+
- 代码现代化运行时:长链路 coding flow、审批检查点、可恢复 runs,以及可导出的运行证据包。
|
|
228
|
+
- 协议桥接运行时:ACP、A2A、AG-UI 与 runtime MCP 共用一套稳定控制面,而不是每个对外接面各写一层胶水。
|
|
229
|
+
|
|
230
|
+
现在推荐的 runtime 治理默认值大致是:
|
|
231
|
+
|
|
232
|
+
```yaml
|
|
233
|
+
governance:
|
|
234
|
+
remoteMcp:
|
|
235
|
+
denyTrustTiers: ["untrusted"]
|
|
236
|
+
denyTenantScopes: ["cross-tenant"]
|
|
237
|
+
denyPromptInjectionRisks: ["high"]
|
|
238
|
+
requireApprovalTransports: ["websocket"]
|
|
239
|
+
```
|
|
240
|
+
|
|
217
241
|
## 什么时候该用
|
|
218
242
|
|
|
219
243
|
下面这些场景适合用 `agent-harness`:
|
package/dist/cli.js
CHANGED
|
@@ -212,6 +212,55 @@ function parseRuntimeInspectOptions(args) {
|
|
|
212
212
|
function renderJson(value) {
|
|
213
213
|
return `${JSON.stringify(value, null, 2)}\n`;
|
|
214
214
|
}
|
|
215
|
+
function isObject(value) {
|
|
216
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
217
|
+
}
|
|
218
|
+
function formatTimestamp(value) {
|
|
219
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
220
|
+
}
|
|
221
|
+
function renderHealthSnapshot(snapshot, workspacePath) {
|
|
222
|
+
const lines = [];
|
|
223
|
+
const status = typeof snapshot.status === "string" ? snapshot.status : "unknown";
|
|
224
|
+
lines.push(`Runtime health ${workspacePath}: ${status}`);
|
|
225
|
+
const checks = isObject(snapshot.checks) ? snapshot.checks : {};
|
|
226
|
+
const checkEntries = Object.entries(checks)
|
|
227
|
+
.filter(([, value]) => isObject(value))
|
|
228
|
+
.map(([name, value]) => {
|
|
229
|
+
const check = value;
|
|
230
|
+
const reason = typeof check.reason === "string" && check.reason.trim().length > 0 ? ` (${check.reason})` : "";
|
|
231
|
+
return ` - ${name}: ${typeof check.status === "string" ? check.status : "unknown"}${reason}`;
|
|
232
|
+
});
|
|
233
|
+
if (checkEntries.length > 0) {
|
|
234
|
+
lines.push("Checks:");
|
|
235
|
+
lines.push(...checkEntries);
|
|
236
|
+
}
|
|
237
|
+
const stats = isObject(snapshot.stats) ? snapshot.stats : {};
|
|
238
|
+
const statEntries = [
|
|
239
|
+
["activeRunSlots", stats.activeRunSlots],
|
|
240
|
+
["pendingRunSlots", stats.pendingRunSlots],
|
|
241
|
+
["pendingApprovals", stats.pendingApprovals],
|
|
242
|
+
["stuckRuns", stats.stuckRuns],
|
|
243
|
+
["llmSuccessRate1m", stats.llmSuccessRate1m],
|
|
244
|
+
["llmP95LatencyMs1m", stats.llmP95LatencyMs1m],
|
|
245
|
+
]
|
|
246
|
+
.filter(([, value]) => typeof value === "number")
|
|
247
|
+
.map(([name, value]) => ` - ${name}: ${value}`);
|
|
248
|
+
if (statEntries.length > 0) {
|
|
249
|
+
lines.push("Stats:");
|
|
250
|
+
lines.push(...statEntries);
|
|
251
|
+
}
|
|
252
|
+
const symptoms = Array.isArray(snapshot.symptoms) ? snapshot.symptoms.filter(isObject) : [];
|
|
253
|
+
if (symptoms.length > 0) {
|
|
254
|
+
lines.push("Symptoms:");
|
|
255
|
+
lines.push(...symptoms.map((symptom) => {
|
|
256
|
+
const code = typeof symptom.code === "string" ? symptom.code : "unknown";
|
|
257
|
+
const severity = typeof symptom.severity === "string" ? symptom.severity : "unknown";
|
|
258
|
+
const message = typeof symptom.message === "string" ? symptom.message : "";
|
|
259
|
+
return ` - ${code}: ${severity}${message ? ` (${message})` : ""}`;
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
return `${lines.join("\n")}\n`;
|
|
263
|
+
}
|
|
215
264
|
async function sleep(ms) {
|
|
216
265
|
await new Promise((resolve) => {
|
|
217
266
|
setTimeout(resolve, ms);
|
|
@@ -225,8 +274,14 @@ function renderApprovalList(approvals) {
|
|
|
225
274
|
const status = typeof approval.status === "string" ? approval.status : "unknown";
|
|
226
275
|
const toolName = typeof approval.toolName === "string" ? approval.toolName : "unknown_tool";
|
|
227
276
|
const approvalId = typeof approval.approvalId === "string" ? approval.approvalId : "unknown";
|
|
277
|
+
const threadId = typeof approval.threadId === "string" ? ` thread=${approval.threadId}` : "";
|
|
278
|
+
const runId = typeof approval.runId === "string" ? ` run=${approval.runId}` : "";
|
|
228
279
|
const reason = typeof approval.approvalReason === "string" ? ` reason=${approval.approvalReason}` : "";
|
|
229
|
-
|
|
280
|
+
const requestedAt = formatTimestamp(approval.requestedAt);
|
|
281
|
+
const resolvedAt = formatTimestamp(approval.resolvedAt);
|
|
282
|
+
const requested = requestedAt ? ` requested=${requestedAt}` : "";
|
|
283
|
+
const resolved = resolvedAt ? ` resolved=${resolvedAt}` : "";
|
|
284
|
+
return `${approvalId} status=${status} tool=${toolName}${threadId}${runId}${reason}${requested}${resolved}`;
|
|
230
285
|
}).join("\n") + "\n";
|
|
231
286
|
}
|
|
232
287
|
function renderRunList(runs) {
|
|
@@ -238,7 +293,13 @@ function renderRunList(runs) {
|
|
|
238
293
|
const threadId = typeof run.threadId === "string" ? run.threadId : "unknown";
|
|
239
294
|
const agentId = typeof run.agentId === "string" ? run.agentId : "unknown";
|
|
240
295
|
const state = typeof run.state === "string" ? run.state : "unknown";
|
|
241
|
-
|
|
296
|
+
const currentAgent = typeof run.currentAgentId === "string" ? ` current=${run.currentAgentId}` : "";
|
|
297
|
+
const resumable = typeof run.resumable === "boolean" ? ` resumable=${run.resumable}` : "";
|
|
298
|
+
const updatedAt = formatTimestamp(run.updatedAt);
|
|
299
|
+
const lastActivityAt = formatTimestamp(run.lastActivityAt);
|
|
300
|
+
const updated = updatedAt ? ` updated=${updatedAt}` : "";
|
|
301
|
+
const lastActivity = lastActivityAt ? ` activity=${lastActivityAt}` : "";
|
|
302
|
+
return `${runId} thread=${threadId} agent=${agentId}${currentAgent} state=${state}${resumable}${updated}${lastActivity}`;
|
|
242
303
|
}).join("\n") + "\n";
|
|
243
304
|
}
|
|
244
305
|
export async function runCli(argv, io = {}, deps = {}) {
|
|
@@ -431,10 +492,7 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
431
492
|
const workspacePath = path.resolve(cwd, parsed.workspaceRoot ?? ".");
|
|
432
493
|
if (subcommand === "health") {
|
|
433
494
|
const snapshot = await runtime.getHealth();
|
|
434
|
-
stdout(parsed.json ? renderJson(snapshot) :
|
|
435
|
-
if (!parsed.json) {
|
|
436
|
-
stdout(renderJson(snapshot));
|
|
437
|
-
}
|
|
495
|
+
stdout(parsed.json ? renderJson(snapshot) : renderHealthSnapshot(snapshot, workspacePath));
|
|
438
496
|
await runtime.stop();
|
|
439
497
|
return 0;
|
|
440
498
|
}
|
|
@@ -155,6 +155,12 @@ spec:
|
|
|
155
155
|
# Keep transport-specific and trust-specific policy here rather than scattering it across app code.
|
|
156
156
|
governance:
|
|
157
157
|
remoteMcp:
|
|
158
|
+
denyTrustTiers:
|
|
159
|
+
- untrusted
|
|
160
|
+
denyTenantScopes:
|
|
161
|
+
- cross-tenant
|
|
162
|
+
denyPromptInjectionRisks:
|
|
163
|
+
- high
|
|
158
164
|
requireApprovalTransports:
|
|
159
165
|
- websocket
|
|
160
166
|
riskByTransport:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.189";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.189";
|
|
@@ -49,6 +49,17 @@ export class PolicyEngine {
|
|
|
49
49
|
const trimmed = value.trim();
|
|
50
50
|
return trimmed.startsWith("mcp/") ? trimmed : `mcp/${trimmed}`;
|
|
51
51
|
};
|
|
52
|
+
const readStringSet = (value) => new Set(Array.isArray(value)
|
|
53
|
+
? value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim())
|
|
54
|
+
: []);
|
|
55
|
+
const riskRank = {
|
|
56
|
+
low: 0,
|
|
57
|
+
medium: 1,
|
|
58
|
+
high: 2,
|
|
59
|
+
};
|
|
60
|
+
const maxPromptInjectionRisk = remoteMcp.maxPromptInjectionRisk === "low" || remoteMcp.maxPromptInjectionRisk === "medium" || remoteMcp.maxPromptInjectionRisk === "high"
|
|
61
|
+
? remoteMcp.maxPromptInjectionRisk
|
|
62
|
+
: undefined;
|
|
52
63
|
const allowServerRefs = new Set(Array.isArray(remoteMcp.allowServerRefs)
|
|
53
64
|
? remoteMcp.allowServerRefs
|
|
54
65
|
.map((item) => normalizeServerRef(item))
|
|
@@ -59,9 +70,14 @@ export class PolicyEngine {
|
|
|
59
70
|
.map((item) => normalizeServerRef(item))
|
|
60
71
|
.filter((item) => Boolean(item))
|
|
61
72
|
: []);
|
|
62
|
-
const denyTransports =
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
const denyTransports = readStringSet(remoteMcp.denyTransports);
|
|
74
|
+
const allowTrustTiers = readStringSet(remoteMcp.allowTrustTiers);
|
|
75
|
+
const denyTrustTiers = readStringSet(remoteMcp.denyTrustTiers);
|
|
76
|
+
const allowTenantScopes = readStringSet(remoteMcp.allowTenantScopes);
|
|
77
|
+
const denyTenantScopes = readStringSet(remoteMcp.denyTenantScopes);
|
|
78
|
+
const denyPromptInjectionRisks = readStringSet(remoteMcp.denyPromptInjectionRisks);
|
|
79
|
+
const allowOauthScopes = readStringSet(remoteMcp.allowOauthScopes);
|
|
80
|
+
const denyOauthScopes = readStringSet(remoteMcp.denyOauthScopes);
|
|
65
81
|
const tools = binding.execution?.params?.tools ?? binding.langchainAgentParams?.tools ?? binding.deepAgentParams?.tools ?? [];
|
|
66
82
|
const deniedRemoteTools = tools.flatMap((tool) => {
|
|
67
83
|
if (tool.type !== "mcp") {
|
|
@@ -80,13 +96,43 @@ export class PolicyEngine {
|
|
|
80
96
|
const transport = typeof inlineMcpServer?.transport === "string" && inlineMcpServer.transport.trim().length > 0
|
|
81
97
|
? inlineMcpServer.transport.trim()
|
|
82
98
|
: undefined;
|
|
99
|
+
const trustTier = inlineMcpServer?.trustTier === "trusted" || inlineMcpServer?.trustTier === "reviewed" || inlineMcpServer?.trustTier === "untrusted"
|
|
100
|
+
? inlineMcpServer.trustTier
|
|
101
|
+
: undefined;
|
|
102
|
+
const tenantScope = inlineMcpServer?.tenantScope === "workspace" ||
|
|
103
|
+
inlineMcpServer?.tenantScope === "project" ||
|
|
104
|
+
inlineMcpServer?.tenantScope === "tenant" ||
|
|
105
|
+
inlineMcpServer?.tenantScope === "cross-tenant"
|
|
106
|
+
? inlineMcpServer.tenantScope
|
|
107
|
+
: undefined;
|
|
108
|
+
const promptInjectionRisk = inlineMcpServer?.promptInjectionRisk === "low" ||
|
|
109
|
+
inlineMcpServer?.promptInjectionRisk === "medium" ||
|
|
110
|
+
inlineMcpServer?.promptInjectionRisk === "high"
|
|
111
|
+
? inlineMcpServer.promptInjectionRisk
|
|
112
|
+
: undefined;
|
|
113
|
+
const oauth = typeof inlineMcpServer?.oauth === "object" && inlineMcpServer.oauth && !Array.isArray(inlineMcpServer.oauth)
|
|
114
|
+
? inlineMcpServer.oauth
|
|
115
|
+
: undefined;
|
|
116
|
+
const oauthScopes = Array.isArray(oauth?.scopes)
|
|
117
|
+
? oauth.scopes.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim())
|
|
118
|
+
: [];
|
|
83
119
|
const serverDenied = serverRef ? denyServerRefs.has(serverRef) || (allowServerRefs.size > 0 && !allowServerRefs.has(serverRef)) : false;
|
|
84
120
|
const transportDenied = transport ? denyTransports.has(transport) : false;
|
|
85
|
-
|
|
121
|
+
const trustDenied = trustTier ? denyTrustTiers.has(trustTier) || (allowTrustTiers.size > 0 && !allowTrustTiers.has(trustTier)) : false;
|
|
122
|
+
const tenantDenied = tenantScope ? denyTenantScopes.has(tenantScope) || (allowTenantScopes.size > 0 && !allowTenantScopes.has(tenantScope)) : false;
|
|
123
|
+
const promptRiskDenied = (promptInjectionRisk ? denyPromptInjectionRisks.has(promptInjectionRisk) : false)
|
|
124
|
+
|| (promptInjectionRisk && maxPromptInjectionRisk ? riskRank[promptInjectionRisk] > riskRank[maxPromptInjectionRisk] : false);
|
|
125
|
+
const oauthDenied = oauthScopes.some((scope) => denyOauthScopes.has(scope))
|
|
126
|
+
|| (allowOauthScopes.size > 0 && oauthScopes.some((scope) => !allowOauthScopes.has(scope)));
|
|
127
|
+
return serverDenied || transportDenied || trustDenied || tenantDenied || promptRiskDenied || oauthDenied
|
|
86
128
|
? [{
|
|
87
129
|
toolName: tool.name,
|
|
88
130
|
...(serverRef ? { serverRef } : {}),
|
|
89
131
|
...(transport ? { transport } : {}),
|
|
132
|
+
...(trustTier ? { trustTier } : {}),
|
|
133
|
+
...(tenantScope ? { tenantScope } : {}),
|
|
134
|
+
...(promptInjectionRisk ? { promptInjectionRisk } : {}),
|
|
135
|
+
...(oauthScopes.length > 0 ? { oauthScopes } : {}),
|
|
90
136
|
}]
|
|
91
137
|
: [];
|
|
92
138
|
});
|
|
@@ -102,6 +148,9 @@ export class PolicyEngine {
|
|
|
102
148
|
if (tool.transport) {
|
|
103
149
|
return `${tool.toolName} (${tool.transport})`;
|
|
104
150
|
}
|
|
151
|
+
if (tool.trustTier) {
|
|
152
|
+
return `${tool.toolName} (${tool.trustTier})`;
|
|
153
|
+
}
|
|
105
154
|
return tool.toolName;
|
|
106
155
|
});
|
|
107
156
|
reasons.push(`runtime governance denied remote MCP access: ${details.join(", ")}`);
|
package/package.json
CHANGED