@botbotgo/agent-harness 0.0.188 → 0.0.189
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 +19 -0
- package/README.zh.md +19 -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 +1 -1
package/README.md
CHANGED
|
@@ -217,6 +217,25 @@ Real products need a runtime that can answer harder questions:
|
|
|
217
217
|
- It lets YAML own assembly and operating policy while code keeps a small, stable surface
|
|
218
218
|
- It goes deep on runtime concerns that upstream libraries do not fully productize
|
|
219
219
|
|
|
220
|
+
## What To Sell First
|
|
221
|
+
|
|
222
|
+
The product story should stay scenario-shaped instead of drifting back to a generic "multi-agent runtime" pitch.
|
|
223
|
+
|
|
224
|
+
- Enterprise internal agent runtime: approvals, restart-safe recovery, operator evidence, and policy-owned MCP access.
|
|
225
|
+
- Code modernization runtime: long-running coding flows, approval checkpoints, resumable runs, and exported evidence packages.
|
|
226
|
+
- Protocol bridge runtime: ACP, A2A, AG-UI, and runtime MCP on one stable control plane instead of bespoke per-surface glue.
|
|
227
|
+
|
|
228
|
+
Typical runtime governance defaults now look like:
|
|
229
|
+
|
|
230
|
+
```yaml
|
|
231
|
+
governance:
|
|
232
|
+
remoteMcp:
|
|
233
|
+
denyTrustTiers: ["untrusted"]
|
|
234
|
+
denyTenantScopes: ["cross-tenant"]
|
|
235
|
+
denyPromptInjectionRisks: ["high"]
|
|
236
|
+
requireApprovalTransports: ["websocket"]
|
|
237
|
+
```
|
|
238
|
+
|
|
220
239
|
## When To Use It
|
|
221
240
|
|
|
222
241
|
Use `agent-harness` when:
|
package/README.zh.md
CHANGED
|
@@ -214,6 +214,25 @@ AI 让 agent 逻辑、工具调用和工作流代码更容易生成,真正更
|
|
|
214
214
|
- 复杂装配与运行策略交给 YAML,代码面保持小而稳
|
|
215
215
|
- 在上游库未充分产品化的运行时问题上做深做透
|
|
216
216
|
|
|
217
|
+
## 先卖哪三类场景
|
|
218
|
+
|
|
219
|
+
产品叙事应该保持场景化,不要再漂回“通用 multi-agent runtime”。
|
|
220
|
+
|
|
221
|
+
- 企业内部 agent 运行时:审批、重启恢复、operator 证据链,以及由策略持有的 MCP 访问控制。
|
|
222
|
+
- 代码现代化运行时:长链路 coding flow、审批检查点、可恢复 runs,以及可导出的运行证据包。
|
|
223
|
+
- 协议桥接运行时:ACP、A2A、AG-UI 与 runtime MCP 共用一套稳定控制面,而不是每个对外接面各写一层胶水。
|
|
224
|
+
|
|
225
|
+
现在推荐的 runtime 治理默认值大致是:
|
|
226
|
+
|
|
227
|
+
```yaml
|
|
228
|
+
governance:
|
|
229
|
+
remoteMcp:
|
|
230
|
+
denyTrustTiers: ["untrusted"]
|
|
231
|
+
denyTenantScopes: ["cross-tenant"]
|
|
232
|
+
denyPromptInjectionRisks: ["high"]
|
|
233
|
+
requireApprovalTransports: ["websocket"]
|
|
234
|
+
```
|
|
235
|
+
|
|
217
236
|
## 什么时候该用
|
|
218
237
|
|
|
219
238
|
下面这些场景适合用 `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.188";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.188";
|
|
@@ -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(", ")}`);
|