Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions agent/lib/worker-cancellation-delivery.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { parseWorkerTaskNotification } from "@/lib/eve-task-notifications";

const runtime = globalThis as typeof globalThis & {
openInstinctWorkerCancellationTurns?: Map<string, string>;
};
Expand All @@ -9,10 +11,10 @@ export function recordWorkerCancellationTurn(
turnId: string,
message: string
) {
const taskId = /^Background task (\S+) \(worker\) is cancelled\.$/u.exec(
message
)?.[1];
if (taskId) cancellationTurns.set(turnKey(sessionId, turnId), taskId);
const notification = parseWorkerTaskNotification(message);
if (notification?.kind === "cancelled") {
cancellationTurns.set(turnKey(sessionId, turnId), notification.taskId);
}
}

export function consumeWorkerCancellationTurn(
Expand Down
27 changes: 7 additions & 20 deletions app/(authenticated)/(manager)/chat/_components/agent-chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,13 @@ import { Shimmer } from "@/components/ai-elements/shimmer";
import { summarizeChatUsage } from "@/app/(authenticated)/(manager)/_lib/chat-usage";
import { getLatestTurnFailure } from "@/app/(authenticated)/(manager)/chat/_lib/turn-failure";
import type { ChatUsage } from "@/lib/chat";
import { parseWorkerTaskNotification } from "@/lib/eve-task-notifications";
import { cn } from "@/lib/utils";
import { AgentMessage } from "./agent-message";
import { collectSubagentSessions } from "@/app/_lib/subagent-sessions";
import { SubagentPanel } from "./subagent-panel";

const AGENT_NAME = "Local Vault Assistant";
const backgroundWorkerDelivery =
/^Background task (\S+) \(worker\) (?:update: |needs input\.$|is cancelled\.$|is completed\.\n\nResult:\n|failed\.\n\nError:\n)/u;
const backgroundWorkerAuthorization =
/^Background task (\S+) needs authorization\.$/u;
const taskCancelResultSchema = z.object({
kind: z.literal("tool-result"),
output: z.object({ tasks: z.array(z.unknown()) }),
Expand Down Expand Up @@ -401,13 +398,10 @@ export function backgroundWorkerDeliveryMessageIds(
}

if (event.type !== "message.received") continue;
const taskId =
backgroundWorkerDelivery.exec(event.data.message)?.[1] ??
backgroundWorkerAuthorization.exec(event.data.message)?.[1];
const notification = parseWorkerTaskNotification(event.data.message);
const taskId = notification?.taskId;
if (taskId && taskIds.has(taskId)) {
const isCancellation = event.data.message.endsWith(
"(worker) is cancelled."
);
const isCancellation = notification.kind === "cancelled";
if (!isCancellation) messageIds.add(`${event.data.turnId}:user`);
if (isCancellation && cancelledTaskIds.delete(taskId)) {
messageIds.add(`${event.data.turnId}:user`);
Expand Down Expand Up @@ -454,16 +448,9 @@ function hasPendingBackgroundWorker(events: readonly MessageStreamEvent[]) {
}

if (event.type !== "message.received") continue;
const deliveredTaskId =
backgroundWorkerDelivery.exec(event.data.message)?.[1] ??
backgroundWorkerAuthorization.exec(event.data.message)?.[1];
if (
deliveredTaskId &&
!event.data.message.startsWith(
`Background task ${deliveredTaskId} (worker) update: `
)
) {
taskIds.delete(deliveredTaskId);
const notification = parseWorkerTaskNotification(event.data.message);
if (notification && notification.kind !== "update") {
taskIds.delete(notification.taskId);
}
}

Expand Down
41 changes: 13 additions & 28 deletions lib/browser/benchmark.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { z } from "zod";
import type { MessageStreamEvent } from "eve/client";
import { parseWorkerTaskNotification } from "../eve-task-notifications";
import { parseTaskCompletionOutput } from "../task-completion";

const workerTaskNotificationPrefix = /^Background task (\S+) \(worker\) /u;
const terminalTaskControlSchema = z.object({
tasks: z.array(
z.object({
Expand Down Expand Up @@ -144,34 +144,19 @@ export function readTaskCompletion(events: readonly MessageStreamEvent[]) {

function readWorkerTaskNotification(event: MessageStreamEvent) {
if (event.type !== "message.received") return undefined;
const match = workerTaskNotificationPrefix.exec(event.data.message);
if (!match) return undefined;
const [, taskId] = match;
if (!taskId) return undefined;
const message = event.data.message.slice(match[0].length);

if (message === "is cancelled.")
return { status: "cancelled" as const, taskId };

const completedPrefix = "is completed.\n\nResult:\n";
if (message.startsWith(completedPrefix)) {
return {
output: message.slice(completedPrefix.length),
status: "completed" as const,
taskId,
};
}

const failedPrefix = "failed.\n\nError:\n";
if (message.startsWith(failedPrefix)) {
return {
output: message.slice(failedPrefix.length),
status: "failed" as const,
taskId,
};
const notification = parseWorkerTaskNotification(event.data.message);
if (
notification?.kind !== "cancelled" &&
notification?.kind !== "completed" &&
notification?.kind !== "failed"
) {
return undefined;
}

return undefined;
return {
output: notification.output,
status: notification.kind,
taskId: notification.taskId,
};
}

export function readBackgroundWorkerTasks(
Expand Down
67 changes: 67 additions & 0 deletions lib/eve-task-notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Eve limitation: background-task delivery has no typed event, only these prose messages, forcing this custom parser.
type TaskNotification =
| { readonly kind: "authorization"; readonly taskId: string }
| TaskAgentNotification;

interface TaskAgentNotification {
readonly agentName: string;
readonly kind:
| "cancelled"
| "completed"
| "failed"
| "needs-input"
| "update";
readonly output?: string;
readonly taskId: string;
}

const notificationPrefix = /^Background task (\S+) \(([\w-]+)\) /u;
const authorizationNotification =
/^Background task (\S+) needs authorization\.$/u;
const completedPrefix = "is completed.\n\nResult:\n";
const failedPrefix = "failed.\n\nError:\n";

function parseTaskNotification(message: string): TaskNotification | undefined {
const authorized = authorizationNotification.exec(message)?.[1];
if (authorized) return { kind: "authorization", taskId: authorized };

const match = notificationPrefix.exec(message);
const taskId = match?.[1];
const agentName = match?.[2];
if (!match || !taskId || !agentName) return undefined;
const rest = message.slice(match[0].length);

if (rest === "is cancelled.") return { agentName, kind: "cancelled", taskId };
if (rest === "needs input.")
return { agentName, kind: "needs-input", taskId };
if (rest.startsWith("update: ")) return { agentName, kind: "update", taskId };
if (rest.startsWith(completedPrefix)) {
return {
agentName,
kind: "completed",
output: rest.slice(completedPrefix.length),
taskId,
};
}
if (rest.startsWith(failedPrefix)) {
return {
agentName,
kind: "failed",
output: rest.slice(failedPrefix.length),
taskId,
};
}
return undefined;
}

export function parseWorkerTaskNotification(message: string) {
const notification = parseTaskNotification(message);
if (!notification) return undefined;
if (
notification.kind !== "authorization" &&
notification.agentName !== "worker"
) {
return undefined;
}
return notification;
}
56 changes: 3 additions & 53 deletions lib/manager/chrome-passwords.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Papa from "papaparse";
import type { ManagerMutation } from ".";
import {
loginIdentifierSchema,
Expand All @@ -15,13 +16,8 @@ type VaultImportItem = Extract<
>["items"][number];

export function parseChromePasswordsCsv(csv: string) {
const rows = parseCsv(csv);
const headers = rows.shift()?.map((header) =>
header
.replace(/^\uFEFF/, "")
.trim()
.toLowerCase()
);
const rows = Papa.parse<string[]>(csv.replace(/^\uFEFF/u, "")).data;
const headers = rows.shift()?.map((header) => header.trim().toLowerCase());
if (!headers) throw new Error("Choose a Chrome passwords CSV file.");

const indexes = {
Expand Down Expand Up @@ -116,49 +112,3 @@ function originFromUrl(value: string) {
return undefined;
}
}

function parseCsv(csv: string) {
const rows: string[][] = [];
let field = "";
let quoted = false;
let row: string[] = [];

for (let index = 0; index < csv.length; index += 1) {
const character = csv.charAt(index);
if (quoted) {
if (character === '"') {
if (csv[index + 1] === '"') {
field += '"';
index += 1;
} else {
quoted = false;
}
} else {
field += character;
}
continue;
}

if (character === '"' && field.length === 0) {
quoted = true;
} else if (character === ",") {
row.push(field);
field = "";
} else if (character === "\n" || character === "\r") {
if (character === "\r" && csv[index + 1] === "\n") index += 1;
row.push(field);
rows.push(row);
field = "";
row = [];
} else {
field += character;
}
}

if (quoted) throw new Error("This CSV has an unfinished quoted value.");
if (field.length > 0 || row.length > 0) {
row.push(field);
rows.push(row);
}
return rows;
}
Loading