newmark-agent 0.6.7 → 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.
@@ -5429,6 +5429,8 @@ class Agent {
5429
5429
  conversationId: this.activeConversationId || 'default',
5430
5430
  actorId: this.runtimeActorId,
5431
5431
  workspaceId: this.workspace.current?.id || '',
5432
+ runtimeKey: process.env.NEWMARK_RUNTIME_KEY || undefined,
5433
+ runId: this.currentWorkRunId() || undefined,
5432
5434
  backend: process.env.NEWMARK_WSL_DISTRO ? 'wsl' : (process.platform === 'win32' ? 'windows' : process.platform),
5433
5435
  signal,
5434
5436
  }).then((content) => {
@@ -1964,6 +1964,8 @@ async function executeNewmarkTool(agent, name, args, inputSchema, signal, onSett
1964
1964
  conversationId: agent.activeConversationId || 'default',
1965
1965
  actorId: agent.runtimeActorId,
1966
1966
  workspaceId: (0, terminalTakeover_1.terminalTakeoverWorkspaceId)(wsDir),
1967
+ runtimeKey: process.env.NEWMARK_RUNTIME_KEY || undefined,
1968
+ runId: agent.currentWorkRunId() || undefined,
1967
1969
  backend: process.env.NEWMARK_WSL_DISTRO ? 'wsl' : (process.platform === 'win32' ? 'windows' : process.platform),
1968
1970
  signal,
1969
1971
  });
@@ -2016,6 +2018,8 @@ async function executeNewmarkTool(agent, name, args, inputSchema, signal, onSett
2016
2018
  conversationId: agent.activeConversationId || 'default',
2017
2019
  actorId: agent.runtimeActorId,
2018
2020
  workspaceId: (0, terminalTakeover_1.terminalTakeoverWorkspaceId)(wsDir),
2021
+ runtimeKey: process.env.NEWMARK_RUNTIME_KEY || undefined,
2022
+ runId: agent.currentWorkRunId() || undefined,
2019
2023
  backend: process.env.NEWMARK_WSL_DISTRO ? 'wsl' : (process.platform === 'win32' ? 'windows' : process.platform),
2020
2024
  inspectBrowserImage: name === 'browser_use' ? (dataUrl, prompt) => agent.inspectBrowserImage(dataUrl, prompt, signal) : undefined,
2021
2025
  allowEphemeralVisionImage: (name === 'screen_capture' || name === 'computer_use' || name === 'browser_use' || name === 'pdf_read' || name === 'ocr_read'),
@@ -21,7 +21,7 @@ class ComputerUseSessionRegistry {
21
21
  if (normalizedAction === 'takeover_stop')
22
22
  return null;
23
23
  const enabled = this.enabledByRuntime.get(runtimeKey) !== false;
24
- const readOnly = normalizedAction === 'observe' || normalizedAction === 'app_list' || normalizedAction === 'app_observe' || normalizedAction === 'wait';
24
+ const readOnly = normalizedAction === 'observe' || normalizedAction === 'app_list' || normalizedAction === 'app_observe' || normalizedAction === 'wait' || normalizedAction === 'wait_for';
25
25
  if (!enabled && !readOnly && !dryRun && normalizedAction !== 'takeover_start') {
26
26
  return JSON.stringify({
27
27
  ok: false,
@@ -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
@@ -12,7 +12,7 @@ export interface ToolPolicyDecision {
12
12
  settingsVisible: boolean;
13
13
  reason?: string;
14
14
  }
15
- export declare const PLAN_COMPUTER_USE_ACTIONS: readonly ["observe", "app_list", "app_observe"];
15
+ export declare const PLAN_COMPUTER_USE_ACTIONS: readonly ["observe", "app_list", "app_observe", "wait_for"];
16
16
  export declare const PLAN_BROWSER_USE_ACTIONS: readonly ["observe", "navigate", "wait", "extract"];
17
17
  /** 判断一个工具是否可参与并行调度。缺省 false(独占)。
18
18
  * 优先看 toolchain registry 推断的 riskLevel('read' 工具天然并发安全,
@@ -78,7 +78,7 @@ const PLAN_READ_ONLY_TOOLS = new Set([
78
78
  'branch_read',
79
79
  'question',
80
80
  ]);
81
- exports.PLAN_COMPUTER_USE_ACTIONS = ['observe', 'app_list', 'app_observe'];
81
+ exports.PLAN_COMPUTER_USE_ACTIONS = ['observe', 'app_list', 'app_observe', 'wait_for'];
82
82
  exports.PLAN_BROWSER_USE_ACTIONS = ['observe', 'navigate', 'wait', 'extract'];
83
83
  const PLAN_COMPUTER_USE_ACTION_SET = new Set(exports.PLAN_COMPUTER_USE_ACTIONS);
84
84
  const PLAN_BROWSER_USE_ACTION_SET = new Set(exports.PLAN_BROWSER_USE_ACTIONS);
@@ -31,6 +31,8 @@ export interface ComputerUseOptions {
31
31
  captureMaxWidth?: number;
32
32
  captureMaxHeight?: number;
33
33
  sparseWaitMs?: number;
34
+ includeUiTree?: boolean;
35
+ timeoutMs?: number;
34
36
  signal?: AbortSignal;
35
37
  gradientColors?: string[];
36
38
  gradientSpeed?: number;