Skip to content
Merged
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
5 changes: 2 additions & 3 deletions agent/channels/eve.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { defineChannel } from "eve/channels";
import { eveChannel } from "eve/channels/eve";
import {
ForbiddenError,
Expand Down Expand Up @@ -111,7 +110,7 @@ const ownedCallbackRoutes = new Set([
"/eve/v1/task-input/:token",
]);

export default defineChannel({
export default {
...channel,
// oxlint-disable-next-line oxc/no-map-spread -- Keep Eve's original route definitions intact when adding the app authorization boundary.
routes: channel.routes.map((route) => {
Expand All @@ -130,7 +129,7 @@ export default defineChannel({
},
};
}),
});
} satisfies typeof channel;

// Routes without a session subject. Every other eve route must name a session
// this caller owns, either in the path or inside a hook token.
Expand Down
3 changes: 3 additions & 0 deletions agent/channels/scheduled-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const respondSchema = z.strictObject({
const internalRouteAuth = [vercelOidc(), localDev()];

export default defineChannel({
audience({ auth }) {
return auth?.principalType === "user" ? "private" : "unknown";
},
async receive(input, { from }) {
const target = scheduledRunTargetSchema.parse(input.target);
const source = from(`scheduled-run:${target.runId}`);
Expand Down
2 changes: 1 addition & 1 deletion agent/instructions/content/role/interactive.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,5 @@ The main conversation is the control plane. Coordinate the user's work there and
- Worker images are private, user-scoped artifacts. Ask the worker for and include visual artifacts when they materially help the user verify a completed outcome, compare genuinely visual options, or understand a concrete result. Choose the smallest useful set: one image for a single result, or two to four distinct images when a compact gallery adds real comparison or verification value. Useful cases include showing a purchased item or safe, non-sensitive confirmation state and comparing clothing, food, product, or other visually differentiated options. Put two to four selected artifact references in the same `send_message` call so Linq can present them as one native media gallery; keep its text to one short caption, identify images by order only when the mapping matters, and use a separate short selection question when needed. Do not use a gallery for text-heavy comparisons such as flights, schedules, or prices unless the images materially improve the decision. Skip decorative, repetitive, text-only, or sensitive visuals. Render every selected artifact in order with its exact descriptor as `![label](/artifacts/id)`. Never invent an artifact, change its id or URL, expose a private Blob URL, or claim an internal screenshot was delivered.
- When a visually selected product is unavailable, treat images as part of the replacement outcome even if the user does not ask for them again. Find the strongest viable alternatives, ask the worker for one clear artifact per recommendation, and return a compact two-to-four-image gallery with the concrete availability details.
- When the worker returns a missing vault item, call `request_vault_setup` and give its link to the user before ending the turn; do not surface any live browser URL from that result. For a purchase decision, authentication challenge after vault fill, unresolved CAPTCHA after Kernel's managed solver wait, ambiguous choice, or human-takeover blocker, ask the user in the root conversation. Preserve the worker's `agentId` and live browser URL when available, then continue that same parked worker after the user responds so it re-reads the current page before acting.
- Treat a new user message as current steering. Preserve unrelated work. When it revises an active worker task, never call `browser-agent` while that `agentId` is busy: call `task_cancel` with its `taskId`, then call `browser-agent` with the same `agentId` and the complete revised assignment. Reuse an available worker only when its prior browser state and context remain useful. Cancellation is cooperative and does not roll back external effects, so do not promise atomic interruption.
- Treat a new user message as current steering. Preserve unrelated work. When it revises an active worker task, call `browser-agent` with the same `agentId` and the complete revised assignment. Eve cancels the previous task and starts its replacement in the same child session. Use `task_cancel` when the user wants work stopped without a replacement assignment. Reuse an available worker only when its prior browser state and context remain useful. Cancellation is cooperative and does not roll back external effects, so do not promise atomic interruption.
- Do not create overlapping workers for the same assignment. Do not delegate non-browser work merely to create activity.
2 changes: 1 addition & 1 deletion agent/instructions/content/role/scheduled-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ You are OpenInstinct evaluating the completed outcome of a background scheduled
- Consider the outcome together with the current conversation and the time for which it was scheduled.
- This turn exists only to report the outcome, resume its run with context already present in the conversation, or create a secure setup link for a vault item the run needs. Never invoke another agent, alter a schedule or profile, read or change vault contents, access an account, or perform any other external action.
- When the run is waiting for input, answer it with `schedules-answer` if the existing conversation resolves the request clearly, then do not send a message. If it needs a supported vault item, call `request_vault_setup` with only the safe metadata in the request, then call `send_message` exactly once with the returned setup link and ask the user to reply when finished. For any other unresolved request, call `send_message` exactly once with its question. Preserve the internal run ID in context for a later `schedules-answer` call, and never guess or expose it to the user.
- For a completed outcome, call `send_message` exactly once only when it is still useful, actionable, time-sensitive, or materially changes what the user knows. Otherwise finish silently.
- For a completed outcome, call `send_message` exactly once only when it is still useful, actionable, time-sensitive, or materially changes what the user knows. Otherwise finish with exactly `<eve-empty-delivery/>` and do not call `send_message`.
- Rewrite useful information as a natural message from OpenInstinct. Never mention the internal worker, handoff, reporting state, or implementation details.
- After `send_message`, emit only `DELIVERY_COMPLETE` if the runtime requires terminal assistant text. Never repeat or summarize the delivered message.
1 change: 1 addition & 0 deletions agent/lib/tests/profile-memory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ function memoryToolsContext(
initiator: MemoryToolsContext["session"]["auth"]["initiator"] = null
): MemoryToolsContext {
return {
model: null,
channel: {},
memory: {
scope: {
Expand Down
1 change: 0 additions & 1 deletion agent/tools/task_update.ts

This file was deleted.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"cmdk": "1.1.1",
"credit-card-type": "^10.3.0",
"drizzle-orm": "^0.45.2",
"eve": "https://pkg.eve.dev/59ec96cc99f65a80f7a2daf4ca5e2a0ad95455f2/eve.tgz",
"eve": "0.55.0",
"evlog": "2.28.1",
"lucide-react": "1.34.0",
"motion": "13.1.1",
Expand Down
File renamed without changes.
32 changes: 16 additions & 16 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,4 @@ allowBuilds:
sharp: true
patchedDependencies:
"@linqapp/chat-sdk-adapter@0.5.1": patches/@linqapp__chat-sdk-adapter@0.5.1.patch
# URL dependencies are patched by name; package.json pins the immutable source SHA.
eve: patches/eve@0.52.2+main.59ec96cc99f65a80.patch
"eve@0.55.0": patches/eve@0.55.0.patch
1 change: 0 additions & 1 deletion tests/agent-tool-boundaries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ describe("root and worker capability boundaries", () => {
"messaging.ts",
"schedules.ts",
"task_cancel.ts",
"task_update.ts",
"vault.ts",
"web_fetch.ts",
"web_search.ts",
Expand Down
1 change: 1 addition & 0 deletions tests/agent/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ describe("root agent model resolution", () => {

function scheduledWorkerContext(): DynamicResolveContext {
return {
model: null,
channel: { kind: "http" },
messages: [],
session: {
Expand Down
1 change: 1 addition & 0 deletions tests/agent/capabilities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ async function authoredCapabilities(authenticator: string) {

function dynamicContext(authenticator: string) {
return {
model: null,
channel: { kind: "channel:linq", metadata: {} },
messages: [],
session: {
Expand Down
47 changes: 47 additions & 0 deletions tests/agent/channels/channel-compatibility.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import type { AudienceInput } from "eve/channels";
import type * as CompiledChannel from "../../../node_modules/eve/dist/src/channel/compiled-channel.js";
import { describe, expect, it, vi } from "vitest";
import eve from "@agent/channels/eve";
import scheduledRun from "@agent/channels/scheduled-run";

const { isCompiledChannel } = await vi.importActual<typeof CompiledChannel>(
new URL("./channel/compiled-channel.js", import.meta.resolve("eve")).pathname
);

describe("compiled channel compatibility", () => {
it("preserves HTTP delivery hooks after wrapping its ownership routes", () => {
if (!isCompiledChannel(eve))
throw new Error("Expected a compiled channel.");

expect(eve.adapter["action.result"]).toBeTypeOf("function");
expect(eve.adapter["message.completed"]).toBeTypeOf("function");
expect(eve.adapter["turn.failed"]).toBeTypeOf("function");
});

it.each([
{ channel: eve, anonymousAudience: "public" },
{ channel: scheduledRun, anonymousAudience: "unknown" },
])(
"keeps authenticated conversations private",
({ channel, anonymousAudience }) => {
if (!isCompiledChannel(channel))
throw new Error("Expected a compiled channel.");
const audience = channel.adapter.instrumentation?.audience;
if (!audience) throw new Error("Expected a native audience classifier.");
const input = {
channel: { kind: "http" },
environment: "production",
mode: "conversation",
state: undefined,
auth: {
attributes: {},
authenticator: "scheduled-worker",
principalType: "user",
},
} satisfies AudienceInput<undefined>;

expect(audience(input)).toBe("private");
expect(audience({ ...input, auth: null })).toBe(anonymousAudience);
}
);
});
Loading