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: 7 additions & 3 deletions apps/brunch-agent/.oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"paths": [
{
"name": "@hashintel/petrinaut",
"message": "The Brunch server must remain independent of Petrinaut implementations."
"message": "The Brunch server must remain independent of Petrinaut UI. Import catalogs from @hashintel/petrinaut-core, not the editor package."
}
],
"patterns": [
Expand All @@ -35,8 +35,12 @@
"message": "The Brunch application must not depend on unpublished HASH packages."
},
{
"group": ["@hashintel/petrinaut/*", "@hashintel/petrinaut-*"],
"message": "The Brunch server must remain independent of Petrinaut implementations."
"group": [
"@hashintel/petrinaut/*",
"@hashintel/petrinaut-cli",
"@hashintel/petrinaut-cli/*"
],
"message": "The Brunch server must remain independent of Petrinaut UI and CLI. Import catalogs from @hashintel/petrinaut-core, not the editor package."
}
]
}
Expand Down
8 changes: 5 additions & 3 deletions apps/brunch-agent/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

This application belongs to the Brunch context rooted at
`../../libs/@hashintel/brunch-agent/`. Read that context's `AGENTS.md` and current `MISSION.md`
before changing this application. Consult `CONTEXT.md` or historical design documents only when a
before changing this application. If `MISSION.next.md` exists, it is a scratchpad for later
concerns and is not execution authority. Consult `CONTEXT.md` or historical design documents only when a
concrete question requires them; ADRs and specs are hypotheses, not implementation obligations.
HASH root guidance takes precedence.

The application composes the Flue runtime, HTTP routes, and the Brunch packages required by the
current mission. It must remain independent of Petrinaut implementation packages;
`apps/petrinaut-website` meets it through the AI SDK/HTTP transport.
current mission. It must remain independent of Petrinaut UI (`@hashintel/petrinaut`); it may import
published catalogs from `@hashintel/petrinaut-core` (for example user-guide page ids the panel
already executes). `apps/petrinaut-website` meets the editor through the AI SDK/HTTP transport.
40 changes: 33 additions & 7 deletions apps/brunch-agent/README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
# Brunch agent application

## Run the process-model panel locally
## Run the Petrinaut panel locally

From the repository root, make `ANTHROPIC_API_KEY` available in the environment and run:

```sh
yarn dev:brunch
```

The command starts the Brunch server at `http://127.0.0.1:4321` and the real Petrinaut website at
`http://127.0.0.1:4915`. The website proxies `/api/chat` to Brunch, where the panel runs the SDCPN
process-model elicitor.
The first step builds the Petrinaut libraries the panel imports (`dist/` and design-system
codegen). Then it starts the Brunch server at `http://127.0.0.1:4321` and the real Petrinaut
website at `http://127.0.0.1:4915`. The website proxies `/api/chat` to Brunch. The panel talks to one plain
Flue chat agent: streamed text and reasoning, one server `ping` tool, and the existing Petrinaut
`readPetrinautDoc` client tool. There is no elicitation, capture, or `brunch_ask` on this path.

Conversations persist in `apps/brunch-agent/.data-wipe-me/conversations.db`. Owned target documents
persist as per-document JSON files under `apps/brunch-agent/.data-wipe-me/target-documents/`.
`BRUNCH_DEV_DB_PATH` and `BRUNCH_DEV_TARGET_DOCUMENT_DIR` override those local paths.
Conversations persist in `apps/brunch-agent/.data-wipe-me/conversations.db`. `BRUNCH_DEV_DB_PATH`
overrides that local path. Flue history is the conversation log; the browser may cache messages
but reload hydrates from `GET /api/chat?id=`.

The mounted Flue URL `/agents/chat/:id` requires the same principal and conversation identity (`x-brunch-principal` and `x-brunch-conversation`) as `/api/chat`; the path id is the hash of those, not a bearer token.

Print a human-readable transcript of one conversation from that same Flue history (server already
running):

```sh
yarn workspace @apps/brunch-agent transcript -- --principal <key> --id <conversationId>
```

## Voice dock

A second input modality joins the same chat door. It is not a voice route and does not own
provider audio or session state.

| | |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| URL | `POST /api/chat` (and `GET /api/chat?id=` to hydrate) |
| Identity | `x-brunch-principal` plus body `id` (the conversation id). The server hashes those into the Flue instance id. |
| Initial turn | JSON `{ id, trigger: "submit-message", messages }` whose last user text part is the utterance. |
| Client-tool follow-up | Same POST, with `messageId` of the assistant message and completed client-tool parts (`providerExecuted` not true). Correlated by `toolCallId`. |
| Response | AI SDK UI-message stream (SSE). |

`OPTIONS /api/chat` is the CORS preflight for that same contract.
2 changes: 1 addition & 1 deletion apps/brunch-agent/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Elicitation harness β€” dev app</title>
<title>Brunch Flue chat</title>
</head>
<body>
<div id="root"></div>
Expand Down
13 changes: 4 additions & 9 deletions apps/brunch-agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,26 @@
"name": "@apps/brunch-agent",
"version": "0.0.0-private",
"private": true,
"description": "Remote Brunch server, local development loop, target gallery, and diagnostic probe surface.",
"description": "Remote Brunch server, local development loop, and Petrinaut /api/chat door.",
"license": "AGPL-3.0",
"type": "module",
"scripts": {
"baseline:harness": "node --experimental-strip-types ../../libs/@hashintel/brunch-agent/evaluations/protocols/process-model-elicitation/baseline/harness-run.ts",
"build": "vite build && vite build --config vite.client.config.ts",
"dev": "vite dev",
"fix:eslint": "oxlint --fix --type-aware --type-check --report-unused-disable-directives-severity=error .",
"lint:eslint": "oxlint --type-aware --type-check --report-unused-disable-directives-severity=error .",
"lint:tsc": "tsgo --noEmit",
"petrinaut:dev": "vite dev --config petrinaut-local.vite.config.ts",
"test:unit": "vitest run --config vitest.config.ts"
"test:unit": "vitest run --config vitest.config.ts",
"transcript": "node --experimental-strip-types src/transcript-cli.ts"
},
"dependencies": {
"@flue/opentelemetry": "2.0.3",
"@flue/react": "2.0.3",
"@flue/runtime": "2.0.3",
"@flue/sdk": "2.0.3",
"@hashintel/brunch-agent": "workspace:*",
"@hashintel/brunch-agent-binding-flue": "workspace:*",
"@hashintel/brunch-agent-plugin-gherkin": "workspace:*",
"@hashintel/brunch-agent-plugin-sdcpn": "workspace:*",
"@hashintel/brunch-agent-transport-aisdk": "workspace:*",
"@hashintel/petrinaut-core": "workspace:*",
"@opentelemetry/api": "1.9.1",
"hono": "4.13.2",
"react": "19.2.6",
Expand All @@ -34,8 +31,6 @@
"devDependencies": {
"@earendil-works/pi-ai": "0.83.0",
"@flue/vite": "2.0.3",
"@opentelemetry/sdk-trace-base": "2.9.0",
"@opentelemetry/sdk-trace-node": "2.9.0",
"@types/node": "22.18.13",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
Expand Down
36 changes: 36 additions & 0 deletions apps/brunch-agent/src/agent-ownership.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/** Hono middleware: the mounted Flue route enforces the same ownership rule as `/api/chat`. */

import { BRUNCH_PRINCIPAL_HEADER } from "@hashintel/brunch-agent-transport-aisdk/headers";

import { ownsFlueInstance } from "./conversation-identity.ts";
import { BRUNCH_CONVERSATION_HEADER } from "./conversation-payload.ts";

import type { MiddlewareHandler } from "hono";

export const agentOwnershipGuard = (mountPrefix: string): MiddlewareHandler => {
return async (context, next) => {
const principalKey = context.req.header(BRUNCH_PRINCIPAL_HEADER)?.trim();
const conversationId = context.req
.header(BRUNCH_CONVERSATION_HEADER)
?.trim();
if (
principalKey === undefined ||
principalKey.length === 0 ||
conversationId === undefined ||
conversationId.length === 0
) {
return context.json({ error: "unauthorized" }, 401);
}
const instanceId = context.req.path
.slice(mountPrefix.length)
.split("/")
.find((segment) => segment.length > 0);
if (
instanceId === undefined ||
!ownsFlueInstance(principalKey, conversationId, instanceId)
) {
return context.json({ error: "forbidden" }, 403);
}
return next();
};
};
32 changes: 32 additions & 0 deletions apps/brunch-agent/src/agents/chat-agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use agent";
/**
* One plain Flue chat agent for the Petrinaut panel throughline.
*
* No elicitation, capture, or plugin. The model can call a server-side ping
* and a browser-executed Petrinaut doc reader; Flue history is the session log.
*/

import { useModel, useTool } from "@flue/runtime";

import { ping } from "../tools/ping.ts";
import { readPetrinautDoc } from "../tools/read-petrinaut-doc.ts";

export const CHAT_MODEL_ID =
process.env["BRUNCH_CHAT_MODEL"] || "claude-haiku-4-5";

export function ChatAgent() {
useModel(`anthropic/${CHAT_MODEL_ID}`);
useTool(ping);
useTool(readPetrinautDoc);
return [
"You are a concise assistant inside the Petrinaut editor.",
"Call ping when you need to confirm the server tool path.",
"When the user asks how Petrinaut's UI works, call readPetrinautDoc.",
"A client-tool-result signal is JSON [{ toolCallId, toolName, output }]. Treat output as the browser's result for that call and continue helping the user.",
].join("\n");
}

/**
* Pinned, and never to be edited: conversation storage keys on this literal.
*/
ChatAgent.agentName = "brunch-chat-agent";
81 changes: 0 additions & 81 deletions apps/brunch-agent/src/agents/gherkin-elicitor.ts

This file was deleted.

66 changes: 0 additions & 66 deletions apps/brunch-agent/src/agents/sdcpn-elicitor.ts

This file was deleted.

Loading
Loading