feat: file system access, behind a path policy and an approval (#25)
This commit was merged in pull request #25.
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { environment } from "@vicinae/api";
|
||||
import { runAgent } from "../agent";
|
||||
import { confirmWithAlert, createApprovalLedger } from "../approval";
|
||||
import { toStoredAttachment } from "../attachments";
|
||||
import { createPolicy, displayPath } from "../files";
|
||||
import { ipPlace } from "../geo";
|
||||
import { buildMessages, composeSystemPrompt } from "../history";
|
||||
import { isSelfHosted } from "../ollama";
|
||||
@@ -205,6 +208,18 @@ export function useChat(options: UseChatOptions): UseChat {
|
||||
const controller = new AbortController();
|
||||
abortRef.current = controller;
|
||||
|
||||
// Built per turn rather than per session: the ledger inside it is the record of
|
||||
// what the user approved while answering this one question, and the next question
|
||||
// starts by asking again. The policy is cheap to rebuild and holds no state.
|
||||
const policy = createPolicy({
|
||||
supportPath: environment.supportPath,
|
||||
readRoots: preferences.extraReadRoots,
|
||||
writeRoots: preferences.extraWriteRoots,
|
||||
});
|
||||
const fileAccess = preferences.fileEnabled
|
||||
? { policy, approve: createApprovalLedger(confirmWithAlert).approve }
|
||||
: undefined;
|
||||
|
||||
const messages = buildMessages(
|
||||
composeSystemPrompt(preferences.systemPrompt, {
|
||||
model: preferences.model,
|
||||
@@ -212,6 +227,11 @@ export function useChat(options: UseChatOptions): UseChat {
|
||||
selfHosted: isSelfHosted(preferences.host),
|
||||
webTools: preferences.webEnabled,
|
||||
vision: visionRef.current,
|
||||
fileTools: preferences.fileEnabled,
|
||||
fileRoots: {
|
||||
read: policy.readRoots.map((root) => displayPath(root, policy.home)),
|
||||
write: policy.writeRoots.map((root) => displayPath(root, policy.home)),
|
||||
},
|
||||
}),
|
||||
base.turns,
|
||||
prompt,
|
||||
@@ -233,7 +253,10 @@ export function useChat(options: UseChatOptions): UseChat {
|
||||
// cap, so the two conventions meet here.
|
||||
maxToolRounds: preferences.maxToolRounds ?? null,
|
||||
messages,
|
||||
tools: toolsFor({ webEnabled: preferences.webEnabled }),
|
||||
tools: toolsFor({
|
||||
webEnabled: preferences.webEnabled,
|
||||
fileEnabled: preferences.fileEnabled,
|
||||
}),
|
||||
toolContext: {
|
||||
host: preferences.host,
|
||||
apiKey: preferences.apiKey,
|
||||
@@ -241,6 +264,7 @@ export function useChat(options: UseChatOptions): UseChat {
|
||||
// Only wired up when the user switched it on, so an off setting
|
||||
// cannot reach the network for a location at all.
|
||||
ipPlace: preferences.ipGeolocation ? ipPlace : undefined,
|
||||
files: fileAccess,
|
||||
},
|
||||
signal: controller.signal,
|
||||
events: {
|
||||
|
||||
Reference in New Issue
Block a user