@zhivex-ai/harness 1.4.0-rc.8 → 1.4.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/CHANGELOG.md +13 -0
- package/README.md +124 -72
- package/SECURITY.md +1 -1
- package/SUPPORT.md +2 -2
- package/dist/acp-cli.js +13 -12
- package/dist/{cli-3bzvq594.js → cli-2h37qyvg.js} +4 -4
- package/dist/{cli-entry-yqwa1943.js → cli-entry-20w6nh0k.js} +321 -16
- package/dist/{cli-entry-25xefnws.js → cli-entry-4ngptr62.js} +3 -3
- package/dist/{cli-entry-qav71mxw.js → cli-entry-w0hterjs.js} +1 -1
- package/dist/cli.js +2 -2
- package/dist/compat/code-support.js +8 -7
- package/dist/{config-pwbnze4g.js → config-j9624t6t.js} +1 -1
- package/dist/{config-depqpsae.js → config-qt69hkqj.js} +4 -3
- package/dist/engine/acp.js +12 -11
- package/dist/engine/client.js +11 -10
- package/dist/engine/desktop-providers-v1.js +2 -1
- package/dist/engine/desktop-state-v1.js +8 -7
- package/dist/engine/index.js +15 -14
- package/dist/engine/mcp-stdio-v1.js +7 -6
- package/dist/engine/protocol.js +12 -0
- package/dist/engine/service.js +12 -11
- package/dist/hostile-repository-demo.js +322 -17
- package/dist/{index-1k248k0w.js → index-5jhgatga.js} +5 -5
- package/dist/{index-pcn2bk8j.js → index-61gthdz6.js} +1 -1
- package/dist/{index-sbzvrpm7.js → index-6dft2vev.js} +2 -2
- package/dist/{index-09zphhwt.js → index-8zjmnyw5.js} +2 -2
- package/dist/{index-txrqqd2r.js → index-937cb7y8.js} +6 -6
- package/dist/{index-jdb1q8yn.js → index-bpc596v3.js} +15 -10
- package/dist/index-fm84men1.js +239 -0
- package/dist/{index-zmx264xy.js → index-m34q25jj.js} +2 -2
- package/dist/index-m49jx72n.js +220 -0
- package/dist/{index-9v72fpya.js → index-m82cfspw.js} +16 -10
- package/dist/{index-ngg2168h.js → index-nmjdwd4p.js} +1 -1
- package/dist/{index-w9pjt7td.js → index-pdmevfxv.js} +3 -3
- package/dist/{index-nqhnh1hz.js → index-pp474137.js} +5 -5
- package/dist/{index-vga76vmb.js → index-rkbf7dqg.js} +2 -2
- package/dist/{index-fny13znr.js → index-s2k2edts.js} +3 -3
- package/dist/{index-7y6b4yjx.js → index-tfzta544.js} +4 -4
- package/dist/index.js +15 -14
- package/dist/providers/vertex-auth.d.ts +5 -1
- package/dist/runtime/error-diagnostics.d.ts +8 -0
- package/dist/runtime/provider-transient.d.ts +33 -0
- package/dist/runtime/time-to-safe-fix.d.ts +8 -0
- package/dist/service-cli.js +12 -11
- package/dist/{service-client-cli-ra3yzvjg.js → service-client-cli-fn54ev6w.js} +3 -3
- package/dist/verify-historical-migrations.js +322 -17
- package/dist/zhx.js +2 -2
- package/docs/CLI.md +1 -1
- package/docs/CODE_RELEASE.md +38 -1
- package/docs/FIRST_USE.md +6 -6
- package/docs/PROJECT_MEMORY.md +1 -1
- package/evaluations/representative-assembly-matrix.json +10 -1
- package/examples/README.md +5 -5
- package/package.json +1 -1
- package/dist/index-8705zxpr.js +0 -145
|
@@ -98,6 +98,8 @@ function providerDiagnostic(body, status) {
|
|
|
98
98
|
reason = "permission";
|
|
99
99
|
else if (["rate_limit_exceeded", "rate_limit_error", "RESOURCE_EXHAUSTED"].includes(code))
|
|
100
100
|
reason = "rate_limit";
|
|
101
|
+
else if (["UNAVAILABLE", "DEADLINE_EXCEEDED", "ABORTED", "INTERNAL"].includes(code))
|
|
102
|
+
reason = "server_error";
|
|
101
103
|
else if (["model_not_found", "model_not_available", "NOT_FOUND"].includes(code))
|
|
102
104
|
reason = "model_unavailable";
|
|
103
105
|
else if (["invalid_request_error", "INVALID_ARGUMENT"].includes(code) || status === 400 || status === 422)
|
|
@@ -108,6 +110,201 @@ function providerDiagnostic(body, status) {
|
|
|
108
110
|
}
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
// src/runtime/provider-transient.ts
|
|
114
|
+
var PROVIDER_STATUS_CATEGORIES = [
|
|
115
|
+
"ABORTED",
|
|
116
|
+
"RESOURCE_EXHAUSTED",
|
|
117
|
+
"UNAVAILABLE",
|
|
118
|
+
"DEADLINE_EXCEEDED",
|
|
119
|
+
"INTERNAL",
|
|
120
|
+
"STREAM_RESET"
|
|
121
|
+
];
|
|
122
|
+
var VERTEX_TRANSIENT_ATTEMPTS = 3;
|
|
123
|
+
var TRANSIENT_HTTP = {
|
|
124
|
+
408: "DEADLINE_EXCEEDED",
|
|
125
|
+
409: "ABORTED",
|
|
126
|
+
425: "UNAVAILABLE",
|
|
127
|
+
429: "RESOURCE_EXHAUSTED",
|
|
128
|
+
500: "INTERNAL",
|
|
129
|
+
502: "UNAVAILABLE",
|
|
130
|
+
503: "UNAVAILABLE",
|
|
131
|
+
504: "DEADLINE_EXCEEDED"
|
|
132
|
+
};
|
|
133
|
+
var TRANSIENT_GRPC = {
|
|
134
|
+
4: "DEADLINE_EXCEEDED",
|
|
135
|
+
8: "RESOURCE_EXHAUSTED",
|
|
136
|
+
10: "ABORTED",
|
|
137
|
+
13: "INTERNAL",
|
|
138
|
+
14: "UNAVAILABLE"
|
|
139
|
+
};
|
|
140
|
+
var CATEGORY_HTTP = {
|
|
141
|
+
ABORTED: 409,
|
|
142
|
+
RESOURCE_EXHAUSTED: 429,
|
|
143
|
+
UNAVAILABLE: 503,
|
|
144
|
+
DEADLINE_EXCEEDED: 504,
|
|
145
|
+
INTERNAL: 500,
|
|
146
|
+
STREAM_RESET: undefined
|
|
147
|
+
};
|
|
148
|
+
var SYMBOLIC = new Set(["ABORTED", "RESOURCE_EXHAUSTED", "UNAVAILABLE", "DEADLINE_EXCEEDED", "INTERNAL"]);
|
|
149
|
+
var STREAM_RESET_CODES = new Set(["ECONNRESET", "ECONNABORTED", "EPIPE", "ETIMEDOUT", "UND_ERR_SOCKET", "ERR_STREAM_PREMATURE_CLOSE"]);
|
|
150
|
+
var HTTP_IN_MESSAGE = /(?:^|[^\d])(408|409|425|429|500|502|503|504)(?=$|[^\d])/;
|
|
151
|
+
var STREAM_RESET_MESSAGE = /stream reset|RST_STREAM|ECONNRESET|socket hang up|ERR_STREAM_PREMATURE_CLOSE|UND_ERR_SOCKET/i;
|
|
152
|
+
var isProviderStatus = (value) => typeof value === "string" && PROVIDER_STATUS_CATEGORIES.includes(value);
|
|
153
|
+
var httpStatusValue = (value) => typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599 ? value : undefined;
|
|
154
|
+
var definitiveClientStatus = (status) => status !== undefined && status >= 400 && status < 500 && TRANSIENT_HTTP[status] === undefined;
|
|
155
|
+
function readProviderFailureRecord(record, depth = 0) {
|
|
156
|
+
let retryable = false;
|
|
157
|
+
let clientError = false;
|
|
158
|
+
let httpStatus;
|
|
159
|
+
let providerStatus;
|
|
160
|
+
const noteHttp = (status) => {
|
|
161
|
+
if (status === undefined)
|
|
162
|
+
return;
|
|
163
|
+
httpStatus ??= status;
|
|
164
|
+
const category = TRANSIENT_HTTP[status];
|
|
165
|
+
if (category) {
|
|
166
|
+
retryable = true;
|
|
167
|
+
providerStatus ??= category;
|
|
168
|
+
} else if (status >= 400 && status < 500)
|
|
169
|
+
clientError = true;
|
|
170
|
+
};
|
|
171
|
+
const noteCategory = (category) => {
|
|
172
|
+
if (!category || clientError)
|
|
173
|
+
return;
|
|
174
|
+
retryable = true;
|
|
175
|
+
providerStatus ??= category;
|
|
176
|
+
if (httpStatus === undefined)
|
|
177
|
+
httpStatus = CATEGORY_HTTP[category];
|
|
178
|
+
};
|
|
179
|
+
const absorb = (value) => {
|
|
180
|
+
if (depth >= 2 || !value || typeof value !== "object" || value === record)
|
|
181
|
+
return;
|
|
182
|
+
const signals = readProviderFailureRecord(value, depth + 1);
|
|
183
|
+
noteHttp(signals.httpStatus);
|
|
184
|
+
noteCategory(signals.providerStatus);
|
|
185
|
+
};
|
|
186
|
+
noteHttp(httpStatusValue(record.status) ?? httpStatusValue(record.statusCode) ?? httpStatusValue(record.httpStatusCode));
|
|
187
|
+
if (typeof record.status === "string" && /^\d{3}$/.test(record.status))
|
|
188
|
+
noteHttp(Number(record.status));
|
|
189
|
+
if (typeof record.code === "number" && record.code >= 100 && record.code <= 599)
|
|
190
|
+
noteHttp(record.code);
|
|
191
|
+
if (typeof record.code === "number" && record.code >= 0 && record.code <= 16)
|
|
192
|
+
noteCategory(TRANSIENT_GRPC[record.code]);
|
|
193
|
+
if (typeof record.code === "string") {
|
|
194
|
+
if (STREAM_RESET_CODES.has(record.code))
|
|
195
|
+
noteCategory("STREAM_RESET");
|
|
196
|
+
else if (SYMBOLIC.has(record.code))
|
|
197
|
+
noteCategory(record.code);
|
|
198
|
+
}
|
|
199
|
+
if (typeof record.status === "string" && SYMBOLIC.has(record.status))
|
|
200
|
+
noteCategory(record.status);
|
|
201
|
+
if (isProviderStatus(record.providerStatus))
|
|
202
|
+
noteCategory(record.providerStatus);
|
|
203
|
+
if (record.diagnosticCode === "VERTEX_STREAM_TOOL_TRUNCATED" || record.reason === "stream_truncated")
|
|
204
|
+
noteCategory("STREAM_RESET");
|
|
205
|
+
if (record.code === "PROVIDER_UNAVAILABLE" && record.retryable === true)
|
|
206
|
+
retryable = true;
|
|
207
|
+
const body = record.responseBody ?? record.body;
|
|
208
|
+
if (!clientError) {
|
|
209
|
+
if (typeof body === "string")
|
|
210
|
+
noteCategory(categoryFromBody(body));
|
|
211
|
+
else
|
|
212
|
+
absorb(body);
|
|
213
|
+
absorb(record.error);
|
|
214
|
+
}
|
|
215
|
+
if (!retryable && !clientError && typeof record.message === "string") {
|
|
216
|
+
const match = record.message.match(HTTP_IN_MESSAGE);
|
|
217
|
+
if (match?.[1])
|
|
218
|
+
noteHttp(Number(match[1]));
|
|
219
|
+
else if (STREAM_RESET_MESSAGE.test(record.message))
|
|
220
|
+
noteCategory("STREAM_RESET");
|
|
221
|
+
}
|
|
222
|
+
if (httpStatus !== undefined && definitiveClientStatus(httpStatus))
|
|
223
|
+
return { retryable: false, httpStatus };
|
|
224
|
+
return { retryable, ...httpStatus !== undefined ? { httpStatus } : {}, ...providerStatus ? { providerStatus } : {} };
|
|
225
|
+
}
|
|
226
|
+
function categoryFromBody(body) {
|
|
227
|
+
if (Buffer.byteLength(body) > 8192)
|
|
228
|
+
return;
|
|
229
|
+
try {
|
|
230
|
+
const parsed = JSON.parse(body);
|
|
231
|
+
if (!parsed || typeof parsed !== "object")
|
|
232
|
+
return;
|
|
233
|
+
const detail = parsed.error;
|
|
234
|
+
const source = detail && typeof detail === "object" ? detail : parsed;
|
|
235
|
+
return readProviderFailureRecord(source).providerStatus;
|
|
236
|
+
} catch {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function classifyProviderFailure(error) {
|
|
241
|
+
if (error && typeof error === "object") {
|
|
242
|
+
const top = readProviderFailureRecord(error);
|
|
243
|
+
if (top.httpStatus !== undefined && definitiveClientStatus(top.httpStatus))
|
|
244
|
+
return { retryable: false, httpStatus: top.httpStatus };
|
|
245
|
+
}
|
|
246
|
+
const seen = new Set;
|
|
247
|
+
let retryable = false;
|
|
248
|
+
let httpStatus;
|
|
249
|
+
let providerStatus;
|
|
250
|
+
const visit = (current, depth) => {
|
|
251
|
+
if (!current || typeof current !== "object" || depth > 5 || seen.has(current))
|
|
252
|
+
return;
|
|
253
|
+
seen.add(current);
|
|
254
|
+
const record = current;
|
|
255
|
+
const signals = readProviderFailureRecord(record);
|
|
256
|
+
httpStatus ??= signals.httpStatus;
|
|
257
|
+
providerStatus ??= signals.providerStatus;
|
|
258
|
+
if (httpStatus !== undefined && providerStatus === undefined)
|
|
259
|
+
providerStatus = TRANSIENT_HTTP[httpStatus];
|
|
260
|
+
if (record.retryable === false)
|
|
261
|
+
return;
|
|
262
|
+
if (signals.retryable)
|
|
263
|
+
retryable = true;
|
|
264
|
+
visit(record.cause, depth + 1);
|
|
265
|
+
if (record.error && typeof record.error === "object")
|
|
266
|
+
visit(record.error, depth + 1);
|
|
267
|
+
if (record.details && typeof record.details === "object")
|
|
268
|
+
visit(record.details, depth + 1);
|
|
269
|
+
if (Array.isArray(record.chain))
|
|
270
|
+
for (const entry of record.chain)
|
|
271
|
+
visit(entry, depth + 1);
|
|
272
|
+
};
|
|
273
|
+
visit(error, 0);
|
|
274
|
+
if (retryable && httpStatus === undefined && providerStatus)
|
|
275
|
+
httpStatus = CATEGORY_HTTP[providerStatus];
|
|
276
|
+
return { retryable, ...httpStatus !== undefined ? { httpStatus } : {}, ...providerStatus ? { providerStatus } : {} };
|
|
277
|
+
}
|
|
278
|
+
function isRetryableProviderFailure(error) {
|
|
279
|
+
return classifyProviderFailure(error).retryable;
|
|
280
|
+
}
|
|
281
|
+
function transientBackoffMs(attempt, baseMs = 250) {
|
|
282
|
+
const exponential = Math.min(baseMs * 2 ** Math.max(0, attempt), 1e4);
|
|
283
|
+
return exponential + Math.floor(Math.random() * exponential);
|
|
284
|
+
}
|
|
285
|
+
var thoughtOnlyText = (text) => {
|
|
286
|
+
try {
|
|
287
|
+
const parsed = JSON.parse(text);
|
|
288
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
289
|
+
return false;
|
|
290
|
+
const record = parsed;
|
|
291
|
+
return record.thought === true && Object.keys(record).every((key) => key === "thought" || key === "thoughtSignature");
|
|
292
|
+
} catch {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
function isEmptyModelTurn(result) {
|
|
297
|
+
if (result.finishReason === "content-filter" || result.finishReason === "error")
|
|
298
|
+
return false;
|
|
299
|
+
if ((result.images?.length ?? 0) > 0 || (result.audio?.length ?? 0) > 0)
|
|
300
|
+
return false;
|
|
301
|
+
const parts = result.messages?.flatMap((message) => message.parts ?? []) ?? [];
|
|
302
|
+
if (parts.some((part) => part.type === "tool-call" || part.type === "image" || part.type === "audio"))
|
|
303
|
+
return false;
|
|
304
|
+
const texts = [result.text ?? "", ...parts.filter((part) => part.type === "text").map((part) => part.text ?? "")];
|
|
305
|
+
return texts.every((text) => text.trim().length === 0 || thoughtOnlyText(text.trim()));
|
|
306
|
+
}
|
|
307
|
+
|
|
111
308
|
// src/providers/vertex-auth.ts
|
|
112
309
|
var CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
|
|
113
310
|
function vertexRoute(env) {
|
|
@@ -157,13 +354,31 @@ class VertexAuthenticationError extends Error {
|
|
|
157
354
|
this.name = "VertexAuthenticationError";
|
|
158
355
|
}
|
|
159
356
|
}
|
|
160
|
-
function createVertexModel(model, env, createAuth) {
|
|
357
|
+
function createVertexModel(model, env, createAuth, retry) {
|
|
161
358
|
const route = vertexRoute(env);
|
|
162
359
|
const adapter = createVertex({
|
|
163
360
|
...route,
|
|
164
361
|
getAccessToken: vertexTokenSource(env, route, createAuth)
|
|
165
362
|
})(model);
|
|
166
|
-
|
|
363
|
+
const attempts = retry?.attempts ?? VERTEX_TRANSIENT_ATTEMPTS;
|
|
364
|
+
const delayFor = (input) => retry?.delayMs ?? ((attempt) => input.retryBackoffMs === 0 ? 0 : transientBackoffMs(attempt, input.retryBackoffMs ?? 250));
|
|
365
|
+
const generate = async (input) => withVertexAttempts(input.abortSignal, vertexAttemptBudget(input, attempts), delayFor(input), async () => {
|
|
366
|
+
try {
|
|
367
|
+
return await adapter.generate({ ...input, maxRetries: 0 });
|
|
368
|
+
} catch (error) {
|
|
369
|
+
throw safeVertexError(error);
|
|
370
|
+
}
|
|
371
|
+
}, (result) => !isEmptyModelTurn(result));
|
|
372
|
+
const innerStream = adapter.stream?.bind(adapter);
|
|
373
|
+
const retrying = innerStream ? {
|
|
374
|
+
...adapter,
|
|
375
|
+
generate,
|
|
376
|
+
async stream(input) {
|
|
377
|
+
const events = await withVertexAttempts(input.abortSignal, vertexAttemptBudget(input, attempts), delayFor(input), () => collectVertexStream(() => innerStream({ ...input, maxRetries: 0 })), (events2) => !isEmptyVertexStream(events2));
|
|
378
|
+
return replayVertexStream(events);
|
|
379
|
+
}
|
|
380
|
+
} : { ...adapter, generate };
|
|
381
|
+
return wrapLanguageModel2(retrying, [{
|
|
167
382
|
name: "harness-vertex-sanitized-errors",
|
|
168
383
|
async wrapGenerate(_context, next) {
|
|
169
384
|
try {
|
|
@@ -189,19 +404,109 @@ function createVertexModel(model, env, createAuth) {
|
|
|
189
404
|
}
|
|
190
405
|
}]);
|
|
191
406
|
}
|
|
407
|
+
async function collectVertexStream(next) {
|
|
408
|
+
let stream;
|
|
409
|
+
try {
|
|
410
|
+
stream = await next();
|
|
411
|
+
} catch (error) {
|
|
412
|
+
throw safeVertexError(error);
|
|
413
|
+
}
|
|
414
|
+
const events = [];
|
|
415
|
+
try {
|
|
416
|
+
for await (const event of stream) {
|
|
417
|
+
if (event.type === "error")
|
|
418
|
+
throw safeVertexError(event.error);
|
|
419
|
+
events.push(event);
|
|
420
|
+
}
|
|
421
|
+
} catch (error) {
|
|
422
|
+
throw error instanceof Error && error.message.startsWith("Vertex request failed.") ? error : safeVertexError(error);
|
|
423
|
+
}
|
|
424
|
+
return events;
|
|
425
|
+
}
|
|
426
|
+
function isEmptyVertexStream(events) {
|
|
427
|
+
let text = "";
|
|
428
|
+
let finishReason;
|
|
429
|
+
let tools = false;
|
|
430
|
+
let media = false;
|
|
431
|
+
for (const event of events) {
|
|
432
|
+
if (event.type === "text-delta")
|
|
433
|
+
text += event.textDelta;
|
|
434
|
+
else if (event.type === "tool-call" || event.type === "tool-approval-request")
|
|
435
|
+
tools = true;
|
|
436
|
+
else if (event.type === "image-generation")
|
|
437
|
+
media = true;
|
|
438
|
+
else if (event.type === "finish")
|
|
439
|
+
finishReason = event.finishReason;
|
|
440
|
+
}
|
|
441
|
+
return isEmptyModelTurn({
|
|
442
|
+
text,
|
|
443
|
+
finishReason,
|
|
444
|
+
...tools ? { messages: [{ parts: [{ type: "tool-call" }] }] } : {},
|
|
445
|
+
...media ? { images: [{}] } : {}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
function replayVertexStream(events) {
|
|
449
|
+
return async function* () {
|
|
450
|
+
yield* events;
|
|
451
|
+
}();
|
|
452
|
+
}
|
|
453
|
+
async function withVertexAttempts(abortSignal, attempts, delayMs, run, accept) {
|
|
454
|
+
let last;
|
|
455
|
+
for (let attempt = 0;attempt < attempts; attempt += 1) {
|
|
456
|
+
if (abortSignal?.aborted)
|
|
457
|
+
throw new DOMException("Vertex request cancelled.", "AbortError");
|
|
458
|
+
try {
|
|
459
|
+
const value = await run();
|
|
460
|
+
if (accept(value) || attempt === attempts - 1)
|
|
461
|
+
return value;
|
|
462
|
+
last = value;
|
|
463
|
+
} catch (error) {
|
|
464
|
+
const safe = error instanceof Error && (error.name === "AbortError" || error.message.startsWith("Vertex request failed.")) ? error : safeVertexError(error);
|
|
465
|
+
if (safe.name === "AbortError" || abortSignal?.aborted || attempt === attempts - 1 || !isRetryableProviderFailure(safe))
|
|
466
|
+
throw safe;
|
|
467
|
+
}
|
|
468
|
+
await sleep(delayMs(attempt));
|
|
469
|
+
}
|
|
470
|
+
return last;
|
|
471
|
+
}
|
|
472
|
+
var sleep = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
|
|
473
|
+
function vertexAttemptBudget(input, fallback) {
|
|
474
|
+
const requested = [
|
|
475
|
+
typeof input.maxProviderRequests === "number" ? input.maxProviderRequests : undefined,
|
|
476
|
+
typeof input.maxRetries === "number" ? input.maxRetries + 1 : undefined
|
|
477
|
+
].filter((value) => value !== undefined);
|
|
478
|
+
if (requested.length === 0)
|
|
479
|
+
return Math.max(1, fallback);
|
|
480
|
+
return Math.max(1, Math.min(...requested));
|
|
481
|
+
}
|
|
192
482
|
function safeVertexError(error) {
|
|
193
483
|
if (error instanceof VertexAuthenticationError)
|
|
194
484
|
return error;
|
|
195
485
|
const record = error && typeof error === "object" ? error : {};
|
|
196
486
|
if (record.name === "AbortError")
|
|
197
487
|
return new DOMException("Vertex request cancelled.", "AbortError");
|
|
198
|
-
const
|
|
488
|
+
const failure = classifyProviderFailure(error);
|
|
489
|
+
const body = providerBody(record.responseBody) ?? providerBody(record.body);
|
|
490
|
+
const preserved = providerDiagnosticSchema.safeParse(record.provider);
|
|
491
|
+
const provider = body !== undefined ? providerDiagnostic(body, failure.httpStatus) : preserved.success ? preserved.data : providerDiagnostic(undefined, failure.httpStatus);
|
|
199
492
|
return Object.assign(new Error("Vertex request failed. Check the sanitized provider diagnosis."), {
|
|
200
|
-
...
|
|
201
|
-
|
|
202
|
-
|
|
493
|
+
...failure.httpStatus !== undefined ? { status: failure.httpStatus } : {},
|
|
494
|
+
...failure.providerStatus ? { providerStatus: failure.providerStatus } : {},
|
|
495
|
+
provider,
|
|
496
|
+
retryable: failure.retryable
|
|
203
497
|
});
|
|
204
498
|
}
|
|
499
|
+
function providerBody(value) {
|
|
500
|
+
if (typeof value === "string")
|
|
501
|
+
return value;
|
|
502
|
+
if (!value || typeof value !== "object")
|
|
503
|
+
return;
|
|
504
|
+
try {
|
|
505
|
+
return JSON.stringify(value).slice(0, 8192);
|
|
506
|
+
} catch {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
205
510
|
|
|
206
511
|
// src/providers/qwen-hosted-policy.ts
|
|
207
512
|
import { wrapLanguageModel as wrapLanguageModel3 } from "@zhivex-ai/core";
|
|
@@ -1923,13 +2228,6 @@ class HarnessExecutionError extends HarnessError {
|
|
|
1923
2228
|
this.name = "HarnessExecutionError";
|
|
1924
2229
|
}
|
|
1925
2230
|
}
|
|
1926
|
-
var numericStatuses = (error) => {
|
|
1927
|
-
if (!error || typeof error !== "object")
|
|
1928
|
-
return;
|
|
1929
|
-
const record = error;
|
|
1930
|
-
const values = [record.status, record.statusCode].filter((value) => typeof value === "number" && Number.isSafeInteger(value));
|
|
1931
|
-
return values.length > 0 ? values : undefined;
|
|
1932
|
-
};
|
|
1933
2231
|
var normalizeHarnessError = (error) => {
|
|
1934
2232
|
const diagnostic = providerStreamDiagnostic(error);
|
|
1935
2233
|
if (diagnostic)
|
|
@@ -1937,9 +2235,16 @@ var normalizeHarnessError = (error) => {
|
|
|
1937
2235
|
if (error instanceof HarnessError)
|
|
1938
2236
|
return error;
|
|
1939
2237
|
const message = error instanceof Error ? error.message : "Harness execution failed.";
|
|
1940
|
-
const
|
|
1941
|
-
if (
|
|
1942
|
-
|
|
2238
|
+
const failure = classifyProviderFailure(error);
|
|
2239
|
+
if (failure.retryable) {
|
|
2240
|
+
const record = error && typeof error === "object" ? error : {};
|
|
2241
|
+
const body = typeof record.responseBody === "string" ? record.responseBody : typeof record.body === "string" ? record.body : undefined;
|
|
2242
|
+
const providerError = new HarnessProviderError("Provider request failed.", { cause: error, retryable: true });
|
|
2243
|
+
return Object.assign(providerError, {
|
|
2244
|
+
...failure.httpStatus !== undefined ? { status: failure.httpStatus } : {},
|
|
2245
|
+
...failure.providerStatus ? { providerStatus: failure.providerStatus } : {},
|
|
2246
|
+
provider: providerDiagnostic(body, failure.httpStatus)
|
|
2247
|
+
});
|
|
1943
2248
|
}
|
|
1944
2249
|
const name = error && typeof error === "object" && typeof error.name === "string" ? error.name : "";
|
|
1945
2250
|
const code = error && typeof error === "object" && typeof error.code === "string" ? error.code : "";
|
|
@@ -14397,7 +14702,7 @@ import { z as z34 } from "zod";
|
|
|
14397
14702
|
// package.json
|
|
14398
14703
|
var package_default = {
|
|
14399
14704
|
name: "@zhivex-ai/harness",
|
|
14400
|
-
version: "1.4.0
|
|
14705
|
+
version: "1.4.0",
|
|
14401
14706
|
description: "Governed, provider-portable runtime for coding agents with durable approvals and isolated repository execution.",
|
|
14402
14707
|
type: "module",
|
|
14403
14708
|
license: "MIT",
|
package/dist/zhx.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
HARNESS_VERSION,
|
|
4
4
|
shortCliHelp
|
|
5
|
-
} from "./cli-entry-
|
|
5
|
+
} from "./cli-entry-w0hterjs.js";
|
|
6
6
|
import {
|
|
7
7
|
__require
|
|
8
8
|
} from "./cli-entry-1bx83sw4.js";
|
|
@@ -16,6 +16,6 @@ if (argv.length === 1 && ["--version", "-v", "version"].includes(argv[0])) {
|
|
|
16
16
|
process.stdout.write(`${shortCliHelp(HARNESS_VERSION)}
|
|
17
17
|
`);
|
|
18
18
|
} else {
|
|
19
|
-
const { runCli } = await import("./cli-
|
|
19
|
+
const { runCli } = await import("./cli-2h37qyvg.js");
|
|
20
20
|
await runCli(argv);
|
|
21
21
|
}
|
package/docs/CLI.md
CHANGED
|
@@ -108,7 +108,7 @@ An empty draft shows a muted task hint. Shortcuts sit below the complete draft,
|
|
|
108
108
|
adapt to terminal width, and give way to command and history searches. Sending or
|
|
109
109
|
cancelling removes these editor hints before model output or approval questions.
|
|
110
110
|
|
|
111
|
-
The console opens with the Zhivex logo and a compact welcome panel. Project/model context sits beside the logo when space permits. Before each task, the composer shows the model, approval mode, pending approval state and attachment count. Long labels are bounded to the terminal width; full configuration remains available through `/status`. Type `/` to open the command menu; keep typing to search names and descriptions, use Up/Down to select, Tab to insert, and Enter to submit. Escape dismisses the menu. Enter on a partial command inserts its selection; a separate Enter submits it, including approval commands. Only commands supported by the connected runtime appear. Narrow terminals use a compact logo; `NO_COLOR` disables logo color. The console completes slash-command prefixes with Tab. `/paste` captures a bounded
|
|
111
|
+
The console opens with the Zhivex logo and a compact welcome panel. Project/model context sits beside the logo when space permits. Before each task, the composer shows the model, approval mode, pending approval state and attachment count. Long labels are bounded to the terminal width; full configuration remains available through `/status`. Type `/` to open the command menu; keep typing to search names and descriptions, use Up/Down to select, Tab to insert, and Enter to submit. Escape dismisses the menu. Enter on a partial command inserts its selection; a separate Enter submits it, including approval commands. Only commands supported by the connected runtime appear. Narrow terminals use a compact logo; `NO_COLOR` disables logo color. `zhivex-code` shows the logo only on the first welcome of a new session, when stderr is a terminal, `TERM` is not `dumb`, and the size is at least 48 columns by 24 rows; otherwise it shows `( Z )`, and a restored session shows no logo. The console completes slash-command prefixes with Tab. `/paste` captures a bounded
|
|
112
112
|
multiline draft: finish with `.end` on a separate line, review the preview, then type
|
|
113
113
|
`send` at the separate confirmation prompt. Slash commands inside the draft remain
|
|
114
114
|
literal model input. Use this mode when the terminal does not support bracketed
|
package/docs/CODE_RELEASE.md
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
# Code release procedure
|
|
2
2
|
|
|
3
|
-
## Current
|
|
3
|
+
## Current stable preparation: 2026-10-09
|
|
4
|
+
|
|
5
|
+
This checkout prepares Code `0.3.0`, pinned exactly to Harness `1.4.0`, for npm
|
|
6
|
+
`latest`. Published Code `0.2.0` and Harness `1.3.0` on `latest` remain
|
|
7
|
+
unchanged. Published npm `next` is Code `0.3.0-rc.8` and Harness `1.4.0-rc.8`.
|
|
8
|
+
|
|
9
|
+
Annotated tags `v1.4.0-rc.8` and `code-v0.3.0-rc.8` both resolve to
|
|
10
|
+
`5bba06c86fc82b34b67cec607053948f8e00d0d1`. Against that commit, engine runtime
|
|
11
|
+
source under `src/` is identical. This Code package is not: main includes the
|
|
12
|
+
shared review screen ([PR201](https://github.com/Zhivex/zhivex-harness/pull/201)),
|
|
13
|
+
the status line, summary and 48×24 logo
|
|
14
|
+
([PR210](https://github.com/Zhivex/zhivex-harness/pull/210)), and the public
|
|
15
|
+
README ([PR200](https://github.com/Zhivex/zhivex-harness/pull/200)). Web
|
|
16
|
+
([PR211](https://github.com/Zhivex/zhivex-harness/pull/211)) and desktop
|
|
17
|
+
([PR203](https://github.com/Zhivex/zhivex-harness/pull/203),
|
|
18
|
+
[PR211](https://github.com/Zhivex/zhivex-harness/pull/211)) are also not identical
|
|
19
|
+
to that tag. The authoritative file-level comparison is in the
|
|
20
|
+
[Harness release procedure](https://github.com/Zhivex/zhivex-harness/blob/main/docs/RELEASE.md).
|
|
21
|
+
|
|
22
|
+
The Code manifest dist-tag is `latest` because `0.3.0` is a stable version.
|
|
23
|
+
Desktop stays private alpha with the same engine binding. SDK pins, approval and
|
|
24
|
+
transport protections, and provider/API tiers stay as they are on main. The
|
|
25
|
+
RC.8 preparation snapshot below remains historical; its evidence and approvals
|
|
26
|
+
do not certify or authorize `0.3.0`.
|
|
27
|
+
|
|
28
|
+
This preparation creates no tags, paid calls, campaign dispatch or publication.
|
|
29
|
+
After review and user integration, require all four successful exact-main workflows
|
|
30
|
+
(`ci.yml`, `codeql.yml`, `code-journey.yml`, `web.yml`) and direct-main CodeQL.
|
|
31
|
+
New explicit campaign/publication authority is required before any Harness
|
|
32
|
+
`v1.4.0` or Code `code-v0.3.0` tag/dispatch. Harness must first pass its
|
|
33
|
+
unchanged protected exact-artifact, OCI, live and representative gates and actual
|
|
34
|
+
registry integrity/provenance verification. Code's protected OIDC workflow then
|
|
35
|
+
uses the matching reviewed SHA with `channel=latest`, `mode=oidc` and explicit
|
|
36
|
+
confirmation, resolving the actual published Harness dependency without overrides.
|
|
37
|
+
Retained-artifact terminal/web, four-manager acceptance and registry bytes with
|
|
38
|
+
source-bound provenance remain mandatory. Preserve published `next` and all history.
|
|
39
|
+
|
|
40
|
+
## Historical RC.8 preparation: 2026-10-08
|
|
4
41
|
|
|
5
42
|
This checkout prepares Code `0.3.0-rc.8`, pinned exactly to Harness
|
|
6
43
|
`1.4.0-rc.8`, for npm `next`. The matched engine includes the reviewed
|
package/docs/FIRST_USE.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# First use
|
|
2
2
|
|
|
3
|
-
This guide targets
|
|
3
|
+
This guide targets stable `1.4.0`, pending publication to npm `latest`. The versioned install commands below become available only after protected publication. npm `latest` remains stable 1.3.0 until that publication.
|
|
4
4
|
|
|
5
5
|
## Install and start
|
|
6
6
|
|
|
7
7
|
Install Node.js 22.13.0 or newer and Git. Then:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
npm install -g @zhivex-ai/harness@1.4.0
|
|
10
|
+
npm install -g @zhivex-ai/harness@1.4.0
|
|
11
11
|
cd /path/to/your/project
|
|
12
12
|
zhx
|
|
13
13
|
```
|
|
@@ -82,16 +82,16 @@ For command execution, configure [OCI isolation](EXECUTION_ENVIRONMENTS.md).
|
|
|
82
82
|
With Bun 1.4.0 or newer and a supported Node runtime:
|
|
83
83
|
|
|
84
84
|
```sh
|
|
85
|
-
bun add --global @zhivex-ai/harness@1.4.0
|
|
85
|
+
bun add --global @zhivex-ai/harness@1.4.0
|
|
86
86
|
zhx
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
For a disposable version/help/environment check without a global installation:
|
|
90
90
|
|
|
91
91
|
```sh
|
|
92
|
-
bunx @zhivex-ai/harness@1.4.0
|
|
93
|
-
bunx @zhivex-ai/harness@1.4.0
|
|
94
|
-
bunx @zhivex-ai/harness@1.4.0
|
|
92
|
+
bunx @zhivex-ai/harness@1.4.0 --version
|
|
93
|
+
bunx @zhivex-ai/harness@1.4.0 --help
|
|
94
|
+
bunx @zhivex-ai/harness@1.4.0 doctor
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
For source development, use the [contributor guide](https://github.com/Zhivex/zhivex-harness/blob/main/CONTRIBUTING.md).
|
package/docs/PROJECT_MEMORY.md
CHANGED
|
@@ -7,7 +7,7 @@ generated automatically; hosts may submit a suggestion for operator review.
|
|
|
7
7
|
|
|
8
8
|
## CLI
|
|
9
9
|
|
|
10
|
-
Use `zhx` or `zhivex-code` with Harness 1.4.0
|
|
10
|
+
Use `zhx` or `zhivex-code` with Harness 1.4.0 (Code 0.3.0), pending publication. Commands
|
|
11
11
|
print bounded JSON, including the selected workspace/scope hashes. Quote content
|
|
12
12
|
as one argument. Memory commands do not start a model or read credentials.
|
|
13
13
|
|
|
@@ -61,7 +61,8 @@
|
|
|
61
61
|
"v1.4.0-rc.5",
|
|
62
62
|
"v1.4.0-rc.6",
|
|
63
63
|
"v1.4.0-rc.7",
|
|
64
|
-
"v1.4.0-rc.8"
|
|
64
|
+
"v1.4.0-rc.8",
|
|
65
|
+
"v1.4.0"
|
|
65
66
|
],
|
|
66
67
|
"expectedModels": [
|
|
67
68
|
{
|
|
@@ -543,6 +544,14 @@
|
|
|
543
544
|
"qwen": "qwen3.8-flash",
|
|
544
545
|
"openai": "gpt-6-luna"
|
|
545
546
|
}
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
"releaseTag": "v1.4.0",
|
|
550
|
+
"models": {
|
|
551
|
+
"meta": "muse-spark-1.3-contributor",
|
|
552
|
+
"qwen": "qwen3.8-flash",
|
|
553
|
+
"openai": "gpt-6-luna"
|
|
554
|
+
}
|
|
546
555
|
}
|
|
547
556
|
],
|
|
548
557
|
"expectedCases": [
|
package/examples/README.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
# Examples for Zhivex Harness 1.
|
|
1
|
+
# Examples for Zhivex Harness 1.4
|
|
2
2
|
|
|
3
3
|
## Isolated Bun installation
|
|
4
4
|
|
|
5
|
-
Requirements: Bun 1.4.0+, Node.js 22.13.0+, and Git on macOS or Linux. After protected
|
|
5
|
+
Requirements: Bun 1.4.0+, Node.js 22.13.0+, and Git on macOS or Linux. After protected stable publication, this installs the 1.4.0 artifact in a temporary consumer, without building the source checkout or changing global tools:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
harness_demo_dir=$(mktemp -d)
|
|
9
9
|
cd "$harness_demo_dir"
|
|
10
10
|
printf '{"name":"harness-first-run","private":true}\n' > package.json
|
|
11
|
-
bun add --exact --ignore-scripts @zhivex-ai/harness@1.4.0
|
|
11
|
+
bun add --exact --ignore-scripts @zhivex-ai/harness@1.4.0
|
|
12
12
|
./node_modules/.bin/zhx --version
|
|
13
13
|
./node_modules/.bin/zhx --help
|
|
14
14
|
./node_modules/.bin/zhx doctor --provider openai --json
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Version must print `1.4.0
|
|
17
|
+
Version must print `1.4.0`; help lists commands. Doctor does not contact a provider. Without `OPENAI_API_KEY`, it reports the missing credential and exits with code `3`; inspect the JSON checks before proceeding. A directory without Git initialization can also produce a diagnostic warning. No paid model call is required for these checks. Both `zhx` and `zhivex-harness` are installed aliases; Bun manages installation, and their shebang runs Node.
|
|
18
18
|
|
|
19
|
-
To continue in your actual project, use the [global installation and credential setup](../README.md#
|
|
19
|
+
To continue in your actual project, use the [global installation and credential setup](../README.md#quick-start), then `zhx init --profile daily --provider openai` and `zhx doctor --profile daily`. Real tasks require the selected provider credential in the process environment. Profiles store provider/model choices, never credentials. See [CLI](../docs/CLI.md) and [support](../SUPPORT.md).
|
|
20
20
|
|
|
21
21
|
## Configuration and change examples
|
|
22
22
|
|
package/package.json
CHANGED