Skip to content

call goose serve from tauri frontend via goose-acp client#8549

Merged
lifeizhou-ap merged 18 commits into
mainfrom
lifei/goose-serve-from-tauri-frontend
Apr 16, 2026
Merged

call goose serve from tauri frontend via goose-acp client#8549
lifeizhou-ap merged 18 commits into
mainfrom
lifei/goose-serve-from-tauri-frontend

Conversation

@lifeizhou-ap
Copy link
Copy Markdown
Collaborator

Summary

Calling goose serve via web socket from tauri frontend

Why

  • Tauri IPC doesn't stream natively, but the browser does
  • Frontend does not tie to Tauri, can be portable to other web clients
  • We can reuse the goose acp client

Changes

  • Move ACP protocol handling from Rust Tauri backend to TypeScript
  • Frontend talks directly to goose serve over WebSocket, bypassing the Rust middleware
  • Controlled by feature flag (USE_DIRECT_ACP)
  • functions in acp.ts wired through the new path using USE_DIRECT_ACP feature flag
  • Old Rust path still works when flag is off

TODO

  • Cleanup and remove the feature toggle
  • ui/acp (@aaif/goose-acp) upgrades to be compatible with @agentclientprotocol/sdk@0.19.0 (latest version)
  • goose serve includes messageId on each ContentChunk notification
  • move create websocket stream logic to @aaif/goose-acp

Testing

Manual

Related Issues

Relates to #ISSUE_ID
Discussion: LINK (if any)

Screenshots/Demos (for UX changes)

Before:

After:

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2cee286d7b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +27 to +29
const existing = prepared.get(key) ?? prepared.get(sessionId);
if (existing) {
return existing.gooseSessionId;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reconcile provider before reusing prepared ACP sessions

prepareSession returns an existing prepared session unconditionally, even when the caller passes a different providerId. In direct ACP mode this means provider switches in the UI are ignored for already-prepared sessions, so prompts can execute on the old/default provider and model updates can fail due to provider/model mismatch.

Useful? React with 👍 / 👎.

Comment thread ui/goose2/src/shared/api/acp.ts Outdated
Comment on lines +109 to +110
if (USE_DIRECT_ACP) {
return directAcp.setModel(sessionId, modelId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Set model using the mapped Goose session ID

In direct mode acpSetModel sends the local UI sessionId directly, but drafts and persona-scoped sessions may be backed by a different Goose session ID created in prepareSession. When those IDs differ, model updates target a non-existent ACP thread and the send flow fails before prompting.

Useful? React with 👍 / 👎.

Comment thread ui/goose2/src/shared/api/acp.ts Outdated
Comment on lines +207 to +208
if (USE_DIRECT_ACP) {
await directAcp.cancelSession(sessionId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Cancel direct ACP prompts via tracked session mapping

acpCancelSession ignores the tracked local→Goose session mapping (and the passed personaId) and cancels using the local sessionId. For draft/persona streams where the ACP session ID differs, stop requests report success but the backend generation continues and keeps streaming chunks.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d4c917a0a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ui/goose2/src/shared/api/acp.ts Outdated
Comment on lines +166 to +167
await directAcp.loadSession(gooseSessionId, effectiveWorkingDir);
sessionTracker.registerSession(sessionId, gooseSessionId, "goose", effectiveWorkingDir);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Register session mapping before loading replay

In direct ACP mode this function registers the local→Goose session mapping after loadSession resolves, but replay notifications are handled during loadSession. The notification handler resolves IDs via getLocalSessionId(gooseSessionId) ?? gooseSessionId, so when sessionId and gooseSessionId differ (the acpSessionId path used in AppShell), replay chunks are routed to the wrong session key and history can load into the wrong thread or appear missing. Bind/register the mapping before invoking directAcp.loadSession.

Useful? React with 👍 / 👎.

Comment on lines +179 to +180
if (USE_DIRECT_ACP) {
return directAcp.exportSession(sessionId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve Goose session ID before direct export

The direct branch exports using the UI-local sessionId, but prepareSession can create a different Goose session ID via newSession and store that only in acpSessionTracker. For newly promoted draft chats, this makes Session History export fail with "not found" until sessions are reloaded from ACP. Look up the mapped Goose ID (or persisted acpSessionId) before calling exportSession.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 42abb685ef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +49 to +50
const response = await acpApi.newSession(workingDir);
gooseSessionId = response.sessionId;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Set provider on newly created direct ACP sessions

When prepareSession falls back to newSession, the requested providerId is never applied before returning, yet the tracker caches that provider as if it were active. On first-send from a new draft (or any unmapped session), prompts can run on Goose’s default provider and model selection can fail if the selected model belongs to another provider. Apply the provider immediately after newSession (or pass provider metadata into session creation) before storing the prepared entry.

Useful? React with 👍 / 👎.

? resolveEffectiveWorkingDir(null, await getHomeDir())
: undefined);
await acpLoadSession(sessionId, gooseSessionId, workingDir);
useChatStore.getState().setSessionLoading(sessionId, false);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep replay mode active until load notifications are drained

Clearing loadingSessionIds immediately after acpLoadSession resolves can terminate replay buffering before all replay notifications arrive. In direct ACP mode, loadSession returns from the RPC call without an explicit replay-complete handshake in this path, so late chunks are processed as live events (or dropped by live-stream guards), which can produce missing/truncated history when opening a session.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For direct ACP, I don’t think this is a real issue. I traced the raw websocket traffic for session/load and saw the expected ACP ordering: replay session/update notifications arrived before the matching session/load response, and no replay updates arrived after the response.

In this path, await acpLoadSession(...) is the completion boundary handled by the ACP SDK’s request/response lifecycle, so clearing loadingSessionIds after it resolves is consistent with the acp spec.

Comment thread ui/goose2/src/shared/api/acp.ts Outdated
sessionId: string,
): Promise<AcpSessionInfo> {
if (USE_DIRECT_ACP) {
return directAcp.forkSession(sessionId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve Goose session ID before direct duplicate

The direct duplicate path forks using the UI-local sessionId instead of the tracked Goose session ID. For sessions created via newSession (where local and Goose IDs differ), this sends forkSession to a non-existent backend session and duplicate fails until a full reload rehydrates IDs. Use acpSessionTracker (or persisted acpSessionId) before invoking forkSession.

Useful? React with 👍 / 👎.

Comment thread ui/goose2/package.json
"test-driver": "tsx tests/app-e2e/lib/test-driver-cli.ts"
},
"dependencies": {
"@aaif/goose-acp": "^0.16.0",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably be "@aaif/goose-sdk": "workspace:*",, assuming the pnpm workspace is all set up correctly here

@lifeizhou-ap lifeizhou-ap enabled auto-merge April 16, 2026 07:28
@lifeizhou-ap lifeizhou-ap added this pull request to the merge queue Apr 16, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 257f9fdf00

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const messageId = crypto.randomUUID();
setActiveMessageId(gooseSessionId, messageId);

await directAcp.prompt(gooseSessionId, content);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Apply provider before direct prompt dispatch

The direct ACP branch sends prompt without reconciling providerId, so this path can run on a stale backend provider when the session was previously prepared under a different provider. This is reachable because useChat only calls acpPrepareSession for drafts or when a model ID is present, so provider changes on existing sessions can skip re-prepare and still hit this call, producing responses from the wrong provider/model combination.

Useful? React with 👍 / 👎.

Comment on lines +42 to +45
try {
await acpApi.loadSession(sessionId, workingDir);
gooseSessionId = sessionId;
} catch {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict new-session fallback to not-found load errors

This swallows all loadSession failures and immediately creates a new session, which can silently fork history when the failure is transient (connection drop, backend error, invalid cwd) rather than “session not found.” In those cases users can be moved to a fresh thread with lost continuity instead of seeing the real error, so the fallback should only trigger for explicit not-found responses.

Useful? React with 👍 / 👎.

Comment on lines +53 to +57
const exported = await exportSession(sessionId);
const result = searchSession(sessionId, exported, trimmed);
if (result) results.push(result);
} catch {
// skip sessions that fail to export
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Translate local IDs before export-based session search

Search exports each provided sessionId directly and silently skips failures, but direct ACP keeps local→Goose remapping in acpSessionTracker for promoted draft/persona sessions. When callers pass local IDs (e.g., sessions without persisted acpSessionId yet), exports fail and those sessions are omitted from search results until a reload rehydrates IDs, causing incomplete or inconsistent search behavior.

Useful? React with 👍 / 👎.

Merged via the queue into main with commit 50ac3dc Apr 16, 2026
24 checks passed
@lifeizhou-ap lifeizhou-ap deleted the lifei/goose-serve-from-tauri-frontend branch April 16, 2026 07:41
jh-block added a commit to sunilkumarvalmiki/goose that referenced this pull request Apr 16, 2026
…l-placeholder

* origin/main: (64 commits)
  fix: expand tool calls by default when Response Style is Detailed (aaif-goose#8478)
  fix: create logs dir before writing llm request log (aaif-goose#8522)
  fix: enable token usage tracking and configurable stream timeout for Ollama provider (aaif-goose#8493)
  fix tauri-plugin-dialog version constraint to match other plugins (aaif-goose#8542)
  call goose serve from tauri frontend via goose-acp client (aaif-goose#8549)
  failed the script when bundle:default fails and cleanup "alpha"  (aaif-goose#8580)
  pass globally unique conversation identifier as sessionId in databricks api call (aaif-goose#8576)
  fix: use sqlx chrono decode for thread timestamps instead of manual parsing (aaif-goose#8575)
  docs: remove stale gemini-acp references (aaif-goose#8572)
  show individual untracked files in git changes widget (aaif-goose#8574)
  fix: update publishing flow to include new sdk dir (aaif-goose#8573)
  fix: remove double border on content in chat (aaif-goose#8545)
  chore(goose2): `just goose2 <command>` with the addition of `just goose2 kill` (aaif-goose#8570)
  Lifei/oltp data (aaif-goose#8458)
  Sidebar polish: search copy, dividers, project reorder, fix DnD (aaif-goose#8568)
  remove the workflow_dispatch check (aaif-goose#8563)
  fix: one more rename (aaif-goose#8562)
  fix(desktop): accept self-signed certs from configured external goosed host (aaif-goose#8400)
  alexhancock/npm-bumps (aaif-goose#8557)
  Remove npm publish from release for now (aaif-goose#8558)
  ...
michaelneale added a commit that referenced this pull request Apr 17, 2026
* main: (37 commits)
  polish: refine sidebar activity indicators, add placeholder token, and tidy search field (#8606)
  feat: add /edit command to cli for on-demand prompt editing (#8566)
  docs(mcp): add Rendex MCP Server extension tutorial (#8541)
  Lifei/delete tauri backend acp (#8582)
  chore: set goose binaries as executable in package.json (#8589)
  feat: add Novita AI as declarative provider (#8432)
  feat: add Kimi Code provider with OAuth device flow authentication (#8466)
  fix: chat loading-state model placeholder (#8431)
  fix: expand tool calls by default when Response Style is Detailed (#8478)
  fix: create logs dir before writing llm request log (#8522)
  fix: enable token usage tracking and configurable stream timeout for Ollama provider (#8493)
  fix tauri-plugin-dialog version constraint to match other plugins (#8542)
  call goose serve from tauri frontend via goose-acp client (#8549)
  failed the script when bundle:default fails and cleanup "alpha"  (#8580)
  pass globally unique conversation identifier as sessionId in databricks api call (#8576)
  fix: use sqlx chrono decode for thread timestamps instead of manual parsing (#8575)
  docs: remove stale gemini-acp references (#8572)
  show individual untracked files in git changes widget (#8574)
  fix: update publishing flow to include new sdk dir (#8573)
  fix: remove double border on content in chat (#8545)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants