fix(fs): observe absence before guarded recreation
This commit is contained in:
@@ -137,8 +137,8 @@ export function applyEditTool(ctx: Context, sandbox: FsSandboxSurface): void {
|
||||
// model-facing remedy; anything else passes through.
|
||||
throw remediateFsError(sandbox.mapError(error, sandboxPolicy))
|
||||
}
|
||||
// Record the observed version (a no-op when no policy plugin listens).
|
||||
ctx.emit('fs/observed', target, outcome.version, exec)
|
||||
// Record the present observation (a no-op when no policy plugin listens).
|
||||
ctx.emit('fs/observed', target, { kind: 'present', version: outcome.version }, exec)
|
||||
return {
|
||||
path: target.displayPath,
|
||||
before: outcome.before,
|
||||
|
||||
@@ -138,10 +138,13 @@ export function applyReadTool(ctx: Context, caps: ReadToolCaps): void {
|
||||
const input = parseReadArgs(args, caps.limit)
|
||||
const target = await ctx.fs.resolve(input.filePath, sessionResolveOptions(exec, input.filePath))
|
||||
|
||||
// One stat: type check + size routing + the version recorded as observed.
|
||||
// One stat: absence observation OR type check + size routing + present version.
|
||||
// A concurrent write can only make a later guarded mutation fail stale and require reread.
|
||||
const info = await ctx.fs.stat(target, exec.signal)
|
||||
if (!info) throw new FsError(`cannot read "${target.displayPath}": not found`, 'FS_NOT_FOUND')
|
||||
if (!info) {
|
||||
ctx.emit('fs/observed', target, { kind: 'absent' }, exec)
|
||||
throw new FsError(`cannot read "${target.displayPath}": not found`, 'FS_NOT_FOUND')
|
||||
}
|
||||
if (info.type !== 'file') throw new FsError(`cannot read "${target.displayPath}": not a regular file`, 'FS_NOT_REGULAR_FILE')
|
||||
|
||||
// Stream when the file is large OR size is unknown, so a size-less backend
|
||||
@@ -161,10 +164,10 @@ export function applyReadTool(ctx: Context, caps: ReadToolCaps): void {
|
||||
lines: window.lines,
|
||||
totalLines: window.totalLines,
|
||||
}
|
||||
// Record the observed version (a no-op when no policy plugin listens). The
|
||||
// Record the present observation (a no-op when no policy plugin listens). The
|
||||
// read already succeeded; an fs/observed listener is contractually a
|
||||
// synchronous, side-effect-only recorder.
|
||||
ctx.emit('fs/observed', target, info.version, exec)
|
||||
ctx.emit('fs/observed', target, { kind: 'present', version: info.version }, exec)
|
||||
return outcome
|
||||
},
|
||||
// Result-time display: a `read` card carrying the structured line window a
|
||||
|
||||
@@ -118,8 +118,8 @@ export function applyWriteTool(ctx: Context, sandbox: FsSandboxSurface): void {
|
||||
// model-facing remedy; anything else passes through.
|
||||
throw remediateFsError(sandbox.mapError(error, sandboxPolicy))
|
||||
}
|
||||
// Record the observed version (a no-op when no policy plugin listens).
|
||||
ctx.emit('fs/observed', target, outcome.version, exec)
|
||||
// Record the present observation (a no-op when no policy plugin listens).
|
||||
ctx.emit('fs/observed', target, { kind: 'present', version: outcome.version }, exec)
|
||||
return {
|
||||
path: target.displayPath,
|
||||
operation: outcome.operation,
|
||||
|
||||
Reference in New Issue
Block a user