@zhivex-ai/harness 1.5.0 → 1.6.3

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 (61) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +3 -1
  3. package/SUPPORT.md +1 -1
  4. package/contracts/stable-api-signatures.json +7 -7
  5. package/dist/acp-cli.js +7 -7
  6. package/dist/approvals/auto-approve.d.ts +137 -0
  7. package/dist/audit/index.js +2 -2
  8. package/dist/{audit-pack-kvw7ng34.js → audit-pack-s9n693by.js} +3 -3
  9. package/dist/{cli-entry-75j3r49b.js → cli-entry-1epvek01.js} +2 -2
  10. package/dist/{cli-entry-q8bgkn9m.js → cli-entry-mbcq96nx.js} +18 -17
  11. package/dist/{cli-entry-928p8h41.js → cli-entry-nyzyvf24.js} +75 -11
  12. package/dist/{cli-entry-h2yc9x37.js → cli-entry-rsx0wpvs.js} +10 -2
  13. package/dist/{cli-vyrytn3m.js → cli-yxvq8qf1.js} +6 -6
  14. package/dist/cli.js +2 -2
  15. package/dist/compat/code-support.js +5 -5
  16. package/dist/engine/acp.js +8 -8
  17. package/dist/engine/client.js +7 -7
  18. package/dist/engine/desktop-state-v1.js +4 -4
  19. package/dist/engine/index.js +10 -10
  20. package/dist/engine/mcp-stdio-v1.js +4 -4
  21. package/dist/engine/service.js +8 -8
  22. package/dist/hostile-repository-demo.js +100 -27
  23. package/dist/{index-vvzc5ezz.js → index-2yyfg69h.js} +1 -1
  24. package/dist/{index-pq3kz1r8.js → index-3wwfn5n6.js} +18 -17
  25. package/dist/{index-63gbp0cz.js → index-4mn6m3bq.js} +1 -1
  26. package/dist/{index-jpdvn09j.js → index-5nmm0642.js} +1 -1
  27. package/dist/{index-wwthv10a.js → index-amjttjaj.js} +1 -1
  28. package/dist/{index-5zbq9ztj.js → index-hhrnrqfc.js} +1 -1
  29. package/dist/{index-vc7yxz50.js → index-j466sekf.js} +4 -4
  30. package/dist/{index-96ezrvg2.js → index-nxmk6re7.js} +2 -2
  31. package/dist/{index-xfedewpt.js → index-rgqzdgv6.js} +2 -2
  32. package/dist/{index-6fp7kdpx.js → index-vn91b9z1.js} +12 -4
  33. package/dist/{index-ggstwr0v.js → index-w0m1nckc.js} +74 -10
  34. package/dist/index.js +9 -9
  35. package/dist/providers/bedrock.d.ts +1 -1
  36. package/dist/review/reveal-invisible.d.ts +13 -0
  37. package/dist/service-cli.js +8 -8
  38. package/dist/{service-client-cli-v6qptg9x.js → service-client-cli-xbj7j5kj.js} +3 -3
  39. package/dist/verify-historical-migrations.js +100 -27
  40. package/dist/workspace/text-integrity.d.ts +19 -0
  41. package/dist/workspace/workspace.d.ts +8 -3
  42. package/dist/zhx.js +2 -2
  43. package/docs/AUTO_APPROVE.md +51 -0
  44. package/docs/AZURE_OPENAI.md +4 -4
  45. package/docs/BEDROCK.md +4 -4
  46. package/docs/CLI.md +13 -0
  47. package/docs/CODE_RELEASE.md +21 -0
  48. package/docs/CREDENTIALS.md +1 -1
  49. package/docs/DURABLE_OPERATIONS.md +1 -1
  50. package/docs/ENGINE_API.md +1 -1
  51. package/docs/EXTENSIBILITY.md +1 -1
  52. package/docs/FIRST_USE.md +6 -6
  53. package/docs/MINIMUM_TASK_AUTHORITY.md +5 -5
  54. package/docs/PROJECT_MEMORY.md +1 -1
  55. package/docs/TOOL_POLICY.md +3 -0
  56. package/docs/VERTEX.md +2 -2
  57. package/docs/WEB_LIMITS.md +2 -2
  58. package/docs/adr/0002-isolated-mcp-stdio.md +1 -1
  59. package/evaluations/representative-assembly-matrix.json +37 -1
  60. package/examples/README.md +3 -3
  61. package/package.json +18 -17
@@ -20,7 +20,7 @@ import {
20
20
  runtimeManifest,
21
21
  taskSources,
22
22
  tokenUsageCheckpointStore
23
- } from "./index-wwthv10a.js";
23
+ } from "./index-amjttjaj.js";
24
24
  import {
25
25
  HARNESS_MCP_CONFIG_SCHEMA_VERSION,
26
26
  closeMcpHostSession,
@@ -29,7 +29,7 @@ import {
29
29
  mcpConfigurationFingerprintInput,
30
30
  normalizeHarnessMcpConfiguration,
31
31
  resolveMcpHostSession
32
- } from "./index-5zbq9ztj.js";
32
+ } from "./index-hhrnrqfc.js";
33
33
  import {
34
34
  EnvironmentPatchDriftError,
35
35
  Workspace,
@@ -40,7 +40,7 @@ import {
40
40
  isHardIgnored,
41
41
  replacementEditSchema,
42
42
  resolvePackageCheckCommand
43
- } from "./index-ggstwr0v.js";
43
+ } from "./index-w0m1nckc.js";
44
44
  import {
45
45
  DEFAULT_PROVIDER_REGISTRY,
46
46
  HARNESS_CONFIG_SCHEMA_VERSION,
@@ -88,7 +88,7 @@ import {
88
88
  } from "./index-fqqz1yt8.js";
89
89
  import {
90
90
  HARNESS_VERSION
91
- } from "./index-pq3kz1r8.js";
91
+ } from "./index-3wwfn5n6.js";
92
92
  import {
93
93
  FileSizeLimitError,
94
94
  MAX_EDIT_CHANGES,
@@ -1832,6 +1832,7 @@ var record = (value) => value !== null && typeof value === "object" && !Array.is
1832
1832
  var LOCAL_TOOLS = LOCAL_TOOL_NAMES;
1833
1833
  var MAX_USER_STEERING = 10;
1834
1834
  var CONTEXT_PRIORITY = "Latest user > chronological user steering > historical objective. Assistant never overrides user facts. Questions retain task.";
1835
+ var EARLIER = "[before later user messages] ";
1835
1836
  var summarizeHarnessMessages = (messages, maxCharacters = 4000) => {
1836
1837
  const redaction = createRedactionPolicy({ includeEmails: true });
1837
1838
  const clean = (text, limit = 512) => redaction.redactText(text).replace(/\bBearer\s+\S+/gi, "Bearer [REDACTED]").replace(/\b([A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)|api[_-]?key|access[_-]?token|password)\s*([=:])\s*(?:"[^"]*"|'[^']*'|\S+)/gi, "$1$2[REDACTED]").replace(/\b(?:sk|ghp|gho|github_pat)-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]").replace(/\s+/g, " ").trim().slice(0, limit);
@@ -1857,6 +1858,12 @@ var summarizeHarnessMessages = (messages, maxCharacters = 4000) => {
1857
1858
  omitted = true;
1858
1859
  }
1859
1860
  };
1861
+ const markRecentEarlier = () => {
1862
+ const marked = recent.map((value) => value.startsWith(EARLIER) ? value : `${EARLIER}${value}`);
1863
+ recent.length = 0;
1864
+ for (const value of marked)
1865
+ add(recent, value, 4);
1866
+ };
1860
1867
  const rememberObservation = (tool2, detail) => {
1861
1868
  if (typeof tool2 !== "string" || !LOCAL_TOOLS.has(tool2) || typeof detail !== "string")
1862
1869
  return;
@@ -1974,6 +1981,7 @@ var summarizeHarnessMessages = (messages, maxCharacters = 4000) => {
1974
1981
  objective = text;
1975
1982
  if (historicalObjective)
1976
1983
  add(steering, text, MAX_USER_STEERING);
1984
+ markRecentEarlier();
1977
1985
  } else
1978
1986
  add(recent, `${message.role}: ${text}`, 4);
1979
1987
  } else if (part.type === "tool-call") {
@@ -403,6 +403,55 @@ import { createHash as createHash2, randomUUID } from "node:crypto";
403
403
  import { link, lstat as lstat3, mkdir as mkdir2, open as open2, readdir, realpath as realpath2, rename, stat, unlink } from "node:fs/promises";
404
404
  import path3 from "node:path";
405
405
 
406
+ // src/workspace/text-integrity.ts
407
+ var controlLabel = (code) => code === 0 ? "NUL byte" : `U+${code.toString(16).padStart(4, "0")}`;
408
+ var firstDisallowedControl = (text) => {
409
+ let line = 1;
410
+ for (let index = 0;index < text.length; index += 1) {
411
+ const code = text.charCodeAt(index);
412
+ if (code === 10) {
413
+ line += 1;
414
+ continue;
415
+ }
416
+ if (code === 9 || code === 13)
417
+ continue;
418
+ if (code < 32 || code === 127 || code >= 128 && code <= 159)
419
+ return { line, label: controlLabel(code) };
420
+ }
421
+ return;
422
+ };
423
+ var firstDisallowedControlBytes = (bytes) => {
424
+ let line = 1;
425
+ for (let index = 0;index < bytes.length; index += 1) {
426
+ const code = bytes[index] ?? 0;
427
+ if (code === 10) {
428
+ line += 1;
429
+ continue;
430
+ }
431
+ if (code === 9 || code === 13)
432
+ continue;
433
+ if (code < 32 || code === 127)
434
+ return { line, label: controlLabel(code) };
435
+ }
436
+ return;
437
+ };
438
+ var refuseControlMessage = (hit) => `Refusing to write a control byte into a text file: ${hit.label} at L${hit.line}. The file was not changed. Remove the control byte.`;
439
+ var binaryAfterEditMessage = (hit) => `file is binary after edit: ${hit.label} at L${hit.line}. The edit was reverted. Read the file again before retrying.`;
440
+ var textGuards = {
441
+ beforeWrite(content, allowControlBytes) {
442
+ if (allowControlBytes)
443
+ return;
444
+ const problem = firstDisallowedControl(content);
445
+ if (problem)
446
+ throw new HarnessWorkspaceError(refuseControlMessage(problem));
447
+ },
448
+ afterWrite(bytes, allowControlBytes) {
449
+ if (allowControlBytes)
450
+ return;
451
+ return firstDisallowedControlBytes(bytes);
452
+ }
453
+ };
454
+
406
455
  // src/workspace/path-policy.ts
407
456
  var HARD_IGNORES = new Set([".git", ".next", ".turbo", ".zhivex-harness", "coverage", "dist", "node_modules"]);
408
457
  var isSensitiveName = (name) => {
@@ -732,8 +781,11 @@ class Workspace {
732
781
  maxBytes: MAX_FILE_BYTES
733
782
  });
734
783
  const { contents } = file;
735
- if (!allowBinary && contents.includes(0))
736
- throw new HarnessWorkspaceError("The file appears to be binary and cannot be read as text.");
784
+ if (!allowBinary && contents.includes(0)) {
785
+ const hit = firstDisallowedControlBytes(contents);
786
+ const where = hit ? `: ${hit.label} at L${hit.line}` : "";
787
+ throw new HarnessWorkspaceError(`The file appears to be binary and cannot be read as text${where}.`);
788
+ }
737
789
  return {
738
790
  path: wirePath(path3.relative(this.root, safe.path)),
739
791
  absolutePath: safe.path,
@@ -1400,19 +1452,23 @@ class Workspace {
1400
1452
  if (position < 0 || text.indexOf(edit.oldText, position + 1) >= 0) {
1401
1453
  throw new HarnessWorkspaceError("Replacement requires exactly one literal match; include more surrounding context.");
1402
1454
  }
1403
- return [{ path: edit.path, expectedDigest: edit.expectedDigest, content: text.slice(0, position) + edit.newText + text.slice(position + edit.oldText.length) }];
1455
+ return [{
1456
+ path: edit.path,
1457
+ expectedDigest: edit.expectedDigest,
1458
+ content: text.slice(0, position) + edit.newText + text.slice(position + edit.oldText.length)
1459
+ }];
1404
1460
  }
1405
- async applyReplacement(input) {
1461
+ async applyReplacement(input, host = {}) {
1406
1462
  const changes = await this.replacementChanges(input);
1407
- return this.applyPatch({ proposalId: createEditProposal({ changes }).proposalId, changes });
1463
+ return this.applyPatch({ proposalId: createEditProposal({ changes }).proposalId, changes }, host);
1408
1464
  }
1409
- async applyPatch(input) {
1410
- return this.applyPatchWithModes(input, new Map);
1465
+ async applyPatch(input, host = {}) {
1466
+ return this.applyPatchWithModes(input, new Map, undefined, host);
1411
1467
  }
1412
- async applyPatchWithModes(input, modes, assertActive) {
1413
- return withWorkspaceMutation(this.root, () => this.applyPatchWithModesLocked(input, modes, assertActive));
1468
+ async applyPatchWithModes(input, modes, assertActive, host = {}) {
1469
+ return withWorkspaceMutation(this.root, () => this.applyPatchWithModesLocked(input, modes, assertActive, host));
1414
1470
  }
1415
- async applyPatchWithModesLocked(input, modes, assertActive) {
1471
+ async applyPatchWithModesLocked(input, modes, assertActive, host = {}) {
1416
1472
  const proposal = validateEditProposal(input);
1417
1473
  this.checkAuditCapacity(proposal.changes.length);
1418
1474
  const targetPaths = new Set(proposal.changes.map((change) => change.path));
@@ -1457,6 +1513,7 @@ class Workspace {
1457
1513
  throw new HarnessWorkspaceError(`Stale patch mode rejected for ${change.path}: expected ${modeBinding.beforeMode.toString(8)}, found ${before.mode.toString(8)}.`);
1458
1514
  }
1459
1515
  }
1516
+ textGuards.beforeWrite(change.content, host.allowControlBytes === true);
1460
1517
  const temporary = await this.stageFile(safe.path, change.content, modeBinding?.afterMode ?? before?.mode ?? 420);
1461
1518
  prepared.push({
1462
1519
  change,
@@ -1496,6 +1553,13 @@ class Workspace {
1496
1553
  await unlink(item.temporary);
1497
1554
  }
1498
1555
  committed.push(item);
1556
+ const published = await readRegularFileNoFollow(item.target, {
1557
+ label: "The published file",
1558
+ maxBytes: MAX_FILE_BYTES
1559
+ });
1560
+ const corrupt = textGuards.afterWrite(published.contents, host.allowControlBytes === true);
1561
+ if (corrupt)
1562
+ throw new HarnessWorkspaceError(binaryAfterEditMessage(corrupt));
1499
1563
  await assertActive?.();
1500
1564
  }
1501
1565
  } catch (error) {
package/dist/index.js CHANGED
@@ -63,7 +63,7 @@ import {
63
63
  inspectHarnessState,
64
64
  readHarnessStateBackup,
65
65
  stateBackupBundleSchema
66
- } from "./index-xfedewpt.js";
66
+ } from "./index-rgqzdgv6.js";
67
67
  import {
68
68
  CLI_COMMAND_OPTION_CONTRACTS,
69
69
  CLI_OPTION_DEFINITIONS,
@@ -109,13 +109,13 @@ import {
109
109
  recoverHarnessLocalService,
110
110
  requestHarnessLocalService,
111
111
  startHarnessLocalService
112
- } from "./index-vvzc5ezz.js";
112
+ } from "./index-2yyfg69h.js";
113
113
  import {
114
114
  createHarnessClientAdapter
115
- } from "./index-vc7yxz50.js";
115
+ } from "./index-j466sekf.js";
116
116
  import {
117
117
  openWorkspaceCheckpointStore
118
- } from "./index-63gbp0cz.js";
118
+ } from "./index-4mn6m3bq.js";
119
119
  import {
120
120
  DEFAULT_HARNESS_CONTEXT_MANIFEST,
121
121
  DEFAULT_HARNESS_PROJECT_INSTRUCTIONS,
@@ -154,7 +154,7 @@ import {
154
154
  runHarness,
155
155
  runHarnessReviewGroup,
156
156
  selectHarnessModel
157
- } from "./index-6fp7kdpx.js";
157
+ } from "./index-vn91b9z1.js";
158
158
  import {
159
159
  HARNESS_OPERATIONS_SCHEMA_VERSION,
160
160
  HARNESS_SQLITE_FILE,
@@ -165,7 +165,7 @@ import {
165
165
  listHarnessRuns,
166
166
  migrateLegacyFileRuns,
167
167
  openHarnessPersistence
168
- } from "./index-wwthv10a.js";
168
+ } from "./index-amjttjaj.js";
169
169
  import {
170
170
  HARNESS_MCP_CONFIG_SCHEMA_VERSION,
171
171
  HARNESS_MCP_PERMISSIONS,
@@ -173,7 +173,7 @@ import {
173
173
  createHttpMcpClient,
174
174
  loadHarnessMcpConfiguration,
175
175
  normalizeHarnessMcpConfiguration
176
- } from "./index-5zbq9ztj.js";
176
+ } from "./index-hhrnrqfc.js";
177
177
  import {
178
178
  CliOciRuntimeAdapter,
179
179
  HARNESS_EXECUTION_ARTIFACT_SCHEMA_VERSION,
@@ -185,7 +185,7 @@ import {
185
185
  describeOciCommand,
186
186
  executionFingerprintInput,
187
187
  harnessExecutionSession
188
- } from "./index-ggstwr0v.js";
188
+ } from "./index-w0m1nckc.js";
189
189
  import {
190
190
  BUILTIN_PROVIDER_REGISTRATIONS,
191
191
  DEFAULT_ALLOWED_CHECKS,
@@ -231,7 +231,7 @@ import {
231
231
  BUN_ENGINE_RANGE,
232
232
  HARNESS_VERSION,
233
233
  NODE_ENGINE_RANGE
234
- } from "./index-pq3kz1r8.js";
234
+ } from "./index-3wwfn5n6.js";
235
235
  import {
236
236
  EDIT_CONTRACT_SCHEMA_VERSION,
237
237
  EDIT_DIGEST_ALGORITHM,
@@ -4,7 +4,7 @@ export declare const BEDROCK_TRANSPORT_ENVIRONMENT: readonly ["AWS_REGION", "AWS
4
4
  export declare const BEDROCK_CREDENTIAL_ENVIRONMENT: readonly ["BEDROCK_API_KEY", "AWS_BEARER_TOKEN_BEDROCK"];
5
5
  /**
6
6
  * SDK matrix: Converse is Tier B and `runtime: "openai"` is Tier A.
7
- * Published `@zhivex-ai/bedrock@2.0.7` reports those labels. Converse includes
7
+ * Published `@zhivex-ai/bedrock@2.1.0` reports those labels. Converse includes
8
8
  * reasoning and `toolChoiceNone`. Harness reports the installed label and
9
9
  * does not relabel the model.
10
10
  */
@@ -0,0 +1,13 @@
1
+ export interface RevealedPiece {
2
+ text: string;
3
+ /** The text stands in for a character a terminal or browser would hide. */
4
+ mark: boolean;
5
+ }
6
+ /**
7
+ * Show control, zero-width, and bidi characters, and mark tabs and trailing whitespace.
8
+ * Newlines stay newlines so a diff line remains one row. A second pass leaves the result unchanged.
9
+ */
10
+ export declare const revealInvisiblePieces: (line: string) => RevealedPiece[];
11
+ export declare const revealInvisible: (line: string) => string;
12
+ /** Reveal each line and keep the newline structure of a whole file. */
13
+ export declare const revealInvisibleText: (value: string) => string;
@@ -2,21 +2,21 @@
2
2
  import {
3
3
  recoverHarnessLocalService,
4
4
  startHarnessLocalService
5
- } from "./index-vvzc5ezz.js";
6
- import"./index-vc7yxz50.js";
7
- import"./index-63gbp0cz.js";
5
+ } from "./index-2yyfg69h.js";
6
+ import"./index-j466sekf.js";
7
+ import"./index-4mn6m3bq.js";
8
8
  import {
9
9
  createHarness
10
- } from "./index-6fp7kdpx.js";
11
- import"./index-wwthv10a.js";
12
- import"./index-5zbq9ztj.js";
13
- import"./index-ggstwr0v.js";
10
+ } from "./index-vn91b9z1.js";
11
+ import"./index-amjttjaj.js";
12
+ import"./index-hhrnrqfc.js";
13
+ import"./index-w0m1nckc.js";
14
14
  import"./index-1265j7t2.js";
15
15
  import"./index-c39197wq.js";
16
16
  import"./index-mzydbzp8.js";
17
17
  import"./index-bt2p8x7r.js";
18
18
  import"./index-fqqz1yt8.js";
19
- import"./index-pq3kz1r8.js";
19
+ import"./index-3wwfn5n6.js";
20
20
  import"./index-6dxrbnxm.js";
21
21
  import"./index-4gt045j0.js";
22
22
  import"./index-bpc596v3.js";
@@ -9,17 +9,17 @@ import {
9
9
  navigateConsole,
10
10
  printPolicyInspection,
11
11
  sanitizeTerminalText
12
- } from "./cli-entry-h2yc9x37.js";
12
+ } from "./cli-entry-rsx0wpvs.js";
13
13
  import {
14
14
  CLI_EVENT_SCHEMA_VERSION,
15
15
  CLI_JSON_SCHEMA_VERSION,
16
16
  changeEnvelopeSchema,
17
17
  harnessPolicyDecisionEventSchema,
18
18
  serializeStreamResult
19
- } from "./cli-entry-928p8h41.js";
19
+ } from "./cli-entry-nyzyvf24.js";
20
20
  import {
21
21
  HARNESS_VERSION
22
- } from "./cli-entry-q8bgkn9m.js";
22
+ } from "./cli-entry-mbcq96nx.js";
23
23
  import {
24
24
  HARNESS_ERROR_CODES,
25
25
  HARNESS_ERROR_SCHEMA_VERSION,
@@ -5905,6 +5905,55 @@ var runPortableProcess = async (command, options = {}) => {
5905
5905
  }
5906
5906
  };
5907
5907
 
5908
+ // src/workspace/text-integrity.ts
5909
+ var controlLabel = (code) => code === 0 ? "NUL byte" : `U+${code.toString(16).padStart(4, "0")}`;
5910
+ var firstDisallowedControl = (text2) => {
5911
+ let line = 1;
5912
+ for (let index = 0;index < text2.length; index += 1) {
5913
+ const code = text2.charCodeAt(index);
5914
+ if (code === 10) {
5915
+ line += 1;
5916
+ continue;
5917
+ }
5918
+ if (code === 9 || code === 13)
5919
+ continue;
5920
+ if (code < 32 || code === 127 || code >= 128 && code <= 159)
5921
+ return { line, label: controlLabel(code) };
5922
+ }
5923
+ return;
5924
+ };
5925
+ var firstDisallowedControlBytes = (bytes) => {
5926
+ let line = 1;
5927
+ for (let index = 0;index < bytes.length; index += 1) {
5928
+ const code = bytes[index] ?? 0;
5929
+ if (code === 10) {
5930
+ line += 1;
5931
+ continue;
5932
+ }
5933
+ if (code === 9 || code === 13)
5934
+ continue;
5935
+ if (code < 32 || code === 127)
5936
+ return { line, label: controlLabel(code) };
5937
+ }
5938
+ return;
5939
+ };
5940
+ var refuseControlMessage = (hit) => `Refusing to write a control byte into a text file: ${hit.label} at L${hit.line}. The file was not changed. Remove the control byte.`;
5941
+ var binaryAfterEditMessage = (hit) => `file is binary after edit: ${hit.label} at L${hit.line}. The edit was reverted. Read the file again before retrying.`;
5942
+ var textGuards = {
5943
+ beforeWrite(content, allowControlBytes) {
5944
+ if (allowControlBytes)
5945
+ return;
5946
+ const problem = firstDisallowedControl(content);
5947
+ if (problem)
5948
+ throw new HarnessWorkspaceError(refuseControlMessage(problem));
5949
+ },
5950
+ afterWrite(bytes, allowControlBytes) {
5951
+ if (allowControlBytes)
5952
+ return;
5953
+ return firstDisallowedControlBytes(bytes);
5954
+ }
5955
+ };
5956
+
5908
5957
  // src/workspace/path-policy.ts
5909
5958
  var HARD_IGNORES = new Set([".git", ".next", ".turbo", ".zhivex-harness", "coverage", "dist", "node_modules"]);
5910
5959
  var isSensitiveName = (name) => {
@@ -6234,8 +6283,11 @@ class Workspace {
6234
6283
  maxBytes: MAX_FILE_BYTES
6235
6284
  });
6236
6285
  const { contents } = file;
6237
- if (!allowBinary && contents.includes(0))
6238
- throw new HarnessWorkspaceError("The file appears to be binary and cannot be read as text.");
6286
+ if (!allowBinary && contents.includes(0)) {
6287
+ const hit = firstDisallowedControlBytes(contents);
6288
+ const where = hit ? `: ${hit.label} at L${hit.line}` : "";
6289
+ throw new HarnessWorkspaceError(`The file appears to be binary and cannot be read as text${where}.`);
6290
+ }
6239
6291
  return {
6240
6292
  path: wirePath(path11.relative(this.root, safe.path)),
6241
6293
  absolutePath: safe.path,
@@ -6902,19 +6954,23 @@ class Workspace {
6902
6954
  if (position < 0 || text2.indexOf(edit.oldText, position + 1) >= 0) {
6903
6955
  throw new HarnessWorkspaceError("Replacement requires exactly one literal match; include more surrounding context.");
6904
6956
  }
6905
- return [{ path: edit.path, expectedDigest: edit.expectedDigest, content: text2.slice(0, position) + edit.newText + text2.slice(position + edit.oldText.length) }];
6957
+ return [{
6958
+ path: edit.path,
6959
+ expectedDigest: edit.expectedDigest,
6960
+ content: text2.slice(0, position) + edit.newText + text2.slice(position + edit.oldText.length)
6961
+ }];
6906
6962
  }
6907
- async applyReplacement(input) {
6963
+ async applyReplacement(input, host = {}) {
6908
6964
  const changes = await this.replacementChanges(input);
6909
- return this.applyPatch({ proposalId: createEditProposal({ changes }).proposalId, changes });
6965
+ return this.applyPatch({ proposalId: createEditProposal({ changes }).proposalId, changes }, host);
6910
6966
  }
6911
- async applyPatch(input) {
6912
- return this.applyPatchWithModes(input, new Map);
6967
+ async applyPatch(input, host = {}) {
6968
+ return this.applyPatchWithModes(input, new Map, undefined, host);
6913
6969
  }
6914
- async applyPatchWithModes(input, modes, assertActive) {
6915
- return withWorkspaceMutation(this.root, () => this.applyPatchWithModesLocked(input, modes, assertActive));
6970
+ async applyPatchWithModes(input, modes, assertActive, host = {}) {
6971
+ return withWorkspaceMutation(this.root, () => this.applyPatchWithModesLocked(input, modes, assertActive, host));
6916
6972
  }
6917
- async applyPatchWithModesLocked(input, modes, assertActive) {
6973
+ async applyPatchWithModesLocked(input, modes, assertActive, host = {}) {
6918
6974
  const proposal = validateEditProposal(input);
6919
6975
  this.checkAuditCapacity(proposal.changes.length);
6920
6976
  const targetPaths = new Set(proposal.changes.map((change) => change.path));
@@ -6959,6 +7015,7 @@ class Workspace {
6959
7015
  throw new HarnessWorkspaceError(`Stale patch mode rejected for ${change.path}: expected ${modeBinding.beforeMode.toString(8)}, found ${before.mode.toString(8)}.`);
6960
7016
  }
6961
7017
  }
7018
+ textGuards.beforeWrite(change.content, host.allowControlBytes === true);
6962
7019
  const temporary = await this.stageFile(safe.path, change.content, modeBinding?.afterMode ?? before?.mode ?? 420);
6963
7020
  prepared.push({
6964
7021
  change,
@@ -6998,6 +7055,13 @@ class Workspace {
6998
7055
  await unlink(item.temporary);
6999
7056
  }
7000
7057
  committed.push(item);
7058
+ const published = await readRegularFileNoFollow(item.target, {
7059
+ label: "The published file",
7060
+ maxBytes: MAX_FILE_BYTES
7061
+ });
7062
+ const corrupt = textGuards.afterWrite(published.contents, host.allowControlBytes === true);
7063
+ if (corrupt)
7064
+ throw new HarnessWorkspaceError(binaryAfterEditMessage(corrupt));
7001
7065
  await assertActive?.();
7002
7066
  }
7003
7067
  } catch (error) {
@@ -13534,6 +13598,7 @@ var record2 = (value) => value !== null && typeof value === "object" && !Array.i
13534
13598
  var LOCAL_TOOLS = LOCAL_TOOL_NAMES;
13535
13599
  var MAX_USER_STEERING = 10;
13536
13600
  var CONTEXT_PRIORITY = "Latest user > chronological user steering > historical objective. Assistant never overrides user facts. Questions retain task.";
13601
+ var EARLIER = "[before later user messages] ";
13537
13602
  var summarizeHarnessMessages = (messages, maxCharacters = 4000) => {
13538
13603
  const redaction2 = createRedactionPolicy8({ includeEmails: true });
13539
13604
  const clean = (text3, limit = 512) => redaction2.redactText(text3).replace(/\bBearer\s+\S+/gi, "Bearer [REDACTED]").replace(/\b([A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)|api[_-]?key|access[_-]?token|password)\s*([=:])\s*(?:"[^"]*"|'[^']*'|\S+)/gi, "$1$2[REDACTED]").replace(/\b(?:sk|ghp|gho|github_pat)-[A-Za-z0-9_-]{8,}\b/g, "[REDACTED]").replace(/\s+/g, " ").trim().slice(0, limit);
@@ -13559,6 +13624,12 @@ var summarizeHarnessMessages = (messages, maxCharacters = 4000) => {
13559
13624
  omitted = true;
13560
13625
  }
13561
13626
  };
13627
+ const markRecentEarlier = () => {
13628
+ const marked = recent.map((value) => value.startsWith(EARLIER) ? value : `${EARLIER}${value}`);
13629
+ recent.length = 0;
13630
+ for (const value of marked)
13631
+ add(recent, value, 4);
13632
+ };
13562
13633
  const rememberObservation = (tool3, detail) => {
13563
13634
  if (typeof tool3 !== "string" || !LOCAL_TOOLS.has(tool3) || typeof detail !== "string")
13564
13635
  return;
@@ -13676,6 +13747,7 @@ var summarizeHarnessMessages = (messages, maxCharacters = 4000) => {
13676
13747
  objective = text3;
13677
13748
  if (historicalObjective)
13678
13749
  add(steering, text3, MAX_USER_STEERING);
13750
+ markRecentEarlier();
13679
13751
  } else
13680
13752
  add(recent, `${message.role}: ${text3}`, 4);
13681
13753
  } else if (part.type === "tool-call") {
@@ -15355,7 +15427,7 @@ import { z as z34 } from "zod";
15355
15427
  // package.json
15356
15428
  var package_default = {
15357
15429
  name: "@zhivex-ai/harness",
15358
- version: "1.5.0",
15430
+ version: "1.6.3",
15359
15431
  description: "Governed, provider-portable runtime for coding agents with durable approvals and isolated repository execution.",
15360
15432
  type: "module",
15361
15433
  license: "MIT",
@@ -15455,6 +15527,7 @@ var package_default = {
15455
15527
  "LICENSE",
15456
15528
  "docs/CHANGE_ENVELOPES.md",
15457
15529
  "docs/CLI.md",
15530
+ "docs/AUTO_APPROVE.md",
15458
15531
  "docs/WEB_LIMITS.md",
15459
15532
  "docs/CODE_RELEASE.md",
15460
15533
  "docs/CODE_WEB_TASKS.md",
@@ -15571,26 +15644,26 @@ var package_default = {
15571
15644
  "smoke:mcp:installed": "bun run build && bun scripts/mcp-installed-smoke.ts"
15572
15645
  },
15573
15646
  dependencies: {
15574
- "@zhivex-ai/agents": "1.11.0",
15575
- "@zhivex-ai/anthropic": "0.14.0",
15576
- "@zhivex-ai/azure-openai": "0.7.8",
15577
- "@zhivex-ai/bedrock": "2.0.7",
15578
- "@zhivex-ai/core": "1.34.0",
15579
- "@zhivex-ai/gemini": "0.13.0",
15580
- "@zhivex-ai/meta": "0.2.9",
15581
- "@zhivex-ai/openai": "0.16.0",
15582
- "@zhivex-ai/qwen": "0.16.4",
15583
- "@zhivex-ai/vertex": "1.2.7",
15584
- "google-auth-library": "11.1.0",
15585
- zod: "4.6.5"
15647
+ "@zhivex-ai/agents": "1.12.0",
15648
+ "@zhivex-ai/anthropic": "0.15.0",
15649
+ "@zhivex-ai/azure-openai": "0.8.0",
15650
+ "@zhivex-ai/bedrock": "2.1.0",
15651
+ "@zhivex-ai/core": "1.37.0",
15652
+ "@zhivex-ai/gemini": "0.14.0",
15653
+ "@zhivex-ai/meta": "0.3.0",
15654
+ "@zhivex-ai/openai": "0.17.0",
15655
+ "@zhivex-ai/qwen": "0.18.1",
15656
+ "@zhivex-ai/vertex": "1.3.0",
15657
+ "google-auth-library": "11.3.1",
15658
+ zod: "4.6.7"
15586
15659
  },
15587
15660
  overrides: {
15588
- "@zhivex-ai/core": "1.34.0"
15661
+ "@zhivex-ai/core": "1.37.0"
15589
15662
  },
15590
15663
  devDependencies: {
15591
- "@agentclientprotocol/sdk": "1.8.0",
15664
+ "@agentclientprotocol/sdk": "2.1.0",
15592
15665
  "@modelcontextprotocol/server": "2.3.1",
15593
- "@types/bun": "1.4.2",
15666
+ "@types/bun": "1.4.4",
15594
15667
  "@types/node": "^26.6.5",
15595
15668
  typescript: "7.0.2",
15596
15669
  "typescript-compiler-api": "npm:typescript@6.0.3"
@@ -15601,7 +15674,7 @@ var package_default = {
15601
15674
  },
15602
15675
  packageManager: "bun@1.4.0",
15603
15676
  optionalDependencies: {
15604
- "@napi-rs/keyring": "2.1.1"
15677
+ "@napi-rs/keyring": "2.1.2"
15605
15678
  }
15606
15679
  };
15607
15680
 
@@ -0,0 +1,19 @@
1
+ export interface ControlHit {
2
+ line: number;
3
+ label: string;
4
+ }
5
+ /** C0 controls other than tab, LF, and CR, plus DEL and Unicode C1 controls. */
6
+ export declare const firstDisallowedControl: (text: string) => ControlHit | undefined;
7
+ /** Raw C0/DEL bytes. UTF-8 continuation bytes are left alone. */
8
+ export declare const firstDisallowedControlBytes: (bytes: Uint8Array) => ControlHit | undefined;
9
+ export declare const refuseControlMessage: (hit: ControlHit) => string;
10
+ export declare const binaryAfterEditMessage: (hit: ControlHit) => string;
11
+ /**
12
+ * Text edits refuse NUL and other control bytes unless the host passes
13
+ * allowControlBytes. That switch is not part of the model tool schema.
14
+ * afterWrite is the publication check; tests replace it to prove the rollback path.
15
+ */
16
+ export declare const textGuards: {
17
+ beforeWrite(content: string, allowControlBytes: boolean): void;
18
+ afterWrite(bytes: Uint8Array, allowControlBytes: boolean): ControlHit | undefined;
19
+ };
@@ -1,5 +1,9 @@
1
1
  import { type ReplacementEdit } from "./replacement-edits.js";
2
2
  import { type ApplyEditProposalInput, type ApplyPatchResult, type FileDigest, type MoveFileInput, type MoveFileResult, type MutationAuditEntry, type QuarantineFileInput, type QuarantineFileResult, type RestoreFileInput, type RestoreFileResult } from "./edit-contracts.js";
3
+ /** Host-only. Model tool input cannot set this, and the default is off. */
4
+ interface HostTextEditOptions {
5
+ allowControlBytes?: boolean;
6
+ }
3
7
  export type HarnessCheck = string;
4
8
  export interface WorkspaceFile {
5
9
  path: string;
@@ -188,12 +192,12 @@ export declare class Workspace {
188
192
  }[];
189
193
  }>;
190
194
  private replacementChanges;
191
- applyReplacement(input: ReplacementEdit): Promise<ApplyPatchResult>;
192
- applyPatch(input: ApplyEditProposalInput): Promise<ApplyPatchResult>;
195
+ applyReplacement(input: ReplacementEdit, host?: HostTextEditOptions): Promise<ApplyPatchResult>;
196
+ applyPatch(input: ApplyEditProposalInput, host?: HostTextEditOptions): Promise<ApplyPatchResult>;
193
197
  applyPatchWithModes(input: ApplyEditProposalInput, modes: ReadonlyMap<string, {
194
198
  beforeMode?: number;
195
199
  afterMode: number;
196
- }>, assertActive?: () => Promise<void>): Promise<ApplyPatchResult>;
200
+ }>, assertActive?: () => Promise<void>, host?: HostTextEditOptions): Promise<ApplyPatchResult>;
197
201
  private applyPatchWithModesLocked;
198
202
  /** @deprecated Use applyPatch with an inspected expectedDigest. */
199
203
  writeFile(relativePath: string, content: string, overwrite?: boolean): Promise<{
@@ -236,3 +240,4 @@ export declare class Workspace {
236
240
  staged: CommandResult;
237
241
  }>;
238
242
  }
243
+ export {};
package/dist/zhx.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  HARNESS_VERSION,
4
4
  NODE_ENGINE_RANGE,
5
5
  shortCliHelp
6
- } from "./cli-entry-q8bgkn9m.js";
6
+ } from "./cli-entry-mbcq96nx.js";
7
7
  import {
8
8
  __require
9
9
  } from "./cli-entry-1bx83sw4.js";
@@ -41,6 +41,6 @@ if (argv.length === 1 && ["--version", "-v", "version"].includes(argv[0])) {
41
41
  process.stdout.write(`${shortCliHelp(HARNESS_VERSION)}
42
42
  `);
43
43
  } else {
44
- const { runCli } = await import("./cli-vyrytn3m.js");
44
+ const { runCli } = await import("./cli-yxvq8qf1.js");
45
45
  await runCli(argv);
46
46
  }