Resolve stored blob references when reading agent files

This commit is contained in:
2026-10-08 00:09:53 -04:00
parent 592e829fec
commit 6a897df716
2 changed files with 45 additions and 2 deletions
+7 -2
View File
@@ -3,6 +3,7 @@ import { join, posix, relative, resolve, sep } from 'node:path';
import { config } from '../config.js'; import { config } from '../config.js';
import { nativeInstance, nativeRequest } from './native.js'; import { nativeInstance, nativeRequest } from './native.js';
import { getHost } from '../hosts/index.js'; import { getHost } from '../hosts/index.js';
import { getFileBlob } from '../db/file-blobs.js';
function remote(path: string) { function remote(path: string) {
for (const root of [config.serversRoot, config.t6ServersRoot]) { for (const root of [config.serversRoot, config.t6ServersRoot]) {
@@ -49,8 +50,12 @@ export async function nativeFileManifest(root: string, directories: string[], ig
export async function readFile(path: string): Promise<Buffer> { export async function readFile(path: string): Promise<Buffer> {
const target = remote(path); const target = remote(path);
if (!target) return fs.readFile(path); if (!target) return fs.readFile(path);
const result = await nativeRequest<{ contentBase64: string | null }>(target.instance, 'files.read', { path: target.path }); const result = await nativeRequest<{ contentSha256?: string; contentBase64?: string | null } | null>(target.instance, 'files.read', { path: target.path });
return result.contentBase64 === null ? missing(path) : Buffer.from(result.contentBase64, 'base64'); // Stored receipts reference shared bytes; read them directly without a base64 round trip.
if (typeof result?.contentSha256 === 'string') return getFileBlob(result.contentSha256);
if (result?.contentBase64 === null) return missing(path);
if (typeof result?.contentBase64 === 'string') return Buffer.from(result.contentBase64, 'base64');
throw new Error(`Agent returned an invalid response for files.read: ${target.path}`);
} }
export async function exists(path: string): Promise<boolean> { export async function exists(path: string): Promise<boolean> {
+38
View File
@@ -1,3 +1,4 @@
import { join } from 'node:path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { newFixture, type Fixture } from '../helpers/fixture.js'; import { newFixture, type Fixture } from '../helpers/fixture.js';
@@ -74,3 +75,40 @@ describe('overlapping native observations', () => {
expect(await current).toEqual({ generation: 2 }); expect(await current).toEqual({ generation: 2 });
}); });
}); });
describe('native file reads through stored agent receipts', () => {
it.each([
{ name: 'binary content', content: Buffer.from([0, 255, 1, 128, 10]) },
{ name: 'empty content', content: Buffer.alloc(0) }
])('resolves $name from a stored blob reference', async ({ content }) => {
const api = await setup();
const { readFile } = await import('../../src/lib/server/backends/files.js');
const reading = readFile(join(fixture.t5, 'test', 'configs/server.cfg'));
const operation = api.listHostOperations(api.host.id)[0];
expect(operation).toMatchObject({ kind: 'files.read', payload: { path: 'configs/server.cfg' } });
api.reportHostOperationStatus(api.host.id, operation.id, {
state: 'completed', result: { contentBase64: content.toString('base64') }
});
expect(api.getHostOperationStatus(operation.id)?.result).toEqual({ contentSha256: expect.any(String) });
expect(await reading).toEqual(content);
});
it('preserves missing-file errors from the agent', async () => {
const api = await setup();
const { readFile } = await import('../../src/lib/server/backends/files.js');
const reading = expect(readFile(join(fixture.t5, 'test', 'missing'))).rejects.toMatchObject({ code: 'ENOENT' });
api.reportHostOperationStatus(api.host.id, api.listHostOperations(api.host.id)[0].id, {
state: 'completed', result: { contentBase64: null }
});
await reading;
});
it.each([null, {}, { contentBase64: 123 }, { contentSha256: 123 }])('reports an invalid response for malformed file content: %j', async result => {
const api = await setup();
const { readFile } = await import('../../src/lib/server/backends/files.js');
const reading = expect(readFile(join(fixture.t5, 'test', 'configs/server.cfg')))
.rejects.toThrow('Agent returned an invalid response for files.read: configs/server.cfg');
api.reportHostOperationStatus(api.host.id, api.listHostOperations(api.host.id)[0].id, { state: 'completed', result });
await reading;
});
});