newmark-agent 0.6.4 → 0.6.8

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 (76) hide show
  1. package/config.example.json +13 -3
  2. package/dist/cli-commands.d.ts +1 -1
  3. package/dist/cli-commands.js +54 -3
  4. package/dist/cli-discovery.js +3 -0
  5. package/dist/conversation-utility-host.bundle.cjs +4472 -1767
  6. package/dist/conversation-utility-host.js +94 -7
  7. package/dist/core/agent.d.ts +126 -12
  8. package/dist/core/agent.js +785 -172
  9. package/dist/core/agentKernelRunner.d.ts +7 -1
  10. package/dist/core/agentKernelRunner.js +160 -20
  11. package/dist/core/autoRouter.d.ts +49 -44
  12. package/dist/core/autoRouter.js +117 -302
  13. package/dist/core/computerUseSession.js +1 -1
  14. package/dist/core/config.js +27 -11
  15. package/dist/core/continuation/contracts.d.ts +1 -1
  16. package/dist/core/continuation/store.d.ts +91 -1
  17. package/dist/core/continuation/store.js +291 -61
  18. package/dist/core/conversationKernel.d.ts +49 -7
  19. package/dist/core/conversationKernel.js +303 -51
  20. package/dist/core/conversationStateDocument.d.ts +18 -0
  21. package/dist/core/conversationStateDocument.js +88 -0
  22. package/dist/core/conversationVisualBudget.d.ts +74 -0
  23. package/dist/core/conversationVisualBudget.js +153 -0
  24. package/dist/core/electronUtilityAgentClient.d.ts +3 -2
  25. package/dist/core/electronUtilityAgentClient.js +32 -8
  26. package/dist/core/electronUtilityRuntimePool.d.ts +28 -3
  27. package/dist/core/electronUtilityRuntimePool.js +93 -24
  28. package/dist/core/fileWriteObservation.d.ts +41 -0
  29. package/dist/core/fileWriteObservation.js +454 -0
  30. package/dist/core/hostRuntimeHooks.d.ts +23 -0
  31. package/dist/core/hostRuntimeHooks.js +17 -0
  32. package/dist/core/jevDecision.d.ts +144 -0
  33. package/dist/core/jevDecision.js +226 -0
  34. package/dist/core/memoryProbe.d.ts +24 -0
  35. package/dist/core/memoryProbe.js +104 -0
  36. package/dist/core/mobilePairing.d.ts +7 -1
  37. package/dist/core/mobilePairing.js +9 -1
  38. package/dist/core/performanceDiagnostics.d.ts +1 -1
  39. package/dist/core/routeDecisionValidator.d.ts +86 -0
  40. package/dist/core/routeDecisionValidator.js +249 -0
  41. package/dist/core/routeEligibility.d.ts +52 -0
  42. package/dist/core/routeEligibility.js +108 -0
  43. package/dist/core/runtimeMemoryBudget.d.ts +6 -0
  44. package/dist/core/runtimeMemoryBudget.js +12 -0
  45. package/dist/core/toolPolicy.d.ts +1 -1
  46. package/dist/core/toolPolicy.js +1 -1
  47. package/dist/core/utilityAgentProtocol.d.ts +8 -10
  48. package/dist/core/utilityHostToolRouter.js +2 -0
  49. package/dist/core/visualDownscale.d.ts +6 -0
  50. package/dist/core/visualDownscale.js +137 -0
  51. package/dist/core/wslAgentClient.d.ts +1 -2
  52. package/dist/core/wslAgentClient.js +0 -8
  53. package/dist/core/wslAgentProtocol.d.ts +1 -10
  54. package/dist/core/wslAgentRuntimePool.d.ts +1 -3
  55. package/dist/core/wslAgentRuntimePool.js +17 -22
  56. package/dist/llm/provider.d.ts +3 -1
  57. package/dist/llm/provider.js +53 -9
  58. package/dist/main.js +107 -30
  59. package/dist/preload.js +3 -1
  60. package/dist/server.js +30 -18
  61. package/dist/tools/computerUse.d.ts +6 -0
  62. package/dist/tools/computerUse.js +406 -21
  63. package/dist/tools/computerUsePowerShellHost.d.ts +6 -1
  64. package/dist/tools/computerUsePowerShellHost.js +105 -31
  65. package/dist/tools/index.d.ts +3 -4
  66. package/dist/tools/index.js +146 -66
  67. package/dist/tui/src/adapters/core-runtime-adapter.js +13 -1
  68. package/dist/tui/src/i18n.js +12 -1
  69. package/dist/tui/src/render.js +9 -2
  70. package/dist/tui/src/settings-schema.js +19 -0
  71. package/dist/tui/src/state.js +69 -1
  72. package/dist/ui/index.html +651 -233
  73. package/dist/ui/lucide-sprite.svg +0 -8
  74. package/dist/wsl-agent-host.bundle.cjs +4302 -1767
  75. package/dist/wsl-agent-host.js +0 -3
  76. package/package.json +39 -10
@@ -0,0 +1,41 @@
1
+ export interface FileReadRange {
2
+ startLine?: number;
3
+ endLine?: number;
4
+ }
5
+ export type FileObservationRequirement = {
6
+ kind: 'full';
7
+ } | {
8
+ kind: 'edit';
9
+ oldText: string;
10
+ };
11
+ export interface FileMutationContext {
12
+ targetPath: string;
13
+ canonicalPath: string;
14
+ exists: boolean;
15
+ bytes?: Buffer;
16
+ text?: string;
17
+ assertBeforeCommit(): void;
18
+ }
19
+ export interface FileMutationOutcome<T> {
20
+ value: T;
21
+ committed: boolean;
22
+ }
23
+ export type FileMutationResult<T> = {
24
+ ok: true;
25
+ value: T;
26
+ } | {
27
+ ok: false;
28
+ error: string;
29
+ };
30
+ export declare class FileWriteObservationGate {
31
+ private readonly receipts;
32
+ read(targetPath: string, scope: string, range?: FileReadRange): Promise<string>;
33
+ mutate<T>(targetPath: string, scope: string, requirement: FileObservationRequirement, apply: (context: FileMutationContext) => Promise<FileMutationOutcome<T>> | FileMutationOutcome<T>): Promise<FileMutationResult<T>>;
34
+ private inspect;
35
+ private inspectMissing;
36
+ private assertBeforeCommit;
37
+ private receiptKey;
38
+ private pruneReceipts;
39
+ private withLock;
40
+ }
41
+ //# sourceMappingURL=fileWriteObservation.d.ts.map
@@ -0,0 +1,454 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FileWriteObservationGate = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const crypto = __importStar(require("crypto"));
40
+ const READ_CHAR_LIMIT = 20_000;
41
+ const RECEIPT_TTL_MS = 5 * 60 * 1000;
42
+ const MAX_INLINE_RESULT_CHARS = 24_000;
43
+ const MAX_RECEIPTS = 256;
44
+ const MAX_READ_RANGES = 128;
45
+ const MAX_RECEIPT_CHARS = 150_000;
46
+ const processPathMutationLocks = new Map();
47
+ function errorCode(error) {
48
+ return error && typeof error === 'object' ? String(error.code || '') : '';
49
+ }
50
+ function sha256(value) {
51
+ return crypto.createHash('sha256').update(value).digest('hex');
52
+ }
53
+ function statIdentity(stat) {
54
+ return [
55
+ stat.dev,
56
+ stat.ino,
57
+ stat.mode,
58
+ stat.size,
59
+ stat.mtimeMs,
60
+ stat.ctimeMs,
61
+ ].join(':');
62
+ }
63
+ function directoryIdentity(stat) {
64
+ return [stat.dev, stat.ino, stat.mode].join(':');
65
+ }
66
+ function lineStarts(text) {
67
+ const starts = [0];
68
+ for (let index = 0; index < text.length; index += 1) {
69
+ if (text.charCodeAt(index) === 10)
70
+ starts.push(index + 1);
71
+ }
72
+ return starts;
73
+ }
74
+ function lineCount(text, starts = lineStarts(text)) {
75
+ if (text.length === 0)
76
+ return 0;
77
+ const newlineCount = starts.length - 1;
78
+ return text.endsWith('\n') ? newlineCount : newlineCount + 1;
79
+ }
80
+ function mergeLineRanges(ranges, totalLines) {
81
+ if (totalLines === 0)
82
+ return ranges.length > 0;
83
+ const sorted = [...ranges].sort((a, b) => a.startLine - b.startLine || a.endLine - b.endLine);
84
+ let nextLine = 1;
85
+ for (const range of sorted) {
86
+ if (range.endLine < nextLine)
87
+ continue;
88
+ if (range.startLine > nextLine)
89
+ return false;
90
+ nextLine = Math.max(nextLine, range.endLine + 1);
91
+ if (nextLine > totalLines)
92
+ return true;
93
+ }
94
+ return nextLine > totalLines;
95
+ }
96
+ function pathKey(value) {
97
+ const normalized = path.resolve(value);
98
+ const windowsMountedPath = /^\/mnt\/[a-z]\//i.test(normalized);
99
+ return process.platform === 'win32' || windowsMountedPath ? normalized.toLowerCase() : normalized;
100
+ }
101
+ class FileWriteObservationGate {
102
+ receipts = new Map();
103
+ async read(targetPath, scope, range = {}) {
104
+ const absolutePath = path.resolve(targetPath);
105
+ const receiptKey = this.receiptKey(scope, absolutePath);
106
+ this.pruneReceipts();
107
+ const initial = this.inspect(absolutePath);
108
+ const lockKey = pathKey(initial.canonicalPath || absolutePath);
109
+ return this.withLock(lockKey, () => {
110
+ const snapshot = this.inspect(absolutePath);
111
+ if (snapshot.kind === 'blocked') {
112
+ this.receipts.delete(receiptKey);
113
+ return '[read blocked] ' + snapshot.error;
114
+ }
115
+ if (pathKey(snapshot.canonicalPath) !== lockKey) {
116
+ this.receipts.delete(receiptKey);
117
+ return '[read blocked] The target path changed while it was being read. Read it again.';
118
+ }
119
+ if (snapshot.kind === 'missing') {
120
+ this.receipts.set(receiptKey, {
121
+ canonicalPath: snapshot.canonicalPath,
122
+ exists: false,
123
+ fingerprint: snapshot.fingerprint,
124
+ observedAt: Date.now(),
125
+ totalLines: 0,
126
+ full: true,
127
+ ranges: [],
128
+ });
129
+ return '[read] Target does not exist: ' + absolutePath
130
+ + '\n[write gate] Exact-path absence observed. A write may create this target; the observation expires in five minutes.'
131
+ + '\nNearest existing directory: ' + snapshot.parentAnchor.canonicalPath;
132
+ }
133
+ const startProvided = range.startLine !== undefined;
134
+ const endProvided = range.endLine !== undefined;
135
+ if (startProvided !== endProvided) {
136
+ return '[read blocked] Provide both start_line and end_line, or omit both.';
137
+ }
138
+ const explicitRange = startProvided && endProvided;
139
+ const text = snapshot.text;
140
+ const starts = lineStarts(text);
141
+ const totalLines = snapshot.totalLines;
142
+ let startLine = 1;
143
+ let endLine = totalLines;
144
+ let selected = text;
145
+ let fullRead = !explicitRange;
146
+ if (explicitRange) {
147
+ startLine = Number(range.startLine);
148
+ endLine = Number(range.endLine);
149
+ if (!Number.isInteger(startLine) || !Number.isInteger(endLine) || startLine < 1 || endLine < startLine) {
150
+ return '[read blocked] start_line/end_line must be positive integers with end_line >= start_line.';
151
+ }
152
+ if (totalLines === 0) {
153
+ if (startLine !== 1 || endLine !== 1)
154
+ return '[read blocked] The empty file only supports line range 1-1.';
155
+ startLine = 1;
156
+ endLine = 0;
157
+ selected = '';
158
+ fullRead = true;
159
+ }
160
+ else {
161
+ if (startLine > totalLines || endLine > totalLines) {
162
+ return '[read blocked] Requested line range is outside this file (1-' + totalLines + ').';
163
+ }
164
+ const startOffset = starts[startLine - 1];
165
+ const endOffset = endLine >= totalLines ? text.length : starts[endLine];
166
+ selected = text.slice(startOffset, endOffset);
167
+ fullRead = startLine === 1 && endLine === totalLines;
168
+ }
169
+ }
170
+ if (!explicitRange && text.length > READ_CHAR_LIMIT) {
171
+ const prior = this.receipts.get(receiptKey);
172
+ if (!prior || prior.fingerprint !== snapshot.fingerprint || Date.now() - prior.observedAt > RECEIPT_TTL_MS) {
173
+ this.receipts.delete(receiptKey);
174
+ }
175
+ return text.slice(0, READ_CHAR_LIMIT)
176
+ + '\n...[truncated]'
177
+ + '\n[write gate] No new full-read receipt was issued. Use read with start_line/end_line to inspect exact ranges; overwrite/delete requires complete line coverage.';
178
+ }
179
+ if (selected.length > READ_CHAR_LIMIT) {
180
+ return '[read blocked] Requested range exceeds the 20000-character read limit. Split it into smaller line ranges.';
181
+ }
182
+ if (selected.length + absolutePath.length + 1200 > MAX_INLINE_RESULT_CHARS) {
183
+ return '[read blocked] The returned path and line range would be truncated before reaching the model. Read a smaller range; no mutation receipt was issued.';
184
+ }
185
+ if (!snapshot.receiptEligible) {
186
+ this.receipts.delete(receiptKey);
187
+ const content = selected || (text.length === 0 ? '[empty file]' : '');
188
+ return content
189
+ + '\n[write gate] No mutation receipt: ' + (snapshot.receiptReason || 'the target is not a stable plain-text file.');
190
+ }
191
+ const now = Date.now();
192
+ let receipt = this.receipts.get(receiptKey);
193
+ if (!receipt
194
+ || receipt.fingerprint !== snapshot.fingerprint
195
+ || receipt.canonicalPath !== snapshot.canonicalPath
196
+ || now - receipt.observedAt > RECEIPT_TTL_MS) {
197
+ receipt = {
198
+ canonicalPath: snapshot.canonicalPath,
199
+ exists: true,
200
+ fingerprint: snapshot.fingerprint,
201
+ observedAt: now,
202
+ totalLines,
203
+ full: false,
204
+ ranges: [],
205
+ };
206
+ }
207
+ const replacedRange = receipt.ranges.find(item => item.startLine === startLine && item.endLine === endLine);
208
+ const nextRangeCount = receipt.ranges.length + (replacedRange ? 0 : 1);
209
+ const currentReceiptChars = receipt.ranges.reduce((total, item) => total + item.text.length, 0);
210
+ const nextReceiptChars = currentReceiptChars - (replacedRange?.text.length || 0) + selected.length;
211
+ if (nextRangeCount > MAX_READ_RANGES || nextReceiptChars > MAX_RECEIPT_CHARS) {
212
+ return '[read blocked] The observation ledger reached its bounded range capacity. Read fewer, larger ranges; no new receipt range was added.';
213
+ }
214
+ receipt.observedAt = now;
215
+ receipt.ranges = receipt.ranges.filter(item => item.startLine !== startLine || item.endLine !== endLine);
216
+ receipt.ranges.push({ startLine, endLine, text: selected });
217
+ receipt.full = receipt.full || fullRead || mergeLineRanges(receipt.ranges, totalLines);
218
+ this.receipts.set(receiptKey, receipt);
219
+ this.pruneReceipts();
220
+ const content = selected || (text.length === 0 ? '[empty file]' : '');
221
+ const rangeLabel = explicitRange
222
+ ? '[read] ' + absolutePath + ' (lines ' + startLine + '-' + endLine + ' of ' + totalLines + ')'
223
+ : '[read] ' + absolutePath + ' (complete text; ' + totalLines + ' lines)';
224
+ const grant = receipt.full
225
+ ? '\n[write gate] Complete target observation recorded for this conversation, actor, workspace, and run. It is single-use and expires in five minutes.'
226
+ : '\n[write gate] Lines observed; edit may target text inside this range. Read every line before overwrite/delete.';
227
+ return rangeLabel + '\n--- BEGIN READ CONTENT ---\n' + content + '\n--- END READ CONTENT ---' + grant;
228
+ });
229
+ }
230
+ async mutate(targetPath, scope, requirement, apply) {
231
+ const absolutePath = path.resolve(targetPath);
232
+ const receiptKey = this.receiptKey(scope, absolutePath);
233
+ this.pruneReceipts();
234
+ const initial = this.inspect(absolutePath);
235
+ const lockKey = pathKey(initial.canonicalPath || absolutePath);
236
+ return this.withLock(lockKey, async () => {
237
+ const snapshot = this.inspect(absolutePath);
238
+ if (snapshot.kind === 'blocked') {
239
+ return { ok: false, error: '[write gate blocked] ' + snapshot.error };
240
+ }
241
+ if (pathKey(snapshot.canonicalPath) !== lockKey) {
242
+ return { ok: false, error: '[write gate blocked] The target path changed before mutation. Read the target again.' };
243
+ }
244
+ const receipt = this.receipts.get(receiptKey);
245
+ if (!receipt) {
246
+ return {
247
+ ok: false,
248
+ error: '[write gate blocked] Read this exact target with the read tool before changing it. A read of another path, grep/glob output, or a write function internal read does not count.',
249
+ };
250
+ }
251
+ if (Date.now() - receipt.observedAt > RECEIPT_TTL_MS) {
252
+ this.receipts.delete(receiptKey);
253
+ return { ok: false, error: '[write gate blocked] The read observation expired. Read the target again before changing it.' };
254
+ }
255
+ if (receipt.exists !== (snapshot.kind === 'file')
256
+ || receipt.canonicalPath !== snapshot.canonicalPath
257
+ || receipt.fingerprint !== snapshot.fingerprint) {
258
+ this.receipts.delete(receiptKey);
259
+ return { ok: false, error: '[write gate blocked] The target changed after it was read. Read the current target again before changing it.' };
260
+ }
261
+ if (requirement.kind === 'full' && snapshot.kind === 'file' && !receipt.full) {
262
+ return {
263
+ ok: false,
264
+ error: '[write gate blocked] The complete file has not been read. Use read with line ranges that cover the whole file before overwrite/delete.',
265
+ };
266
+ }
267
+ if (requirement.kind === 'edit') {
268
+ const oldText = requirement.oldText;
269
+ if (!oldText || !receipt.ranges.some(item => item.text.includes(oldText))) {
270
+ return {
271
+ ok: false,
272
+ error: '[write gate blocked] Read the exact lines containing old_str before editing. Use read with a range that includes the full match.',
273
+ };
274
+ }
275
+ }
276
+ const context = {
277
+ targetPath: absolutePath,
278
+ canonicalPath: snapshot.canonicalPath,
279
+ exists: snapshot.kind === 'file',
280
+ ...(snapshot.kind === 'file' ? { bytes: snapshot.bytes, text: snapshot.text } : {}),
281
+ assertBeforeCommit: () => this.assertBeforeCommit(absolutePath, snapshot),
282
+ };
283
+ const outcome = await apply(context);
284
+ if (outcome.committed)
285
+ this.receipts.delete(receiptKey);
286
+ return { ok: true, value: outcome.value };
287
+ });
288
+ }
289
+ inspect(targetPath) {
290
+ const absolutePath = path.resolve(targetPath);
291
+ let linkStat;
292
+ try {
293
+ linkStat = fs.lstatSync(absolutePath);
294
+ }
295
+ catch (error) {
296
+ if (errorCode(error) !== 'ENOENT') {
297
+ return { kind: 'blocked', targetPath: absolutePath, error: 'Unable to inspect target: ' + String(error) };
298
+ }
299
+ return this.inspectMissing(absolutePath);
300
+ }
301
+ let canonicalPath;
302
+ let followedStat;
303
+ try {
304
+ canonicalPath = fs.realpathSync.native(absolutePath);
305
+ followedStat = fs.statSync(absolutePath);
306
+ }
307
+ catch (error) {
308
+ return { kind: 'blocked', targetPath: absolutePath, error: 'Unable to resolve target: ' + String(error) };
309
+ }
310
+ if (!followedStat.isFile()) {
311
+ return {
312
+ kind: 'blocked',
313
+ targetPath: absolutePath,
314
+ canonicalPath,
315
+ error: 'The target is not a regular file: ' + absolutePath,
316
+ };
317
+ }
318
+ let bytes;
319
+ let afterLinkStat;
320
+ let afterFollowedStat;
321
+ let afterCanonicalPath;
322
+ try {
323
+ bytes = fs.readFileSync(absolutePath);
324
+ afterLinkStat = fs.lstatSync(absolutePath);
325
+ afterFollowedStat = fs.statSync(absolutePath);
326
+ afterCanonicalPath = fs.realpathSync.native(absolutePath);
327
+ }
328
+ catch (error) {
329
+ return { kind: 'blocked', targetPath: absolutePath, canonicalPath, error: 'Unable to read target: ' + String(error) };
330
+ }
331
+ if (canonicalPath !== afterCanonicalPath
332
+ || statIdentity(linkStat) !== statIdentity(afterLinkStat)
333
+ || statIdentity(followedStat) !== statIdentity(afterFollowedStat)) {
334
+ return {
335
+ kind: 'blocked',
336
+ targetPath: absolutePath,
337
+ canonicalPath: afterCanonicalPath,
338
+ error: 'The target changed while it was being read. Read it again.',
339
+ };
340
+ }
341
+ const text = bytes.toString('utf8');
342
+ const validUtf8 = Buffer.compare(Buffer.from(text, 'utf8'), bytes) === 0;
343
+ const isSymbolicLink = linkStat.isSymbolicLink();
344
+ const receiptReason = isSymbolicLink
345
+ ? 'the target is a symbolic link; use its resolved file path'
346
+ : (!validUtf8 ? 'the target is not valid UTF-8 text' : '');
347
+ const fingerprint = sha256(JSON.stringify({
348
+ canonicalPath,
349
+ linkIdentity: statIdentity(linkStat),
350
+ fileIdentity: statIdentity(followedStat),
351
+ contentHash: sha256(bytes),
352
+ }));
353
+ return {
354
+ kind: 'file',
355
+ targetPath: absolutePath,
356
+ canonicalPath,
357
+ fingerprint,
358
+ bytes,
359
+ text,
360
+ totalLines: lineCount(text),
361
+ receiptEligible: !isSymbolicLink && validUtf8,
362
+ ...(receiptReason ? { receiptReason } : {}),
363
+ };
364
+ }
365
+ inspectMissing(targetPath) {
366
+ const parentPath = path.dirname(targetPath);
367
+ let cursor = parentPath;
368
+ let anchor;
369
+ while (true) {
370
+ try {
371
+ const stat = fs.statSync(cursor);
372
+ if (!stat.isDirectory()) {
373
+ return { kind: 'blocked', targetPath, error: 'The parent path is not a directory: ' + cursor };
374
+ }
375
+ const canonicalPath = fs.realpathSync.native(cursor);
376
+ anchor = {
377
+ lexicalPath: path.resolve(cursor),
378
+ canonicalPath,
379
+ identity: directoryIdentity(stat),
380
+ };
381
+ break;
382
+ }
383
+ catch (error) {
384
+ if (errorCode(error) !== 'ENOENT' || path.dirname(cursor) === cursor) {
385
+ return { kind: 'blocked', targetPath, error: 'Unable to inspect the parent directory: ' + String(error) };
386
+ }
387
+ cursor = path.dirname(cursor);
388
+ }
389
+ }
390
+ const canonicalTarget = path.resolve(anchor.canonicalPath, path.relative(anchor.lexicalPath, targetPath));
391
+ const fingerprint = sha256(JSON.stringify({
392
+ kind: 'missing',
393
+ canonicalTarget,
394
+ parentPath: anchor.canonicalPath,
395
+ parentIdentity: anchor.identity,
396
+ }));
397
+ return {
398
+ kind: 'missing',
399
+ targetPath,
400
+ canonicalPath: canonicalTarget,
401
+ fingerprint,
402
+ parentAnchor: anchor,
403
+ };
404
+ }
405
+ assertBeforeCommit(targetPath, observed) {
406
+ const current = this.inspect(targetPath);
407
+ if (observed.kind === 'file') {
408
+ if (current.kind !== 'file'
409
+ || current.canonicalPath !== observed.canonicalPath
410
+ || current.fingerprint !== observed.fingerprint) {
411
+ throw new Error('The target changed after it was read. Read it again before changing it.');
412
+ }
413
+ return;
414
+ }
415
+ if (observed.kind !== 'missing' || current.kind === 'blocked' || current.kind === 'file'
416
+ || current.canonicalPath !== observed.canonicalPath) {
417
+ throw new Error('The target path changed after it was read. Read it again before creating it.');
418
+ }
419
+ }
420
+ receiptKey(scope, targetPath) {
421
+ return scope + '\u0000' + pathKey(targetPath);
422
+ }
423
+ pruneReceipts() {
424
+ const now = Date.now();
425
+ for (const [key, receipt] of this.receipts) {
426
+ if (now - receipt.observedAt > RECEIPT_TTL_MS)
427
+ this.receipts.delete(key);
428
+ }
429
+ while (this.receipts.size > MAX_RECEIPTS) {
430
+ const oldest = this.receipts.keys().next().value;
431
+ if (oldest === undefined)
432
+ break;
433
+ this.receipts.delete(oldest);
434
+ }
435
+ }
436
+ async withLock(key, action) {
437
+ const previous = processPathMutationLocks.get(key);
438
+ let release;
439
+ const current = new Promise(resolve => { release = resolve; });
440
+ processPathMutationLocks.set(key, current);
441
+ if (previous)
442
+ await previous;
443
+ try {
444
+ return await action();
445
+ }
446
+ finally {
447
+ release();
448
+ if (processPathMutationLocks.get(key) === current)
449
+ processPathMutationLocks.delete(key);
450
+ }
451
+ }
452
+ }
453
+ exports.FileWriteObservationGate = FileWriteObservationGate;
454
+ //# sourceMappingURL=fileWriteObservation.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Packaged-build replacement for the source-only runtime probe
3
+ * (`src/core/memoryProbe.ts`).
4
+ *
5
+ * `scripts/build-conversation-utility-host.cjs --strip-diagnostics` resolves the
6
+ * probe import to this module, so a shipped bundle contains no sampling, no
7
+ * heap-snapshot capability, no probe file path and no probe naming. Only the
8
+ * neutral no-op hook surface remains, and the probe implementation itself stays
9
+ * in source for diagnostics builds.
10
+ */
11
+ export interface HostRuntimeHooksOptions {
12
+ /** Runtime root used when no explicit probe output is configured. */
13
+ root: string;
14
+ intervalMs?: number;
15
+ snapshotRssMiB?: number;
16
+ }
17
+ export interface HostRuntimeHooks {
18
+ enabled: boolean;
19
+ countPosted(value: unknown): void;
20
+ stop(): void;
21
+ }
22
+ export declare function installHostRuntimeHooks(_options: HostRuntimeHooksOptions): HostRuntimeHooks;
23
+ //# sourceMappingURL=hostRuntimeHooks.d.ts.map
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ /**
3
+ * Packaged-build replacement for the source-only runtime probe
4
+ * (`src/core/memoryProbe.ts`).
5
+ *
6
+ * `scripts/build-conversation-utility-host.cjs --strip-diagnostics` resolves the
7
+ * probe import to this module, so a shipped bundle contains no sampling, no
8
+ * heap-snapshot capability, no probe file path and no probe naming. Only the
9
+ * neutral no-op hook surface remains, and the probe implementation itself stays
10
+ * in source for diagnostics builds.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.installHostRuntimeHooks = installHostRuntimeHooks;
14
+ function installHostRuntimeHooks(_options) {
15
+ return { enabled: false, countPosted() { }, stop() { } };
16
+ }
17
+ //# sourceMappingURL=hostRuntimeHooks.js.map
@@ -0,0 +1,144 @@
1
+ /**
2
+ * JEV decision contract — the only Auto model-selection decision source.
3
+ *
4
+ * Auto Router v2 replaces the previous adaptive scorer (rule filters + task
5
+ * heuristics + quality_by_task history + EWMA preference learning + user
6
+ * ratings + tool-outcome learning + cost/speed/reliability weights + cache
7
+ * affinity) with:
8
+ *
9
+ * RouteEligibilityGuard → JevDecisionEngine → RouteDecisionValidator
10
+ *
11
+ * The engine receives *only* live facts: the request, the explicit user
12
+ * configuration, the eligible candidate catalog and the current operational
13
+ * health of each endpoint. It must never receive, read or persist routing
14
+ * feedback history. There is deliberately no `recordFeedback`, no preference
15
+ * window and no per-task quality ledger in this file, and the reviewer-facing
16
+ * invariant test asserts that a change in any of those stored histories cannot
17
+ * change the resolved deployment.
18
+ *
19
+ * Production routes use `CurrentModelJevDecisionEngine`: a native strict-JSON
20
+ * call to the current eligible conversation model. Deterministic routing
21
+ * policy does not live in this module; Agent validates the model response
22
+ * before it can be used.
23
+ */
24
+ import type { AutoRouteCandidate, DeploymentRef, RouteMode, RoutePolicy, RouteRequest, TaskClass, ValidationLevel, ValidationStatus } from './autoRouter';
25
+ import type { StreamToken } from './types';
26
+ /** Version stamped into every route audit entry. */
27
+ export declare const JEV_ROUTE_MODEL_VERSION = "jev-route-1.0";
28
+ export declare const CURRENT_MODEL_JEV_VERSION = "current-model-jev-1.0";
29
+ export declare const DEFAULT_JEV_TIMEOUT_MS = 3000;
30
+ export declare const DEFAULT_JEV_MAX_ALTERNATES = 2;
31
+ /** Native strict-schema output requested from the current conversation model. */
32
+ export declare const JEV_ROUTE_DECISION_SCHEMA: Readonly<Record<string, unknown>>;
33
+ export interface JevStrictJsonClient {
34
+ chatStrictJson(model: string, messages: Array<Record<string, unknown>>, systemPrompt: string | null, temperature: number, maxTokens: number, schema: Readonly<Record<string, unknown>>, signal?: AbortSignal, reasoningTier?: string, schemaName?: string, onUsage?: (usage: NonNullable<StreamToken['usage']>) => void, reasoningControlRequired?: boolean): Promise<string>;
35
+ }
36
+ export interface CurrentModelJevClient {
37
+ provider: JevStrictJsonClient;
38
+ model: string;
39
+ /** Audit-only identity of the current model used for this decision. */
40
+ modelVersion: string;
41
+ /** Catalog metadata says this deployment exposes a reasoning mode. */
42
+ thinkingCapable?: boolean;
43
+ onUsage?: (usage: NonNullable<StreamToken['usage']>) => void;
44
+ }
45
+ export type CurrentModelJevClientResolver = (request: JevRouteRequest) => CurrentModelJevClient | null | Promise<CurrentModelJevClient | null>;
46
+ /**
47
+ * Uses the current conversation's eligible model to make the JEV choice. The
48
+ * provider call bypasses Agent routing to avoid recursion and requires native
49
+ * JSON Schema output with reasoning disabled.
50
+ */
51
+ export declare class CurrentModelJevDecisionEngine implements JevDecisionEngine {
52
+ private readonly resolveClient;
53
+ constructor(resolveClient: CurrentModelJevClientResolver);
54
+ decide(request: JevRouteRequest, signal?: AbortSignal): Promise<JevRouteDecision>;
55
+ }
56
+ export interface JevCandidateOperational {
57
+ latencyMs?: number;
58
+ reliability?: number;
59
+ throughput?: number;
60
+ toolValidity?: number;
61
+ circuit: 'closed' | 'open' | 'half_open';
62
+ }
63
+ export interface JevCandidate {
64
+ deployment: DeploymentRef;
65
+ capabilities: string[];
66
+ maxContextTokens: number;
67
+ validation: {
68
+ level: ValidationLevel;
69
+ status: ValidationStatus;
70
+ };
71
+ cost: {
72
+ inputPerM?: number;
73
+ outputPerM?: number;
74
+ };
75
+ operational: JevCandidateOperational;
76
+ /** Explicit user configuration (config `route_preference`), never history. */
77
+ userPreference?: number;
78
+ preview: boolean;
79
+ fallbackOnly: boolean;
80
+ }
81
+ export interface JevRouteRequest {
82
+ transactionId: string;
83
+ task: {
84
+ text: string;
85
+ taskClasses: TaskClass[];
86
+ estimatedInputTokens: number;
87
+ expectedOutputTokens: number;
88
+ requiredCapabilities: string[];
89
+ intelligence: string;
90
+ };
91
+ objective: {
92
+ tendency: RouteMode;
93
+ maxExpectedCostUsd?: number;
94
+ };
95
+ previous?: {
96
+ deployment?: DeploymentRef;
97
+ };
98
+ candidates: JevCandidate[];
99
+ }
100
+ export interface JevRouteDecision {
101
+ primary: DeploymentRef;
102
+ alternates: DeploymentRef[];
103
+ reasonCodes: string[];
104
+ confidence?: number;
105
+ modelVersion: string;
106
+ }
107
+ export interface JevDecisionEngine {
108
+ decide(request: JevRouteRequest, signal?: AbortSignal): Promise<JevRouteDecision>;
109
+ }
110
+ export interface JevRequestBuildInput {
111
+ transactionId: string;
112
+ taskText: string;
113
+ taskClasses: TaskClass[];
114
+ estimatedInputTokens: number;
115
+ expectedOutputTokens: number;
116
+ requiredCapabilities: string[];
117
+ intelligence: string;
118
+ policy: RoutePolicy;
119
+ request: RouteRequest;
120
+ candidates: AutoRouteCandidate[];
121
+ previousDeployment?: DeploymentRef | null;
122
+ }
123
+ export declare function toJevCandidate(candidate: AutoRouteCandidate): JevCandidate;
124
+ export declare function buildJevRouteRequest(input: JevRequestBuildInput): JevRouteRequest;
125
+ export declare class JevDecisionError extends Error {
126
+ readonly code: JevDecisionErrorCode;
127
+ constructor(code: JevDecisionErrorCode, message: string);
128
+ }
129
+ export type JevDecisionErrorCode = 'no_candidates' | 'timeout' | 'runtime_unavailable' | 'invalid_output' | 'invalid_json' | 'empty_result';
130
+ export interface JevDecisionOutcome {
131
+ decision?: JevRouteDecision;
132
+ latencyMs: number;
133
+ error?: {
134
+ code: JevDecisionErrorCode;
135
+ message: string;
136
+ };
137
+ timedOut: boolean;
138
+ }
139
+ /**
140
+ * Hot-path budget for model selection. The decision must never block a Build:
141
+ * a runtime that exceeds the budget is abandoned and Auto routing fails closed.
142
+ */
143
+ export declare function decideWithinBudget(engine: JevDecisionEngine, request: JevRouteRequest, timeoutMs: number): Promise<JevDecisionOutcome>;
144
+ //# sourceMappingURL=jevDecision.d.ts.map