@zhivex-ai/harness 1.2.0 → 1.3.0-rc.7
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/CHANGELOG.md +64 -0
- package/README.md +9 -10
- package/contracts/engine-api.json +2921 -0
- package/contracts/public-api.json +267 -1
- package/contracts/stable-api-signatures.json +43 -43
- package/dist/acp-cli.js +137 -15002
- package/dist/approvals/approval-history.d.ts +22 -0
- package/dist/approvals/explicit-review.d.ts +24 -0
- package/dist/approvals/host-policy-identity.d.ts +3 -0
- package/dist/cli/arguments.d.ts +6 -2
- package/dist/cli/checkpoints.d.ts +230 -0
- package/dist/cli/cli-options.d.ts +2 -41
- package/dist/cli/cli-profiles.d.ts +4 -2
- package/dist/cli/cli-stream.d.ts +2 -167
- package/dist/cli/governance-report.d.ts +5 -0
- package/dist/cli/policy-decisions.d.ts +9 -0
- package/dist/cli/policy.d.ts +5 -0
- package/dist/cli/presentation.d.ts +3 -1
- package/dist/cli/run-document.d.ts +2 -91
- package/dist/cli/terminal/terminal-continuation.d.ts +2 -3
- package/dist/cli/terminal/terminal-ui.d.ts +4 -0
- package/dist/{cli-entry-c2f58zj2.js → cli-entry-ae7x44d4.js} +14969 -10731
- package/dist/{cli-entry-427869g9.js → cli-entry-d5788aay.js} +388 -47
- package/dist/cli-entry-ev76ezcd.js +656 -0
- package/dist/{cli-3jmy4h5h.js → cli-t7hrhayq.js} +918 -2223
- package/dist/cli.js +3 -5
- package/dist/client/acp-mcp-admission.d.ts +32 -0
- package/dist/client/acp-mcp-host.d.ts +19 -0
- package/dist/client/acp-mcp-session.d.ts +14 -0
- package/dist/client/acp-stdio.d.ts +2 -0
- package/dist/client/acp.d.ts +4 -1
- package/dist/client/adapter.d.ts +13 -1
- package/dist/client/continuation.d.ts +3 -0
- package/dist/client/json-contracts.d.ts +107 -4
- package/dist/client/local-service.d.ts +2 -0
- package/dist/client/protocol.d.ts +127 -1
- package/dist/client/run-document.d.ts +91 -0
- package/dist/client/service-events.d.ts +4 -0
- package/dist/client/stream.d.ts +167 -0
- package/dist/compat/cli-options.d.ts +53 -0
- package/dist/compat/code-support.d.ts +22 -0
- package/dist/compat/code-support.js +74 -0
- package/dist/{config-1k9t4jhh.js → config-c002f06f.js} +1 -1
- package/dist/config-j3jga5wh.js +61 -0
- package/dist/context/adaptive-compaction.d.ts +1 -0
- package/dist/context/compaction.d.ts +1 -1
- package/dist/context/request-measurements.d.ts +17 -0
- package/dist/context/request-projection.d.ts +10 -0
- package/dist/context/task-memory.d.ts +1 -0
- package/dist/engine/acp.d.ts +6 -0
- package/dist/engine/acp.js +205 -0
- package/dist/engine/client.d.ts +6 -0
- package/dist/engine/client.js +28 -0
- package/dist/engine/desktop-providers-v1.d.ts +4 -0
- package/dist/engine/desktop-providers-v1.js +18 -0
- package/dist/engine/desktop-state-v1.d.ts +10 -0
- package/dist/engine/desktop-state-v1.js +168 -0
- package/dist/engine/index.d.ts +73 -0
- package/dist/engine/index.js +977 -0
- package/dist/engine/mcp-stdio-v1.d.ts +11 -0
- package/dist/engine/mcp-stdio-v1.js +108 -0
- package/dist/engine/models.d.ts +3 -0
- package/dist/engine/models.js +894 -0
- package/dist/engine/protocol.d.ts +7 -0
- package/dist/engine/protocol.js +182 -0
- package/dist/engine/service.d.ts +5 -0
- package/dist/engine/service.js +28 -0
- package/dist/execution/acceptance-snapshot.d.ts +2 -0
- package/dist/execution/execution-environment.d.ts +10 -0
- package/dist/execution/mcp-oci-channel.d.ts +11 -0
- package/dist/execution/mcp-oci-server.d.ts +38 -0
- package/dist/execution/mcp-oci-workspace.d.ts +23 -0
- package/dist/execution/mcp-resource-recovery.d.ts +19 -0
- package/dist/execution/mcp-workspace-snapshot.d.ts +20 -0
- package/dist/hostile-repository-demo.js +3368 -1304
- package/dist/index-1bx83sw4.js +4 -0
- package/dist/index-2gz7jv7g.js +549 -0
- package/dist/{cli-entry-b1mfvmxe.js → index-2jrdcj6b.js} +72 -115
- package/dist/index-3ah1f369.js +356 -0
- package/dist/index-42gp2hy8.js +1555 -0
- package/dist/index-4jw3tvy8.js +301 -0
- package/dist/index-8705zxpr.js +145 -0
- package/dist/index-8vm2m34s.js +2080 -0
- package/dist/index-8zzhggxv.js +477 -0
- package/dist/index-a97bezbz.js +1073 -0
- package/dist/index-gq9xagx2.js +1343 -0
- package/dist/index-h0mqx2ed.js +1373 -0
- package/dist/index-jgrx804e.js +5589 -0
- package/dist/index-qfpz5y2v.js +174 -0
- package/dist/index-t60rva31.js +1004 -0
- package/dist/index-wg1sd3at.js +80 -0
- package/dist/index-xqxeypt5.js +888 -0
- package/dist/index-yb239ghk.js +961 -0
- package/dist/index-z0qxmkm2.js +3389 -0
- package/dist/index-z4e8d0ph.js +1768 -0
- package/dist/index.js +251 -18649
- package/dist/integrations/mcp-host-session.d.ts +26 -0
- package/dist/integrations/mcp-stdio-admission.d.ts +54 -0
- package/dist/integrations/mcp-stdio-client.d.ts +44 -0
- package/dist/integrations/mcp-stdio-framing.d.ts +18 -0
- package/dist/integrations/mcp-stdio-launch.d.ts +20 -0
- package/dist/integrations/mcp-stdio-session.d.ts +7 -0
- package/dist/integrations/mcp-stdio-tools.d.ts +12 -0
- package/dist/internal/desktop/catalog.d.ts +2 -0
- package/dist/internal/desktop/persistence.d.ts +2 -3
- package/dist/internal/desktop/providers.d.ts +1 -0
- package/dist/models/catalog.d.ts +2 -0
- package/dist/persistence/governance-context.d.ts +126 -0
- package/dist/persistence/governance-report.d.ts +299 -0
- package/dist/persistence/host-database-backup.d.ts +28 -0
- package/dist/persistence/mcp-execution-journal.d.ts +19 -0
- package/dist/persistence/mcp-reconciliation.d.ts +33 -0
- package/dist/persistence/mcp-resource-journal.d.ts +54 -0
- package/dist/persistence/workspace-checkpoints.d.ts +138 -0
- package/dist/providers/anthropic-continuation.d.ts +5 -0
- package/dist/providers/providers.d.ts +1 -1
- package/dist/providers/vertex-auth.d.ts +27 -0
- package/dist/runtime/delegation-contracts.d.ts +9 -0
- package/dist/runtime/delegation-correction.d.ts +9 -0
- package/dist/runtime/delegation-result.d.ts +73 -0
- package/dist/runtime/error-diagnostics.d.ts +86 -0
- package/dist/runtime/harness.d.ts +11 -1
- package/dist/runtime/policy-decisions.d.ts +66 -0
- package/dist/runtime/policy-inspection.d.ts +52 -0
- package/dist/runtime/provider-diagnostics.d.ts +1 -0
- package/dist/runtime/runtime-instructions.d.ts +8 -0
- package/dist/runtime/task-acceptance-checks.d.ts +103 -0
- package/dist/runtime/task-acceptance-delivery.d.ts +107 -0
- package/dist/runtime/task-acceptance-host.d.ts +20 -0
- package/dist/runtime/task-acceptance-record.d.ts +107 -0
- package/dist/runtime/task-acceptance-validation.d.ts +25 -0
- package/dist/runtime/task-acceptance.d.ts +47 -0
- package/dist/runtime/time-to-safe-fix.d.ts +83 -0
- package/dist/runtime/tool-policy-file.d.ts +23 -0
- package/dist/runtime/tool-policy-paths.d.ts +5 -0
- package/dist/runtime/tool-policy.d.ts +6 -0
- package/dist/service-cli.js +19 -14504
- package/dist/{service-client-cli-yx56n0hc.js → service-client-cli-kns4mf57.js} +307 -248
- package/dist/verify-historical-migrations.js +3314 -1251
- package/dist/workspace/path-policy.d.ts +4 -0
- package/dist/zhx.js +3 -5
- package/docs/ACP.md +14 -2
- package/docs/ANTHROPIC.md +33 -0
- package/docs/CLI.md +132 -0
- package/docs/CLIENT_PROTOCOL.md +45 -0
- package/docs/CODE_RELEASE.md +93 -0
- package/docs/ENGINE_API.md +319 -0
- package/docs/EXTENSIBILITY.md +46 -3
- package/docs/FIRST_USE.md +6 -6
- package/docs/LOCAL_SERVICE.md +13 -0
- package/docs/MCP_STDIO.md +20 -0
- package/docs/README.md +6 -0
- package/docs/STABILITY.md +9 -1
- package/docs/SUPPORT_MATRIX.md +1 -1
- package/docs/TOOL_POLICY.md +81 -0
- package/docs/VERTEX.md +50 -0
- package/docs/adr/0002-isolated-mcp-stdio.md +135 -0
- package/docs/fixtures/mcp-stdio-contract-v1.json +189 -0
- package/docs/support-matrix.json +2 -0
- package/evaluations/representative-assembly-matrix.json +64 -1
- package/evaluations/task-acceptance-routes.json +7 -0
- package/examples/README.md +1 -1
- package/package.json +74 -12
- package/dist/cli-entry-64pdp3k5.js +0 -17
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// src/cli/terminal/session-grants.ts
|
|
2
|
+
function checkApprovalGrant(approval, workspace) {
|
|
3
|
+
if (approval.kind !== "local-tool" || approval.name !== "run_check")
|
|
4
|
+
return;
|
|
5
|
+
try {
|
|
6
|
+
const args = JSON.parse(approval.arguments);
|
|
7
|
+
if (!args || typeof args !== "object" || Array.isArray(args))
|
|
8
|
+
return;
|
|
9
|
+
const value = args;
|
|
10
|
+
if (Object.keys(value).sort().join(",") !== "check,expectedScript" || typeof value.check !== "string" || typeof value.expectedScript !== "string" || !value.expectedScript.trim())
|
|
11
|
+
return;
|
|
12
|
+
return JSON.stringify([workspace, approval.provider, approval.childAgentId ?? "", approval.name, value.check, value.expectedScript]);
|
|
13
|
+
} catch {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/cli/terminal/terminal-ui.ts
|
|
19
|
+
var DEFAULT_APPROVAL_SUMMARY_CHARACTERS = 1200;
|
|
20
|
+
var EXACT_REVIEW_TOOLS = new Set([
|
|
21
|
+
"apply_patch",
|
|
22
|
+
"apply_reviewed_edits",
|
|
23
|
+
"apply_reviewed_replacement",
|
|
24
|
+
"verify_and_apply_reviewed_edits",
|
|
25
|
+
"move_file",
|
|
26
|
+
"quarantine_file",
|
|
27
|
+
"restore_file",
|
|
28
|
+
"run_check",
|
|
29
|
+
"run_environment_command",
|
|
30
|
+
"run_environment_batch",
|
|
31
|
+
"run_environment_shell",
|
|
32
|
+
"apply_environment_patch",
|
|
33
|
+
"verify_and_apply_environment_patch"
|
|
34
|
+
]);
|
|
35
|
+
var TERMINAL_CONTROL_CHARACTERS = /[\u0000-\u0008\u000b\u000c\u000d\u000e-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/gu;
|
|
36
|
+
var escapeCodePoint = (character) => {
|
|
37
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
38
|
+
return codePoint <= 65535 ? `\\u${codePoint.toString(16).padStart(4, "0")}` : `\\u{${codePoint.toString(16)}}`;
|
|
39
|
+
};
|
|
40
|
+
var sanitizeTerminalText = (value) => value.replace(TERMINAL_CONTROL_CHARACTERS, escapeCodePoint);
|
|
41
|
+
var readableApprovalArguments = (argumentsText) => {
|
|
42
|
+
try {
|
|
43
|
+
return sanitizeTerminalText(JSON.stringify(JSON.parse(argumentsText), null, 2));
|
|
44
|
+
} catch {
|
|
45
|
+
return sanitizeTerminalText(argumentsText);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var boundedText = (value, maximumCharacters) => {
|
|
49
|
+
if (value.length <= maximumCharacters) {
|
|
50
|
+
return { text: value, omitted: 0 };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
text: `${value.slice(0, maximumCharacters)}…`,
|
|
54
|
+
omitted: value.length - maximumCharacters
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
var formatApproval = (approval, options = {}) => {
|
|
58
|
+
const detail = options.detail ?? "summary";
|
|
59
|
+
const maximumCharacters = options.maxSummaryCharacters ?? DEFAULT_APPROVAL_SUMMARY_CHARACTERS;
|
|
60
|
+
if (!Number.isSafeInteger(maximumCharacters) || maximumCharacters < 1) {
|
|
61
|
+
throw new Error("Approval summary length must be a positive safe integer.");
|
|
62
|
+
}
|
|
63
|
+
const completePayload = readableApprovalArguments(approval.arguments);
|
|
64
|
+
const shouldShowComplete = detail === "full" || EXACT_REVIEW_TOOLS.has(approval.name);
|
|
65
|
+
const payload = shouldShowComplete ? { text: completePayload, omitted: 0 } : boundedText(completePayload, maximumCharacters);
|
|
66
|
+
const identity = [
|
|
67
|
+
detail === "full" && approval.id ? `approval ${sanitizeTerminalText(approval.id)}` : undefined,
|
|
68
|
+
detail === "full" && approval.inputDigest ? `input ${sanitizeTerminalText(approval.inputDigest)}` : undefined,
|
|
69
|
+
approval.serverLabel ? `server ${sanitizeTerminalText(approval.serverLabel)}` : undefined,
|
|
70
|
+
approval.childAgentId ? `child ${sanitizeTerminalText(approval.childAgentId)}` : undefined
|
|
71
|
+
].filter((value) => value !== undefined);
|
|
72
|
+
const header = `[${sanitizeTerminalText(approval.kind ?? "provider")}] ` + `${sanitizeTerminalText(approval.name)}` + (identity.length > 0 ? ` · ${identity.join(" · ")}` : "");
|
|
73
|
+
const omissionNotice = payload.omitted > 0 ? `
|
|
74
|
+
[${payload.omitted} characters omitted; press v to view the complete payload]` : "";
|
|
75
|
+
const scope = [];
|
|
76
|
+
if (EXACT_REVIEW_TOOLS.has(approval.name)) {
|
|
77
|
+
try {
|
|
78
|
+
const args = JSON.parse(approval.arguments);
|
|
79
|
+
if (Array.isArray(args.changes))
|
|
80
|
+
for (const change of args.changes) {
|
|
81
|
+
if (typeof change?.path === "string")
|
|
82
|
+
scope.push(`${change.expectedDigest === null ? "ADD" : "MODIFY"} ${change.path}`);
|
|
83
|
+
}
|
|
84
|
+
if (approval.name === "quarantine_file" && typeof args.path === "string")
|
|
85
|
+
scope.push(`REMOVE (recoverable quarantine) ${args.path}`);
|
|
86
|
+
if (approval.name === "move_file")
|
|
87
|
+
scope.push(`MOVE ${args.source} -> ${args.destination}`);
|
|
88
|
+
if (approval.name.includes("verify_and_apply"))
|
|
89
|
+
scope.push("Verification: pending; approval authorizes the checks and conditional application, not a successful result.");
|
|
90
|
+
} catch {}
|
|
91
|
+
}
|
|
92
|
+
if (detail === "summary" && approval.kind === "local-tool" && approval.name === "run_check") {
|
|
93
|
+
try {
|
|
94
|
+
const args = JSON.parse(approval.arguments);
|
|
95
|
+
if (typeof args.check === "string" && typeof args.expectedScript === "string" && Object.keys(args).every((key) => ["check", "expectedScript"].includes(key))) {
|
|
96
|
+
return `Run check: ${sanitizeTerminalText(args.check)}${identity.length ? ` · ${identity.join(" · ")}` : ""}
|
|
97
|
+
|
|
98
|
+
${sanitizeTerminalText(args.expectedScript)}
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
} catch {}
|
|
102
|
+
}
|
|
103
|
+
return `${header}
|
|
104
|
+
${scope.map(sanitizeTerminalText).map((line) => `${line}
|
|
105
|
+
`).join("")}${payload.text}${omissionNotice}`;
|
|
106
|
+
};
|
|
107
|
+
var promptEnded = (error) => {
|
|
108
|
+
if (!error || typeof error !== "object")
|
|
109
|
+
return false;
|
|
110
|
+
const candidate = error;
|
|
111
|
+
return candidate.name === "AbortError" || candidate.code === "ABORT_ERR" || candidate.code === "ERR_USE_AFTER_CLOSE";
|
|
112
|
+
};
|
|
113
|
+
var normalizedDecision = (answer) => answer.trim().toLowerCase();
|
|
114
|
+
var resolveTerminalApprovals = async (approvals, options) => {
|
|
115
|
+
if (options.requireCompleteReview && approvals.reduce((bytes, approval) => bytes + Buffer.byteLength(approval.arguments), 0) > 256 * 1024) {
|
|
116
|
+
options.write(`Explicit review exceeds the complete payload limit; approvals remain pending.
|
|
117
|
+
`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const responses = [];
|
|
121
|
+
const staged = new Set(options.sessionGrants);
|
|
122
|
+
for (let index = 0;index < approvals.length; index += 1) {
|
|
123
|
+
const approval = approvals[index];
|
|
124
|
+
const grant = !options.requireCompleteReview && options.sessionGrants && options.workspace ? checkApprovalGrant(approval, options.workspace) : undefined;
|
|
125
|
+
if (grant && staged.has(grant)) {
|
|
126
|
+
responses.push({ provider: approval.provider, approvalRequestId: approval.id, approve: true, reason: "Exact check approved for this CLI session." });
|
|
127
|
+
options.onDecisionOrigin?.("automatic");
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
options.write(`
|
|
131
|
+
Approval required ${index + 1}/${approvals.length}:
|
|
132
|
+
` + `${formatApproval(approval, {
|
|
133
|
+
detail: options.requireCompleteReview ? "full" : "summary",
|
|
134
|
+
...options.maxSummaryCharacters !== undefined ? { maxSummaryCharacters: options.maxSummaryCharacters } : {}
|
|
135
|
+
})}
|
|
136
|
+
`);
|
|
137
|
+
if (options.workspace)
|
|
138
|
+
options.write(`Workspace: ${sanitizeTerminalText(options.workspace)}
|
|
139
|
+
`);
|
|
140
|
+
for (;; ) {
|
|
141
|
+
let answer;
|
|
142
|
+
try {
|
|
143
|
+
const received = options.select ? await options.select("Permission required", [
|
|
144
|
+
{ value: "n", label: "Reject" },
|
|
145
|
+
{ value: "y", label: "Allow once", detail: "Only the action shown above" },
|
|
146
|
+
...grant ? [{ value: "s", label: "Allow this exact check for this session", detail: "Changing the script requires approval again" }] : [],
|
|
147
|
+
{ value: "v", label: "View technical details" },
|
|
148
|
+
{ value: "q", label: "Leave pending" }
|
|
149
|
+
]) : await options.ask(`Approve? [y]es/${grant ? "[s]ession/" : ""}[n]o/[v]iew/[q]uit (default: no) `);
|
|
150
|
+
if (typeof received !== "string")
|
|
151
|
+
return;
|
|
152
|
+
answer = normalizedDecision(received);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (promptEnded(error))
|
|
155
|
+
return;
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
if (answer === "v" || answer === "view") {
|
|
159
|
+
options.write(`
|
|
160
|
+
Complete approval payload:
|
|
161
|
+
${formatApproval(approval, { detail: "full" })}
|
|
162
|
+
`);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (answer === "q" || answer === "quit") {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (answer === "y" || answer === "yes" || answer === "s" && grant) {
|
|
169
|
+
if (answer === "s" && grant)
|
|
170
|
+
staged.add(grant);
|
|
171
|
+
responses.push({
|
|
172
|
+
provider: approval.provider,
|
|
173
|
+
approvalRequestId: approval.id,
|
|
174
|
+
approve: true,
|
|
175
|
+
reason: options.approvedReason ?? "Approved interactively."
|
|
176
|
+
});
|
|
177
|
+
options.onDecisionOrigin?.("interactive");
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
if (answer === "" || answer === "n" || answer === "no") {
|
|
181
|
+
responses.push({
|
|
182
|
+
provider: approval.provider,
|
|
183
|
+
approvalRequestId: approval.id,
|
|
184
|
+
approve: false,
|
|
185
|
+
reason: options.deniedReason ?? "Denied by the operator."
|
|
186
|
+
});
|
|
187
|
+
options.onDecisionOrigin?.("interactive");
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
options.write(`Choose y, n, v, or q.
|
|
191
|
+
`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
for (const key of staged)
|
|
195
|
+
options.sessionGrants?.add(key);
|
|
196
|
+
return responses;
|
|
197
|
+
};
|
|
198
|
+
var terminalSupportsColor = (isTTY, env = process.env) => isTTY && !Object.prototype.hasOwnProperty.call(env, "NO_COLOR") && env.TERM !== "dumb" && env.FORCE_COLOR !== "0";
|
|
199
|
+
var verificationReceipt = (name, output) => {
|
|
200
|
+
if (!output || typeof output !== "object")
|
|
201
|
+
return;
|
|
202
|
+
const value = output;
|
|
203
|
+
const receipt = name === "run_check" || name === "run_environment_command" ? value : name.startsWith("verify_and_apply_") && value.verification && typeof value.verification === "object" ? value.verification : undefined;
|
|
204
|
+
if (!receipt || !Number.isSafeInteger(receipt.exitCode))
|
|
205
|
+
return;
|
|
206
|
+
const command = receipt.command;
|
|
207
|
+
const prefix = Array.isArray(command) ? JSON.stringify(command.slice(0, -1)) : "";
|
|
208
|
+
const supported = [
|
|
209
|
+
'["bun","--no-env-file","run"]',
|
|
210
|
+
'["npm","--ignore-scripts","run"]',
|
|
211
|
+
'["pnpm","--ignore-scripts","run"]',
|
|
212
|
+
'["yarn","run"]'
|
|
213
|
+
];
|
|
214
|
+
const last = Array.isArray(command) ? command.at(-1) : undefined;
|
|
215
|
+
const check = name === "run_check" && supported.includes(prefix) && typeof last === "string" && /^[A-Za-z0-9:_-]{1,100}$/.test(last) ? last : undefined;
|
|
216
|
+
return { exitCode: receipt.exitCode, timedOut: receipt.timedOut === true, check };
|
|
217
|
+
};
|
|
218
|
+
var paint = (text, code, color) => color ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
219
|
+
var terminalRunFailure = (error) => {
|
|
220
|
+
const message = error instanceof Error ? error.message : error && typeof error === "object" && ("message" in error) && typeof error.message === "string" ? error.message : "";
|
|
221
|
+
const provider = /^(Meta|Qwen|OpenAI|Gemini) request failed with status ([45]\d{2})(?: \((previous response unavailable|context length exceeded|tools rejected|input rejected|output token limit rejected|previous response rejected|reason unavailable)\))?\.$/.exec(message);
|
|
222
|
+
if (provider)
|
|
223
|
+
return `${provider[1]} request failed · HTTP ${provider[2]}${provider[3] ? ` · ${provider[3]}` : ""}`;
|
|
224
|
+
const budget = /^Agent budget exceeded including child runs: (maxInputTokens|maxOutputTokens|maxTotalTokens|maxToolCalls|maxToolErrors|maxSteps) limit (\d+), actual (\d+)\.$/.exec(message);
|
|
225
|
+
if (budget)
|
|
226
|
+
return `budget exceeded · ${budget[1]} · ${budget[3]} / ${budget[2]}`;
|
|
227
|
+
const denied = /^Tool "(read_file|read_files|list_files|search_files|search_many)" failed: The path is protected by the harness policy: (node_modules|\.git|\.zhivex-harness|dist|coverage|\.next|\.turbo)$/.exec(message);
|
|
228
|
+
if (denied)
|
|
229
|
+
return `read denied · protected path ${denied[2]}`;
|
|
230
|
+
if (message === "Agent compaction result still exceeds maxEstimatedInputTokens." || message === "Agent compaction cannot satisfy its limits without removing protected messages.") {
|
|
231
|
+
return "context could not fit after compaction · retained messages exceed the input limit";
|
|
232
|
+
}
|
|
233
|
+
const streamOverflow = /^Stream replay buffer exceeded its limit of (\d+) events\.$/.exec(message);
|
|
234
|
+
if (streamOverflow)
|
|
235
|
+
return `event replay limit reached · ${streamOverflow[1]} events`;
|
|
236
|
+
const stateOverflow = /^Agent run state is (\d+) bytes and exceeds maxStateBytes=(\d+)\. Offload large tool outputs to artifacts or raise the explicit limit\.$/.exec(message);
|
|
237
|
+
if (stateOverflow)
|
|
238
|
+
return `durable state limit reached · ${stateOverflow[1]} / ${stateOverflow[2]} bytes · inspect /status before /continue`;
|
|
239
|
+
if (/^Tool is not registered \(sha256:[a-f0-9]{64}\)\.$/.test(message)) {
|
|
240
|
+
return "requested tool is not registered · use an available tool with its exact name";
|
|
241
|
+
}
|
|
242
|
+
const cap = /^(maxInputTokens|maxOutputTokens|maxTotalTokens) budget (exceeded|exhausted)$/.exec(message);
|
|
243
|
+
if (cap)
|
|
244
|
+
return `budget ${cap[2]} · ${cap[1]}`;
|
|
245
|
+
if (message === "Agent exhausted maxSteps before reaching a terminal response.") {
|
|
246
|
+
return "step limit reached before a final response";
|
|
247
|
+
}
|
|
248
|
+
return "run failed · cause unavailable (inspect run diagnostics)";
|
|
249
|
+
};
|
|
250
|
+
var formatTerminalEvent = (event, options = {}) => {
|
|
251
|
+
const color = options.color ?? false;
|
|
252
|
+
switch (event.type) {
|
|
253
|
+
case "text-delta":
|
|
254
|
+
return;
|
|
255
|
+
case "tool-call":
|
|
256
|
+
return `${paint("↳", 36, color)} tool · ${sanitizeTerminalText(event.toolCall.name)}`;
|
|
257
|
+
case "tool-result": {
|
|
258
|
+
const receipt = verificationReceipt(event.toolResult.toolName, event.toolResult.output);
|
|
259
|
+
if (receipt) {
|
|
260
|
+
const failed = event.toolResult.isError || receipt.timedOut || receipt.exitCode !== 0;
|
|
261
|
+
return `${paint(failed ? "✗" : "✓", failed ? 31 : 32, color)} check · ${receipt.check ?? sanitizeTerminalText(event.toolResult.toolName)} · exit ${receipt.exitCode}${receipt.timedOut ? " · timed out" : ""}`;
|
|
262
|
+
}
|
|
263
|
+
return event.toolResult.isError ? `${paint("✗", 31, color)} tool · ${sanitizeTerminalText(event.toolResult.toolName)} · error` : `${paint("✓", 32, color)} tool · ${sanitizeTerminalText(event.toolResult.toolName)}`;
|
|
264
|
+
}
|
|
265
|
+
case "tool-approval-request":
|
|
266
|
+
case "agent-approval-request":
|
|
267
|
+
return `${paint("!", 33, color)} approval · ` + `${sanitizeTerminalText(event.approval.kind ?? "provider")} · ` + sanitizeTerminalText(event.approval.name);
|
|
268
|
+
case "agent-approval-resolved":
|
|
269
|
+
return event.approval.approve ? `${paint("✓", 32, color)} approval · approved` : `${paint("✗", 31, color)} approval · denied`;
|
|
270
|
+
case "provider-data":
|
|
271
|
+
return `${paint("·", 90, color)} provider · ${sanitizeTerminalText(event.provider)}`;
|
|
272
|
+
case "image-generation":
|
|
273
|
+
return `${paint("·", 36, color)} image · ${sanitizeTerminalText(event.provider)}` + (event.partial ? " · partial" : " · complete");
|
|
274
|
+
case "finish":
|
|
275
|
+
return `${paint("·", 90, color)} model stream` + (event.finishReason ? ` · ${sanitizeTerminalText(event.finishReason)}` : " · finished");
|
|
276
|
+
case "error":
|
|
277
|
+
return `${paint("✗", 31, color)} ${terminalRunFailure(event.error)}`;
|
|
278
|
+
case "agent-run-start":
|
|
279
|
+
return `${paint("●", 36, color)} run · step ${event.currentStep}/${event.maxSteps}`;
|
|
280
|
+
case "agent-step-start":
|
|
281
|
+
return `${paint("●", 36, color)} step ${event.stepIndex + 1}`;
|
|
282
|
+
case "agent-step-finish":
|
|
283
|
+
return `${paint(event.step.status === "failed" ? "✗" : "✓", event.step.status === "failed" ? 31 : 32, color)} ` + `step ${event.step.index + 1} · ${sanitizeTerminalText(event.step.status)} · ` + `${event.step.toolResults.length} tool${event.step.toolResults.length === 1 ? "" : "s"}`;
|
|
284
|
+
case "agent-compaction":
|
|
285
|
+
return `${paint("↺", 36, color)} context · ${event.compaction.messageCountBefore} → ` + `${event.compaction.messageCountAfter} messages`;
|
|
286
|
+
case "agent-run-finish":
|
|
287
|
+
if (event.status === "failed") {
|
|
288
|
+
const cause = terminalRunFailure(event.state.error);
|
|
289
|
+
return `${paint("✗", 31, color)} ${cause}` + (cause === "step limit reached before a final response" ? ` (${event.state.currentStep}/${event.state.maxSteps}). Session retained; use /limits to adjust the next turn, then ask to continue.` : "");
|
|
290
|
+
}
|
|
291
|
+
return `${paint(event.status === "completed" ? "✓" : "●", event.status === "completed" ? 32 : 33, color)} ` + `run · ${sanitizeTerminalText(event.status)} · ` + `${sanitizeTerminalText(event.state.provider)}/${sanitizeTerminalText(event.state.modelId)}`;
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
var formatTerminalHeader = (input, options = {}) => {
|
|
295
|
+
const color = options.color ?? false;
|
|
296
|
+
const title = input.sessionTitle ? ` · ${sanitizeTerminalText(input.sessionTitle)}` : "";
|
|
297
|
+
return `${paint(`Zhivex Harness ${sanitizeTerminalText(input.version)}`, 36, color)} · ` + `${sanitizeTerminalText(input.provider)}/${sanitizeTerminalText(input.model)}
|
|
298
|
+
` + `session ${sanitizeTerminalText(input.sessionId)}${title}`;
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
export { DEFAULT_APPROVAL_SUMMARY_CHARACTERS, sanitizeTerminalText, formatApproval, resolveTerminalApprovals, terminalSupportsColor, formatTerminalEvent, formatTerminalHeader };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// src/providers/vertex-auth.ts
|
|
2
|
+
import { GoogleAuth } from "google-auth-library";
|
|
3
|
+
import { createVertex } from "@zhivex-ai/vertex";
|
|
4
|
+
import { wrapLanguageModel } from "@zhivex-ai/core";
|
|
5
|
+
|
|
6
|
+
// src/runtime/provider-diagnostics.ts
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
var providerDiagnosticSchema = z.strictObject({
|
|
9
|
+
reason: z.enum(["previous_response_unavailable", "context_length_exceeded", "authentication", "payment_required", "permission", "rate_limit", "model_unavailable", "invalid_request", "server_error", "unknown"]),
|
|
10
|
+
parameter: z.enum(["previous_response_id", "input", "messages", "tools", "model", "max_output_tokens", "max_tokens", "temperature", "other", "none"]),
|
|
11
|
+
bodyState: z.enum(["parsed", "malformed", "too_large", "unavailable", "read_timeout"])
|
|
12
|
+
});
|
|
13
|
+
function providerDiagnostic(body, status) {
|
|
14
|
+
let reason = status === 401 ? "authentication" : status === 402 ? "payment_required" : status === 403 ? "permission" : status === 429 ? "rate_limit" : status && status >= 500 ? "server_error" : "unknown";
|
|
15
|
+
let parameter = "none";
|
|
16
|
+
if (typeof body !== "string")
|
|
17
|
+
return { reason, parameter, bodyState: "unavailable" };
|
|
18
|
+
if (Buffer.byteLength(body) > 8192)
|
|
19
|
+
return { reason, parameter, bodyState: "too_large" };
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(body);
|
|
22
|
+
const detail = parsed?.error ?? parsed;
|
|
23
|
+
const code = [detail?.code, detail?.status, detail?.type].find((value) => typeof value === "string") ?? "";
|
|
24
|
+
const message = typeof detail?.message === "string" ? detail.message : "";
|
|
25
|
+
const param = providerDiagnosticSchema.shape.parameter.safeParse(detail?.param);
|
|
26
|
+
parameter = param.success ? param.data : detail?.param == null ? "none" : "other";
|
|
27
|
+
if (/response/i.test(message) && /not found|expired/i.test(message) && (parameter === "previous_response_id" || parameter === "none"))
|
|
28
|
+
reason = "previous_response_unavailable";
|
|
29
|
+
else if (["context_length_exceeded", "context_window_exceeded"].includes(code))
|
|
30
|
+
reason = "context_length_exceeded";
|
|
31
|
+
else if (["invalid_api_key", "authentication_error", "UNAUTHENTICATED"].includes(code))
|
|
32
|
+
reason = "authentication";
|
|
33
|
+
else if (["permission_denied", "permission_error", "PERMISSION_DENIED"].includes(code))
|
|
34
|
+
reason = "permission";
|
|
35
|
+
else if (["rate_limit_exceeded", "rate_limit_error", "RESOURCE_EXHAUSTED"].includes(code))
|
|
36
|
+
reason = "rate_limit";
|
|
37
|
+
else if (["model_not_found", "model_not_available", "NOT_FOUND"].includes(code))
|
|
38
|
+
reason = "model_unavailable";
|
|
39
|
+
else if (["invalid_request_error", "INVALID_ARGUMENT"].includes(code) || status === 400 || status === 422)
|
|
40
|
+
reason = "invalid_request";
|
|
41
|
+
return { reason, parameter, bodyState: "parsed" };
|
|
42
|
+
} catch {
|
|
43
|
+
return { reason, parameter, bodyState: "malformed" };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/providers/vertex-auth.ts
|
|
48
|
+
var CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
|
|
49
|
+
function vertexRoute(env) {
|
|
50
|
+
const projectId = (env.GOOGLE_CLOUD_PROJECT ?? "").trim();
|
|
51
|
+
const location = (env.VERTEX_LOCATION ?? "").trim();
|
|
52
|
+
if (!/^(?:[a-z][a-z0-9-]{4,61}[a-z0-9]|[0-9]{6,20})$/.test(projectId)) {
|
|
53
|
+
throw new Error("Vertex requires a valid GOOGLE_CLOUD_PROJECT.");
|
|
54
|
+
}
|
|
55
|
+
if (!/^(?:global|us|eu|[a-z]+-[a-z]+[0-9]+)$/.test(location)) {
|
|
56
|
+
throw new Error("Vertex requires an explicit valid VERTEX_LOCATION.");
|
|
57
|
+
}
|
|
58
|
+
return Object.freeze({ projectId, location });
|
|
59
|
+
}
|
|
60
|
+
function vertexConfigured(env) {
|
|
61
|
+
try {
|
|
62
|
+
vertexRoute(env);
|
|
63
|
+
return true;
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
var VERTEX_ENVIRONMENT = ["GOOGLE_CLOUD_PROJECT", "VERTEX_LOCATION", "GOOGLE_APPLICATION_CREDENTIALS"];
|
|
69
|
+
function vertexEnvironment(env) {
|
|
70
|
+
return Object.fromEntries(VERTEX_ENVIRONMENT.flatMap((name) => env[name] ? [[name, env[name]]] : []));
|
|
71
|
+
}
|
|
72
|
+
function vertexTokenSource(env, route, createAuth = (options) => new GoogleAuth(options)) {
|
|
73
|
+
const keyFilename = env.GOOGLE_APPLICATION_CREDENTIALS?.trim();
|
|
74
|
+
const auth = createAuth({
|
|
75
|
+
projectId: route.projectId,
|
|
76
|
+
scopes: [CLOUD_PLATFORM_SCOPE],
|
|
77
|
+
...keyFilename ? { keyFilename } : {}
|
|
78
|
+
});
|
|
79
|
+
return async () => {
|
|
80
|
+
try {
|
|
81
|
+
const token = await auth.getAccessToken();
|
|
82
|
+
if (!token)
|
|
83
|
+
throw new Error("Empty ADC token");
|
|
84
|
+
return token;
|
|
85
|
+
} catch {
|
|
86
|
+
throw new VertexAuthenticationError;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
class VertexAuthenticationError extends Error {
|
|
92
|
+
status = 401;
|
|
93
|
+
retryable = false;
|
|
94
|
+
constructor() {
|
|
95
|
+
super("Vertex ADC credentials are unavailable or could not be refreshed. Configure Application Default Credentials and retry.");
|
|
96
|
+
this.name = "VertexAuthenticationError";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function createVertexModel(model, env, createAuth) {
|
|
100
|
+
const route = vertexRoute(env);
|
|
101
|
+
const adapter = createVertex({
|
|
102
|
+
...route,
|
|
103
|
+
getAccessToken: vertexTokenSource(env, route, createAuth)
|
|
104
|
+
})(model);
|
|
105
|
+
return wrapLanguageModel(adapter, [{
|
|
106
|
+
name: "harness-vertex-sanitized-errors",
|
|
107
|
+
async wrapGenerate(_context, next) {
|
|
108
|
+
try {
|
|
109
|
+
return await next();
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw safeVertexError(error);
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
async wrapStream(_context, next) {
|
|
115
|
+
let stream;
|
|
116
|
+
try {
|
|
117
|
+
stream = await next();
|
|
118
|
+
} catch (error) {
|
|
119
|
+
throw safeVertexError(error);
|
|
120
|
+
}
|
|
121
|
+
return async function* () {
|
|
122
|
+
try {
|
|
123
|
+
yield* stream;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
throw safeVertexError(error);
|
|
126
|
+
}
|
|
127
|
+
}();
|
|
128
|
+
}
|
|
129
|
+
}]);
|
|
130
|
+
}
|
|
131
|
+
function safeVertexError(error) {
|
|
132
|
+
if (error instanceof VertexAuthenticationError)
|
|
133
|
+
return error;
|
|
134
|
+
const record = error && typeof error === "object" ? error : {};
|
|
135
|
+
if (record.name === "AbortError")
|
|
136
|
+
return new DOMException("Vertex request cancelled.", "AbortError");
|
|
137
|
+
const status = typeof record.status === "number" && Number.isInteger(record.status) && record.status >= 100 && record.status <= 599 ? record.status : undefined;
|
|
138
|
+
return Object.assign(new Error("Vertex request failed. Check the sanitized provider diagnosis."), {
|
|
139
|
+
...status ? { status } : {},
|
|
140
|
+
provider: providerDiagnostic(record.responseBody ?? record.body, status),
|
|
141
|
+
retryable: record.retryable === true
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export { providerDiagnosticSchema, providerDiagnostic, vertexRoute, vertexConfigured, VERTEX_ENVIRONMENT, vertexEnvironment, vertexTokenSource, createVertexModel };
|