feat: replace cloud session sharing with local export#234
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
🧰 Additional context used📓 Path-based instructions (4)packages/opencode/**/*.ts📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)
Files:
packages/opencode/test/**/*.test.{ts,tsx}📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)
Files:
packages/app/**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (packages/app/AGENTS.md)
Files:
packages/desktop-electron/src/**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (packages/desktop-electron/AGENTS.md)
Files:
🧠 Learnings (43)📓 Common learnings📚 Learning: 2026-04-25T12:52:47.074ZApplied to files:
📚 Learning: 2026-04-22T09:32:54.556ZApplied to files:
📚 Learning: 2026-04-20T14:36:31.032ZApplied to files:
📚 Learning: 2026-04-20T14:36:31.032ZApplied to files:
📚 Learning: 2026-04-20T14:36:31.032ZApplied to files:
📚 Learning: 2026-04-20T14:36:31.032ZApplied to files:
📚 Learning: 2026-04-20T14:36:31.032ZApplied to files:
📚 Learning: 2026-04-20T14:36:04.113ZApplied to files:
📚 Learning: 2026-04-22T08:49:47.800ZApplied to files:
📚 Learning: 2026-04-20T14:36:31.032ZApplied to files:
📚 Learning: 2026-04-25T12:52:32.462ZApplied to files:
📚 Learning: 2026-04-23T08:51:00.819ZApplied to files:
📚 Learning: 2026-04-20T14:36:21.288ZApplied to files:
📚 Learning: 2026-04-20T14:36:21.288ZApplied to files:
📚 Learning: 2026-04-25T12:52:35.671ZApplied to files:
📚 Learning: 2026-04-25T12:52:36.999ZApplied to files:
📚 Learning: 2026-04-20T14:36:21.288ZApplied to files:
📚 Learning: 2026-04-23T08:51:04.230ZApplied to files:
📚 Learning: 2026-04-20T14:36:04.113ZApplied to files:
📚 Learning: 2026-04-20T14:36:04.113ZApplied to files:
📚 Learning: 2026-04-24T03:51:54.050ZApplied to files:
📚 Learning: 2026-04-20T14:36:31.032ZApplied to files:
📚 Learning: 2026-04-20T14:36:31.032ZApplied to files:
📚 Learning: 2026-04-20T14:36:04.113ZApplied to files:
📚 Learning: 2026-04-20T14:36:31.032ZApplied to files:
📚 Learning: 2026-04-24T00:02:53.315ZApplied to files:
📚 Learning: 2026-04-20T14:36:21.288ZApplied to files:
📚 Learning: 2026-04-20T14:36:21.288ZApplied to files:
📚 Learning: 2026-04-20T14:36:21.288ZApplied to files:
📚 Learning: 2026-04-20T14:36:21.288ZApplied to files:
📚 Learning: 2026-04-20T14:36:21.288ZApplied to files:
📚 Learning: 2026-04-23T07:23:23.849ZApplied to files:
📚 Learning: 2026-04-23T15:10:21.635ZApplied to files:
📚 Learning: 2026-04-20T14:36:21.288ZApplied to files:
📚 Learning: 2026-04-24T13:03:10.835ZApplied to files:
📚 Learning: 2026-04-25T09:19:30.734ZApplied to files:
📚 Learning: 2026-04-20T14:36:21.288ZApplied to files:
📚 Learning: 2026-04-20T14:36:21.288ZApplied to files:
📚 Learning: 2026-04-20T14:36:21.288ZApplied to files:
📚 Learning: 2026-04-20T14:36:21.288ZApplied to files:
📚 Learning: 2026-04-20T14:36:04.113ZApplied to files:
📚 Learning: 2026-04-20T14:36:21.288ZApplied to files:
🔇 Additional comments (16)
📝 WalkthroughWalkthroughAdds local session export: new Platform/exportSession API, renderer + preload + IPC + main handling to fetch session export from server, save to user-selected path, server-side GET /:sessionID/export endpoint, a comprehensive Export module with sanitization, and feature-gating to disable cloud sharing. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User (Renderer)
participant UI as Message Timeline (Renderer)
participant Preload as Preload API (contextBridge)
participant Main as Electron Main (IPC)
participant Server as Backend Server
participant FS as File System
User->>UI: Click "Export" option
UI->>UI: Build sanitized default filename
UI->>Preload: api.exportSession(sessionID, directory, defaultName)
Preload->>Main: ipcRenderer.invoke("export-session", args)
Main->>Main: validate args, getServerReadyData()
Main->>Server: GET /session/<id>/export
Server-->>Main: return export JSON body
Main->>Main: open save dialog (JSON filter)
User->>Main: choose path / confirm
Main->>FS: write file (UTF-8)
FS-->>Main: write success
Main-->>Preload: { ok: true, path }
Preload-->>UI: result
UI->>User: show success or error toast
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Code Review
This pull request implements a local session export feature, replacing cloud sharing in certain environments like PawWork. It introduces a new Export service to handle session tree construction, statistics, and data sanitization, alongside Electron IPC handlers for file saving. A CloudShareGate was also added to manage the availability of cloud sharing. Feedback highlights the need for proper percent-decoding of data URLs per RFC 2397, more defensive property access when handling Effect causes, and a more robust approach to path discovery than using __dirname.
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/opencode/test/share/session-pawwork-fail-closed.test.ts (1)
84-88: 🧹 Nitpick | 🔵 TrivialRemove unused imports instead of suppressing warnings.
The
CauseandOptionimports are not used in the test logic. If they're intended for future tests, add them when needed. Keeping unused imports withvoidsuppression adds noise.🧹 Remove unused imports
-import { Effect, Cause, Layer, Option } from "effect" +import { Effect, Layer } from "effect" ... -// Suppress unused-import warning for Cause/Option which are exported from this test surface -// to make the failure-extraction pattern reusable by other share tests later. -void Cause -void Option🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/opencode/test/share/session-pawwork-fail-closed.test.ts` around lines 84 - 88, Remove the unused imports and their suppression lines: delete the import entries for Cause and Option and the two lines "void Cause" and "void Option" (they're the unused-symbol suppression) so the test file no longer contains dead imports; if you plan to use Cause/Option later, add them back when needed in the functions/classes that reference them.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/desktop-electron/src/main/ipc.ts`:
- Around line 238-263: The ipcMain handler for "export-session" currently calls
deps.getServerReadyData() without error handling; wrap the await
deps.getServerReadyData() call in a try-catch inside the "export-session"
handler (the function passed to ipcMain.handle) and on failure return a graceful
IPC error result (e.g., { ok: false, error: (err as Error).message } or a
specific code like "server_unavailable") instead of allowing a thrown rejection
to bubble up; keep the subsequent logic using the local server variable
unchanged and ensure typings remain compatible with the existing
fetchExport(server, directory, sessionID) call.
In `@packages/desktop-electron/src/main/server-client.ts`:
- Around line 15-23: The buildAuthHeader function currently falls back to the
literal username "opencode" when server.username is null but server.password is
provided; add a concise inline comment above or inside buildAuthHeader (or next
to the Authorization line) explaining this is an intentional
compatibility/convention fallback for cases where only a password is supplied
(reference function name buildAuthHeader and type ServerReadyData) so future
readers know this behavior is deliberate.
In `@packages/opencode/src/server/instance/session.ts`:
- Around line 487-492: The catch block in Export.session currently relies on
fragile string matching of err.message; instead import and use the NotFoundError
class from "@/storage/db" and check with "err instanceof NotFoundError"
(matching the pattern used in middleware.ts/AppRuntime.runPromise) so that when
Session.get throws NotFoundError you return c.json({ error: "session_not_found",
sessionID }, 404); otherwise rethrow the error.
In `@packages/opencode/src/session/export.ts`:
- Around line 72-85: extractReasonFromCause currently inspects a private
"reasons" array on the Cause which is fragile; replace the manual
cast-and-iterate logic with Effect's Cause utilities by using
Cause.failures(cause) and Cause.defects(cause) (or their TypeScript equivalents)
to collect failure/defect payloads, then derive a string by checking for string
payloads, .message, or _tag on those collected values; update
extractReasonFromCause to call these utilities (instead of accessing reasons)
and return the first meaningful string or "unknown".
- Around line 21-28: The hashFile function currently uses fs.readFile directly;
replace this raw fs usage with the FileSystem.FileSystem service per guidelines:
obtain the FileSystem instance (e.g., via dependency injection or the existing
Effect service pattern used in this module) and call its readFile API inside
hashFile, then compute the sha256 from the returned buffer and preserve the same
try/catch behavior returning "sha256:..." on success or undefined on error;
update imports to remove fs/promises and reference FileSystem.FileSystem instead
to locate the change around the hashFile function.
In `@packages/opencode/src/share/session.ts`:
- Line 27: Replace the direct gate access (e.g., the yield*
ShareRuntime.CloudShareGate usage in this file) with the shared effect exported
from runtime by calling ShareRuntime.ensureEnabled so this module reuses the
centralized gate/failure semantics; update all similar occurrences in this file
(the block around the current const gate = yield* ShareRuntime.CloudShareGate
and the code covering lines ~30–46) to invoke ShareRuntime.ensureEnabled instead
of duplicating the gate check.
In `@packages/opencode/src/share/share-next.ts`:
- Around line 292-293: The disabled branch in Effect.fn("ShareNext.create")
currently returns an empty Share ({ id: "", url: "", secret: ""}) which hides
the failure; update the disabled/gate.isEnabled() path to signal failure
explicitly instead of a fake Share—either return a widened type
(nullable/Option/Result) and return null/None/Error for this branch, or throw a
clear error (e.g., throw new Error("sharing disabled")) so callers of
create(SessionID) can distinguish disabled from a valid Share; adjust the
function signature and callers of create accordingly (or document the thrown
error) to handle the new failure shape.
In `@packages/opencode/test/share/share-next.test.ts`:
- Line 16: Add a disabled-gate test case in the share-next.test harness: when
instantiating ShareRuntime (or the test harness that provides CloudShareGate),
pass an override CloudShareGate with isEnabled: () => false and run the same
create/remove flows; assert that create and remove do not call the HTTP layer
(mock fetch or the network mock) and do not persist any share rows (verify
DB/store mock methods were not called). Specifically target the ShareRuntime
usage and the create/remove calls so the disabled branch is exercised alongside
the existing enabled tests.
---
Outside diff comments:
In `@packages/opencode/test/share/session-pawwork-fail-closed.test.ts`:
- Around line 84-88: Remove the unused imports and their suppression lines:
delete the import entries for Cause and Option and the two lines "void Cause"
and "void Option" (they're the unused-symbol suppression) so the test file no
longer contains dead imports; if you plan to use Cause/Option later, add them
back when needed in the functions/classes that reference them.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 263acca9-5e4f-4b59-ac2b-95cb3c677697
📒 Files selected for processing (22)
packages/app/src/context/platform.tsxpackages/app/src/i18n/en.tspackages/app/src/i18n/zh.tspackages/app/src/pages/session/message-timeline.tsxpackages/desktop-electron/src/main/index.tspackages/desktop-electron/src/main/ipc.tspackages/desktop-electron/src/main/server-client.tspackages/desktop-electron/src/preload/index.tspackages/desktop-electron/src/preload/types.tspackages/desktop-electron/src/renderer/index.tsxpackages/opencode/src/cli/cmd/export.tspackages/opencode/src/effect/app-runtime.tspackages/opencode/src/server/instance/session.tspackages/opencode/src/session/export.tspackages/opencode/src/session/message-v2.tspackages/opencode/src/share/runtime.tspackages/opencode/src/share/session.tspackages/opencode/src/share/share-next.tspackages/opencode/test/cli/export.test.tspackages/opencode/test/session/export.test.tspackages/opencode/test/share/session-pawwork-fail-closed.test.tspackages/opencode/test/share/share-next.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: unit-windows-opencode-server-tools
- GitHub Check: unit-windows-opencode-config-project
- GitHub Check: unit-windows-desktop
- GitHub Check: unit-windows-opencode-session
- GitHub Check: unit-windows-app
- GitHub Check: unit-opencode
- GitHub Check: unit-desktop
- GitHub Check: typecheck
- GitHub Check: smoke-macos-arm64
- GitHub Check: e2e-artifacts
- GitHub Check: analyze-js-ts
🧰 Additional context used
📓 Path-based instructions (5)
packages/app/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/app/AGENTS.md)
Always prefer
createStoreover multiplecreateSignalcalls in SolidJS
Files:
packages/app/src/i18n/zh.tspackages/app/src/i18n/en.tspackages/app/src/context/platform.tsxpackages/app/src/pages/session/message-timeline.tsx
packages/desktop-electron/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/desktop-electron/AGENTS.md)
Renderer process should only call
window.apifromsrc/preload
Files:
packages/desktop-electron/src/renderer/index.tsxpackages/desktop-electron/src/main/index.tspackages/desktop-electron/src/preload/index.tspackages/desktop-electron/src/preload/types.tspackages/desktop-electron/src/main/ipc.tspackages/desktop-electron/src/main/server-client.ts
packages/opencode/**/*.ts
📄 CodeRabbit inference engine (packages/opencode/AGENTS.md)
packages/opencode/**/*.ts: UseEffect.gen(function* () { ... })for Effect composition
UseEffect.fn("Domain.method")for named/traced effects andEffect.fnUntracedfor internal helpers; these accept pipeable operators as extra arguments to avoid unnecessary outer.pipe()wrappers
UseEffect.callbackfor callback-based APIs
PreferDateTime.nowAsDateovernew Date(yield* Clock.currentTimeMillis)when you need aDatein Effect code
UseSchema.Classfor multi-field data in Effect schemas
Use branded schemas (Schema.brand) for single-value types in Effect
UseSchema.TaggedErrorClassfor typed errors in Effect schemas
UseSchema.Defectinstead ofunknownfor defect-like causes in Effect code
InEffect.gen/Effect.fn, preferyield* new MyError(...)overyield* Effect.fail(new MyError(...))for direct early-failure branches
UsemakeRuntimefromsrc/effect/run-service.tsfor all services; it returns{ runPromise, runFork, runCallback }backed by a sharedmemoMapthat deduplicates layers
UseInstanceStatefromsrc/effect/instance-state.tsfor per-directory or per-project state that needs per-instance cleanup; do work directly in theInstanceState.makeclosure whereScopedCachehandles run-once semantics
UseEffect.addFinalizerorEffect.acquireReleaseinside theInstanceState.makeclosure for cleanup (subscriptions, process teardown, etc.)
UseEffect.forkScopedinside theInstanceState.makeclosure for background stream consumers — the fiber is interrupted when the instance is disposed
PreferFileSystem.FileSysteminstead of rawfs/promisesfor effectful file I/O in Effect services
PreferChildProcessSpawner.ChildProcessSpawnerwithChildProcess.make(...)instead of custom process wrappers in Effect services
PreferHttpClient.HttpClientinstead of rawfetchin Effect services
PreferPath.Path,Config,Clock, andDateTimeservices when those concerns are already inside Effect code
For backgroun...
Files:
packages/opencode/src/session/message-v2.tspackages/opencode/src/effect/app-runtime.tspackages/opencode/test/share/share-next.test.tspackages/opencode/src/share/share-next.tspackages/opencode/src/cli/cmd/export.tspackages/opencode/test/cli/export.test.tspackages/opencode/src/server/instance/session.tspackages/opencode/src/share/runtime.tspackages/opencode/src/share/session.tspackages/opencode/test/share/session-pawwork-fail-closed.test.tspackages/opencode/test/session/export.test.tspackages/opencode/src/session/export.ts
packages/opencode/test/**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (packages/opencode/test/AGENTS.md)
packages/opencode/test/**/*.test.{ts,tsx}: Use thetmpdirfunction fromfixture/fixture.tsto create temporary directories for tests with automatic cleanup. Useawait usingsyntax to ensure automatic cleanup when the variable goes out of scope.
When using thetmpdirfunction with git repository support, pass thegit: trueoption to initialize a git repo with a root commit.
Use theconfigoption intmpdirto write anopencode.jsonconfig file during test setup by passing a partial Config.Info object.
Use theinitoption intmpdirto define custom setup functions that can return extra data accessible viatmp.extra, and use thedisposeoption for custom cleanup logic.
UsetestEffect(...)fromtest/lib/effect.tsfor tests that exercise Effect services or Effect-based workflows.
Useit.effect(...)when the test should run withTestClockandTestConsole. Useit.live(...)when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Prefer Effect-aware helpers fromfixture/fixture.tsover building manual runtimes in tests: usetmpdirScoped()for scoped temp directories,provideInstance(dir)(effect)for low-level binding without directory creation,provideTmpdirInstance(...)for single temp instance binding, orprovideTmpdirServer(...)for tests that also need the test LLM server.
Defineconst it = testEffect(...)near the top of the test file and keep the test body insideEffect.gen(function* () { ... }). Yield services directly withyield* MyService.Serviceoryield* MyTool.
Avoid customManagedRuntime,attach(...), or ad hocrun(...)wrappers in Effect tests whentestEffect(...)already provides the runtime.
When a test needs instance-local state, preferprovideTmpdirInstance(...)orprovideInstance(...)over manualInstance.provide(...)inside Promise-style tests.
Files:
packages/opencode/test/share/share-next.test.tspackages/opencode/test/cli/export.test.tspackages/opencode/test/share/session-pawwork-fail-closed.test.tspackages/opencode/test/session/export.test.ts
packages/desktop-electron/src/main/ipc.ts
📄 CodeRabbit inference engine (packages/desktop-electron/AGENTS.md)
Main process should register IPC handlers in
src/main/ipc.ts
Files:
packages/desktop-electron/src/main/ipc.ts
🧠 Learnings (35)
📓 Common learnings
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/ui/src/theme/context.tsx:11-16
Timestamp: 2026-04-22T09:32:58.310Z
Learning: In Astro-Han/pawwork (`packages/ui/src/theme/context.tsx` and related files), the renaming of localStorage theme keys from `opencode-*` to `pawwork-*` (THEME_ID, COLOR_SCHEME, THEME_CSS_LIGHT, THEME_CSS_DARK) is intentional and should NOT include a migration path from the old keys. Migrating would re-couple PawWork and OpenCode browser storage namespaces, which the PR is explicitly designed to avoid. A reset to the PawWork default theme on upgrade is acceptable by design.
📚 Learning: 2026-04-22T05:32:29.012Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 98
File: packages/desktop-electron/src/main/menu-labels.ts:1-2
Timestamp: 2026-04-22T05:32:29.012Z
Learning: In Astro-Han/pawwork, the app i18n layer (`packages/app/src/i18n/`) only contains `en.ts` and `zh.ts`, and `normalizeLocale` (in `packages/app/src/context/language.tsx`) only returns `"en"` or `"zh"`. The desktop `MenuLocale = "en" | "zh"` union in `packages/desktop-electron/src/main/menu-labels.ts` is intentionally limited to these two locales and is not a broader restriction — do not flag it as overly restrictive or suggest adding other locales.
Applied to files:
packages/app/src/i18n/zh.tspackages/app/src/i18n/en.ts
📚 Learning: 2026-04-24T17:08:44.294Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 224
File: packages/app/src/i18n/zh.ts:0-0
Timestamp: 2026-04-24T17:08:44.294Z
Learning: In Astro-Han/pawwork PR `#224`, the first-occurrence `PawWork 爪印` branding rule originally specified in issue `#196` was superseded by an updated Chinese-branding spec. On all zh UI surfaces in `packages/app/src/i18n/zh.ts` (e.g., `dialog.model.unpaid.freeModels.title`, `session.new.subtitle`, `sidebar.gettingStarted.line1`), the correct and intentional target is fully localized `爪印` branding — no `PawWork` prefix. Do NOT flag these strings as missing the first-occurrence `PawWork 爪印` rule in future reviews.
Applied to files:
packages/app/src/i18n/zh.ts
📚 Learning: 2026-04-23T07:23:23.849Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 180
File: packages/app/src/components/session/session-new-view.tsx:13-18
Timestamp: 2026-04-23T07:23:23.849Z
Learning: In pawwork (Astro-Han/pawwork), prefer using `createStore` instead of multiple `createSignal` calls only when the signals represent **coupled** object state that is updated together (i.e., there is at least one shared batch-update site where the state is changed in the same transaction). If the state fields are **independent** and are mutated by separate handlers (e.g., one handler updates only `selectedSkill` while another updates only `mode`), keep them as individual `createSignal` calls—using `createStore` for truly independent fields adds boilerplate without behavioral benefit.
Applied to files:
packages/app/src/i18n/zh.tspackages/app/src/i18n/en.tspackages/app/src/context/platform.tsxpackages/app/src/pages/session/message-timeline.tsx
📚 Learning: 2026-04-23T15:10:21.635Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 191
File: packages/app/src/components/session/pawwork-skill-meta.ts:38-39
Timestamp: 2026-04-23T15:10:21.635Z
Learning: This repo configures Tailwind v4 with `--color-*: initial`, which effectively breaks standard Tailwind palette utilities (e.g., `text-violet-500` can resolve to no CSS variable and render as a no-op/black). For brand/accent colors that are not backed by semantic design tokens, use inline styles with the exact hex value (e.g., `style={{ color: '#8B5FBF' }}` / `homeIconStyle: { color: '#8B5FBF' }`) and add a short comment explaining that Tailwind palette utilities won’t work due to the `--color-*: initial` setup. Do not suggest replacing these inline hex colors with Tailwind palette classes anywhere in this repo.
Applied to files:
packages/app/src/i18n/zh.tspackages/app/src/i18n/en.tspackages/app/src/context/platform.tsxpackages/app/src/pages/session/message-timeline.tsx
📚 Learning: 2026-04-20T14:36:08.774Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/desktop-electron/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:08.774Z
Learning: Applies to packages/desktop-electron/src/**/*.{ts,tsx,js,jsx} : Renderer process should only call `window.api` from `src/preload`
Applied to files:
packages/desktop-electron/src/renderer/index.tsxpackages/desktop-electron/src/preload/index.tspackages/desktop-electron/src/preload/types.tspackages/desktop-electron/src/main/ipc.ts
📚 Learning: 2026-04-25T09:19:30.734Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/index.ts:537-537
Timestamp: 2026-04-25T09:19:30.734Z
Learning: In Astro-Han/pawwork (packages/desktop-electron/src/main/), follow the IPC registration convention: the bootstrap entry (packages/desktop-electron/src/main/index.ts) should directly call each module’s exported register*Ipc() function. Do not route/centralize these sub-module IPC registrations through src/main/ipc.ts. Keep sub-module IPC features cohesive (e.g., src/main/ipc/about.ts should own its types/helpers and expose register*Ipc()), and allow index.ts to aggregate by calling each register*Ipc() directly.
Applied to files:
packages/desktop-electron/src/main/index.tspackages/desktop-electron/src/main/ipc.tspackages/desktop-electron/src/main/server-client.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `makeRuntime` from `src/effect/run-service.ts` for all services; it returns `{ runPromise, runFork, runCallback }` backed by a shared `memoMap` that deduplicates layers
Applied to files:
packages/opencode/src/effect/app-runtime.tspackages/opencode/test/share/share-next.test.tspackages/opencode/src/share/runtime.tspackages/opencode/src/share/session.tspackages/opencode/test/share/session-pawwork-fail-closed.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Avoid custom `ManagedRuntime`, `attach(...)`, or ad hoc `run(...)` wrappers in Effect tests when `testEffect(...)` already provides the runtime.
Applied to files:
packages/opencode/src/effect/app-runtime.tspackages/opencode/test/share/share-next.test.tspackages/opencode/src/share/runtime.tspackages/opencode/test/share/session-pawwork-fail-closed.test.ts
📚 Learning: 2026-04-22T09:32:54.556Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/opencode/test/provider/provider.test.ts:64-85
Timestamp: 2026-04-22T09:32:54.556Z
Learning: In `packages/opencode/test/provider/provider.test.ts`, the file intentionally uses AppRuntime-based async helpers (`run`, `list`, `getProvider`, etc.) rather than `testEffect(...)` for all tests. Converting individual tests to `testEffect` while leaving the rest on the async pattern would create internal inconsistency. A full harness migration of this file is the right approach if the pattern needs to change, but that should be a separate PR.
Applied to files:
packages/opencode/src/effect/app-runtime.tspackages/opencode/test/share/share-next.test.tspackages/opencode/src/share/runtime.tspackages/opencode/test/share/session-pawwork-fail-closed.test.tspackages/opencode/test/session/export.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Prefer Effect-aware helpers from `fixture/fixture.ts` over building manual runtimes in tests: use `tmpdirScoped()` for scoped temp directories, `provideInstance(dir)(effect)` for low-level binding without directory creation, `provideTmpdirInstance(...)` for single temp instance binding, or `provideTmpdirServer(...)` for tests that also need the test LLM server.
Applied to files:
packages/opencode/src/effect/app-runtime.tspackages/opencode/test/share/share-next.test.tspackages/opencode/src/share/runtime.tspackages/opencode/test/share/session-pawwork-fail-closed.test.tspackages/opencode/test/session/export.test.ts
📚 Learning: 2026-04-25T09:19:30.734Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 231
File: packages/desktop-electron/src/main/index.ts:537-537
Timestamp: 2026-04-25T09:19:30.734Z
Learning: In Astro-Han/pawwork (`packages/desktop-electron/src/main/`), the convention for IPC registration is that `index.ts` (the bootstrap entry) directly calls each registration function. `registerIpcHandlers({...})` in `src/main/ipc.ts` is a single fat options-bag with inline handler bodies. New, cohesive IPC features (e.g., About) are placed in sub-modules like `src/main/ipc/about.ts`, which own their own exported types, helpers, and a `register*Ipc()` function. The bootstrap in `index.ts` calls each `register*Ipc()` directly — this is intentional, not fragmentation. Do NOT suggest routing sub-module IPC registrations through `ipc.ts`.
Applied to files:
packages/desktop-electron/src/preload/index.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Import test utilities from `../fixtures` instead of `playwright/test`
Applied to files:
packages/opencode/test/share/share-next.test.tspackages/opencode/test/cli/export.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/packages/app/src/testing/**/*.ts : Test-only hooks must be inert unless explicitly enabled and should not add normal-runtime listeners, reactive subscriptions, or per-update allocations
Applied to files:
packages/opencode/test/share/share-next.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Define `const it = testEffect(...)` near the top of the test file and keep the test body inside `Effect.gen(function* () { ... })`. Yield services directly with `yield* MyService.Service` or `yield* MyTool`.
Applied to files:
packages/opencode/test/share/share-next.test.tspackages/opencode/src/share/runtime.tspackages/opencode/src/share/session.tspackages/opencode/test/share/session-pawwork-fail-closed.test.tspackages/opencode/test/session/export.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use `testEffect(...)` from `test/lib/effect.ts` for tests that exercise Effect services or Effect-based workflows.
Applied to files:
packages/opencode/test/share/share-next.test.tspackages/opencode/test/share/session-pawwork-fail-closed.test.ts
📚 Learning: 2026-04-23T08:51:00.819Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 186
File: packages/opencode/test/plugin/workspace-adaptor.test.ts:139-144
Timestamp: 2026-04-23T08:51:00.819Z
Learning: For pawwork tests under packages/opencode/test/**, auth.json teardown may intentionally combine `Filesystem.write` (from `packages/opencode/src/util/filesystem.ts`) with `node:fs/promises` `unlink` for cleanup. Do not flag this as inconsistent style; it is the established/intentional pattern because `Filesystem` does not provide a `remove`/`unlink` helper.
Applied to files:
packages/opencode/test/share/share-next.test.tspackages/opencode/test/cli/export.test.tspackages/opencode/test/share/session-pawwork-fail-closed.test.tspackages/opencode/test/session/export.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Call `project.trackSession(sessionID, directory?)` and `project.trackDirectory(directory)` for any resources created outside the fixture so teardown can clean them up
Applied to files:
packages/desktop-electron/src/preload/types.tspackages/opencode/test/session/export.test.ts
📚 Learning: 2026-04-22T08:49:47.800Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 126
File: packages/desktop-electron/src/main/index-sidecar-source.test.ts:3-11
Timestamp: 2026-04-22T08:49:47.800Z
Learning: In `packages/desktop-electron/src/main/index-sidecar-source.test.ts` (Astro-Han/pawwork), the test intentionally uses `expect(source).toContain` / `expect(source).not.toContain` string matching against the raw `index.ts` source text as a lightweight sidecar contract guard. The maintainer has explicitly chosen not to introduce an AST parser (e.g., `babel/parser` or acorn) for this purpose. Do not flag these string-based assertions as fragile or suggest converting them to AST-based matching.
Applied to files:
packages/opencode/test/cli/export.test.tspackages/opencode/test/share/session-pawwork-fail-closed.test.tspackages/opencode/test/session/export.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `tmpdir` function from `fixture/fixture.ts` to create temporary directories for tests with automatic cleanup. Use `await using` syntax to ensure automatic cleanup when the variable goes out of scope.
Applied to files:
packages/opencode/test/cli/export.test.tspackages/opencode/test/session/export.test.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `Path.Path`, `Config`, `Clock`, and `DateTime` services when those concerns are already inside Effect code
Applied to files:
packages/opencode/src/share/runtime.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.cached` when multiple concurrent callers should share a single in-flight computation rather than storing `Fiber | undefined` or `Promise | undefined` manually
Applied to files:
packages/opencode/src/share/runtime.tspackages/opencode/src/share/session.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `FileSystem.FileSystem` instead of raw `fs/promises` for effectful file I/O in Effect services
Applied to files:
packages/opencode/src/share/runtime.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : For background loops or scheduled tasks, use `Effect.repeat` or `Effect.schedule` with `Effect.forkScoped` in the layer definition
Applied to files:
packages/opencode/src/share/runtime.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Prefer `ChildProcessSpawner.ChildProcessSpawner` with `ChildProcess.make(...)` instead of custom process wrappers in Effect services
Applied to files:
packages/opencode/src/share/runtime.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.gen(function* () { ... })` for Effect composition
Applied to files:
packages/opencode/src/share/session.ts
📚 Learning: 2026-04-20T14:36:21.288Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:21.288Z
Learning: Applies to packages/opencode/**/*.ts : Use `Effect.forkScoped` inside the `InstanceState.make` closure for background stream consumers — the fiber is interrupted when the instance is disposed
Applied to files:
packages/opencode/src/share/session.ts
📚 Learning: 2026-04-24T03:51:54.050Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 206
File: packages/app/e2e/prompt/prompt-footer-focus.spec.ts:131-143
Timestamp: 2026-04-24T03:51:54.050Z
Learning: In Astro-Han/pawwork E2E tests (packages/app/e2e/fixtures.ts), `project.prompt(text)` internally calls `trackSession(next.sessionID, active.directory)` after the prompt submission is observed and the active session is resolved. Tests that obtain a `sessionID` from `project.prompt()` do NOT need to call `project.trackSession(sessionID)` manually — it is already registered for teardown. Calling it again would be duplicate ownership.
Applied to files:
packages/opencode/test/share/session-pawwork-fail-closed.test.tspackages/opencode/test/session/export.test.ts
📚 Learning: 2026-04-24T00:02:53.315Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 203
File: packages/app/e2e/sidebar/sidebar-session-links.spec.ts:34-55
Timestamp: 2026-04-24T00:02:53.315Z
Learning: In Astro-Han/pawwork E2E tests (`packages/app/e2e/**/*.spec.ts`), `project.trackDirectory()` and `project.trackSession()` cannot be called before `project.open()` — the `project` fixture throws until `open()` initializes its internal state. The correct pattern is: call `project.trackSession(sessionID)` from inside the `beforeGoto` callback (where state already exists), call `project.trackDirectory(directory)` and cross-workspace `project.trackSession(id, directory)` immediately after `project.open()` returns, and rely on explicit `finally` cleanup (e.g. `cleanupSession` / `cleanupTestProject`) for any resources created before `open()` that cannot yet be tracked via the fixture.
Applied to files:
packages/opencode/test/share/session-pawwork-fail-closed.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use `it.effect(...)` when the test should run with `TestClock` and `TestConsole`. Use `it.live(...)` when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.
Applied to files:
packages/opencode/test/share/session-pawwork-fail-closed.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : When a test needs instance-local state, prefer `provideTmpdirInstance(...)` or `provideInstance(...)` over manual `Instance.provide(...)` inside Promise-style tests.
Applied to files:
packages/opencode/test/share/session-pawwork-fail-closed.test.ts
📚 Learning: 2026-04-23T15:25:31.118Z
Learnt from: Astro-Han
Repo: Astro-Han/pawwork PR: 193
File: packages/app/e2e/sidebar/sidebar-leading-slot.spec.ts:5-55
Timestamp: 2026-04-23T15:25:31.118Z
Learning: In Astro-Han/pawwork E2E tests, reaching a real "running" session state requires the `project` fixture (for model bootstrap) plus `llm.wait(1)` orchestration. The bare `sdk` fixture used by `withSession` does not trigger an LLM call even with `agent: "build"` + `system` prompt set via `sdk.session.promptAsync`, so simulating running state is not lightweight in the current test infrastructure.
Applied to files:
packages/opencode/test/share/session-pawwork-fail-closed.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `config` option in `tmpdir` to write an `opencode.json` config file during test setup by passing a partial Config.Info object.
Applied to files:
packages/opencode/test/session/export.test.ts
📚 Learning: 2026-04-20T14:36:31.032Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/opencode/test/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:31.032Z
Learning: Applies to packages/opencode/test/**/*.test.{ts,tsx} : Use the `init` option in `tmpdir` to define custom setup functions that can return extra data accessible via `tmp.extra`, and use the `dispose` option for custom cleanup logic.
Applied to files:
packages/opencode/test/session/export.test.ts
📚 Learning: 2026-04-20T14:36:04.113Z
Learnt from: CR
Repo: Astro-Han/pawwork PR: 0
File: packages/app/e2e/AGENTS.md:0-0
Timestamp: 2026-04-20T14:36:04.113Z
Learning: Applies to packages/app/e2e/**/*.spec.ts : Use fixture-managed cleanup with `withSession(sdk, title, callback)` for temporary sessions instead of calling `sdk.session.delete(...)` directly
Applied to files:
packages/opencode/test/session/export.test.ts
🔇 Additional comments (29)
packages/opencode/src/share/runtime.ts (1)
9-30: Nice fail-closed gate abstraction.Centralizing the default gate and typed disabled error here gives the rest of the share stack a single source of truth for PawWork behavior.
packages/opencode/src/effect/app-runtime.ts (1)
49-49: Good runtime wiring.Providing the gate at the app-layer level keeps the fail-closed behavior consistent anywhere the share services are resolved.
Also applies to: 98-98
packages/opencode/src/share/share-next.ts (1)
16-16: Good gate integration for the background sync path.Skipping subscriber setup when the gate is closed is the right fail-closed behavior for PawWork.
Also applies to: 119-119, 161-161, 325-325, 351-351
packages/opencode/src/share/session.ts (1)
10-10: Good local-only short-circuit.Skipping the background share fork when the gate is closed matches the PR objective and keeps root-session creation fully local in PawWork.
Also applies to: 54-69
packages/app/src/i18n/zh.ts (1)
636-638: LGTM.These keys are consistent with the new export flow and read naturally in the existing zh UI copy.
packages/app/src/i18n/en.ts (1)
700-702: LGTM.The new copy is clear and matches the rest of the session action vocabulary.
packages/opencode/test/cli/export.test.ts (1)
3-5: Good test realignment.Pulling
sanitizefrom the shared export module keeps the CLI assertions locked to the implementation that now owns the export format.packages/opencode/src/session/message-v2.ts (1)
187-187: LGTM!The addition of an optional
metadatafield toFilePartis consistent with other part types (TextPart,ReasoningPart,ToolPart, etc.) that already have this pattern. This enables the export pipeline to attachredacted_binarymetadata when redacting binary attachments.packages/desktop-electron/src/main/index.ts (1)
499-499: LGTM!The
getServerReadyDataaccessor follows the established pattern for providing dependencies to IPC handlers. It exposes the sameserverReady.promisealready used byawaitInitialization, enabling the newexport-sessionIPC handler to obtain server credentials without duplicating initialization logic.packages/desktop-electron/src/preload/index.ts (1)
62-63: LGTM!The preload bridge correctly delegates
exportSessionto the main process via IPC. The method signature aligns with theElectronAPItype definition and the main-process handler.packages/desktop-electron/src/renderer/index.tsx (1)
178-180: LGTM!The
exportSessionmethod correctly delegates towindow.api.exportSession, following the established pattern for platform capabilities in the desktop renderer. This adheres to the coding guideline that renderer process should only callwindow.apifrom the preload bridge.packages/desktop-electron/src/preload/types.ts (1)
77-81: LGTM!The
exportSessiontype definition uses a clear discriminated union for success/failure states, enabling type-safe error handling in consumers. The optionaldefaultNameparameter and theerror: stringfield accommodate the various error codes returned by the IPC handler (invalid_args,cancelled,fetch_failed, etc.).packages/opencode/src/cli/cmd/export.ts (1)
76-79: LGTM!The refactoring correctly delegates to the shared
Exportmodule:
Export.session()produces the versioned snapshot with runtime contextExport.sanitizeSnapshot()handles path/URL redaction when--sanitizeis enabledThis consolidates the export logic and ensures CLI and server endpoints produce consistent output.
packages/app/src/context/platform.tsx (1)
98-106: LGTM!The optional
exportSessionmethod is well-documented and follows the established pattern for desktop-only capabilities in thePlatformtype. The return type correctly matches theElectronAPIcontract.packages/opencode/src/server/instance/session.ts (4)
13-14: LGTM!The imports are correctly added for the new export endpoint and cloud share gating functionality.
444-452: LGTM!The cloud share gating correctly uses
Effect.gen(function* () { ... })per coding guidelines. Returning HTTP 410 with{ error: "cloud_share_disabled" }is appropriate for indicating the feature is intentionally disabled.
461-486: LGTM!The export endpoint is well-documented with OpenAPI metadata and correctly delegates to
Export.session(). The description accurately explains the climb-to-root behavior for child sessions.
592-600: LGTM!The cloud share gating for the DELETE endpoint mirrors the POST endpoint pattern, ensuring consistent behavior when cloud sharing is disabled.
packages/app/src/pages/session/message-timeline.tsx (2)
394-431: LGTM! Well-structured export handler with comprehensive error handling.The
onExportfunction correctly:
- Guards against missing
platform.exportSessioncapability- Builds a filesystem-safe filename with Unicode letter/number support for CJK titles
- Silently handles user cancellation (
"cancelled")- Distinguishes between thrown exceptions and returned errors with appropriate toast feedback
856-867: LGTM! Export menu item correctly integrated.The conditional rendering with
Show when={platform.exportSession}properly gates this desktop-only feature, and closing the menu before invoking the async export prevents UI state issues.packages/desktop-electron/src/main/ipc.ts (1)
42-42: LGTM!The
getServerReadyDatadependency injection follows the established pattern and matches the implementation inindex.ts.packages/desktop-electron/src/main/server-client.ts (1)
25-37: LGTM! Clean fetch wrapper with proper error handling.The function correctly:
- Returns a discriminated union for type-safe error handling
- Maps HTTP errors to identifiable
server_<status>strings- Catches and surfaces network/runtime exceptions
packages/opencode/test/session/export.test.ts (3)
14-51: LGTM! Comprehensive test coverage for core export functionality.The tests thoroughly validate:
- Runtime namespace detection
- Session export structure (schema version, format, timestamps, IDs)
- Correct handling of
sharefield removal andhad_cloud_sharetracking- Empty messages/diffs/children for root-only sessions
74-99: LGTM! Well-designed deterministic ordering test.The test correctly:
- Uses a timing gap to ensure distinct
time.createdvalues- Validates the precondition independently before checking export order
- Uses hard-coded expected order to avoid tautological assertions
183-324: LGTM! Thorough redaction test coverage.Excellent coverage including:
- Standard data URL redaction with metadata preservation
- Non-data URL passthrough
- RFC 2397 edge case with extra parameters (charset)
- Instruction source path/URL sanitization
- Nested tool attachment redaction
packages/opencode/src/session/export.ts (3)
272-299: LGTM! Well-structured session export effect.The function cleanly composes:
- Root session resolution via
climbToRoot- Full tree export with redaction tracking
- Instruction source and model reference collection
- Complete
Snapshotstructure with stats
569-580: LGTM! Sanitize helpers are comprehensive and well-documented.The sanitization pipeline:
- Handles all 12
MessageV2.Parttypes systematically- Uses consistent
[redacted:kind:id]markers for traceability- Documents the type cast necessity at the
sanitizeTreeboundary- Recursively processes the entire session tree
213-219: No action needed. The bundled prompt file path is correct and already verified by tests.The file exists at the expected location (
packages/opencode/src/session/prompt/pawwork.txt), and the test inventory insystem.test.tsexplicitly validates its presence alongside other prompt files. The defensive code—usinghashFile()which returnsundefinedfor missing files—already handles the graceful fallback correctly.packages/opencode/test/share/session-pawwork-fail-closed.test.ts (1)
41-47: The suggestedCause.failures()API does not exist in Effect 4.x; use direct.reasonsaccess with proper filtering instead.Effect 4.x removed
Cause.failures()and restructured Cause as a flat array ofReasonobjects. The current cast to access.reasonsis pragmatic, but can be made more explicit by filtering for Fail reasons:♻️ Replace unsafe type cast with explicit Fail reason filtering
- const reasons = (exit.cause as unknown as { reasons?: ReadonlyArray<{ error?: unknown }> }).reasons ?? [] - const failed = reasons.find((r) => r.error instanceof ShareRuntime.CloudShareDisabled) + const failures = exit.cause.reasons.filter((r) => r._tag === "Fail") + const failed = failures.find((r) => r.error instanceof ShareRuntime.CloudShareDisabled)> Likely an incorrect or invalid review comment.
…fetch timeout) - sanitize covers Tree.diffs (file/patch) on every node + regression test - renderer hides Export when active server isn't sidecar (UI Show guard) - fetchExport adds 10s AbortController timeout, distinguishes timeout vs other errors - ShareNext.create/remove fail closed with typed CloudShareDisabled instead of empty Share stub - export route uses NotFoundError instanceof instead of string match - session/export.ts uses fileURLToPath(import.meta.url) for ESM-safe __dirname - share-next.test.ts: add disabled-gate regression test
Summary
Replace cloud session sharing (
opncd.ai) with a local JSON session export.Export.session(rootID)that climbs to root, recursively walks children, and produces a versioned JSON snapshot. Snapshot covers full session tree (info,messages,diffs),runtime_context(app/version/platform/locale/instruction sources/model refs/stats), and reservesdiagnostics: {}for [Bug] TypeScript LSP can consume excessive CPU and memory on large workspaces #232.GET /:sessionID/export(mounted under existing/sessionrouter;directoryresolved via instance middleware as query param).export-sessionIPC: main process fetches the internal route, opens save dialog, writes file. Renderer never holds the JSON.platform.exportSession. Toast on success/failure; cancel is silent.CloudShareGateEffect Service:SessionShare.share/unshareraise typedCloudShareDisabledfailureShareNext.create/removeand the 5 bus subscribers short-circuitPOST/DELETE /:sessionID/sharereturn HTTP 410cloud_share_disabledopencode exportrewritten to callExport.sessioncore; sanitize helpers moved intosession/export.tsand extended viaExport.sanitizeSnapshotso--sanitizealso redactsruntime_context.instruction_sourcespaths.data:*;base64,...URLs inFilePart.urland tool-completed attachments are replaced with empty string +metadata.redacted_binary = { mime, size_bytes, sha256 }. Regex follows RFC 2397 (allows extra params between mime and;base64).FilePartschema gets an optionalmetadatafield, parallel to existingTextPart/ReasoningPart/ToolPart.Why
Closes #194. PawWork is desktop-only by strategy; routing user session snapshots through
opncd.aiadds an external dependency, exposes user-facing share UI that doesn't fit our positioning, and complicates a debugging loop where the natural artifact is a local JSON file the user can grep, share by attachment, or load into a tool. The new path keeps cloud share code in tree (so opencode upstream parity stays cheap) but fail-closes it under PawWork.Refined spec is in #194 ("Refined implementation spec after design discussion"). Diagnostics extension point reserved for #232.
Related Issue
Closes #194.
How To Verify
Targeted automated:
bun --cwd packages/opencode test test/session/export.test.ts test/cli/export.test.ts test/share/ bun --cwd packages/opencode typecheck bun --cwd packages/app typecheck bun --cwd packages/desktop-electron typecheckAll green locally (12 export tests, 11 cli/share tests, 3 packages typecheck).
Manual smoke walked through with
bun run dev:desktop:pawwork-session-<slug>-<stamp>.jsonschema_version: 1,format: "pawwork-session-export",runtime_context.runtime_namespace: "pawwork",diagnostics: {},had_cloud_share: false,info.sharestripped,instruction_sourcesincludes bundledpawwork.txtwith sha256,model_refsresolves the active provider/model,statsmatches session contentopncd.airequest reachable: code-path proof —share-next.tsshort-circuits create/remove/state subscribers when gate is closed; under PawWork runtime gate is closed; covered byshare-next.test.ts+session-pawwork-fail-closed.test.tsOut of scope for this PR (deferred):
Screenshots or Recordings
Not attaching screenshots; the UI change is a single dropdown menu item rename ("Share" → "Export session log") and removal of the share popover. Manual smoke confirmed live.
Checklist
dev, and my PR title and commit messages use Conventional Commits in EnglishSummary by CodeRabbit
New Features
Tests