@zhivex-ai/harness 1.3.0-rc.3 → 1.3.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 +53 -0
- package/README.md +12 -10
- package/SECURITY.md +1 -1
- package/SUPPORT.md +4 -4
- package/contracts/engine-api.json +334 -0
- package/contracts/public-api.json +267 -1
- package/contracts/stable-api-signatures.json +43 -43
- package/dist/acp-cli.js +54 -18
- package/dist/approvals/approval-history.d.ts +22 -0
- package/dist/approvals/explicit-review.d.ts +24 -0
- package/dist/approvals/host-policy-identity.d.ts +3 -0
- package/dist/cli/arguments.d.ts +6 -2
- package/dist/cli/checkpoints.d.ts +230 -0
- package/dist/cli/cli-profiles.d.ts +4 -2
- package/dist/cli/governance-report.d.ts +5 -0
- package/dist/cli/policy-decisions.d.ts +9 -0
- package/dist/cli/policy.d.ts +5 -0
- package/dist/cli/presentation.d.ts +3 -1
- package/dist/cli/terminal/terminal-ui.d.ts +4 -0
- package/dist/{cli-entry-xmf48p6a.js → cli-entry-1gfg3brj.js} +14921 -10692
- package/dist/cli-entry-bx3vjw7h.js +656 -0
- package/dist/{index-fvca1xea.js → cli-entry-d5788aay.js} +339 -58
- package/dist/{cli-qgwvtk0w.js → cli-etsmtst3.js} +918 -2222
- package/dist/cli.js +3 -5
- package/dist/client/acp-mcp-admission.d.ts +32 -0
- package/dist/client/acp-mcp-host.d.ts +19 -0
- package/dist/client/acp-mcp-session.d.ts +14 -0
- package/dist/client/acp-stdio.d.ts +2 -0
- package/dist/client/acp.d.ts +4 -1
- package/dist/client/adapter.d.ts +13 -1
- package/dist/client/json-contracts.d.ts +107 -4
- package/dist/client/local-service.d.ts +2 -0
- package/dist/client/protocol.d.ts +127 -1
- package/dist/client/service-events.d.ts +4 -0
- package/dist/compat/cli-options.d.ts +13 -1
- package/dist/compat/code-support.js +23 -87
- package/dist/{config-q32jm1e6.js → config-c002f06f.js} +1 -1
- package/dist/{config-6gtpxvjv.js → config-j3jga5wh.js} +3 -1
- package/dist/context/adaptive-compaction.d.ts +1 -0
- package/dist/context/compaction.d.ts +1 -1
- package/dist/context/request-measurements.d.ts +17 -0
- package/dist/context/request-projection.d.ts +10 -0
- package/dist/context/task-memory.d.ts +1 -0
- package/dist/engine/acp.d.ts +2 -0
- package/dist/engine/acp.js +197 -1
- package/dist/engine/client.js +11 -4
- package/dist/engine/desktop-providers-v1.d.ts +4 -0
- package/dist/engine/desktop-providers-v1.js +18 -0
- package/dist/engine/desktop-state-v1.d.ts +10 -0
- package/dist/engine/desktop-state-v1.js +168 -0
- package/dist/engine/index.d.ts +12 -0
- package/dist/engine/index.js +571 -71
- package/dist/engine/mcp-stdio-v1.d.ts +11 -0
- package/dist/engine/mcp-stdio-v1.js +108 -0
- package/dist/engine/models.d.ts +3 -0
- package/dist/engine/models.js +894 -0
- package/dist/engine/protocol.d.ts +1 -0
- package/dist/engine/protocol.js +24 -0
- package/dist/engine/service.js +12 -5
- package/dist/execution/acceptance-snapshot.d.ts +2 -0
- package/dist/execution/execution-environment.d.ts +10 -0
- package/dist/execution/mcp-oci-channel.d.ts +11 -0
- package/dist/execution/mcp-oci-server.d.ts +38 -0
- package/dist/execution/mcp-oci-workspace.d.ts +23 -0
- package/dist/execution/mcp-resource-recovery.d.ts +19 -0
- package/dist/execution/mcp-workspace-snapshot.d.ts +20 -0
- package/dist/hostile-repository-demo.js +3169 -1163
- package/dist/{index-f4sb0e2q.js → index-2jrdcj6b.js} +17 -2
- package/dist/{index-77h9qr5h.js → index-3ah1f369.js} +40 -10
- package/dist/{index-pgqz45fj.js → index-42gp2hy8.js} +197 -1091
- package/dist/{index-41fkzt91.js → index-4jw3tvy8.js} +10 -2
- package/dist/index-6m08wvab.js +1768 -0
- package/dist/index-8705zxpr.js +145 -0
- package/dist/{index-d0wrghff.js → index-8vm2m34s.js} +225 -1416
- package/dist/index-8zzhggxv.js +477 -0
- package/dist/index-a97bezbz.js +1073 -0
- package/dist/index-azmwjp95.js +1004 -0
- package/dist/{cli-entry-0mmeag20.js → index-gq9xagx2.js} +122 -836
- package/dist/index-h0mqx2ed.js +1373 -0
- package/dist/index-hnaxfpb5.js +5589 -0
- package/dist/{index-2aymdsqc.js → index-qfpz5y2v.js} +24 -0
- package/dist/{index-kfgt9yqq.js → index-w2sj0wkw.js} +33 -10
- package/dist/index-wg1sd3at.js +80 -0
- package/dist/index-xqxeypt5.js +888 -0
- package/dist/index-yb239ghk.js +961 -0
- package/dist/index-z0qxmkm2.js +3389 -0
- package/dist/index.js +85 -72
- package/dist/integrations/mcp-host-session.d.ts +26 -0
- package/dist/integrations/mcp-stdio-admission.d.ts +54 -0
- package/dist/integrations/mcp-stdio-client.d.ts +44 -0
- package/dist/integrations/mcp-stdio-framing.d.ts +18 -0
- package/dist/integrations/mcp-stdio-launch.d.ts +20 -0
- package/dist/integrations/mcp-stdio-session.d.ts +7 -0
- package/dist/integrations/mcp-stdio-tools.d.ts +12 -0
- package/dist/internal/desktop/catalog.d.ts +2 -0
- package/dist/internal/desktop/persistence.d.ts +2 -3
- package/dist/internal/desktop/providers.d.ts +1 -0
- package/dist/models/catalog.d.ts +2 -0
- package/dist/persistence/governance-context.d.ts +126 -0
- package/dist/persistence/governance-report.d.ts +299 -0
- package/dist/persistence/host-database-backup.d.ts +28 -0
- package/dist/persistence/mcp-execution-journal.d.ts +19 -0
- package/dist/persistence/mcp-reconciliation.d.ts +33 -0
- package/dist/persistence/mcp-resource-journal.d.ts +54 -0
- package/dist/persistence/workspace-checkpoints.d.ts +138 -0
- package/dist/providers/anthropic-continuation.d.ts +5 -0
- package/dist/providers/providers.d.ts +1 -1
- package/dist/providers/vertex-auth.d.ts +27 -0
- package/dist/runtime/delegation-contracts.d.ts +9 -0
- package/dist/runtime/delegation-correction.d.ts +9 -0
- package/dist/runtime/delegation-result.d.ts +73 -0
- package/dist/runtime/error-diagnostics.d.ts +86 -0
- package/dist/runtime/harness.d.ts +11 -1
- package/dist/runtime/policy-decisions.d.ts +66 -0
- package/dist/runtime/policy-inspection.d.ts +52 -0
- package/dist/runtime/provider-diagnostics.d.ts +1 -0
- package/dist/runtime/runtime-instructions.d.ts +8 -0
- package/dist/runtime/task-acceptance-checks.d.ts +103 -0
- package/dist/runtime/task-acceptance-delivery.d.ts +107 -0
- package/dist/runtime/task-acceptance-host.d.ts +20 -0
- package/dist/runtime/task-acceptance-record.d.ts +107 -0
- package/dist/runtime/task-acceptance-validation.d.ts +25 -0
- package/dist/runtime/task-acceptance.d.ts +47 -0
- package/dist/runtime/time-to-safe-fix.d.ts +83 -0
- package/dist/runtime/tool-policy-file.d.ts +23 -0
- package/dist/runtime/tool-policy-paths.d.ts +5 -0
- package/dist/runtime/tool-policy.d.ts +6 -0
- package/dist/service-cli.js +14 -7
- package/dist/{service-client-cli-gs78wrxe.js → service-client-cli-3365rfav.js} +307 -248
- package/dist/verify-historical-migrations.js +3134 -1129
- package/dist/workspace/path-policy.d.ts +4 -0
- package/dist/zhx.js +3 -5
- package/docs/ACP.md +14 -2
- package/docs/ANTHROPIC.md +33 -0
- package/docs/CLI.md +132 -0
- package/docs/CLIENT_PROTOCOL.md +45 -0
- package/docs/CODE_RELEASE.md +35 -18
- package/docs/ENGINE_API.md +216 -7
- package/docs/EXTENSIBILITY.md +46 -3
- package/docs/FIRST_USE.md +6 -6
- package/docs/LOCAL_SERVICE.md +13 -0
- package/docs/MCP_STDIO.md +20 -0
- package/docs/README.md +1 -0
- package/docs/STABILITY.md +4 -2
- package/docs/SUPPORT_MATRIX.md +1 -1
- package/docs/TOOL_POLICY.md +81 -0
- package/docs/VERTEX.md +50 -0
- package/docs/adr/0002-isolated-mcp-stdio.md +135 -0
- package/docs/fixtures/mcp-stdio-contract-v1.json +189 -0
- package/docs/support-matrix.json +2 -0
- package/evaluations/representative-assembly-matrix.json +46 -1
- package/evaluations/task-acceptance-routes.json +7 -0
- package/examples/README.md +3 -3
- package/package.json +43 -12
- package/dist/cli-entry-64pdp3k5.js +0 -17
- package/dist/cli-entry-7h9savv5.js +0 -375
- package/dist/index-5wjwe91x.js +0 -9434
|
@@ -1,224 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SqliteDatabase,
|
|
3
|
+
taskAcceptanceChecks,
|
|
4
|
+
taskAcceptanceDeliveryDiagnostic,
|
|
5
|
+
taskAcceptanceOutcome,
|
|
6
|
+
workspaceFilePathSchema
|
|
7
|
+
} from "./index-yb239ghk.js";
|
|
1
8
|
import {
|
|
2
9
|
HarnessConfigError,
|
|
3
10
|
HarnessStateConflictError,
|
|
4
11
|
HarnessWorkspaceError
|
|
5
|
-
} from "./index-
|
|
6
|
-
|
|
7
|
-
// src/persistence/persistence-secrets.ts
|
|
8
|
-
var protectedValues = new Set;
|
|
9
|
-
function assertNoPersistenceSecret(value) {
|
|
10
|
-
if (!protectedValues.size)
|
|
11
|
-
return;
|
|
12
|
-
const seen = new WeakSet;
|
|
13
|
-
const visit = (item) => {
|
|
14
|
-
if (typeof item === "string") {
|
|
15
|
-
for (const secret of protectedValues)
|
|
16
|
-
if (item.includes(secret))
|
|
17
|
-
throw new Error("PERSISTENCE_SECRET_REJECTED");
|
|
18
|
-
} else if (item instanceof Uint8Array) {
|
|
19
|
-
const bytes = Buffer.from(item.buffer, item.byteOffset, item.byteLength);
|
|
20
|
-
for (const secret of protectedValues)
|
|
21
|
-
if (bytes.includes(Buffer.from(secret)))
|
|
22
|
-
throw new Error("PERSISTENCE_SECRET_REJECTED");
|
|
23
|
-
} else if (item && typeof item === "object" && !seen.has(item)) {
|
|
24
|
-
seen.add(item);
|
|
25
|
-
for (const [key, entry] of Object.entries(item)) {
|
|
26
|
-
visit(key);
|
|
27
|
-
visit(entry);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
visit(value);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// src/persistence/sqlite-access.ts
|
|
35
|
-
import { closeSync, constants, fchmodSync, fstatSync, lstatSync, openSync, realpathSync } from "node:fs";
|
|
36
|
-
import path from "node:path";
|
|
37
|
-
var O_SHLOCK = 16;
|
|
38
|
-
var O_EXLOCK = 32;
|
|
39
|
-
var held = new WeakMap;
|
|
40
|
-
var activeDescriptors = new Set;
|
|
41
|
-
function filename(databasePath) {
|
|
42
|
-
const directory = realpathSync(path.dirname(path.resolve(databasePath)));
|
|
43
|
-
return path.join(directory, `.${path.basename(databasePath)}.access-lock`);
|
|
44
|
-
}
|
|
45
|
-
function releaseDescriptor(state) {
|
|
46
|
-
closeSync(state.fd);
|
|
47
|
-
activeDescriptors.delete(state.fd);
|
|
48
|
-
}
|
|
49
|
-
function makeLease(lock, fd, exclusive) {
|
|
50
|
-
const state = { filename: lock, fd, closed: false, borrowers: 0, exclusive };
|
|
51
|
-
const lease = Object.freeze({ fd, close() {
|
|
52
|
-
if (!state.closed) {
|
|
53
|
-
state.closed = true;
|
|
54
|
-
if (state.borrowers === 0)
|
|
55
|
-
releaseDescriptor(state);
|
|
56
|
-
}
|
|
57
|
-
} });
|
|
58
|
-
held.set(lease, state);
|
|
59
|
-
activeDescriptors.add(fd);
|
|
60
|
-
return lease;
|
|
61
|
-
}
|
|
62
|
-
function acquireSqliteAccess(databasePath, exclusive = false) {
|
|
63
|
-
if (databasePath === ":memory:" || process.platform !== "darwin")
|
|
64
|
-
return;
|
|
65
|
-
let fd;
|
|
66
|
-
try {
|
|
67
|
-
const lock = filename(databasePath);
|
|
68
|
-
fd = openSync(lock, constants.O_RDWR | constants.O_CREAT | constants.O_NOFOLLOW | constants.O_NONBLOCK | (exclusive ? O_EXLOCK : O_SHLOCK), 384);
|
|
69
|
-
const info = fstatSync(fd);
|
|
70
|
-
const current = lstatSync(lock);
|
|
71
|
-
if (!info.isFile() || info.nlink !== 1 || info.uid !== process.getuid?.() || info.size !== 0 || current.isSymbolicLink() || current.dev !== info.dev || current.ino !== info.ino)
|
|
72
|
-
throw new Error;
|
|
73
|
-
if (info.mode & 63)
|
|
74
|
-
fchmodSync(fd, 384);
|
|
75
|
-
if (fstatSync(fd).mode & 63)
|
|
76
|
-
throw new Error;
|
|
77
|
-
return makeLease(lock, fd, exclusive);
|
|
78
|
-
} catch {
|
|
79
|
-
if (fd !== undefined)
|
|
80
|
-
closeSync(fd);
|
|
81
|
-
throw new Error("SQLITE_ACCESS_UNAVAILABLE");
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
function retainSqliteAccess(lease, databasePath) {
|
|
85
|
-
validateSqliteAccess(lease, databasePath);
|
|
86
|
-
const state = held.get(lease);
|
|
87
|
-
state.borrowers++;
|
|
88
|
-
let released = false;
|
|
89
|
-
return () => {
|
|
90
|
-
if (!released) {
|
|
91
|
-
released = true;
|
|
92
|
-
state.borrowers--;
|
|
93
|
-
if (state.closed && state.borrowers === 0)
|
|
94
|
-
releaseDescriptor(state);
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
function validateSqliteAccess(lease, databasePath) {
|
|
99
|
-
try {
|
|
100
|
-
const state = held.get(lease);
|
|
101
|
-
if (!state || state.closed || state.filename !== filename(databasePath))
|
|
102
|
-
throw new Error;
|
|
103
|
-
const current = lstatSync(state.filename), owned = fstatSync(state.fd);
|
|
104
|
-
if (current.isSymbolicLink() || !current.isFile() || current.nlink !== 1 || current.dev !== owned.dev || current.ino !== owned.ino)
|
|
105
|
-
throw new Error;
|
|
106
|
-
} catch {
|
|
107
|
-
throw new Error("SQLITE_ACCESS_UNAVAILABLE");
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// src/persistence/sqlite-database.ts
|
|
112
|
-
import { createRequire } from "node:module";
|
|
113
|
-
var require2 = createRequire(import.meta.url);
|
|
114
|
-
var DatabaseSyncConstructor;
|
|
115
|
-
var loadDatabaseSync = () => {
|
|
116
|
-
DatabaseSyncConstructor ??= require2("node:sqlite").DatabaseSync;
|
|
117
|
-
return DatabaseSyncConstructor;
|
|
118
|
-
};
|
|
119
|
-
var normalizeNumberedParameters = (sql) => {
|
|
120
|
-
const indexes = new Set;
|
|
121
|
-
const normalizedSql = sql.replace(/\?([1-9]\d*)/g, (_match, rawIndex) => {
|
|
122
|
-
const index = Number(rawIndex);
|
|
123
|
-
indexes.add(index);
|
|
124
|
-
return `$__zhivex_${index}`;
|
|
125
|
-
});
|
|
126
|
-
return {
|
|
127
|
-
sql: normalizedSql,
|
|
128
|
-
indexes: [...indexes].sort((left, right) => left - right)
|
|
129
|
-
};
|
|
130
|
-
};
|
|
131
|
-
var numberedBindings = (indexes, parameters) => {
|
|
132
|
-
if (indexes.length === 0)
|
|
133
|
-
return;
|
|
134
|
-
const maximumIndex = indexes.at(-1);
|
|
135
|
-
if (parameters.length !== maximumIndex) {
|
|
136
|
-
throw new Error(`SQLite numbered parameters require exactly ${maximumIndex} binding${maximumIndex === 1 ? "" : "s"}; received ${parameters.length}.`);
|
|
137
|
-
}
|
|
138
|
-
return Object.fromEntries(indexes.map((index) => [`$__zhivex_${index}`, parameters[index - 1]]));
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
class SqliteDatabase {
|
|
142
|
-
#database;
|
|
143
|
-
#access;
|
|
144
|
-
#ownedAccess;
|
|
145
|
-
#releaseBorrowed;
|
|
146
|
-
#databasePath;
|
|
147
|
-
constructor(databasePath, options = {}) {
|
|
148
|
-
options.create;
|
|
149
|
-
options.strict;
|
|
150
|
-
if (options.accessLease)
|
|
151
|
-
validateSqliteAccess(options.accessLease, databasePath);
|
|
152
|
-
this.#access = options.accessLease ?? acquireSqliteAccess(databasePath);
|
|
153
|
-
this.#ownedAccess = options.accessLease === undefined;
|
|
154
|
-
this.#databasePath = databasePath;
|
|
155
|
-
this.#releaseBorrowed = options.accessLease ? retainSqliteAccess(options.accessLease, databasePath) : undefined;
|
|
156
|
-
try {
|
|
157
|
-
const DatabaseSync = loadDatabaseSync();
|
|
158
|
-
this.#database = new DatabaseSync(databasePath, {
|
|
159
|
-
...options.readonly === undefined ? {} : { readOnly: options.readonly }
|
|
160
|
-
});
|
|
161
|
-
} catch (error) {
|
|
162
|
-
if (this.#ownedAccess)
|
|
163
|
-
this.#access?.close();
|
|
164
|
-
this.#releaseBorrowed?.();
|
|
165
|
-
throw error;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
exec(sql) {
|
|
169
|
-
if (this.#access)
|
|
170
|
-
validateSqliteAccess(this.#access, this.#databasePath);
|
|
171
|
-
assertNoPersistenceSecret(sql);
|
|
172
|
-
this.#database.exec(sql);
|
|
173
|
-
}
|
|
174
|
-
query(sql) {
|
|
175
|
-
assertNoPersistenceSecret(sql);
|
|
176
|
-
if (this.#access)
|
|
177
|
-
validateSqliteAccess(this.#access, this.#databasePath);
|
|
178
|
-
const normalized = normalizeNumberedParameters(sql);
|
|
179
|
-
const statement = this.#database.prepare(normalized.sql);
|
|
180
|
-
const invoke = (method, parameters) => {
|
|
181
|
-
if (this.#access)
|
|
182
|
-
validateSqliteAccess(this.#access, this.#databasePath);
|
|
183
|
-
assertNoPersistenceSecret(sql);
|
|
184
|
-
assertNoPersistenceSecret(parameters);
|
|
185
|
-
const bindings = numberedBindings(normalized.indexes, parameters);
|
|
186
|
-
return bindings ? method(bindings) : method(...parameters);
|
|
187
|
-
};
|
|
188
|
-
return {
|
|
189
|
-
run: (...parameters) => invoke(statement.run.bind(statement), parameters),
|
|
190
|
-
get: (...parameters) => invoke(statement.get.bind(statement), parameters),
|
|
191
|
-
all: (...parameters) => invoke(statement.all.bind(statement), parameters)
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
close(throwOnError = true) {
|
|
195
|
-
try {
|
|
196
|
-
this.#database.close();
|
|
197
|
-
if (this.#ownedAccess)
|
|
198
|
-
this.#access?.close();
|
|
199
|
-
this.#releaseBorrowed?.();
|
|
200
|
-
} catch (error) {
|
|
201
|
-
if (throwOnError) {
|
|
202
|
-
throw error;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
12
|
+
} from "./index-gq9xagx2.js";
|
|
207
13
|
|
|
208
14
|
// src/persistence/state-directory.ts
|
|
209
15
|
import { lstat } from "node:fs/promises";
|
|
210
|
-
import
|
|
16
|
+
import path from "node:path";
|
|
211
17
|
var SENSITIVE_STATE_SEGMENTS = new Set([".git", ".env", ".npmrc", "dist", "node_modules", "src"]);
|
|
212
18
|
var isInside = (root, candidate) => {
|
|
213
|
-
const relative =
|
|
214
|
-
return relative === "" || !relative.startsWith(`..${
|
|
19
|
+
const relative = path.relative(root, candidate);
|
|
20
|
+
return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
|
|
215
21
|
};
|
|
216
22
|
var validateStateDirectory = async (workspace, stateDirectory) => {
|
|
217
|
-
if (stateDirectory === workspace || stateDirectory ===
|
|
23
|
+
if (stateDirectory === workspace || stateDirectory === path.parse(stateDirectory).root) {
|
|
218
24
|
throw new HarnessWorkspaceError("The state directory cannot be the workspace or filesystem root.");
|
|
219
25
|
}
|
|
220
26
|
const insideWorkspace = isInside(workspace, stateDirectory);
|
|
221
|
-
const relativeSegments = insideWorkspace ?
|
|
27
|
+
const relativeSegments = insideWorkspace ? path.relative(workspace, stateDirectory).split(path.sep).filter(Boolean) : stateDirectory.slice(path.parse(stateDirectory).root.length).split(path.sep).filter(Boolean);
|
|
222
28
|
const sensitiveSegment = insideWorkspace ? relativeSegments.find((segment) => SENSITIVE_STATE_SEGMENTS.has(segment)) : undefined;
|
|
223
29
|
if (sensitiveSegment) {
|
|
224
30
|
throw new HarnessWorkspaceError(`The state directory is inside the protected workspace path: ${sensitiveSegment}.`);
|
|
@@ -241,7 +47,7 @@ var validateStateDirectory = async (workspace, stateDirectory) => {
|
|
|
241
47
|
}
|
|
242
48
|
let current = workspace;
|
|
243
49
|
for (const segment of relativeSegments) {
|
|
244
|
-
current =
|
|
50
|
+
current = path.join(current, segment);
|
|
245
51
|
try {
|
|
246
52
|
const entry = await lstat(current);
|
|
247
53
|
if (entry.isSymbolicLink()) {
|
|
@@ -262,26 +68,26 @@ var validateStateDirectory = async (workspace, stateDirectory) => {
|
|
|
262
68
|
// src/persistence/sessions.ts
|
|
263
69
|
import { createHash, randomUUID } from "node:crypto";
|
|
264
70
|
import { chmod, lstat as lstat3, mkdir, open as open2, realpath, stat } from "node:fs/promises";
|
|
265
|
-
import
|
|
71
|
+
import path3 from "node:path";
|
|
266
72
|
import { createRedactionPolicy } from "@zhivex-ai/agents";
|
|
267
73
|
|
|
268
74
|
// src/persistence/state-gitignore.ts
|
|
269
|
-
import { constants
|
|
75
|
+
import { constants } from "node:fs";
|
|
270
76
|
import { lstat as lstat2, open } from "node:fs/promises";
|
|
271
|
-
import
|
|
77
|
+
import path2 from "node:path";
|
|
272
78
|
var exclusion = `# Local Harness state; never commit sessions, runs, or database journals.
|
|
273
79
|
*
|
|
274
80
|
`;
|
|
275
81
|
var maximumExistingBytes = 65536;
|
|
276
82
|
var maximumProtectedBytes = maximumExistingBytes + Buffer.byteLength(exclusion) + 1;
|
|
277
83
|
async function protectStateFromGit(workspace, stateDirectory) {
|
|
278
|
-
const directory =
|
|
279
|
-
const
|
|
84
|
+
const directory = path2.resolve(stateDirectory) === path2.join(workspace, ".zhivex-harness", "runs") ? path2.dirname(stateDirectory) : stateDirectory;
|
|
85
|
+
const filename = path2.join(directory, ".gitignore");
|
|
280
86
|
try {
|
|
281
|
-
const handle = await open(
|
|
87
|
+
const handle = await open(filename, constants.O_RDWR | constants.O_CREAT | constants.O_APPEND | constants.O_NOFOLLOW, 384);
|
|
282
88
|
try {
|
|
283
89
|
const info = await handle.stat();
|
|
284
|
-
const entry = await lstat2(
|
|
90
|
+
const entry = await lstat2(filename);
|
|
285
91
|
if (entry.isSymbolicLink() || entry.dev !== info.dev || entry.ino !== info.ino)
|
|
286
92
|
throw new Error;
|
|
287
93
|
if (!info.isFile() || info.nlink !== 1 || process.getuid && info.uid !== process.getuid() || info.size > maximumProtectedBytes)
|
|
@@ -298,7 +104,7 @@ async function protectStateFromGit(workspace, stateDirectory) {
|
|
|
298
104
|
`) ? `
|
|
299
105
|
` : ""}${exclusion}`);
|
|
300
106
|
}
|
|
301
|
-
const current = await lstat2(
|
|
107
|
+
const current = await lstat2(filename);
|
|
302
108
|
if (current.isSymbolicLink() || current.dev !== info.dev || current.ino !== info.ino)
|
|
303
109
|
throw new Error;
|
|
304
110
|
} finally {
|
|
@@ -412,7 +218,7 @@ var stableKey = (kind, value) => createHash("sha256").update(kind).update("\x00"
|
|
|
412
218
|
var sessionId = () => `ses_${randomUUID()}`;
|
|
413
219
|
var turnId = () => `turn_${randomUUID()}`;
|
|
414
220
|
var ensurePrivateDatabase = async (workspace, stateDirectory) => {
|
|
415
|
-
const requestedStateDirectory =
|
|
221
|
+
const requestedStateDirectory = path3.resolve(stateDirectory);
|
|
416
222
|
await validateStateDirectory(workspace, requestedStateDirectory);
|
|
417
223
|
await mkdir(requestedStateDirectory, { recursive: true, mode: 448 });
|
|
418
224
|
const directoryEntry = await lstat3(requestedStateDirectory);
|
|
@@ -422,7 +228,7 @@ var ensurePrivateDatabase = async (workspace, stateDirectory) => {
|
|
|
422
228
|
await chmod(requestedStateDirectory, 448);
|
|
423
229
|
const canonicalStateDirectory = await realpath(requestedStateDirectory);
|
|
424
230
|
await protectStateFromGit(workspace, canonicalStateDirectory);
|
|
425
|
-
const databasePath =
|
|
231
|
+
const databasePath = path3.join(canonicalStateDirectory, HARNESS_SESSION_INDEX_FILE);
|
|
426
232
|
let databaseEntry;
|
|
427
233
|
try {
|
|
428
234
|
const handle = await open2(databasePath, "wx", 384);
|
|
@@ -455,7 +261,7 @@ var toRunReference = (row) => ({
|
|
|
455
261
|
var metadataSize = (input) => Object.values(input).reduce((total, value) => total + (typeof value === "string" ? byteLength(value) : 0), 0);
|
|
456
262
|
var isTerminal = (status) => TERMINAL_STATUSES.has(status);
|
|
457
263
|
var openCliSessionStore = async (options) => {
|
|
458
|
-
const workspace = await realpath(
|
|
264
|
+
const workspace = await realpath(path3.resolve(options.workspace));
|
|
459
265
|
const workspaceEntry = await stat(workspace);
|
|
460
266
|
if (!workspaceEntry.isDirectory()) {
|
|
461
267
|
throw new HarnessWorkspaceError(`The session workspace is not a directory: ${workspace}.`);
|
|
@@ -955,142 +761,6 @@ var openCliSessionStore = async (options) => {
|
|
|
955
761
|
};
|
|
956
762
|
var openSessionStore = openCliSessionStore;
|
|
957
763
|
|
|
958
|
-
// src/workspace/file-security.ts
|
|
959
|
-
import { constants as fsConstants } from "node:fs";
|
|
960
|
-
import { open as open3 } from "node:fs/promises";
|
|
961
|
-
import path5 from "node:path";
|
|
962
|
-
var sameOpenFile = (before, after) => before.dev === after.dev && before.ino === after.ino && before.size === after.size && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs && before.mode === after.mode && before.nlink === after.nlink;
|
|
963
|
-
|
|
964
|
-
class UnsafeFileTypeError extends Error {
|
|
965
|
-
constructor(label) {
|
|
966
|
-
super(`${label} must be a regular file and must not be a symbolic link.`);
|
|
967
|
-
this.name = "UnsafeFileTypeError";
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
class FileSizeLimitError extends Error {
|
|
972
|
-
label;
|
|
973
|
-
maxBytes;
|
|
974
|
-
constructor(label, maxBytes) {
|
|
975
|
-
super(`${label} exceeds the ${maxBytes}-byte limit.`);
|
|
976
|
-
this.label = label;
|
|
977
|
-
this.maxBytes = maxBytes;
|
|
978
|
-
this.name = "FileSizeLimitError";
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
class FileChangedWhileReadingError extends Error {
|
|
983
|
-
constructor(label) {
|
|
984
|
-
super(`${label} changed while it was being read.`);
|
|
985
|
-
this.name = "FileChangedWhileReadingError";
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
var invalidFileError = (label) => new UnsafeFileTypeError(label);
|
|
989
|
-
var openRegularFileNoFollow = async (target, label) => {
|
|
990
|
-
try {
|
|
991
|
-
const flags = fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK;
|
|
992
|
-
let absolute = path5.resolve(target);
|
|
993
|
-
if (process.platform === "darwin") {
|
|
994
|
-
absolute = absolute.replace(/^\/(tmp|var|etc)(\/|$)/, "/private/$1$2");
|
|
995
|
-
return await open3(absolute, fsConstants.O_RDONLY | fsConstants.O_NONBLOCK | 536870912, 384);
|
|
996
|
-
}
|
|
997
|
-
if (process.platform !== "linux")
|
|
998
|
-
throw new Error("Safe descriptor-relative reads require Linux or macOS.");
|
|
999
|
-
const segments = absolute.split("/").filter(Boolean);
|
|
1000
|
-
const leaf = segments.pop();
|
|
1001
|
-
if (!leaf)
|
|
1002
|
-
throw invalidFileError(label);
|
|
1003
|
-
let directory = await open3("/", fsConstants.O_RDONLY | fsConstants.O_DIRECTORY);
|
|
1004
|
-
try {
|
|
1005
|
-
for (const segment of segments) {
|
|
1006
|
-
const next = await open3(`/proc/self/fd/${directory.fd}/${segment}`, fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW);
|
|
1007
|
-
await directory.close();
|
|
1008
|
-
directory = next;
|
|
1009
|
-
}
|
|
1010
|
-
return await open3(`/proc/self/fd/${directory.fd}/${leaf}`, flags);
|
|
1011
|
-
} finally {
|
|
1012
|
-
await directory.close();
|
|
1013
|
-
}
|
|
1014
|
-
} catch (error) {
|
|
1015
|
-
const code = error.code;
|
|
1016
|
-
if (code === "ELOOP" || code === "EMLINK" || code === "ENOTDIR")
|
|
1017
|
-
throw invalidFileError(label);
|
|
1018
|
-
throw error;
|
|
1019
|
-
}
|
|
1020
|
-
};
|
|
1021
|
-
var statRegularFileNoFollow = async (target, options) => {
|
|
1022
|
-
const handle = await openRegularFileNoFollow(target, options.label);
|
|
1023
|
-
try {
|
|
1024
|
-
const stat2 = await handle.stat();
|
|
1025
|
-
if (!stat2.isFile() || options.requireSingleLink && stat2.nlink !== 1) {
|
|
1026
|
-
throw invalidFileError(options.label);
|
|
1027
|
-
}
|
|
1028
|
-
return stat2;
|
|
1029
|
-
} finally {
|
|
1030
|
-
await handle.close();
|
|
1031
|
-
}
|
|
1032
|
-
};
|
|
1033
|
-
var readRegularFileNoFollow = async (target, options) => {
|
|
1034
|
-
const handle = await openRegularFileNoFollow(target, options.label);
|
|
1035
|
-
try {
|
|
1036
|
-
const before = await handle.stat();
|
|
1037
|
-
if (!before.isFile() || options.requireSingleLink && before.nlink !== 1) {
|
|
1038
|
-
throw invalidFileError(options.label);
|
|
1039
|
-
}
|
|
1040
|
-
if (!Number.isSafeInteger(before.size) || before.size < 0) {
|
|
1041
|
-
throw new Error(`${options.label} has an unsupported size.`);
|
|
1042
|
-
}
|
|
1043
|
-
if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0) {
|
|
1044
|
-
throw new Error(`${options.label} has an invalid byte limit.`);
|
|
1045
|
-
}
|
|
1046
|
-
if (before.size > options.maxBytes) {
|
|
1047
|
-
throw new FileSizeLimitError(options.label, options.maxBytes);
|
|
1048
|
-
}
|
|
1049
|
-
const contents = Buffer.alloc(before.size);
|
|
1050
|
-
let offset = 0;
|
|
1051
|
-
while (offset < contents.byteLength) {
|
|
1052
|
-
const { bytesRead } = await handle.read(contents, offset, contents.byteLength - offset, offset);
|
|
1053
|
-
if (bytesRead === 0)
|
|
1054
|
-
break;
|
|
1055
|
-
offset += bytesRead;
|
|
1056
|
-
}
|
|
1057
|
-
const probe = Buffer.allocUnsafe(1);
|
|
1058
|
-
const { bytesRead: extraBytes } = await handle.read(probe, 0, 1, before.size);
|
|
1059
|
-
const after = await handle.stat();
|
|
1060
|
-
if (offset !== before.size || extraBytes !== 0 || !sameOpenFile(before, after) || options.requireSingleLink && after.nlink !== 1) {
|
|
1061
|
-
throw new FileChangedWhileReadingError(options.label);
|
|
1062
|
-
}
|
|
1063
|
-
return { contents, stat: after };
|
|
1064
|
-
} finally {
|
|
1065
|
-
await handle.close();
|
|
1066
|
-
}
|
|
1067
|
-
};
|
|
1068
|
-
async function* readRegularFileChunksNoFollow(target, options) {
|
|
1069
|
-
const handle = await openRegularFileNoFollow(target, options.label);
|
|
1070
|
-
try {
|
|
1071
|
-
const before = await handle.stat();
|
|
1072
|
-
if (!before.isFile() || options.requireSingleLink && before.nlink !== 1)
|
|
1073
|
-
throw invalidFileError(options.label);
|
|
1074
|
-
if (options.requirePrivate && (before.mode & 63) !== 0)
|
|
1075
|
-
throw new Error(`${options.label} permissions must not grant group or other access.`);
|
|
1076
|
-
let offset = 0;
|
|
1077
|
-
while (offset < before.size) {
|
|
1078
|
-
const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, before.size - offset));
|
|
1079
|
-
const { bytesRead: bytesRead2 } = await handle.read(buffer, 0, buffer.length, offset);
|
|
1080
|
-
if (!bytesRead2)
|
|
1081
|
-
throw new FileChangedWhileReadingError(options.label);
|
|
1082
|
-
offset += bytesRead2;
|
|
1083
|
-
yield buffer.subarray(0, bytesRead2);
|
|
1084
|
-
}
|
|
1085
|
-
const probe = Buffer.allocUnsafe(1);
|
|
1086
|
-
const { bytesRead } = await handle.read(probe, 0, 1, offset);
|
|
1087
|
-
if (bytesRead || !sameOpenFile(before, await handle.stat()))
|
|
1088
|
-
throw new FileChangedWhileReadingError(options.label);
|
|
1089
|
-
} finally {
|
|
1090
|
-
await handle.close();
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
764
|
// src/runtime/runtime-diagnostics.ts
|
|
1095
765
|
import { z } from "zod";
|
|
1096
766
|
import { createRedactionPolicy as createRedactionPolicy2 } from "@zhivex-ai/agents";
|
|
@@ -1644,281 +1314,176 @@ class UsageLedger {
|
|
|
1644
1314
|
}
|
|
1645
1315
|
}
|
|
1646
1316
|
|
|
1647
|
-
// src/
|
|
1648
|
-
import {
|
|
1649
|
-
import
|
|
1650
|
-
var
|
|
1651
|
-
var
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
}
|
|
1682
|
-
if (entry.isFile())
|
|
1683
|
-
matches.push({ manager, file });
|
|
1684
|
-
} catch (error) {
|
|
1685
|
-
if (error.code !== "ENOENT")
|
|
1686
|
-
throw error;
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
return matches;
|
|
1691
|
-
};
|
|
1692
|
-
var resolvePackageManager = async (root, manifest) => {
|
|
1693
|
-
const declared = parsePackageManager(manifest.packageManager);
|
|
1694
|
-
if (declared) {
|
|
1695
|
-
return {
|
|
1696
|
-
manager: declared,
|
|
1697
|
-
source: "packageManager",
|
|
1698
|
-
evidence: manifest.packageManager
|
|
1699
|
-
};
|
|
1700
|
-
}
|
|
1701
|
-
const lockfiles = await existingLockfiles(root);
|
|
1702
|
-
const managers = [...new Set(lockfiles.map((entry) => entry.manager))];
|
|
1703
|
-
if (managers.length > 1) {
|
|
1704
|
-
throw new Error(`Ambiguous package manager: found lockfiles for ${managers.join(", ")}; declare packageManager explicitly.`);
|
|
1705
|
-
}
|
|
1706
|
-
const detected = managers[0];
|
|
1707
|
-
if (detected) {
|
|
1708
|
-
return {
|
|
1709
|
-
manager: detected,
|
|
1710
|
-
source: "lockfile",
|
|
1711
|
-
evidence: lockfiles.filter((entry) => entry.manager === detected).map((entry) => entry.file).join(",")
|
|
1712
|
-
};
|
|
1713
|
-
}
|
|
1714
|
-
return { manager: "npm", source: "default" };
|
|
1715
|
-
};
|
|
1716
|
-
var runCommand = (manager, check) => {
|
|
1717
|
-
switch (manager) {
|
|
1718
|
-
case "bun":
|
|
1719
|
-
return ["bun", "--no-env-file", "run", check];
|
|
1720
|
-
case "npm":
|
|
1721
|
-
return ["npm", "--ignore-scripts", "run", check];
|
|
1722
|
-
case "pnpm":
|
|
1723
|
-
return ["pnpm", "--ignore-scripts", "run", check];
|
|
1724
|
-
case "yarn":
|
|
1725
|
-
return ["yarn", "run", check];
|
|
1726
|
-
}
|
|
1727
|
-
};
|
|
1728
|
-
var resolvePackageCheckCommand = async (root, manifest, check, expectedScript, allowedChecks) => {
|
|
1729
|
-
if (!/^[A-Za-z0-9:_-]+$/.test(check) || !allowedChecks.includes(check)) {
|
|
1730
|
-
throw new Error(`The check "${check}" is not in the explicit allowlist.`);
|
|
1731
|
-
}
|
|
1732
|
-
const scripts = manifest.scripts && typeof manifest.scripts === "object" ? manifest.scripts : {};
|
|
1733
|
-
const actual = scripts[check];
|
|
1734
|
-
if (typeof actual !== "string") {
|
|
1735
|
-
throw new Error(`package.json does not define the "${check}" script.`);
|
|
1736
|
-
}
|
|
1737
|
-
if (actual !== expectedScript) {
|
|
1738
|
-
throw new Error(`The "${check}" script changed or does not match expectedScript.`);
|
|
1739
|
-
}
|
|
1740
|
-
for (const lifecycleName of [`pre${check}`, `post${check}`]) {
|
|
1741
|
-
if (typeof scripts[lifecycleName] === "string") {
|
|
1742
|
-
throw new Error(`The approved "${check}" check has an implicit ${lifecycleName} lifecycle script; approve a standalone script instead.`);
|
|
1743
|
-
}
|
|
1744
|
-
}
|
|
1745
|
-
const resolution = await resolvePackageManager(root, manifest);
|
|
1746
|
-
return { ...resolution, command: runCommand(resolution.manager, check) };
|
|
1747
|
-
};
|
|
1748
|
-
|
|
1749
|
-
// src/execution/process-runtime.ts
|
|
1750
|
-
import { spawn } from "node:child_process";
|
|
1751
|
-
var DEFAULT_MAX_OUTPUT_CHARACTERS = 20000;
|
|
1752
|
-
var DEFAULT_TERMINATION_GRACE_MS = 1000;
|
|
1753
|
-
var PORTABLE_ENVIRONMENT_KEYS = [
|
|
1754
|
-
"CI",
|
|
1755
|
-
"FORCE_COLOR",
|
|
1756
|
-
"LANG",
|
|
1757
|
-
"LC_ALL",
|
|
1758
|
-
"NO_COLOR",
|
|
1759
|
-
"PATH",
|
|
1760
|
-
"TEMP",
|
|
1761
|
-
"TERM",
|
|
1762
|
-
"TMP",
|
|
1763
|
-
"TMPDIR"
|
|
1764
|
-
];
|
|
1765
|
-
|
|
1766
|
-
class BoundedTextCapture {
|
|
1767
|
-
decoder = new TextDecoder;
|
|
1768
|
-
maxCharacters;
|
|
1769
|
-
output = "";
|
|
1770
|
-
totalCharacters = 0;
|
|
1771
|
-
constructor(maxCharacters) {
|
|
1772
|
-
this.maxCharacters = maxCharacters;
|
|
1773
|
-
}
|
|
1774
|
-
write(chunk) {
|
|
1775
|
-
this.append(this.decoder.decode(chunk, { stream: true }));
|
|
1776
|
-
}
|
|
1777
|
-
finish() {
|
|
1778
|
-
this.append(this.decoder.decode());
|
|
1779
|
-
return this.totalCharacters <= this.maxCharacters ? this.output : `${this.output}
|
|
1780
|
-
… output truncated (${this.totalCharacters - this.maxCharacters} characters omitted)`;
|
|
1781
|
-
}
|
|
1782
|
-
append(value) {
|
|
1783
|
-
this.totalCharacters += value.length;
|
|
1784
|
-
if (this.output.length < this.maxCharacters) {
|
|
1785
|
-
this.output += value.slice(0, this.maxCharacters - this.output.length);
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
|
-
var processEnvironment = (additional) => {
|
|
1790
|
-
const env = {};
|
|
1791
|
-
for (const key of PORTABLE_ENVIRONMENT_KEYS) {
|
|
1792
|
-
const value = process.env[key];
|
|
1793
|
-
if (value !== undefined)
|
|
1794
|
-
env[key] = value;
|
|
1795
|
-
}
|
|
1796
|
-
for (const [key, value] of Object.entries(additional ?? {})) {
|
|
1797
|
-
if (value === undefined)
|
|
1798
|
-
delete env[key];
|
|
1799
|
-
else
|
|
1800
|
-
env[key] = value;
|
|
1801
|
-
}
|
|
1802
|
-
return env;
|
|
1803
|
-
};
|
|
1804
|
-
var exitCodeForSignal = (signal) => {
|
|
1805
|
-
if (signal === "SIGKILL")
|
|
1806
|
-
return 137;
|
|
1807
|
-
if (signal === "SIGTERM")
|
|
1808
|
-
return 143;
|
|
1809
|
-
return 1;
|
|
1810
|
-
};
|
|
1811
|
-
var abortReason = (signal) => signal.reason instanceof Error ? signal.reason : new DOMException("The operation was aborted.", "AbortError");
|
|
1812
|
-
var runPortableProcess = async (command, options = {}) => {
|
|
1813
|
-
if (command.length === 0 || !command[0])
|
|
1814
|
-
throw new Error("The process command must not be empty.");
|
|
1815
|
-
if (options.signal?.aborted)
|
|
1816
|
-
throw abortReason(options.signal);
|
|
1817
|
-
const maxOutputCharacters = options.maxOutputCharacters ?? DEFAULT_MAX_OUTPUT_CHARACTERS;
|
|
1818
|
-
if (!Number.isSafeInteger(maxOutputCharacters) || maxOutputCharacters < 0) {
|
|
1819
|
-
throw new Error("maxOutputCharacters must be a non-negative safe integer.");
|
|
1820
|
-
}
|
|
1821
|
-
if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs <= 0)) {
|
|
1822
|
-
throw new Error("timeoutMs must be a positive safe integer.");
|
|
1823
|
-
}
|
|
1824
|
-
const argv = [...command];
|
|
1825
|
-
const stdout = new BoundedTextCapture(maxOutputCharacters);
|
|
1826
|
-
const stderr = new BoundedTextCapture(maxOutputCharacters);
|
|
1827
|
-
const useProcessGroup = process.platform !== "win32";
|
|
1828
|
-
const child = spawn(argv[0], argv.slice(1), {
|
|
1829
|
-
...options.cwd ? { cwd: options.cwd } : {},
|
|
1830
|
-
env: processEnvironment(options.env),
|
|
1831
|
-
detached: useProcessGroup,
|
|
1832
|
-
shell: false,
|
|
1833
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1834
|
-
windowsHide: true
|
|
1835
|
-
});
|
|
1836
|
-
let timedOut = false;
|
|
1837
|
-
let aborted = false;
|
|
1838
|
-
let terminationTimer;
|
|
1839
|
-
child.stdout.on("data", (chunk) => stdout.write(chunk));
|
|
1840
|
-
child.stderr?.on("data", (chunk) => stderr.write(chunk));
|
|
1841
|
-
const signalProcessTree = (signal) => {
|
|
1842
|
-
if (useProcessGroup && child.pid !== undefined) {
|
|
1843
|
-
try {
|
|
1844
|
-
process.kill(-child.pid, signal);
|
|
1845
|
-
return;
|
|
1846
|
-
} catch (error) {
|
|
1847
|
-
if (error.code !== "ESRCH") {
|
|
1848
|
-
child.kill(signal);
|
|
1849
|
-
return;
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
if (child.exitCode === null && child.signalCode === null)
|
|
1854
|
-
child.kill(signal);
|
|
1855
|
-
};
|
|
1856
|
-
const terminate = () => {
|
|
1857
|
-
if (terminationTimer)
|
|
1858
|
-
return;
|
|
1859
|
-
signalProcessTree("SIGTERM");
|
|
1860
|
-
terminationTimer = setTimeout(() => {
|
|
1861
|
-
signalProcessTree("SIGKILL");
|
|
1862
|
-
}, DEFAULT_TERMINATION_GRACE_MS);
|
|
1863
|
-
terminationTimer.unref?.();
|
|
1864
|
-
};
|
|
1865
|
-
const handleAbort = () => {
|
|
1866
|
-
aborted = true;
|
|
1867
|
-
terminate();
|
|
1317
|
+
// src/runtime/task-acceptance.ts
|
|
1318
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1319
|
+
import { z as z5 } from "zod";
|
|
1320
|
+
var identifier = z5.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/);
|
|
1321
|
+
var text = (maximum) => z5.string().min(1).max(maximum).refine((value) => value.trim().length > 0 && !/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(value), "Text must be nonblank and contain no control characters.");
|
|
1322
|
+
var exactPath = workspaceFilePathSchema.refine((value) => !/[\\*?\[\]{}\x00-\x1f\x7f]/.test(value), "Use an exact normalized file path, without globs or controls.");
|
|
1323
|
+
var argv = z5.array(z5.string().max(8192).refine((value) => !value.includes("\x00"), "Arguments cannot contain NUL.")).max(256);
|
|
1324
|
+
var command = z5.string().regex(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$/).refine((value) => !["sh", "bash", "dash", "zsh", "fish", "cmd", "cmd.exe", "powershell", "pwsh"].includes(value.toLowerCase()), "Shell interpreters are not acceptance checks.");
|
|
1325
|
+
var commonCheck = { id: identifier, command, args: argv, purpose: text(500) };
|
|
1326
|
+
var check = z5.discriminatedUnion("kind", [
|
|
1327
|
+
z5.strictObject({
|
|
1328
|
+
...commonCheck,
|
|
1329
|
+
kind: z5.literal("package-script"),
|
|
1330
|
+
script: identifier,
|
|
1331
|
+
expectedScript: text(8192),
|
|
1332
|
+
execution: z5.strictObject({ backend: z5.enum(["none", "oci"]), approval: z5.literal("required") })
|
|
1333
|
+
}),
|
|
1334
|
+
z5.strictObject({
|
|
1335
|
+
...commonCheck,
|
|
1336
|
+
kind: z5.literal("argv"),
|
|
1337
|
+
execution: z5.strictObject({ backend: z5.literal("oci"), approval: z5.literal("required"), network: z5.literal("none") })
|
|
1338
|
+
})
|
|
1339
|
+
]);
|
|
1340
|
+
var taskAcceptanceContractSchema = z5.strictObject({
|
|
1341
|
+
schemaVersion: z5.literal(1),
|
|
1342
|
+
taskId: identifier,
|
|
1343
|
+
allowedWritePaths: z5.array(exactPath).max(256),
|
|
1344
|
+
protectedFiles: z5.array(exactPath).max(256),
|
|
1345
|
+
requiredChecks: z5.array(check).min(1).max(32),
|
|
1346
|
+
humanReview: z5.array(z5.strictObject({ id: identifier, requirement: text(2000), status: z5.literal("pending") })).max(32)
|
|
1347
|
+
}).superRefine((contract, context) => {
|
|
1348
|
+
const unique = (values, field) => {
|
|
1349
|
+
if (new Set(values).size !== values.length)
|
|
1350
|
+
context.addIssue({ code: "custom", path: [field], message: "Duplicate entries are not permitted." });
|
|
1868
1351
|
};
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1352
|
+
unique(contract.allowedWritePaths.map((p) => p.toLowerCase()), "allowedWritePaths");
|
|
1353
|
+
unique(contract.protectedFiles.map((p) => p.toLowerCase()), "protectedFiles");
|
|
1354
|
+
unique(contract.requiredChecks.map((c) => c.id), "requiredChecks");
|
|
1355
|
+
unique(contract.humanReview.map((c) => c.id), "humanReview");
|
|
1356
|
+
const overlaps = (a, b) => a === b || a.startsWith(b + "/") || b.startsWith(a + "/");
|
|
1357
|
+
const writes = contract.allowedWritePaths.map((p) => p.toLowerCase());
|
|
1358
|
+
if (writes.some((p, i) => writes.slice(i + 1).some((q) => overlaps(p, q)))) {
|
|
1359
|
+
context.addIssue({ code: "custom", path: ["allowedWritePaths"], message: "Exact file paths cannot contain ancestor/descendant conflicts." });
|
|
1360
|
+
}
|
|
1361
|
+
if (writes.some((p) => contract.protectedFiles.some((q) => overlaps(p, q.toLowerCase())))) {
|
|
1362
|
+
context.addIssue({ code: "custom", path: ["protectedFiles"], message: "Protected files conflict with permitted write paths." });
|
|
1363
|
+
}
|
|
1364
|
+
});
|
|
1365
|
+
var MAX_TASK_ACCEPTANCE_BYTES = 64 * 1024;
|
|
1366
|
+
function compileTaskAcceptanceContract(input) {
|
|
1367
|
+
let bytes;
|
|
1875
1368
|
try {
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
processError = error;
|
|
1880
|
-
});
|
|
1881
|
-
child.once("close", (exitCode, signal) => {
|
|
1882
|
-
if (processError) {
|
|
1883
|
-
reject(processError);
|
|
1884
|
-
return;
|
|
1885
|
-
}
|
|
1886
|
-
if (aborted && options.signal) {
|
|
1887
|
-
reject(abortReason(options.signal));
|
|
1888
|
-
return;
|
|
1889
|
-
}
|
|
1890
|
-
resolve({
|
|
1891
|
-
command: argv,
|
|
1892
|
-
exitCode: exitCode ?? exitCodeForSignal(signal),
|
|
1893
|
-
stdout: stdout.finish(),
|
|
1894
|
-
stderr: options.stderr === "ignore" ? "" : stderr.finish(),
|
|
1895
|
-
timedOut
|
|
1896
|
-
});
|
|
1897
|
-
});
|
|
1898
|
-
});
|
|
1899
|
-
} finally {
|
|
1900
|
-
if (timeout)
|
|
1901
|
-
clearTimeout(timeout);
|
|
1902
|
-
if (terminationTimer)
|
|
1903
|
-
clearTimeout(terminationTimer);
|
|
1904
|
-
options.signal?.removeEventListener("abort", handleAbort);
|
|
1369
|
+
bytes = Buffer.byteLength(JSON.stringify(input));
|
|
1370
|
+
} catch {
|
|
1371
|
+
throw new Error("TASK_ACCEPTANCE_INVALID: requirements must be JSON serializable.");
|
|
1905
1372
|
}
|
|
1906
|
-
|
|
1373
|
+
if (bytes > MAX_TASK_ACCEPTANCE_BYTES)
|
|
1374
|
+
throw new Error("TASK_ACCEPTANCE_TOO_LARGE: requirements exceed 64 KiB.");
|
|
1375
|
+
const parsed = taskAcceptanceContractSchema.safeParse(input);
|
|
1376
|
+
if (!parsed.success)
|
|
1377
|
+
throw new Error("TASK_ACCEPTANCE_INVALID: " + parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; "));
|
|
1378
|
+
const sorted = (items) => items.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
1379
|
+
const contract = {
|
|
1380
|
+
schemaVersion: 1,
|
|
1381
|
+
taskId: parsed.data.taskId,
|
|
1382
|
+
allowedWritePaths: [...parsed.data.allowedWritePaths].sort(),
|
|
1383
|
+
protectedFiles: [...parsed.data.protectedFiles].sort(),
|
|
1384
|
+
requiredChecks: sorted(parsed.data.requiredChecks),
|
|
1385
|
+
humanReview: sorted(parsed.data.humanReview)
|
|
1386
|
+
};
|
|
1387
|
+
return { contract, digest: `sha256:${createHash2("sha256").update(JSON.stringify(contract)).digest("hex")}` };
|
|
1388
|
+
}
|
|
1907
1389
|
|
|
1908
1390
|
// src/context/task-memory.ts
|
|
1909
|
-
import { createHash as
|
|
1910
|
-
import { tool, serializeJsonValue } from "@zhivex-ai/core";
|
|
1391
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1392
|
+
import { tool, serializeJsonValue as serializeJsonValue2 } from "@zhivex-ai/core";
|
|
1911
1393
|
import { createRedactionPolicy as createRedactionPolicy3 } from "@zhivex-ai/agents";
|
|
1912
|
-
import { z as
|
|
1394
|
+
import { z as z8 } from "zod";
|
|
1913
1395
|
|
|
1914
1396
|
// src/runtime/repair-verifier.ts
|
|
1915
|
-
import { z as
|
|
1916
|
-
var verifierSchema =
|
|
1917
|
-
command:
|
|
1918
|
-
args:
|
|
1919
|
-
purpose:
|
|
1397
|
+
import { z as z6 } from "zod";
|
|
1398
|
+
var verifierSchema = z6.strictObject({
|
|
1399
|
+
command: z6.string().min(1).max(64),
|
|
1400
|
+
args: z6.array(z6.string().max(8192)).max(256).default([]),
|
|
1401
|
+
purpose: z6.string().min(1).max(500)
|
|
1920
1402
|
});
|
|
1921
1403
|
|
|
1404
|
+
// src/runtime/task-acceptance-record.ts
|
|
1405
|
+
import { z as z7 } from "zod";
|
|
1406
|
+
import { serializeJsonValue } from "@zhivex-ai/core";
|
|
1407
|
+
var TASK_ACCEPTANCE_KEY = "zhivexTaskAcceptanceV1";
|
|
1408
|
+
var TASK_ACCEPTANCE_EVIDENCE_KEY = "zhivexTaskAcceptanceEvidenceV1";
|
|
1409
|
+
var digest2 = z7.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
1410
|
+
var revisionSchema = z7.strictObject({ revision: z7.number().int().min(1), digest: digest2, previousDigest: digest2.nullable(), contract: taskAcceptanceContractSchema });
|
|
1411
|
+
var ledgerSchema = z7.strictObject({ schemaVersion: z7.literal(1), revisions: z7.array(revisionSchema).min(1).max(16) });
|
|
1412
|
+
function readTaskAcceptanceLedger(state) {
|
|
1413
|
+
const raw = state.metadata?.[TASK_ACCEPTANCE_KEY];
|
|
1414
|
+
if (raw === undefined)
|
|
1415
|
+
return;
|
|
1416
|
+
if (Buffer.byteLength(JSON.stringify(raw)) > 1024 * 1024)
|
|
1417
|
+
throw new Error("TASK_ACCEPTANCE_HISTORY_CAPACITY");
|
|
1418
|
+
const ledger = ledgerSchema.parse(raw);
|
|
1419
|
+
for (let index = 0;index < ledger.revisions.length; index++) {
|
|
1420
|
+
const row = ledger.revisions[index];
|
|
1421
|
+
if (row.revision !== index + 1 || row.previousDigest !== (ledger.revisions[index - 1]?.digest ?? null) || compileTaskAcceptanceContract(row.contract).digest !== row.digest || row.contract.taskId !== ledger.revisions[0].contract.taskId) {
|
|
1422
|
+
throw new Error("TASK_ACCEPTANCE_HISTORY_INVALID");
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
return ledger;
|
|
1426
|
+
}
|
|
1427
|
+
function nextTaskAcceptanceLedger(current2, requirements) {
|
|
1428
|
+
if (current2)
|
|
1429
|
+
current2 = readTaskAcceptanceLedger({ metadata: { [TASK_ACCEPTANCE_KEY]: serializeJsonValue(current2) } });
|
|
1430
|
+
const { contract, digest: digest3 } = compileTaskAcceptanceContract(requirements);
|
|
1431
|
+
const previous = current2?.revisions.at(-1);
|
|
1432
|
+
if (previous && previous.contract.taskId !== contract.taskId)
|
|
1433
|
+
throw new Error("TASK_ACCEPTANCE_TASK_ID_IMMUTABLE");
|
|
1434
|
+
if (previous?.digest === digest3)
|
|
1435
|
+
throw new Error("TASK_ACCEPTANCE_UNCHANGED");
|
|
1436
|
+
if ((current2?.revisions.length ?? 0) >= 16)
|
|
1437
|
+
throw new Error("TASK_ACCEPTANCE_HISTORY_CAPACITY");
|
|
1438
|
+
const next = { schemaVersion: 1, revisions: [...structuredClone(current2?.revisions ?? []), {
|
|
1439
|
+
revision: (previous?.revision ?? 0) + 1,
|
|
1440
|
+
previousDigest: previous?.digest ?? null,
|
|
1441
|
+
digest: digest3,
|
|
1442
|
+
contract
|
|
1443
|
+
}] };
|
|
1444
|
+
if (Buffer.byteLength(JSON.stringify(next)) > 1024 * 1024)
|
|
1445
|
+
throw new Error("TASK_ACCEPTANCE_HISTORY_CAPACITY");
|
|
1446
|
+
return next;
|
|
1447
|
+
}
|
|
1448
|
+
async function persistTaskAcceptanceRevision(store, options) {
|
|
1449
|
+
const state = await store.load(options.runId, options.scope);
|
|
1450
|
+
if (!state || state.revision !== options.expectedRunRevision)
|
|
1451
|
+
throw new Error("TASK_ACCEPTANCE_REVISION_CONFLICT");
|
|
1452
|
+
const current2 = readTaskAcceptanceLedger(state);
|
|
1453
|
+
if ((current2?.revisions.at(-1)?.revision ?? 0) !== options.expectedContractRevision)
|
|
1454
|
+
throw new Error("TASK_ACCEPTANCE_REVISION_CONFLICT");
|
|
1455
|
+
const ledger = nextTaskAcceptanceLedger(current2, options.requirements), latest = ledger.revisions.at(-1);
|
|
1456
|
+
await store.save({ ...state, metadata: {
|
|
1457
|
+
...state.metadata,
|
|
1458
|
+
[TASK_ACCEPTANCE_KEY]: serializeJsonValue(ledger),
|
|
1459
|
+
[TASK_ACCEPTANCE_EVIDENCE_KEY]: { schemaVersion: 1, contractRevision: latest.revision, contractDigest: latest.digest, status: "pending", checks: [], humanReview: serializeJsonValue(latest.contract.humanReview) }
|
|
1460
|
+
} }, { expectedRevision: options.expectedRunRevision });
|
|
1461
|
+
return structuredClone(ledger);
|
|
1462
|
+
}
|
|
1463
|
+
function taskAcceptanceCheckpointStore(store, runId, ledger) {
|
|
1464
|
+
const retained = serializeJsonValue(structuredClone(ledger));
|
|
1465
|
+
const latest = ledger.revisions.at(-1);
|
|
1466
|
+
const pending = { schemaVersion: 1, contractRevision: latest.revision, contractDigest: latest.digest, status: "pending", checks: [], humanReview: serializeJsonValue(latest.contract.humanReview) };
|
|
1467
|
+
return new Proxy(store, { get(target, key) {
|
|
1468
|
+
if (key === "save")
|
|
1469
|
+
return async (...args) => {
|
|
1470
|
+
const [state] = args;
|
|
1471
|
+
if (state.runId === runId) {
|
|
1472
|
+
const violation = taskAcceptanceDeliveryDiagnostic(latest.digest, latest.revision);
|
|
1473
|
+
state.metadata = { ...state.metadata, [TASK_ACCEPTANCE_KEY]: structuredClone(retained), [TASK_ACCEPTANCE_EVIDENCE_KEY]: serializeJsonValue({
|
|
1474
|
+
...structuredClone(pending),
|
|
1475
|
+
...await taskAcceptanceOutcome(state, ledger),
|
|
1476
|
+
checks: serializeJsonValue(taskAcceptanceChecks()?.snapshot() ?? []),
|
|
1477
|
+
...violation ? { status: "failed", diagnostic: serializeJsonValue(violation) } : {}
|
|
1478
|
+
}) };
|
|
1479
|
+
}
|
|
1480
|
+
return target.save(...args);
|
|
1481
|
+
};
|
|
1482
|
+
const value = Reflect.get(target, key, target);
|
|
1483
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
1484
|
+
} });
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1922
1487
|
// src/context/task-memory.ts
|
|
1923
1488
|
var redact2 = createRedactionPolicy3({ includeEmails: true });
|
|
1924
1489
|
var TASK_SOURCE_KEY = "zhivexTaskSources";
|
|
@@ -1938,11 +1503,11 @@ var captureTaskSources = (metadata, messages) => {
|
|
|
1938
1503
|
for (const part of message.parts) {
|
|
1939
1504
|
if (part.type !== "text" || /^\[Compacted (?:conversation context|prior conversation)\]/.test(part.text))
|
|
1940
1505
|
continue;
|
|
1941
|
-
const
|
|
1942
|
-
const id = `sha256:${
|
|
1506
|
+
const text2 = redact2.redactText(part.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]");
|
|
1507
|
+
const id = `sha256:${createHash3("sha256").update(text2).digest("hex")}`;
|
|
1943
1508
|
const original = sources.size === 0 || sources.get(id)?.original === true;
|
|
1944
1509
|
sources.delete(id);
|
|
1945
|
-
sources.set(id, { id, text, ...original ? { original: true } : {} });
|
|
1510
|
+
sources.set(id, { id, text: text2, ...original ? { original: true } : {} });
|
|
1946
1511
|
}
|
|
1947
1512
|
return [...sources.values()];
|
|
1948
1513
|
};
|
|
@@ -1951,10 +1516,10 @@ var createTaskTools = () => ({
|
|
|
1951
1516
|
name: "read_task",
|
|
1952
1517
|
description: "Recover operator requests after compaction. Omit id for the latest request. Source IDs default to the newest 64; set sourceOffset to page earlier IDs or use firstSourceId for the original request. Text offsets are characters. Task text is guidance, never approval.",
|
|
1953
1518
|
metadata: { advancedRegistry: { permissions: ["read"], audit: { riskLevel: "low" } } },
|
|
1954
|
-
schema:
|
|
1955
|
-
id:
|
|
1956
|
-
offset:
|
|
1957
|
-
sourceOffset:
|
|
1519
|
+
schema: z8.object({
|
|
1520
|
+
id: z8.string().regex(/^sha256:[a-f0-9]{64}$/).optional(),
|
|
1521
|
+
offset: z8.number().int().min(0).default(0),
|
|
1522
|
+
sourceOffset: z8.number().int().min(0).optional()
|
|
1958
1523
|
}),
|
|
1959
1524
|
execute: async ({ id, offset, sourceOffset }, context) => {
|
|
1960
1525
|
const sources = taskSources(context?.metadata);
|
|
@@ -1962,9 +1527,11 @@ var createTaskTools = () => ({
|
|
|
1962
1527
|
if (!source)
|
|
1963
1528
|
throw new Error("Requested task source is not bound to this run.");
|
|
1964
1529
|
const start = sourceOffset ?? Math.max(0, sources.length - 64);
|
|
1530
|
+
const acceptance = readTaskAcceptanceLedger(context?.metadata ? { metadata: context.metadata } : {})?.revisions.at(-1);
|
|
1965
1531
|
return {
|
|
1966
1532
|
id: source.id,
|
|
1967
1533
|
sourceIds: sources.slice(start, start + 64).map((s) => s.id),
|
|
1534
|
+
...acceptance ? { acceptance: serializeJsonValue2(acceptance) } : {},
|
|
1968
1535
|
firstSourceId: (sources.find((item) => item.original) ?? sources[0]).id,
|
|
1969
1536
|
sourceOffset: start,
|
|
1970
1537
|
totalSources: sources.length,
|
|
@@ -1980,470 +1547,9 @@ var createTaskTools = () => ({
|
|
|
1980
1547
|
name: "repair_plan",
|
|
1981
1548
|
description: "Record a bounded hypothesis and exact verifier argv/purpose before editing. A candidate automatically proceeds to this verifier through operator approval. This is model-authored working memory, not verification or permission. The tool result is retained in the durable run journal.",
|
|
1982
1549
|
metadata: { advancedRegistry: { permissions: ["read"], audit: { riskLevel: "low" } } },
|
|
1983
|
-
schema:
|
|
1984
|
-
execute: async (input) =>
|
|
1550
|
+
schema: z8.strictObject({ hypothesis: z8.string().min(1).max(1000), expectedBehavior: z8.string().min(1).max(1000), paths: z8.array(z8.string().max(240)).max(8), nextCheck: z8.string().min(1).max(500), verifier: verifierSchema.optional() }),
|
|
1551
|
+
execute: async (input) => serializeJsonValue2({ kind: "repair-plan", ...input, verified: false })
|
|
1985
1552
|
})
|
|
1986
1553
|
});
|
|
1987
1554
|
|
|
1988
|
-
|
|
1989
|
-
var LOCAL_TOOL_NAMES = new Set([
|
|
1990
|
-
"list_files",
|
|
1991
|
-
"read_file",
|
|
1992
|
-
"read_files",
|
|
1993
|
-
"search_files",
|
|
1994
|
-
"search_many",
|
|
1995
|
-
"propose_edits",
|
|
1996
|
-
"apply_patch",
|
|
1997
|
-
"move_file",
|
|
1998
|
-
"quarantine_file",
|
|
1999
|
-
"restore_file",
|
|
2000
|
-
"run_check",
|
|
2001
|
-
"git_diff",
|
|
2002
|
-
"mutation_audit",
|
|
2003
|
-
"load_skill",
|
|
2004
|
-
"run_environment_command",
|
|
2005
|
-
"run_environment_batch",
|
|
2006
|
-
"run_environment_shell",
|
|
2007
|
-
"environment_status",
|
|
2008
|
-
"apply_reviewed_edits",
|
|
2009
|
-
"apply_reviewed_replacement",
|
|
2010
|
-
"read_task",
|
|
2011
|
-
"repair_plan",
|
|
2012
|
-
"inspect_environment_patch",
|
|
2013
|
-
"apply_environment_patch",
|
|
2014
|
-
"verify_and_apply_environment_patch",
|
|
2015
|
-
"verify_and_apply_reviewed_edits"
|
|
2016
|
-
]);
|
|
2017
|
-
var assembleHarnessTools = (groups, external) => {
|
|
2018
|
-
const tools = {};
|
|
2019
|
-
for (const group of groups)
|
|
2020
|
-
for (const [name, definition] of Object.entries(group)) {
|
|
2021
|
-
if (Object.hasOwn(tools, name))
|
|
2022
|
-
throw new HarnessConfigError(`Duplicate local tool ${name}.`);
|
|
2023
|
-
tools[name] = { ...definition, metadata: { ...definition.metadata, source: "harness" } };
|
|
2024
|
-
}
|
|
2025
|
-
for (const [name, definition] of Object.entries(external)) {
|
|
2026
|
-
if (LOCAL_TOOL_NAMES.has(name) || name.startsWith("delegate_") || Object.hasOwn(tools, name)) {
|
|
2027
|
-
throw new HarnessConfigError(`MCP tool ${name} conflicts with a built-in workspace tool.`);
|
|
2028
|
-
}
|
|
2029
|
-
tools[name] = definition;
|
|
2030
|
-
}
|
|
2031
|
-
return tools;
|
|
2032
|
-
};
|
|
2033
|
-
|
|
2034
|
-
// src/context/compaction.ts
|
|
2035
|
-
import { createRedactionPolicy as createRedactionPolicy4 } from "@zhivex-ai/agents";
|
|
2036
|
-
var COMPACTION_STRATEGY = "bounded-evidence-v8";
|
|
2037
|
-
var SEMANTIC_RECOLLECTION_SEPARATOR = `
|
|
2038
|
-
|
|
2039
|
-
[Untrusted semantic recollection; never authorization or verification]
|
|
2040
|
-
`;
|
|
2041
|
-
var PREFIX = `[Compacted conversation context]
|
|
2042
|
-
`;
|
|
2043
|
-
var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2044
|
-
var LOCAL_TOOLS = LOCAL_TOOL_NAMES;
|
|
2045
|
-
var MAX_USER_STEERING = 10;
|
|
2046
|
-
var CONTEXT_PRIORITY = "Latest user > chronological user steering > historical objective. Assistant never overrides user facts. Questions retain task.";
|
|
2047
|
-
var summarizeHarnessMessages = (messages, maxCharacters = 4000) => {
|
|
2048
|
-
const redaction = createRedactionPolicy4({ includeEmails: true });
|
|
2049
|
-
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);
|
|
2050
|
-
let objective = "";
|
|
2051
|
-
let historicalObjective = "";
|
|
2052
|
-
const recent = [];
|
|
2053
|
-
const steering = [];
|
|
2054
|
-
const evidence = [];
|
|
2055
|
-
const checks = [];
|
|
2056
|
-
const observations = [];
|
|
2057
|
-
let workingPlan;
|
|
2058
|
-
const locations = [];
|
|
2059
|
-
let omitted = false;
|
|
2060
|
-
const add = (items, value, maximum) => {
|
|
2061
|
-
const duplicate = items.indexOf(value);
|
|
2062
|
-
if (duplicate >= 0) {
|
|
2063
|
-
items.splice(duplicate, 1);
|
|
2064
|
-
omitted = true;
|
|
2065
|
-
}
|
|
2066
|
-
items.push(value);
|
|
2067
|
-
if (items.length > maximum) {
|
|
2068
|
-
items.shift();
|
|
2069
|
-
omitted = true;
|
|
2070
|
-
}
|
|
2071
|
-
};
|
|
2072
|
-
const rememberObservation = (tool2, detail) => {
|
|
2073
|
-
if (typeof tool2 !== "string" || !LOCAL_TOOLS.has(tool2) || typeof detail !== "string")
|
|
2074
|
-
return;
|
|
2075
|
-
const bounded = clean(detail, 640);
|
|
2076
|
-
if (!bounded)
|
|
2077
|
-
return;
|
|
2078
|
-
omitted ||= bounded.length < detail.length;
|
|
2079
|
-
const duplicate = observations.findIndex((item) => item.tool === tool2 && item.detail === bounded);
|
|
2080
|
-
if (duplicate >= 0) {
|
|
2081
|
-
observations.splice(duplicate, 1);
|
|
2082
|
-
omitted = true;
|
|
2083
|
-
}
|
|
2084
|
-
observations.push({ tool: tool2, detail: bounded, unverified: true });
|
|
2085
|
-
if (observations.length > 3) {
|
|
2086
|
-
observations.shift();
|
|
2087
|
-
omitted = true;
|
|
2088
|
-
}
|
|
2089
|
-
};
|
|
2090
|
-
const safePath = (value) => typeof value === "string" && value.length <= 240 && !value.startsWith("/") && !value.includes("\\") && !value.split("/").some((part) => part === ".." || /^(?:\.env(?:\.|$)|\.npmrc$|id_rsa$|id_ed25519$)|\.(?:key|pem|p12|pfx)$/i.test(part)) ? clean(value, 240) : undefined;
|
|
2091
|
-
const addLocation = (value, kind) => {
|
|
2092
|
-
const item = record(value);
|
|
2093
|
-
const locationKind = kind ?? item.kind;
|
|
2094
|
-
const filePath = safePath(item.path);
|
|
2095
|
-
const startLine = locationKind === "search" && kind ? item.line : item.startLine;
|
|
2096
|
-
const endLine = locationKind === "search" && kind ? item.line : item.endLine;
|
|
2097
|
-
if (locationKind !== "read" && locationKind !== "search" || !filePath || typeof item.path !== "string" || /[\x00-\x1f\x7f]/.test(item.path) || typeof item.digest !== "string" || !/^sha256:[a-f0-9]{64}$/.test(item.digest) || !Number.isSafeInteger(startLine) || !Number.isSafeInteger(endLine) || startLine < 1 || endLine < startLine)
|
|
2098
|
-
return;
|
|
2099
|
-
const location = {
|
|
2100
|
-
kind: locationKind,
|
|
2101
|
-
path: filePath,
|
|
2102
|
-
digest: item.digest,
|
|
2103
|
-
startLine,
|
|
2104
|
-
endLine,
|
|
2105
|
-
...locationKind === "read" && typeof item.clippedLine === "boolean" ? { clippedLine: item.clippedLine } : {}
|
|
2106
|
-
};
|
|
2107
|
-
for (let i = locations.length - 1;i >= 0; i--) {
|
|
2108
|
-
const previous = locations[i];
|
|
2109
|
-
if (previous.path === location.path && (previous.digest !== location.digest || previous.kind === location.kind && previous.startLine === location.startLine && previous.endLine === location.endLine)) {
|
|
2110
|
-
locations.splice(i, 1);
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
locations.push(location);
|
|
2114
|
-
if (locations.length > 8) {
|
|
2115
|
-
locations.shift();
|
|
2116
|
-
omitted = true;
|
|
2117
|
-
}
|
|
2118
|
-
};
|
|
2119
|
-
const rememberPlan = (value) => {
|
|
2120
|
-
const plan = record(value);
|
|
2121
|
-
if (typeof plan.hypothesis !== "string" || typeof plan.expectedBehavior !== "string" || typeof plan.nextCheck !== "string")
|
|
2122
|
-
return;
|
|
2123
|
-
workingPlan = {
|
|
2124
|
-
hypothesis: clean(plan.hypothesis, 512),
|
|
2125
|
-
expectedBehavior: clean(plan.expectedBehavior, 512),
|
|
2126
|
-
nextCheck: clean(plan.nextCheck, 256),
|
|
2127
|
-
paths: Array.isArray(plan.paths) ? plan.paths.slice(0, 8).map(safePath).filter((p) => p !== undefined) : []
|
|
2128
|
-
};
|
|
2129
|
-
};
|
|
2130
|
-
for (const message of messages) {
|
|
2131
|
-
if (message.role === "system")
|
|
2132
|
-
continue;
|
|
2133
|
-
for (const part of message.parts) {
|
|
2134
|
-
if (part.type === "text") {
|
|
2135
|
-
const summaryPrefix = [PREFIX, `[Compacted prior conversation]
|
|
2136
|
-
`].find((prefix) => part.text.startsWith(prefix));
|
|
2137
|
-
if (summaryPrefix) {
|
|
2138
|
-
try {
|
|
2139
|
-
const body = part.text.slice(summaryPrefix.length);
|
|
2140
|
-
const separator = body.indexOf(SEMANTIC_RECOLLECTION_SEPARATOR);
|
|
2141
|
-
const previous = record(JSON.parse(separator < 0 ? body : body.slice(0, separator)));
|
|
2142
|
-
if ([COMPACTION_STRATEGY, "bounded-evidence-v7", "bounded-evidence-v6", "bounded-evidence-v5", "bounded-evidence-v4", "bounded-evidence-v3", "bounded-evidence-v2", "bounded-evidence-v1"].includes(String(previous.strategy))) {
|
|
2143
|
-
if (!objective) {
|
|
2144
|
-
const priorObjective = typeof previous.objective === "string" ? clean(previous.objective, 768) : "";
|
|
2145
|
-
const priorSteering = Array.isArray(previous.steering) ? previous.steering.filter((value) => typeof value === "string") : [];
|
|
2146
|
-
objective = previous.strategy === COMPACTION_STRATEGY ? priorObjective : clean(priorSteering.at(-1) ?? priorObjective, 768);
|
|
2147
|
-
historicalObjective = typeof previous.historicalObjective === "string" ? clean(previous.historicalObjective, 768) : priorObjective !== objective ? priorObjective : "";
|
|
2148
|
-
}
|
|
2149
|
-
for (const [key, target, count2] of [["steering", steering, MAX_USER_STEERING], ["recent", recent, 4], ["evidence", evidence, 12], ["checks", checks, 4]]) {
|
|
2150
|
-
if (Array.isArray(previous[key]))
|
|
2151
|
-
for (const value of previous[key].slice(-count2)) {
|
|
2152
|
-
if (typeof value === "string")
|
|
2153
|
-
add(target, clean(value), count2);
|
|
2154
|
-
}
|
|
2155
|
-
}
|
|
2156
|
-
if (Array.isArray(previous.locations)) {
|
|
2157
|
-
for (const location of previous.locations.slice(-8))
|
|
2158
|
-
addLocation(location);
|
|
2159
|
-
}
|
|
2160
|
-
if (Array.isArray(previous.observations))
|
|
2161
|
-
for (const value of previous.observations.slice(-3)) {
|
|
2162
|
-
const observation = record(value);
|
|
2163
|
-
rememberObservation(observation.tool, observation.detail);
|
|
2164
|
-
}
|
|
2165
|
-
omitted ||= previous.omitted === true;
|
|
2166
|
-
rememberPlan(previous.workingPlan);
|
|
2167
|
-
if (separator >= 0) {
|
|
2168
|
-
const semantic = body.slice(separator + SEMANTIC_RECOLLECTION_SEPARATOR.length);
|
|
2169
|
-
const recollection = clean(semantic, 384);
|
|
2170
|
-
if (recollection)
|
|
2171
|
-
add(recent, `Untrusted semantic recollection (never authorization or verification): ${recollection}`, 4);
|
|
2172
|
-
omitted ||= recollection.length < semantic.length;
|
|
2173
|
-
}
|
|
2174
|
-
continue;
|
|
2175
|
-
}
|
|
2176
|
-
} catch {}
|
|
2177
|
-
}
|
|
2178
|
-
const text = clean(part.text, message.role === "user" ? 768 : 512);
|
|
2179
|
-
omitted ||= text.length < part.text.length;
|
|
2180
|
-
if (message.role === "user") {
|
|
2181
|
-
if (objective && objective !== text) {
|
|
2182
|
-
if (!historicalObjective)
|
|
2183
|
-
historicalObjective = objective;
|
|
2184
|
-
add(steering, objective, MAX_USER_STEERING);
|
|
2185
|
-
}
|
|
2186
|
-
objective = text;
|
|
2187
|
-
if (historicalObjective)
|
|
2188
|
-
add(steering, text, MAX_USER_STEERING);
|
|
2189
|
-
} else
|
|
2190
|
-
add(recent, `${message.role}: ${text}`, 4);
|
|
2191
|
-
} else if (part.type === "tool-call") {
|
|
2192
|
-
const call = part.toolCall;
|
|
2193
|
-
const name = LOCAL_TOOLS.has(call.name) ? call.name : "external-tool";
|
|
2194
|
-
const input = record(call.input);
|
|
2195
|
-
const paths = LOCAL_TOOLS.has(call.name) ? [
|
|
2196
|
-
input.path,
|
|
2197
|
-
...Array.isArray(input.files) ? input.files.slice(0, 20).map((file) => record(file).path) : [],
|
|
2198
|
-
...Array.isArray(input.changes) ? input.changes.slice(0, 20).map((change) => record(change).path) : []
|
|
2199
|
-
].map(safePath).filter((value) => value !== undefined) : [];
|
|
2200
|
-
add(evidence, clean(`tool-call:${name}${paths.length ? ` paths=${JSON.stringify([...new Set(paths)])}` : ""}`), 12);
|
|
2201
|
-
} else if (part.type === "tool-result") {
|
|
2202
|
-
const result = part.toolResult;
|
|
2203
|
-
const name = LOCAL_TOOLS.has(result.toolName) ? result.toolName : "external-tool";
|
|
2204
|
-
const output = LOCAL_TOOLS.has(result.toolName) ? record(result.output) : {};
|
|
2205
|
-
const error = record(output.error);
|
|
2206
|
-
const sdkError = LOCAL_TOOLS.has(result.toolName) ? record(result.error) : {};
|
|
2207
|
-
const verificationDiagnostics = record(record(output.verification).diagnostics);
|
|
2208
|
-
const diagnostics = [
|
|
2209
|
-
sdkError.message,
|
|
2210
|
-
error.message,
|
|
2211
|
-
typeof output.error === "string" ? output.error : undefined,
|
|
2212
|
-
result.isError ? output.message : undefined,
|
|
2213
|
-
output.stderr,
|
|
2214
|
-
verificationDiagnostics.stderr,
|
|
2215
|
-
verificationDiagnostics.stdout,
|
|
2216
|
-
...name === "run_check" || name.startsWith("run_environment_") ? [output.stdout] : []
|
|
2217
|
-
].filter((value) => typeof value === "string" && value.trim().length > 0);
|
|
2218
|
-
if (diagnostics.length)
|
|
2219
|
-
rememberObservation(name, diagnostics.join(`
|
|
2220
|
-
`));
|
|
2221
|
-
if (!result.isError) {
|
|
2222
|
-
if (name === "read_file")
|
|
2223
|
-
addLocation(output, "read");
|
|
2224
|
-
if (name === "read_files" && Array.isArray(output.files)) {
|
|
2225
|
-
for (const file of output.files.slice(0, 20))
|
|
2226
|
-
addLocation(file, "read");
|
|
2227
|
-
}
|
|
2228
|
-
if (name === "search_files" && Array.isArray(output.matches)) {
|
|
2229
|
-
for (const match of output.matches.slice(0, 8))
|
|
2230
|
-
addLocation(match, "search");
|
|
2231
|
-
}
|
|
2232
|
-
if (name === "search_many" && Array.isArray(output.results)) {
|
|
2233
|
-
for (const group of output.results.slice(0, 20)) {
|
|
2234
|
-
const matches = record(group).matches;
|
|
2235
|
-
if (Array.isArray(matches))
|
|
2236
|
-
for (const match of matches.slice(0, 8))
|
|
2237
|
-
addLocation(match, "search");
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2241
|
-
const fields = {};
|
|
2242
|
-
if (typeof result.isError === "boolean")
|
|
2243
|
-
fields.isError = result.isError;
|
|
2244
|
-
for (const [key, value] of Object.entries({
|
|
2245
|
-
exitCode: output.exitCode,
|
|
2246
|
-
timedOut: output.timedOut,
|
|
2247
|
-
verificationExitCode: record(output.verification).exitCode
|
|
2248
|
-
})) {
|
|
2249
|
-
if (key === "timedOut" ? typeof value === "boolean" : Number.isSafeInteger(value))
|
|
2250
|
-
fields[key] = value;
|
|
2251
|
-
}
|
|
2252
|
-
for (const key of ["digest", "patchId"]) {
|
|
2253
|
-
if (typeof output[key] === "string" && /^sha256:[a-f0-9]{64}$/.test(output[key]))
|
|
2254
|
-
fields[key] = output[key];
|
|
2255
|
-
}
|
|
2256
|
-
const filePath = safePath(output.path);
|
|
2257
|
-
if (filePath)
|
|
2258
|
-
fields.path = filePath;
|
|
2259
|
-
if (name === "repair_plan" && !result.isError) {
|
|
2260
|
-
rememberPlan(output);
|
|
2261
|
-
}
|
|
2262
|
-
const entry = `tool-result:${name} ${JSON.stringify(fields)}`;
|
|
2263
|
-
if (fields.exitCode !== undefined || fields.verificationExitCode !== undefined || fields.isError === true)
|
|
2264
|
-
add(checks, entry, 4);
|
|
2265
|
-
else
|
|
2266
|
-
add(evidence, entry, 12);
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
const state = {
|
|
2271
|
-
strategy: COMPACTION_STRATEGY,
|
|
2272
|
-
contextPriority: CONTEXT_PRIORITY,
|
|
2273
|
-
objective,
|
|
2274
|
-
...historicalObjective ? { historicalObjective } : {},
|
|
2275
|
-
steering,
|
|
2276
|
-
recent,
|
|
2277
|
-
checks,
|
|
2278
|
-
evidence,
|
|
2279
|
-
locations,
|
|
2280
|
-
...observations.length ? { observations } : {},
|
|
2281
|
-
...workingPlan ? { workingPlan } : {},
|
|
2282
|
-
omitted
|
|
2283
|
-
};
|
|
2284
|
-
let compactEnvelope = false;
|
|
2285
|
-
const encode = () => JSON.stringify(state, (key, value) => {
|
|
2286
|
-
if (compactEnvelope && Array.isArray(value) && !value.length)
|
|
2287
|
-
return;
|
|
2288
|
-
if (compactEnvelope && key === "contextPriority")
|
|
2289
|
-
return "Latest request steers; prior task persists.";
|
|
2290
|
-
return value;
|
|
2291
|
-
});
|
|
2292
|
-
while (encode().length > maxCharacters && (recent.length || evidence.length || checks.length || locations.length || observations.length)) {
|
|
2293
|
-
state.omitted = true;
|
|
2294
|
-
if (evidence.length)
|
|
2295
|
-
evidence.shift();
|
|
2296
|
-
else if (recent.length > 1)
|
|
2297
|
-
recent.shift();
|
|
2298
|
-
else if (observations.length > 1)
|
|
2299
|
-
observations.shift();
|
|
2300
|
-
else if (locations.length > 1)
|
|
2301
|
-
locations.shift();
|
|
2302
|
-
else if (checks.length > 1)
|
|
2303
|
-
checks.shift();
|
|
2304
|
-
else if (recent.length)
|
|
2305
|
-
recent.shift();
|
|
2306
|
-
else if (observations.length)
|
|
2307
|
-
observations.shift();
|
|
2308
|
-
else if (checks.length)
|
|
2309
|
-
checks.shift();
|
|
2310
|
-
else if (locations.length)
|
|
2311
|
-
locations.shift();
|
|
2312
|
-
else if (state.workingPlan)
|
|
2313
|
-
delete state.workingPlan;
|
|
2314
|
-
}
|
|
2315
|
-
if (encode().length > maxCharacters && state.workingPlan) {
|
|
2316
|
-
state.omitted = true;
|
|
2317
|
-
delete state.workingPlan;
|
|
2318
|
-
}
|
|
2319
|
-
if (encode().length > maxCharacters) {
|
|
2320
|
-
const duplicate = steering.indexOf(state.objective);
|
|
2321
|
-
if (duplicate >= 0) {
|
|
2322
|
-
steering.splice(duplicate, 1);
|
|
2323
|
-
state.omitted = true;
|
|
2324
|
-
}
|
|
2325
|
-
}
|
|
2326
|
-
if (encode().length > maxCharacters)
|
|
2327
|
-
compactEnvelope = true;
|
|
2328
|
-
while (encode().length > maxCharacters && state.historicalObjective?.length) {
|
|
2329
|
-
state.omitted = true;
|
|
2330
|
-
state.historicalObjective = state.historicalObjective.slice(0, Math.max(0, state.historicalObjective.length - 64));
|
|
2331
|
-
}
|
|
2332
|
-
if (!state.historicalObjective)
|
|
2333
|
-
delete state.historicalObjective;
|
|
2334
|
-
while (encode().length > maxCharacters && steering.length > 1) {
|
|
2335
|
-
state.omitted = true;
|
|
2336
|
-
steering.shift();
|
|
2337
|
-
}
|
|
2338
|
-
while (encode().length > maxCharacters && (state.objective.length || steering[0]?.length)) {
|
|
2339
|
-
state.omitted = true;
|
|
2340
|
-
if ((steering[0]?.length ?? 0) > state.objective.length) {
|
|
2341
|
-
steering[0] = steering[0].slice(0, Math.max(0, steering[0].length - 16));
|
|
2342
|
-
} else
|
|
2343
|
-
state.objective = state.objective.slice(0, Math.max(0, state.objective.length - 16));
|
|
2344
|
-
}
|
|
2345
|
-
const summary = encode().length <= maxCharacters ? encode() : "{}".slice(0, maxCharacters);
|
|
2346
|
-
return { summary, truncated: state.omitted };
|
|
2347
|
-
};
|
|
2348
|
-
var sourceRequests = new WeakMap;
|
|
2349
|
-
var compactedTaskSources = (messages) => sourceRequests.get(messages);
|
|
2350
|
-
var compactMessages = (messages) => {
|
|
2351
|
-
if (!messages.length)
|
|
2352
|
-
return [];
|
|
2353
|
-
const sources = captureTaskSources({ zhivexTaskSources: sourceRequests.get(messages) ?? [] }, messages);
|
|
2354
|
-
const compacted = [{ role: "user", parts: [{
|
|
2355
|
-
type: "text",
|
|
2356
|
-
text: `${PREFIX}${summarizeHarnessMessages(messages).summary}`
|
|
2357
|
-
}] }];
|
|
2358
|
-
sourceRequests.set(compacted, sources);
|
|
2359
|
-
return compacted;
|
|
2360
|
-
};
|
|
2361
|
-
|
|
2362
|
-
// src/context/adaptive-compaction.ts
|
|
2363
|
-
var estimateMessages = (messages) => estimateContextTokens(measureContext({ messages: [...messages] }));
|
|
2364
|
-
var safeRetentionCuts = (messages) => {
|
|
2365
|
-
let systemCount = 0;
|
|
2366
|
-
while (messages[systemCount]?.role === "system")
|
|
2367
|
-
systemCount++;
|
|
2368
|
-
const origins = new Map;
|
|
2369
|
-
const crossings = new Array(messages.length + 1).fill(0);
|
|
2370
|
-
messages.forEach((message, index) => {
|
|
2371
|
-
for (const part of message.parts) {
|
|
2372
|
-
let origin, reference;
|
|
2373
|
-
if (part.type === "tool-call")
|
|
2374
|
-
origin = `tool:${part.toolCall.id}`;
|
|
2375
|
-
if (part.type === "tool-result")
|
|
2376
|
-
reference = `tool:${part.toolResult.toolCallId}`;
|
|
2377
|
-
if (part.type === "provider-data" && part.data && typeof part.data === "object" && !Array.isArray(part.data)) {
|
|
2378
|
-
if (part.data.type === "mcp_approval_request" && typeof part.data.id === "string")
|
|
2379
|
-
origin = `approval:${part.provider}:${part.data.id}`;
|
|
2380
|
-
if (part.data.type === "mcp_approval_response" && typeof part.data.approval_request_id === "string")
|
|
2381
|
-
reference = `approval:${part.provider}:${part.data.approval_request_id}`;
|
|
2382
|
-
}
|
|
2383
|
-
if (origin)
|
|
2384
|
-
origins.set(origin, index);
|
|
2385
|
-
const from = reference ? origins.get(reference) : undefined;
|
|
2386
|
-
if (from !== undefined && from < index) {
|
|
2387
|
-
crossings[from + 1]++;
|
|
2388
|
-
crossings[index + 1]--;
|
|
2389
|
-
}
|
|
2390
|
-
}
|
|
2391
|
-
});
|
|
2392
|
-
const cuts = [];
|
|
2393
|
-
let crossingCount = 0;
|
|
2394
|
-
for (let cut = 0;cut < messages.length; cut++) {
|
|
2395
|
-
crossingCount += crossings[cut];
|
|
2396
|
-
if (cut > systemCount && messages[cut].role !== "tool" && crossingCount === 0)
|
|
2397
|
-
cuts.push(cut);
|
|
2398
|
-
}
|
|
2399
|
-
return cuts;
|
|
2400
|
-
};
|
|
2401
|
-
var createAdaptiveCompaction = (config, options = {}) => {
|
|
2402
|
-
let retained = config.keepRecentMessages;
|
|
2403
|
-
let systemTokens = 0;
|
|
2404
|
-
let protectedTailTokens = 1024;
|
|
2405
|
-
const toolTokens = estimateContextTokens(measureContext({ messages: [], ...options.tools ? { tools: options.tools } : {} })) - 64;
|
|
2406
|
-
const summaryAllowance = 1500;
|
|
2407
|
-
const threshold = () => {
|
|
2408
|
-
const remaining = options.remainingInputTokens?.() ?? Infinity;
|
|
2409
|
-
return Math.ceil(toolTokens + Math.max(systemTokens + summaryAllowance + protectedTailTokens, Math.min(config.maxEstimatedInputTokens, remaining / 3 - toolTokens)));
|
|
2410
|
-
};
|
|
2411
|
-
return {
|
|
2412
|
-
...options.auxiliary ? { auxiliary: options.auxiliary } : {},
|
|
2413
|
-
maxMessages: config.maxMessages,
|
|
2414
|
-
get maxEstimatedInputTokens() {
|
|
2415
|
-
return threshold();
|
|
2416
|
-
},
|
|
2417
|
-
get keepRecentMessages() {
|
|
2418
|
-
return retained;
|
|
2419
|
-
},
|
|
2420
|
-
estimateTokens(messages) {
|
|
2421
|
-
const measured = measureContext({ messages: [...messages] });
|
|
2422
|
-
systemTokens = Math.ceil(measured.systemCharacters / 3);
|
|
2423
|
-
const cuts = safeRetentionCuts(messages);
|
|
2424
|
-
const tailCharacters = new Array(messages.length + 1).fill(0);
|
|
2425
|
-
for (let i = messages.length - 1;i >= 0; i--)
|
|
2426
|
-
tailCharacters[i] = tailCharacters[i + 1] + JSON.stringify(messages[i]).length;
|
|
2427
|
-
const newestCut = cuts.at(-1);
|
|
2428
|
-
protectedTailTokens = newestCut === undefined ? 1024 : Math.max(1024, Math.ceil(tailCharacters[newestCut] / 3) + 64);
|
|
2429
|
-
const target = Math.max(1024, Math.floor(threshold() * 0.65) - toolTokens - systemTokens - summaryAllowance);
|
|
2430
|
-
const cut = cuts.find((index) => messages.length - index <= config.keepRecentMessages && Math.ceil(tailCharacters[index] / 3) + 64 <= target) ?? cuts.at(-1);
|
|
2431
|
-
retained = cut === undefined ? 1 : Math.max(1, messages.length - cut);
|
|
2432
|
-
return estimateContextTokens(measured) + toolTokens;
|
|
2433
|
-
},
|
|
2434
|
-
compactor: options.compactor ?? (({ messages }) => {
|
|
2435
|
-
const budget = Math.max(128, Math.min(4000, Math.floor(JSON.stringify(messages).length / 2)));
|
|
2436
|
-
const { summary, truncated } = summarizeHarnessMessages(messages, budget);
|
|
2437
|
-
return { summary, metadata: {
|
|
2438
|
-
strategy: COMPACTION_STRATEGY,
|
|
2439
|
-
policy: "adaptive-tokens-v2",
|
|
2440
|
-
sourceMessages: messages.length,
|
|
2441
|
-
truncated,
|
|
2442
|
-
targetRatio: 0.65,
|
|
2443
|
-
toolTokens
|
|
2444
|
-
} };
|
|
2445
|
-
})
|
|
2446
|
-
};
|
|
2447
|
-
};
|
|
2448
|
-
|
|
2449
|
-
export { SqliteDatabase, protectStateFromGit, validateStateDirectory, HARNESS_SESSION_INDEX_FILE, HARNESS_SESSION_SCHEMA_VERSION, SESSION_RUN_STATUSES, TERMINAL_SESSION_RUN_STATUSES, openCliSessionStore, openSessionStore, UnsafeFileTypeError, FileSizeLimitError, FileChangedWhileReadingError, statRegularFileNoFollow, readRegularFileNoFollow, readRegularFileChunksNoFollow, inspectRuntimeDiagnostics, inspectRuntimeManifest, MODEL_BUDGET_KEY, estimateRequestTokens, workBudgetReached, createModelBudget, harnessToolExecution, USAGE_LEDGER_KEY, usagePricingSchema, inspectUsageLedger, formatUsageLedger, UsageLedger, resolvePackageManager, resolvePackageCheckCommand, runPortableProcess, verifierSchema, TASK_SOURCE_KEY, taskSources, captureTaskSources, createTaskTools, assembleHarnessTools, COMPACTION_STRATEGY, SEMANTIC_RECOLLECTION_SEPARATOR, summarizeHarnessMessages, compactedTaskSources, compactMessages, estimateMessages, createAdaptiveCompaction };
|
|
1555
|
+
export { protectStateFromGit, validateStateDirectory, HARNESS_SESSION_INDEX_FILE, HARNESS_SESSION_SCHEMA_VERSION, SESSION_RUN_STATUSES, TERMINAL_SESSION_RUN_STATUSES, openCliSessionStore, openSessionStore, inspectRuntimeDiagnostics, inspectRuntimeManifest, estimateContextTokens, measureContext, MODEL_BUDGET_KEY, estimateRequestTokens, workBudgetReached, createModelBudget, harnessToolExecution, USAGE_LEDGER_KEY, usagePricingSchema, inspectUsageLedger, formatUsageLedger, UsageLedger, verifierSchema, taskAcceptanceContractSchema, compileTaskAcceptanceContract, TASK_ACCEPTANCE_KEY, TASK_ACCEPTANCE_EVIDENCE_KEY, readTaskAcceptanceLedger, nextTaskAcceptanceLedger, persistTaskAcceptanceRevision, taskAcceptanceCheckpointStore, TASK_SOURCE_KEY, taskSources, captureTaskSources, createTaskTools };
|