@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.
Files changed (55) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +124 -72
  3. package/SECURITY.md +1 -1
  4. package/SUPPORT.md +2 -2
  5. package/dist/acp-cli.js +13 -12
  6. package/dist/{cli-3bzvq594.js → cli-2h37qyvg.js} +4 -4
  7. package/dist/{cli-entry-yqwa1943.js → cli-entry-20w6nh0k.js} +321 -16
  8. package/dist/{cli-entry-25xefnws.js → cli-entry-4ngptr62.js} +3 -3
  9. package/dist/{cli-entry-qav71mxw.js → cli-entry-w0hterjs.js} +1 -1
  10. package/dist/cli.js +2 -2
  11. package/dist/compat/code-support.js +8 -7
  12. package/dist/{config-pwbnze4g.js → config-j9624t6t.js} +1 -1
  13. package/dist/{config-depqpsae.js → config-qt69hkqj.js} +4 -3
  14. package/dist/engine/acp.js +12 -11
  15. package/dist/engine/client.js +11 -10
  16. package/dist/engine/desktop-providers-v1.js +2 -1
  17. package/dist/engine/desktop-state-v1.js +8 -7
  18. package/dist/engine/index.js +15 -14
  19. package/dist/engine/mcp-stdio-v1.js +7 -6
  20. package/dist/engine/protocol.js +12 -0
  21. package/dist/engine/service.js +12 -11
  22. package/dist/hostile-repository-demo.js +322 -17
  23. package/dist/{index-1k248k0w.js → index-5jhgatga.js} +5 -5
  24. package/dist/{index-pcn2bk8j.js → index-61gthdz6.js} +1 -1
  25. package/dist/{index-sbzvrpm7.js → index-6dft2vev.js} +2 -2
  26. package/dist/{index-09zphhwt.js → index-8zjmnyw5.js} +2 -2
  27. package/dist/{index-txrqqd2r.js → index-937cb7y8.js} +6 -6
  28. package/dist/{index-jdb1q8yn.js → index-bpc596v3.js} +15 -10
  29. package/dist/index-fm84men1.js +239 -0
  30. package/dist/{index-zmx264xy.js → index-m34q25jj.js} +2 -2
  31. package/dist/index-m49jx72n.js +220 -0
  32. package/dist/{index-9v72fpya.js → index-m82cfspw.js} +16 -10
  33. package/dist/{index-ngg2168h.js → index-nmjdwd4p.js} +1 -1
  34. package/dist/{index-w9pjt7td.js → index-pdmevfxv.js} +3 -3
  35. package/dist/{index-nqhnh1hz.js → index-pp474137.js} +5 -5
  36. package/dist/{index-vga76vmb.js → index-rkbf7dqg.js} +2 -2
  37. package/dist/{index-fny13znr.js → index-s2k2edts.js} +3 -3
  38. package/dist/{index-7y6b4yjx.js → index-tfzta544.js} +4 -4
  39. package/dist/index.js +15 -14
  40. package/dist/providers/vertex-auth.d.ts +5 -1
  41. package/dist/runtime/error-diagnostics.d.ts +8 -0
  42. package/dist/runtime/provider-transient.d.ts +33 -0
  43. package/dist/runtime/time-to-safe-fix.d.ts +8 -0
  44. package/dist/service-cli.js +12 -11
  45. package/dist/{service-client-cli-ra3yzvjg.js → service-client-cli-fn54ev6w.js} +3 -3
  46. package/dist/verify-historical-migrations.js +322 -17
  47. package/dist/zhx.js +2 -2
  48. package/docs/CLI.md +1 -1
  49. package/docs/CODE_RELEASE.md +38 -1
  50. package/docs/FIRST_USE.md +6 -6
  51. package/docs/PROJECT_MEMORY.md +1 -1
  52. package/evaluations/representative-assembly-matrix.json +10 -1
  53. package/examples/README.md +5 -5
  54. package/package.json +1 -1
  55. 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
- return wrapLanguageModel2(adapter, [{
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 status = typeof record.status === "number" && Number.isInteger(record.status) && record.status >= 100 && record.status <= 599 ? record.status : undefined;
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
- ...status ? { status } : {},
201
- provider: providerDiagnostic(record.responseBody ?? record.body, status),
202
- retryable: record.retryable === true
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 statuses = numericStatuses(error) ?? [];
1941
- if (statuses.some((status) => status === 429 || status >= 500 && status <= 599)) {
1942
- return new HarnessProviderError(message, { cause: error, retryable: true });
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-rc.8",
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-qav71mxw.js";
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-3bzvq594.js");
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
@@ -1,6 +1,43 @@
1
1
  # Code release procedure
2
2
 
3
- ## Current RC preparation: 2026-10-08
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 candidate `1.4.0-rc.8`, pending publication to npm `next`. The versioned install commands below become available only after protected publication. npm `latest` remains stable 1.3.0.
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-rc.8
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-rc.8
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-rc.8 --version
93
- bunx @zhivex-ai/harness@1.4.0-rc.8 --help
94
- bunx @zhivex-ai/harness@1.4.0-rc.8 doctor
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).
@@ -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-rc.8 (Code 0.3.0-rc.8), pending publication. Commands
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": [
@@ -1,22 +1,22 @@
1
- # Examples for Zhivex Harness 1.3
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 RC publication, this installs the 1.4.0-rc.8 candidate artifact in a temporary consumer, without building the source checkout or changing global tools:
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-rc.8
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-rc.8`; 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.
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#installation), 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).
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhivex-ai/harness",
3
- "version": "1.4.0-rc.8",
3
+ "version": "1.4.0",
4
4
  "description": "Governed, provider-portable runtime for coding agents with durable approvals and isolated repository execution.",
5
5
  "type": "module",
6
6
  "license": "MIT",