-
Notifications
You must be signed in to change notification settings - Fork 4
feat: surface uploadable files to the agent #534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import * as fs from "node:fs/promises"; | ||
| import * as path from "node:path"; | ||
| import type { FileUploadConfig } from "../browser/ariaBrowser.js"; | ||
|
|
||
| /** | ||
| * Resolve the subset of upload-allowlist entries that are existing regular files, | ||
| * as absolute paths. Directory entries and missing/unreadable paths are skipped. | ||
| * | ||
| * Used to advertise concrete uploadable file paths to the agent. Node-only | ||
| * (filesystem access): exported from `index.ts`, never from the browser-safe | ||
| * `core.ts` barrel. | ||
| */ | ||
| export async function resolveAdvertisedUploadFiles( | ||
| allowFileUpload: false | FileUploadConfig, | ||
| ): Promise<string[]> { | ||
| if (!allowFileUpload || allowFileUpload.allowedPaths.length === 0) { | ||
| return []; | ||
| } | ||
| const files: string[] = []; | ||
| for (const entry of allowFileUpload.allowedPaths) { | ||
| try { | ||
| const resolved = path.resolve(entry); | ||
| const stat = await fs.stat(resolved); | ||
| if (stat.isFile()) { | ||
| files.push(resolved); | ||
| } | ||
| } catch { | ||
| // Missing or unreadable entry — cannot be advertised. | ||
| } | ||
| } | ||
| return files; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { describe, it, expect, beforeAll, afterAll } from "vitest"; | ||
| import * as fs from "node:fs/promises"; | ||
| import * as os from "node:os"; | ||
| import * as path from "node:path"; | ||
| import { resolveAdvertisedUploadFiles } from "../../src/utils/uploadFiles.js"; | ||
|
|
||
| describe("resolveAdvertisedUploadFiles", () => { | ||
| let dir: string; | ||
| let filePath: string; | ||
|
|
||
| beforeAll(async () => { | ||
| dir = await fs.mkdtemp(path.join(os.tmpdir(), "pilo-upload-")); | ||
| filePath = path.join(dir, "sample.txt"); | ||
| await fs.writeFile(filePath, "x"); | ||
| }); | ||
|
|
||
| afterAll(async () => { | ||
| await fs.rm(dir, { recursive: true, force: true }); | ||
| }); | ||
|
|
||
| it("returns [] when uploads are disabled", async () => { | ||
| expect(await resolveAdvertisedUploadFiles(false)).toEqual([]); | ||
| }); | ||
|
|
||
| it("returns [] when the allowlist is empty", async () => { | ||
| expect(await resolveAdvertisedUploadFiles({ allowedPaths: [] })).toEqual([]); | ||
| }); | ||
|
|
||
| it("includes file entries, resolved to absolute paths", async () => { | ||
| const result = await resolveAdvertisedUploadFiles({ allowedPaths: [filePath] }); | ||
| expect(result).toEqual([path.resolve(filePath)]); | ||
| }); | ||
|
|
||
| it("excludes directory entries", async () => { | ||
| const result = await resolveAdvertisedUploadFiles({ allowedPaths: [dir] }); | ||
| expect(result).toEqual([]); | ||
| }); | ||
|
|
||
| it("skips missing or unreadable entries", async () => { | ||
| const result = await resolveAdvertisedUploadFiles({ | ||
| allowedPaths: [path.join(dir, "does-not-exist.txt"), filePath], | ||
| }); | ||
| expect(result).toEqual([path.resolve(filePath)]); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ import { | |
| RecoverableError, | ||
| SEARCH_PROVIDERS, | ||
| PLAYWRIGHT_BROWSERS, | ||
| resolveAdvertisedUploadFiles, | ||
| } from "pilo-core"; | ||
| import type { FileUploadConfig, TaskExecutionResult, UserDataCallback } from "pilo-core"; | ||
| import * as path from "node:path"; | ||
|
|
@@ -323,6 +324,7 @@ export async function runTask(options: TaskRunnerOptions): Promise<TaskExecution | |
| ); | ||
| const allowFileUpload: false | FileUploadConfig = | ||
| uploadAllowedPaths.length > 0 ? { allowedPaths: uploadAllowedPaths } : false; | ||
| const advertisedUploadFiles = await resolveAdvertisedUploadFiles(allowFileUpload); | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verified, and the real issue is incomplete test mocks rather than the production call. Fixed by completing the mocks instead. You flagged The CLI suite (
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correction to my earlier reply: I'd also touched |
||
|
|
||
| const browserConfig = { | ||
| browser: browserName as (typeof PLAYWRIGHT_BROWSERS)[number], | ||
|
|
@@ -368,6 +370,7 @@ export async function runTask(options: TaskRunnerOptions): Promise<TaskExecution | |
| trustedHostnames: body.trustedHostnames ?? serverConfig.trusted_hostnames, | ||
| unsafeMode: body.unsafeMode ?? serverConfig.unsafe_mode, | ||
| allowFileUpload, | ||
| advertisedUploadFiles, | ||
| llmProviderTimeoutMs: body.llmProviderTimeoutMs ?? serverConfig.llm_provider_timeout_ms, | ||
| guardrails: body.guardrails, | ||
| searchProvider: body.searchProvider ?? serverConfig.search_provider, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.