diff --git a/.agents/skills/changelog-authoring/SKILL.md b/.agents/skills/changelog-authoring/SKILL.md index 1e5aa674..bfce1eba 100644 --- a/.agents/skills/changelog-authoring/SKILL.md +++ b/.agents/skills/changelog-authoring/SKILL.md @@ -55,6 +55,7 @@ Use `gh pr view --json number,title,body,author,mergedAt` for PR eviden ## Highlights and Ordering - Sort bullets by user impact, not commit order. Breaking changes first, then significant new capabilities or broad user-visible improvements, then smaller features, fixes, and visual polish. +- Keep the opening highlight block contiguous. Place every bold highlight before the first regular bullet; a regular bullet marks the end of the highlight block. - Mark only the strongest highlights with a bold area prefix, such as `- **Chat attachments:** ...`. Usually the first 1–3 bullets; fewer when the release lacks substantial changes, more only when clearly justified. - Treat a change as a highlight only when it introduces a substantial user-facing capability, materially changes a common workflow, or fixes a severe/widespread problem. Do not bold merely because a bullet is first, has a large diff, or was hard to implement. - Keep related platform bullets together only when that does not push a more important change too far down. diff --git a/.agents/skills/communication-style/SKILL.md b/.agents/skills/communication-style/SKILL.md new file mode 100644 index 00000000..d1760fb8 --- /dev/null +++ b/.agents/skills/communication-style/SKILL.md @@ -0,0 +1,81 @@ +--- +name: communication-style +description: Use it always. +author: poteto (pstack) +--- + +# Communication style + +Edit text to remove AI patterns and add human voice. + +## Process + +1. Scan for the patterns below. +2. Rewrite. Preserve meaning, match intended tone. +3. Add soul (see next section). +4. Self-audit: "What makes this obviously AI generated?" Fix remaining tells. + +## Adding soul + +Removing patterns is half the job. Sterile, voiceless writing is just as obvious. + +- **Have opinions.** React to facts instead of neutrally listing pros and cons. +- **Vary rhythm.** Short sentences. Then longer ones that take their time. Mix it up. +- **Acknowledge complexity.** "Impressive but also kind of unsettling" beats "impressive." +- **Use "I" when it fits.** First person isn't unprofessional. +- **Let some mess in.** Perfect structure looks machine-made. +- **Be specific.** Not "this is concerning" but "there's something unsettling about agents churning away at 3am." + +## Patterns to detect and fix + +### Content + +1. **Puffery.** "pivotal moment", "testament to", "evolving landscape", "setting the stage for", "indelible mark", "deeply rooted". Cut puffery, state what happened. +2. **Name-dropping.** Listing media outlets without context. Pick one, say what was said. +3. **Superficial -ing phrases.** "highlighting...", "ensuring...", "reflecting...", "showcasing...", "fostering...". Delete or expand with real sources. +4. **Promotional language.** "nestled", "vibrant", "breathtaking", "groundbreaking", "renowned", "stunning", "must-visit". Use neutral descriptions. +5. **Vague attributions.** "Experts believe", "Industry reports suggest", "Some critics argue". Name the source or delete. +6. **Formulaic challenges.** "Despite challenges... continues to thrive." Replace with specific facts. + +### Language + +7. **AI vocabulary.** Additionally, crucial, delve, enduring, enhance, fostering, garner, interplay, intricate, landscape (abstract), pivotal, showcase, tapestry (abstract), testament, underscore, vibrant. Replace with plain words. +8. **Fancy ways to say "is".** "serves as", "stands as", "boasts", "features". Just say "is" or "has". +9. **"Not just X, but Y."** State the point directly instead. +10. **Rule of three.** Forcing ideas into groups of three. Use the natural number. +11. **Synonym cycling.** Protagonist, main character, central figure, hero all in one paragraph. Pick one, repeat it. +12. **False ranges.** "from X to Y" where X and Y aren't on a meaningful scale. List topics directly. + +### Style + +13. **Em dash overuse.** Avoid em dashes entirely. Use periods or commas only (no parentheses, no en dashes, no hyphen-as-dash substitutes). Em dashes are an AI tell, and reaching for parentheses instead just trades one tell for another. If a thought needs separation, end the sentence or use a comma. +14. **Colon overuse.** Colons are fine before a list or example. Not as mid-sentence connectors. "If you're coming from traditional automation: instead of registering event handlers, you describe conditions" adds nothing with the colon. Rewrite to let the point stand on its own without comparison framing. "Describing when the scheduler should fire works best as plain English." Same meaning, no crutch punctuation. +15. **Boldface overuse.** Don't bold every proper noun or acronym. +16. **Inline-header lists.** The tell is a bold label and colon that restates the line: "**Performance:** Performance improved...". Convert to prose. A bold lead-in that ends in a period, names the item, and is followed by genuinely new detail ("**Schema in TypeScript.** Tables live in one file.") is fine. +17. **Title case headings.** Use sentence case. +18. **Decorative emojis.** Remove from headings and bullets. +19. **Curly quotes.** Replace with straight quotes. + +### Communication artifacts + +20. **Chatbot phrases.** "I hope this helps!", "Let me know if...", "Of course!", "Certainly!", "Found the smoking gun!" Remove. +21. **Cutoff disclaimers.** "While specific details are limited..." Find sources or remove. +22. **Sycophantic tone.** "Great question! You're absolutely right!" Respond directly. + +### Filler + +23. **Filler phrases.** "In order to" becomes "To". "Due to the fact that" becomes "Because". "It is important to note that" gets deleted. +24. **Excessive hedging.** "could potentially possibly be argued that it might" becomes "may". +25. **Generic conclusions.** "The future looks bright." State specific plans or facts. + +### Jargon + +26. **Abstract metaphor nouns.** Substrate, wedge, vector, locus, vantage, nexus, primitive (as noun), harness (as metaphor), surface (as in "API surface"), bedrock, scaffolding (as metaphor), modality, paradigm, gold-plating, ratchet (as metaphor), evacuate (for moving code), endgame, north star, flywheel. These read as technical but usually have a plainer concrete word. "Substrate" becomes "base". "Wedge in" becomes "add". "Vector" becomes "way" or "method". "Gold-plating" becomes "more than the job needs". "Ratchet" becomes the mechanism's real name or "a limit that only tightens". "Evacuate" becomes "move out". "Endgame" becomes "the last phase". Pick the concrete word. + +### Plain speech + +27. **Say what it does, not how it feels.** "the database stays close at hand", "SQL you can read", "types that follow your schema" name a feeling. The fix names the mechanism or a number: "`.toSQL()` returns the exact string sent to the database", "a column rename fails the build". Ask what the sentence tells the reader to do or know, then write it. If you can't restate it as a concrete instruction, fact, or number, cut it. One more check: if the sentence could appear unchanged in another project's docs, it says nothing about this one. Cut it. +28. **Shorten or split dense sentences.** If the reader has to backtrack to parse a sentence, break it in two or drop clauses. One idea per sentence. +29. **Active voice.** Prefer it. Catch "is/are/was/were + past participle" and name the actor: "queries are validated" becomes "the compiler validates queries", "the file is parsed by the loader" becomes "the loader parses the file". Passive is fine only when the actor is unknown or genuinely doesn't matter. +30. **Cut adverbs, or use a stronger verb.** "runs quickly" becomes "is fast" or the number. "significantly improves" becomes the measured delta. An adverb propping up a weak verb means the verb is wrong. +31. **Prefer the plain word.** "utilize" becomes "use", "leverage" becomes "use", "facilitate" becomes "help", "numerous" becomes "many", "in the event that" becomes "if". The fancier synonym is rarely clearer. diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 26cef37e..414a56f0 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -24,13 +24,13 @@ jobs: - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 with: repo-token: ${{ steps.app-token.outputs.token }} - days-before-stale: 60 + days-before-stale: 28 days-before-close: 7 stale-issue-label: stale stale-pr-label: stale stale-issue-message: > This issue has been automatically marked as stale because it has not had - any activity in the last 60 days. It will be closed in 7 days if no + any activity in the last 28 days. It will be closed in 7 days if no further activity occurs. close-issue-message: > This issue has been automatically closed because it has been stale for @@ -38,7 +38,7 @@ jobs: reopen the issue. stale-pr-message: > This pull request has been automatically marked as stale because it has - not had any activity in the last 60 days. It will be closed in 7 days + not had any activity in the last 28 days. It will be closed in 7 days if no further activity occurs. close-pr-message: > This pull request has been automatically closed because it has been diff --git a/AGENTS.md b/AGENTS.md index 0ea1ebb6..9d2a437d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,6 +79,10 @@ task-required reference named by those skills. Skills are canonical for their detailed workflows and checklists. Treating this table as optional advice is a process violation. +**Always load `.agents/skills/communication-style/SKILL.md` at the start of +every task, before any analysis, tool call, or response. Apply its guidance to +all messages and written output, not only to user-facing copy or documentation.** + | Trigger | Required skill | |---|---| | Source/dependency changes, exports or package contracts, build/generated assets, or module ownership | `openchamber-change-discipline` | diff --git a/CHANGELOG.md b/CHANGELOG.md index 74c774a4..5b728db6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,94 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- Files: opening a file over 5,000 lines is no longer blocked — the line-count guard now allows up to 20,000 lines, letting large files reach the virtualized full-file preview instead of being rejected at the open step (thanks @gaojunran). +- Settings: fixed the Cloudflare Tunnel download link shown when cloudflared is not installed (thanks to @AyoubAchour). + +## [1.21.0] - 2026-08-26 + +- **Chat scrolling rebuilt around your message.** Sending parks your message near the top and the reply streams in below it, gliding smoothly a paragraph at a time. Scrolling up immediately hands you the wheel; the scroll-to-bottom pill carries the model's working status while you're away. +- **Keyboard shortcuts redesigned:** single chords for everyday actions, a Cmd/Ctrl+K leader for two-step open/go actions, held Cmd/Ctrl+digit for session tabs and Cmd/Ctrl+Option+digit for panel surfaces. Shortcuts work on non-English keyboard layouts now, tooltips show the binding you actually have set, and old custom bindings reset once. The full map lives in Settings → Shortcuts (registry contributed by @ChangeHow — thanks!). +- **Chat context attachments:** diff comments, terminal selections, browser annotations, linked issues/PRs and the rest now appear in the conversation as compact context cards instead of walls of raw text. +- **Session tabs (opt-in):** the web/desktop header can show open sessions as browser-style tabs (Settings → General → Navigation). A tab switches the whole workspace; closing one never touches the session itself. +- Sessions: switching is much faster in large workspaces — the sidebar no longer rebuilds on switch and recently viewed sessions restore their rendered messages; end-to-end switch time roughly halved with thousands of loaded sessions (thanks to @c-w-xiaohei). +- Permission: cards answer to the keyboard Alt+Enter allows once, Alt+Shift+Enter allows always, Alt+Backspace denies — the keys are printed on the buttons. The auto-accept toggle got Cmd/Ctrl+K, A. +- Sessions: Cmd/Ctrl+Alt+Left/Right steps back and forward through the sessions you opened in this window, browser-history style; with session tabs enabled it moves between neighbouring tabs instead. +- Git: Cmd/Ctrl+Enter in the commit message box commits. Diff review moves between changed files with Alt+Down/Up, expanding a collapsed file on arrival. +- Chat: Cmd/Ctrl+Shift+T now cycles through every thinking level offered by the selected model instead of skipping levels after reaching the end (thanks to @nimobeeren). +- Panels: the context rail got a configure button — a dialog chooses which panels the rail shows. Hidden panels keep their data, stay reachable from the command palette, and leave the digit switcher, so digits always match the icons you see. +- Chat: comment on a reply — select text in a chat message (or a rendered markdown preview in Files) and choose Comment to attach exactly that quote, with a source line range when it can be located, plus your note. The selection stays highlighted while you type. +- Diff: comment like a review — hovering a line shows a + in the gutter; clicking or dragging across lines opens the comment editor for that range, styled like the chat's comments. +- Composer: hovering or tapping a context chip opens a stacked preview of everything attached, where a comment can be edited in place or an item removed before sending. +- Mobile: the chat comment input overlays the composer exactly and rides the keyboard; Enter makes a new line there, with attach on the button. +- Terminal: terminals no longer vanish behind your back — every tab and device shows the ones already running on the server, and background tabs survive the idle cleanup. +- Search: every searchable picker uses one matcher now — best matches first, multi-word queries in any order, punctuation ignored ("gpt4o" finds "gpt-4o"). Ctrl/Cmd+P matches whole file paths. +- Chat: @ file mentions rank files and directories together by match quality, and long paths keep the folder next to the file name visible. +- Chat: a "Follow new content while streaming" checkbox (Settings → Chat → Streaming, on by default) turns automatic following off entirely; with it off, the scroll-to-bottom pill now appears as soon as the reply grows past the visible area. +- Chat: undoing or redoing a parent session now keeps its subagent sessions at the same point in history instead of leaving their later work behind (thanks to @alexandrereyes). +- Command palette: rarely used commands (pin session, copy session ID, multi-run launcher, archived sessions, notes, todos, status, theme) are found by typing but stay off the first screen. +- Mobile: narrowing a browser window past phone size switches into the mobile layout (and back when widened); the old/new mobile layout setting is gone. +- Browser: an agent opening a page with the browser tool no longer pops the browser panel open (or switches the surface you're on) — the page loads in the background and the rail is where you peek at it. +- Usage: the Command Code tile is gone — their official API exposes no usage data, so the tile could only fail. +- Desktop: a relay-paired default host no longer greets every restart with the "Remote Server Unreachable" screen — the stored direct address (often the pairing machine's own loopback) failing its probe now boots the app normally and connects over the relay, picking the direct route back up automatically when it answers again. +- Mobile: on Android browsers the composer now stays above the keyboard in the chat too — the keyboard could cover it with no way to scroll it into view; the draft screen's viewport pinning now covers the chat screen on Android. +- Auth: an expired OpenChamber login is announced within seconds by a banner with a Log in button, instead of being discovered through failing actions. Sending pauses until login, and a conversation that failed to load reloads itself afterwards. +- Chat: a failed send returns your typed prompt to the input — whatever the reason — instead of losing it to an error toast; a mid-send session switch lands it in that session's draft. +- Chat: opening a session or resizing panels could strand the view in a large empty space below the last message; the list now returns to the real end, and a width resize keeps a reader who was at the bottom at the bottom. +- Chat: prompt-rail and message jumps land exactly on the target once the layout finishes measuring, and clicking the last rail item always works. +- Desktop: two windows on different projects no longer hijack each other — one window's session switch could make the other adopt its project mid-typing. Notification clicks and openchamber:// links now open in one window instead of all of them. +- Git: the branch's PR badge no longer picks up a stranger's pull request — with contributor forks added as remotes, a fork's closed PR sharing only the branch name could show up on the local branch. +- Chat: streamed code blocks are syntax-highlighted while streaming, and finished messages no longer jump when line numbers fill in. +- Chat: finished replies no longer flicker — tool cards stopped replaying their reveal animation on completion, and window resizing no longer throws the conversation around at the bottom. +- Mobile: scrolling during a streaming reply works again — a drag immediately takes over, the pill shows up, and load-older no longer throws you to the bottom. +- Fixed file links in messages being checked twice, and against the wrong project directory on the first pass. +- Fixed the selected project or session briefly jumping back to a previous choice when settings responses arrived out of order. +- Fixed sessions staying on "loading sessions" forever after a half-open connection to OpenCode — stalled reads now time out and retry (thanks to @herjarsa). +- Files: previews above the editable size cap show the whole file, virtualized so huge files no longer freeze the app (thanks to @gaojunran). +- VSCode: the chat view no longer sticks on its loading screen on slow or remote connections (thanks to @VinciYan). +- Terminal: mobile keyboards no longer capitalize the first letter of every command. +- Desktop: a freshly installed or updated build no longer loads the previous version's interface from cache. +- Devices: re-pairing a phone keeps the device's existing name instead of resetting it to "OpenChamber Mobile". +- Relay: paired devices no longer get logged out when the app restarts while another local OpenChamber process is running. +- Sessions: headers now find archived sessions too, so an archived session's title no longer goes missing. +- Files: the editor toolbar is always docked under the file tabs; the floating hover toolbar and its setting were removed. +- UI: the chat's scroll fades are back, the first uncached session open fades in, the timeline dialog fits small screens (thanks to @gaojunran), OpenCode notices share one style, draft target menus stay inside the chat area, Linear and Cloudflare tools show their own icons, sidebar tooltips no longer appear on passing hover, and the btw panel's shadow matches the composer. + +## [1.20.0] - 2026-08-23 + +- **Session: /btw side questions.** Type `/btw` followed by your question to ask something off-topic in a temporary session forked from the current conversation, so it inherits the full context but leaves the chat itself untouched. The answer streams into a panel above the composer, which talks to that session while the panel is open; you can collapse it to a slim header bar, keep it as a full session, or discard it. The temporary session stays out of the sidebar and session lists until you keep it (thanks to @jaygupta17). +- **Chat sessions:** start chats without choosing a project. They live in their own Chats section, rather than inheriting a project's repository and worktree context. +- **Desktop/Remote instances:** adding an SSH connection now starts from the hosts in your SSH config instead of a blank command field. Ports, install method and passwords moved behind Advanced settings, and each connection shows Connected, Connecting, or Needs attention with the failure text and a button that resolves it. +- Desktop/Remote instances: connecting to a remote machine now works when bun, OpenChamber or the opencode CLI live in your home directory rather than on the system path. Installing no longer fails with a permission error, and a missing opencode CLI is now reported before the connection starts instead of as a stack trace. +- Desktop/Remote instances: a managed remote server can now also be published to the remote machine's own network, so other devices there reach it without the SSH tunnel. It requires a UI password, and stays private to the tunnel otherwise. +- Desktop/Remote instances: disconnecting from a connection set to not keep the server running now actually stops that remote server. +- Skills catalog: browse curated GitHub skill collections in a card-based catalog with cross-source search, skill counts, stars, recent updates, and links back to each skill's repository. +- Diff: the context-panel diff can now show every change on the current branch against its base branch. OpenChamber detects the base when Git knows it, or lets you choose one once when it does not. +- Dictation: speech is now transcribed after you stop recording. The composer shows a live waveform and timer, and long recordings split at pauses instead of cutting words. +- Settings: the project selector on Providers, Agents, MCP, Commands and Skills now only changes what those pages show. It used to switch the whole app, so opening another project's configuration moved your chat, session list and file tree with it. +- Settings/Projects: a project can now pin a thinking level next to its model, for models that offer levels. Both sit in one Defaults for new chats group, laid out like the Sessions defaults. +- Settings/General: changing the default model, variant or agent no longer repoints an open chat that already carries a model you picked for it. Chats following the default still switch immediately. +- Settings/Providers: the provider you select no longer jumps to a different one on its own. Changing the chat's model or agent, and background provider refreshes, used to move the settings selection with them. +- Settings/Integrations: the experimental page now only lists integrations that can be installed; unavailable and Coming soon entries were removed. +- Chat: file paths in messages now open from the session's project, even if you last browsed files in another project (thanks to @tomzx). +- Chat: app links such as `spotify://` now ask for confirmation before opening another app. You can trust an app link type on one device and manage trusted links in Settings. +- Files/Desktop: files opened from outside the workspace remain readable after their temporary access expires instead of failing until you reopen them (thanks to @pascalandr). +- Diff: creating an inline comment now opens the chat and focuses the composer for your follow-up. +- Chat: in the expanded composer, Enter now starts a new line and Cmd/Ctrl+Enter sends, so a long prompt is harder to send by accident. +- Providers: expanded support for custom providers. +- Small Model: summaries, goal audits, commit messages, and walkthroughs now support more providers. +- Git: generated commit messages now match the repository's recent commit style and language. +- Git: generating a pull request description now picks up the repository's own PR template when it has one, so the draft comes back in your project's sections and checklists instead of the built-in Summary/Why/Testing layout. +- Sidebar: switch between the full project list and a focused view of one project. Sessions created outside OpenChamber now also appear in the sidebar and Recent list without a page refresh (thanks to @tomzx). - Chat: if OpenCode restarts while a response is still running, the chat now stops with an interrupted state and a notification to continue instead of hanging silently (thanks to @sum117). +- Chat: while a reply streams, the model status line under the last message now turns into the finished message's info row in place, instead of jumping when the reply completes. +- Chat: newly sent messages and syntax-highlighted code blocks no longer briefly flicker. Bash output can also grow with its content instead of being cut off. +- Chat: long user messages can be expanded even when their final layout finishes after they first appear. +- Chat: in a chat without a project, the work status card again steps aside when the context panel is open, instead of sitting next to it. +- Usage: Z.ai credit limits now appear alongside its other quota windows. +- Git: pull-request checks in Work status stay current as their status changes. +- UI: the default dialog close button is easier to click or tap (thanks to @rockinrimmer). +- Desktop/Windows: the close button now aligns correctly with the rest of the window chrome. +- Session assist: recaps and suggested follow-ups now work when the Anthropic provider is configured to use a custom endpoint; they previously failed every time instead of using that configured connection. ## [1.19.0] - 2026-08-19 @@ -33,6 +120,9 @@ All notable changes to this project will be documented in this file. - Browser: typing a comment on a page no longer triggers app shortcuts. - Skills Catalog: the source is now named ClawHub instead of "ClawdHub" (thanks to @makeittech). - Chat: dismissing an agent's clarifying questions no longer leaves the session stuck on the question screen — the next task shows its thinking and final response again. +- VSCode: Add Project now adds the chosen folder to the workspace instead of showing a "Failed to add project" toast. +- UI: the model selection menu no longer shows white text on a white highlight when a high-contrast theme is active, so the hovered or selected model stays legible (thanks to @bashrusakh). +- Settings: an explicitly set `OPENCODE_BINARY` environment variable is no longer discarded when settings contain an empty opencodeBinary value; the environment variable keeps pointing the managed OpenCode server at the binary you chose. ## [1.18.4] - 2026-08-14 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9e6ba882..7db3d8bb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -204,7 +204,7 @@ state why it remains valid. If there is genuinely no user-visible change, say so and provide a concrete reason; deleting the evidence section is not an exemption. -### Review Enforcement +### Review enforcement The automated reviewer performs one unified review of correctness, repository guidance compliance, pull request quality, and evidence. It independently @@ -238,6 +238,16 @@ verify a trustworthy result, in which case it applies `review:automation-failed` Each completed review creates a new comment tied to its reviewed HEAD so the conversation remains chronological. Previous review comments are not rewritten. +### Keeping PRs active + +Stale PRs add review load and make it hard to tell what's still being worked on, so the stale bot keeps the open list current. A PR with no activity for 28 days is automatically labeled `stale`, and closed 7 days later if it stays inactive. To keep a PR open: + +- Push updates or respond to review feedback +- Leave a comment if you're waiting on a reviewer +- Add the `pinned`, `security`, or `help wanted` label to exempt a long-running PR from the stale bot + +Reopening a closed PR is fine if it becomes relevant again. + ## Project Structure ``` diff --git a/README.md b/README.md index 7241e03a..93fdf3c7 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![GitHub stars](https://img.shields.io/github/stars/openchamber/openchamber?style=flat&labelColor=100F0F&color=66800B)](https://github.com/openchamber/openchamber/stargazers) [![GitHub release](https://img.shields.io/github/v/release/openchamber/openchamber?style=flat&labelColor=100F0F&color=205EA6)](https://github.com/openchamber/openchamber/releases/latest) [![Discord](https://img.shields.io/badge/Discord-join.svg?style=flat&labelColor=100F0F&color=8B7EC8&logo=discord&logoColor=FFFCF0)](https://discord.gg/ZYRSdnwwKA) -[![Support the project](https://img.shields.io/badge/Support-Project-black?style=flat&labelColor=100F0F&color=EC8B49&logo=ko-fi&logoColor=FFFCF0)](https://ko-fi.com/G2G41SAWNS) +[![Support the project](https://img.shields.io/badge/Support-Project-black?style=flat&labelColor=100F0F&color=EC8B49&logo=patreon&logoColor=FFFCF0)](https://www.patreon.com/openchamber) ## Run agent work. Keep control. Ship from anywhere. diff --git a/bun.lock b/bun.lock index dc2f7923..96ae8e04 100644 --- a/bun.lock +++ b/bun.lock @@ -30,7 +30,7 @@ "@heroui/theme": "^2.4.23", "@lezer/highlight": "^1.2.3", "@octokit/rest": "^22.0.1", - "@opencode-ai/sdk": "1.18.18", + "@opencode-ai/sdk": "1.18.23", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", @@ -97,7 +97,7 @@ }, "packages/electron": { "name": "@openchamber/electron", - "version": "1.18.4", + "version": "1.20.0", "dependencies": { "@openchamber/web": "workspace:*", "electron-context-menu": "^4.1.2", @@ -134,7 +134,7 @@ }, "packages/ui": { "name": "@openchamber/ui", - "version": "1.18.4", + "version": "1.20.0", "dependencies": { "@aparajita/capacitor-secure-storage": "^8.0.0", "@base-ui/react": "^1.4.0", @@ -167,8 +167,9 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@legendapp/list": "3.3.8", "@lezer/highlight": "^1.2.3", - "@opencode-ai/sdk": "1.18.18", + "@opencode-ai/sdk": "1.18.23", "@pierre/diffs": "1.3.0-beta.6", "@replit/codemirror-vim": "^6.4.0", "@simplewebauthn/browser": "13.3.0", @@ -190,6 +191,7 @@ "http-proxy-middleware": "^3.0.5", "katex": "^0.17.0", "marked": "^17.0.3", + "marked-linkify-it": "^4.0.2", "morphdom": "^2.7.7", "motion": "^12.23.24", "next-themes": "^0.4.6", @@ -227,6 +229,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.5.0", "globals": "^16.3.0", + "happy-dom": "^18.0.1", "nodemon": "^3.1.7", "tailwindcss": "^4.0.0", "tsx": "^4.20.6", @@ -238,10 +241,10 @@ }, "packages/vscode": { "name": "openchamber", - "version": "1.18.4", + "version": "1.20.0", "dependencies": { "@openchamber/ui": "workspace:*", - "@opencode-ai/sdk": "1.18.18", + "@opencode-ai/sdk": "1.18.23", "adm-zip": "^0.6.0", "jsonc-parser": "^3.3.1", "react": "^19.1.1", @@ -261,16 +264,15 @@ }, "packages/web": { "name": "@openchamber/web", - "version": "1.18.4", + "version": "1.20.0", "bin": { "openchamber": "./bin/cli.js", }, "dependencies": { "@clack/prompts": "^1.1.0", "@octokit/rest": "^22.0.1", - "@opencode-ai/sdk": "1.18.18", + "@opencode-ai/sdk": "1.18.23", "@simplewebauthn/server": "13.3.1", - "adm-zip": "^0.6.0", "bun-pty": "^0.4.5", "compression": "^1.8.1", "cron-parser": "^4.9.0", @@ -306,7 +308,6 @@ "@remixicon/react": "^4.7.0", "@simplewebauthn/browser": "13.3.0", "@tailwindcss/postcss": "^4.0.0", - "@types/adm-zip": "^0.5.7", "@types/node": "^24.3.1", "@types/react": "^19.1.10", "@types/react-dom": "^19.1.7", @@ -919,6 +920,8 @@ "@kwsites/promise-deferred": ["@kwsites/promise-deferred@1.1.1", "", {}, "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="], + "@legendapp/list": ["@legendapp/list@3.3.8", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": "*", "react-dom": "*", "react-native": "*" }, "optionalPeers": ["react-dom", "react-native"] }, "sha512-GM4Hca/6WDvcY33XXCieR9MaG9CoZmACzwqQwRhKFSaNKfQV1lTLiTOpWuA9wnE8n8+6WeA52DwNKC9yrnPPeg=="], + "@levischuck/tiny-cbor": ["@levischuck/tiny-cbor@0.2.11", "", {}, "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow=="], "@lezer/common": ["@lezer/common@1.5.1", "", {}, "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw=="], @@ -1005,7 +1008,7 @@ "@openchamber/web": ["@openchamber/web@workspace:packages/web"], - "@opencode-ai/sdk": ["@opencode-ai/sdk@1.18.18", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-zJlwXskIR47V1dkPJqeKBgq7nejG1uU8lJaGIGqbX3MWRCT8vKn0fEotbxuPCKnTdmWsDyNGNg9q1qIliDSMDA=="], + "@opencode-ai/sdk": ["@opencode-ai/sdk@1.18.23", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-VouYbL8O2ynLq0atr5fjzCv8YLtFi/zKLQ/uYkCvTJ4CmUdA01XwPn8om+u3TvxnGEfQsBfmThGU141vt3sg5w=="], "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.78.0", "", { "os": "android", "cpu": "arm" }, "sha512-Bu819lmAfZMUHErrpe0cEWj3iaefuUODHSU8+UbXy67V/r7/7f4K3FL0NmbD85E+wiFLDYuhP8Zlv0XnVeXshw=="], @@ -1453,6 +1456,8 @@ "@types/vscode": ["@types/vscode@1.109.0", "", {}, "sha512-0Pf95rnwEIwDbmXGC08r0B4TQhAbsHQ5UyTIgVgoieDe4cOnf92usuR5dEczb6bTKEp7ziZH4TV1TRGPPCExtw=="], + "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.56.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/type-utils": "8.56.1", "@typescript-eslint/utils": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.56.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.56.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg=="], @@ -2141,6 +2146,8 @@ "guid-typescript": ["guid-typescript@1.0.9", "", {}, "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ=="], + "happy-dom": ["happy-dom@18.0.1", "", { "dependencies": { "@types/node": "^20.0.0", "@types/whatwg-mimetype": "^3.0.2", "whatwg-mimetype": "^3.0.0" } }, "sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA=="], + "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], @@ -2425,7 +2432,7 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="], - "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], + "linkify-it": ["linkify-it@6.1.0", "", { "dependencies": { "uc.micro": "^3.0.0" } }, "sha512-wJ/TwpSDTLepCrQoYWYIExIKg5Zchex2Nn5yk2mFnB+6PtdkHtyLx742md9csRjjOnGkKIS/RrbY7l8D6gT9Vw=="], "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], @@ -2481,6 +2488,8 @@ "marked": ["marked@17.0.3", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-jt1v2ObpyOKR8p4XaUJVk3YWRJ5n+i4+rjQopxvV32rSndTJXvIzuUdWWIy/1pFQMkQmvTXawzDNqOH/CUmx6A=="], + "marked-linkify-it": ["marked-linkify-it@4.0.2", "", { "dependencies": { "linkify-it": "^6.1.0" }, "peerDependencies": { "marked": ">=4 <19" } }, "sha512-3nvMW0MHU+ZNBhzSnqRTl+tCkUwIBbg1xbHx5mtJqCH8ieJGLJ0JzV36ESnnufSgZ0mSwO22fBIeNEu5vvYd9w=="], + "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], @@ -3243,7 +3252,7 @@ "typescript-eslint": ["typescript-eslint@8.56.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.56.1", "@typescript-eslint/parser": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/utils": "8.56.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ=="], - "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + "uc.micro": ["uc.micro@3.0.0", "", {}, "sha512-U3PppEkleoTnIfi8BozMx3yju3qc/L6SwqWo2Sw+54PX+PX0q9I+r1Um5HCmqD7n9VDX5/v3vQH/AjA6deDdtw=="], "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], @@ -3353,7 +3362,7 @@ "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], - "whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], @@ -3591,6 +3600,8 @@ "cheerio/undici": ["undici@7.22.0", "", {}, "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg=="], + "cheerio/whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], + "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "cli-truncate/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], @@ -3631,6 +3642,8 @@ "globby/slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], + "happy-dom/@types/node": ["@types/node@20.19.43", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA=="], + "iconv-corefoundation/cli-truncate": ["cli-truncate@2.1.0", "", { "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" } }, "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg=="], "iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], @@ -3649,6 +3662,10 @@ "markdown-it/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "markdown-it/linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], + + "markdown-it/uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "micromark-extension-math/katex": ["katex@0.16.45", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA=="], @@ -3817,6 +3834,8 @@ "glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], + "happy-dom/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "iconv-corefoundation/cli-truncate/slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="], "micromark-extension-math/katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], diff --git a/package.json b/package.json index 704434b4..1f2e1fc0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openchamber-monorepo", - "version": "1.19.0", + "version": "1.21.0", "description": "OpenChamber monorepo workspace for web, ui, and desktop runtimes", "private": true, "type": "module", @@ -115,7 +115,7 @@ "@heroui/theme": "^2.4.23", "@lezer/highlight": "^1.2.3", "@octokit/rest": "^22.0.1", - "@opencode-ai/sdk": "1.18.18", + "@opencode-ai/sdk": "1.18.23", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", diff --git a/packages/docs/content/docs/de/integrations.mdx b/packages/docs/content/docs/de/integrations.mdx index 42f5972b..38b24051 100644 --- a/packages/docs/content/docs/de/integrations.mdx +++ b/packages/docs/content/docs/de/integrations.mdx @@ -1,16 +1,17 @@ --- title: Integrationen -description: Nutze dein Claude-, Command-Code- oder Cursor-Abo als Provider. +description: Nutze dein Claude- oder Cursor-Abo als Provider. --- # Integrationen Eine Integration ist ein kleines Plugin, das OpenChamber einen Provider hinzufügt — auf Basis eines Abos, das du bereits hast. Verwalten kannst du sie unter **Settings → Integrations**. +> **Experimentelle Funktion.** Wir bemühen uns, die Richtlinien der Anbieter zu respektieren, aber Kontobeschränkungen und Sperrungen liegen bei jedem Anbieter. Nutze Integrationen auf eigenes Risiko. + Verfügbare Integrationen: - **Claude Code** — dein Claude Pro- oder Max-Plan, ohne API-Keys -- **Command Code** — dein Command-Code-Plan - **Cursor** — die Modell-Limits deines Cursor-Plans ## Integration installieren @@ -29,19 +30,10 @@ Claude Code nutzt deinen Claude Pro- oder Max-Plan — ohne API-Keys und ohne se 1. Installiere die Integration (siehe oben). 2. Wähle **Set up** und melde dich an. Wenn du die Claude Code CLI noch nicht hast, bietet die Einrichtung an, sie zuerst zu installieren, und meldet dich danach an. -Claude Code ist die einzige Integration hier, die ihre Provider-CLI installiert und angemeldet benötigt. Command Code und Cursor brauchen ihre CLIs nicht. +Claude Code ist die einzige Integration hier, die ihre Provider-CLI installiert und angemeldet benötigt. Cursor braucht seine CLI nicht. **Wie dein Claude-Konto geschützt bleibt:** Diese Integration nutzt das offizielle Claude Agent SDK von Anthropic und deine installierte Claude Code CLI. Sie kapert kein OAuth, extrahiert oder wiederholt keine Browser-Tokens, gibt sich nicht als nicht unterstützter Client aus und umgeht nicht Anthropics Authentifizierung. Sie bleibt auf dem von Anthropic unterstützten Zugriffsweg und trägt daher nicht das mit Token-Hijacking oder unautorisierten Authentifizierungsumgehungen verbundene Sperrrisiko. -## Command Code - -Command Code nutzt deinen Command-Code-Plan. - -1. Installiere die Integration (siehe oben). -2. Wähle **Set up** — es öffnet sich eine Browserseite. Genehmige den Zugriff und kehre zu OpenChamber zurück. - -Auf einem Headless-Server oder in CI setze stattdessen die Umgebungsvariable `COMMAND_CODE_API_KEY`, anstatt dich im Browser anzumelden. - ## Cursor Cursor macht die Modelle deines Cursor-Plans in OpenChamber nutzbar. diff --git a/packages/docs/content/docs/de/magic-prompts.mdx b/packages/docs/content/docs/de/magic-prompts.mdx index d6dd2c68..de0f8e50 100644 --- a/packages/docs/content/docs/de/magic-prompts.mdx +++ b/packages/docs/content/docs/de/magic-prompts.mdx @@ -21,6 +21,64 @@ Einige Prompts haben einen sichtbaren Teil (die Nachricht, die du sehen würdest Anders entschieden? Jeder Prompt hat **Auf Standard zurücksetzen**, und es gibt **Alle zurücksetzen**, wenn du überall neu anfangen möchtest. +## Wo jeder Prompt verwendet wird + +Für jeden Prompt steht unten, wo er läuft und was ihn auslöst. Prüfe den Auslöser vor der Bearbeitung, dann weißt du, welchen Ablauf du änderst. + +### Git + +| Prompt | Wo er läuft | Wann er ausgelöst wird | +| --- | --- | --- | +| Commit-Erstellung | Die Generieren-Schaltfläche im Commit-Feld der Git-Ansicht und im mobilen Changes-Bildschirm | Du erzeugst eine Commit-Nachricht. Ausgewählte Dateien und die letzten Commit-Betreffs des Branchs werden eingesetzt, sodass die Nachricht zum Stil deines Repos passt. | +| PR-Erstellung | Das Pull-Request-Anlegen-Formular im PR-Tab der Git-Ansicht | Du erzeugst Titel und Beschreibung eines PRs. Eingebaut werden Base- und Head-Branch, die Commits und geänderten Dateien dazwischen, dein zusätzlicher Kontext und die PR-Vorlage des Repos, falls vorhanden. | +| Merge/Rebase-Konfliktlösung | Der Konflikt-Dialog der Git-Ansicht, wenn ein Merge oder Rebase auf Konflikten stoppt | Du wählst "Resolve in current session" oder "Resolve in new session". Der Agent liest die konfliktbehafteten Dateien, schlägt eine Lösungsstrategie pro Datei vor und wartet auf deine Bestätigung, bevor er etwas ändert, staged oder fortfährt. | +| Cherry-pick-Konfliktlösung | Der Bereich "Re-integrate commits" einer Worktree-Sitzung | Beim Übertragen der Sitzungs-Commits auf den Zielbranch entsteht ein Konflikt und du übergibst ihn dem Agenten. Der Agent löst im temporären Worktree, staged die Dateien und setzt den Cherry-pick fort. | + +### GitHub + +| Prompt | Wo er läuft | Wann er ausgelöst wird | +| --- | --- | --- | +| PR-Review | Der "Link GitHub PR"-Picker im Anhänge-Menü des Composers und der neue Worktree-Dialog | Zwei Auslöser. Hängst du einen PR als Kontext an, werden die Anweisungen erzeugt und mit deiner nächsten Nachricht mitgesendet. Startest du eine Worktree-Sitzung aus einem PR, bildet der Prompt die erste Nachricht dieser Sitzung, mit dem vollständigen PR-Kontext. | +| Issue-Review | Der neue Worktree-Dialog, wenn der Worktree aus einem Issue startet | Die erste Nachricht der neuen Sitzung reviewed das Issue, mit Titel, Text und Kommentaren als Kontext. | +| Fehlgeschlagene PR-Checks / PR-Kommentare / einzelner PR-Kommentar | — | Wird heute von keinem Ablauf gesendet. Die PR-Ansicht löste sie früher über Ein-Klick-Review-Aktionen aus; fehlgeschlagene Checks und Kommentare werden jetzt als Chat-Kontext-Entwürfe angeheftet. Sie bleiben editierbar, damit bestehende Overrides weiter funktionieren. | + +### Planung + +| Prompt | Wo er läuft | Wann er ausgelöst wird | +| --- | --- | --- | +| Todo-Planung | Das Todos-Panel in der Projekt-Seitenleiste | Du schickst ein Todo an eine Sitzung oder eine neue Worktree-Sitzung. Der Todo-Text wird zur sichtbaren Nachricht; die Anweisungen machen daraus einen fragegesteuerten Planungsdialog statt sofort loszulegen. | +| Plan verbessern | Die Aktion "Improve" für einen gespeicherten Plan in der Plans-Ansicht | Du schickst einen gespeicherten Plan in den Verbesserungsfluss. Der Agent liest zuerst die Plandatei, schlägt dann Änderungen auf Basis des aktuellen Repo-Zustands vor und bietet an, dieselbe Datei zu bearbeiten. | +| Plan umsetzen | Die Aktion "Implement" für einen gespeicherten Plan | Du schickst einen gespeicherten Plan in den Umsetzungsfluss. Der Agent liest die Plandatei und setzt sie komplett um, ohne den Rahmen zu sprengen; nötige Plananpassungen schreibt er in dieselbe Datei zurück. | + +### Sitzung + +Die meisten davon treiben Slash-Befehle an, die du im Composer eingibst. Die meisten erscheinen auch als Starter-Chips im Entwurf einer neuen Sitzung. + +| Prompt | Wo er läuft | Wann er ausgelöst wird | +| --- | --- | --- | +| Codebase-Tour | `/explore` | Du möchtest einen Überblick über die Codebase. | +| Sitzungszusammenfassung | `/summary`, optional `/summary ` | Du fasst die bisherige Konversation zusammen — nützlich zur Übergabe an eine neue Sitzung. Benötigt eine bestehende Sitzung. | +| Workspace-Review | `/workspace-review` | Du lässt den Agenten den aktuellen Workspace-Diff auf Absicht, Korrektheit und Sicherheit prüfen. | +| Feature-Planung | `/plan-feature` | Du machst aus einer groben Feature-Idee über einen geführten Frage-Antwort-Dialog einen Umsetzungsplan. | +| Goal formulieren | `/craft-goal`, optional `/craft-goal ` | Du machst aus einer Idee ein überprüfbares Goal-Ziel für den Goal-Dialog. | +| Catch-up | `/catch-up` | Du kehrst zu einem Projekt zurück und fragst, wo es steht und wie es weitergeht. | +| Debugging | `/debug` | Du untersuchst einen Bug: Der Agent bildet Hypothesen, bestätigt die Ursache aus dem Code und schlägt erst dann eine Lösung vor. | +| Optionen abwägen | `/weigh` | Du weißt, was du bauen willst, aber nicht wie. Der Agent vergleicht zwei oder drei Ansätze und empfiehlt einen. | +| Fusion | Die Aktion "Run fusion" auf einer Multi-run-Gruppe | Du vereinigst die Ausgaben mehrerer Läufe zu einer Antwort. Die Lauf-Ausgaben werden hinter die Anweisungen angehängt. | + +### Prompts ohne Settings-Seite + +Einige Prompts laufen automatisch und haben keine editierbare Seite in den Einstellungen: + +| Prompt | Wann er ausgelöst wird | +| --- | --- | +| Geplante Aufgabe | `/schedule-task`, optional mit einer ersten Idee. Führt durch den Dialog, der eine geplante Aufgabe definiert. | +| Review-Übergabe | `/handoff-review` oder die Review-Schaltfläche in der Diff-Ansicht mit aktivierter Übergabe. Erzeugt die Übergabe in der Arbeitssitzung. | +| Startnachricht der Review-Sitzung | Die erste Nachricht der erzeugten Review-Sitzung — mit Übergabe, wenn eine erzeugt wurde, sonst ohne. | +| Review-Feedback / Umsetzungsantwort | Bringen Nachrichten zwischen den beiden Sitzungen hin und her: Review-Feedback geht zurück an die umsetzende Sitzung, die Antwort des Umsetzers zurück an die Review-Sitzung. | + ## Weiterführend - [Git- & GitHub-Workflows](/git/) — viele dieser Prompts treiben die Git-Abläufe an +- [Notizen, Todos & Pläne](/notes-todos-plans/) — die Todos und Pläne hinter den Planungs-Prompts +- [Multi-run](/multi-run/) — Laufgruppen und Fusion diff --git a/packages/docs/content/docs/de/skills-catalog.mdx b/packages/docs/content/docs/de/skills-catalog.mdx index afb5f31e..7026837b 100644 --- a/packages/docs/content/docs/de/skills-catalog.mdx +++ b/packages/docs/content/docs/de/skills-catalog.mdx @@ -12,7 +12,7 @@ Zum Schreiben eigener Skills siehe [Skills](/skills/). ## Einen Skill installieren 1. Öffne den Katalog. -2. Durchsuche die eingebauten Quellen — das Anthropic-Skills-Repo und die ClawHub-Community-Registry — oder nutze die Suche. +2. Durchsuche die eingebauten Quellen — wie das Anthropic-Skills-Repo — oder nutze die Suche. 3. Wähle einen Skill aus und installiere ihn. 4. Entscheide, wo er installiert werden soll: für alles, was du tust, oder nur für das aktuelle Projekt. diff --git a/packages/docs/content/docs/es/integrations.mdx b/packages/docs/content/docs/es/integrations.mdx index 206e40d8..17582762 100644 --- a/packages/docs/content/docs/es/integrations.mdx +++ b/packages/docs/content/docs/es/integrations.mdx @@ -1,16 +1,17 @@ --- title: Integraciones -description: Usa tu suscripción de Claude, Command Code o Cursor como proveedor. +description: Usa tu suscripción de Claude o Cursor como proveedor. --- # Integraciones Una integración es un pequeño plugin que añade un proveedor a OpenChamber usando una suscripción que ya tienes. Las gestionas en **Settings → Integrations**. +> **Función experimental.** Buscamos respetar las políticas de los proveedores, pero las restricciones y suspensiones de cuentas son decisión de cada proveedor. Usa las integraciones bajo tu propia responsabilidad. + Integraciones disponibles: - **Claude Code** — tu plan Claude Pro o Max, sin claves de API -- **Command Code** — tu plan de Command Code - **Cursor** — los límites de modelos de tu plan de Cursor ## Instalar una integración @@ -29,19 +30,10 @@ Claude Code usa tu plan Claude Pro o Max — sin claves de API y sin una app de 1. Instala la integración (arriba). 2. Elige **Set up** e inicia sesión. Si aún no tienes la CLI de Claude Code, la configuración ofrece instalarla primero y luego iniciar sesión. -Claude Code es la única integración de esta página que requiere tener la CLI de su proveedor instalada y con sesión iniciada. Command Code y Cursor no requieren sus CLIs. +Claude Code es la única integración de esta página que requiere tener la CLI de su proveedor instalada y con sesión iniciada. Cursor no requiere su CLI. **Cómo se protege tu cuenta de Claude:** esta integración usa el Claude Agent SDK oficial de Anthropic y tu CLI de Claude Code instalada. No secuestra OAuth, no extrae ni reutiliza tokens del navegador, no se hace pasar por un cliente no admitido ni omite la autenticación de Anthropic. Se mantiene en la vía de acceso admitida por Anthropic, por lo que no conlleva el riesgo de baneo asociado al secuestro de tokens o a rodeos de autenticación no autorizados. -## Command Code - -Command Code usa tu plan de Command Code. - -1. Instala la integración (arriba). -2. Elige **Set up** — se abre una página en el navegador. Autoriza el acceso y vuelve a OpenChamber. - -En una máquina sin interfaz gráfica o en CI, define la variable de entorno `COMMAND_CODE_API_KEY` en lugar de iniciar sesión en el navegador. - ## Cursor Cursor hace disponibles en OpenChamber los modelos incluidos en tu plan de Cursor. diff --git a/packages/docs/content/docs/es/magic-prompts.mdx b/packages/docs/content/docs/es/magic-prompts.mdx index 05a70599..8f7d3159 100644 --- a/packages/docs/content/docs/es/magic-prompts.mdx +++ b/packages/docs/content/docs/es/magic-prompts.mdx @@ -21,6 +21,64 @@ Algunos prompts tienen una parte visible (el mensaje que verías) y una parte de ¿Cambiaste de opinión? Cada prompt tiene **reset to default**, y hay un **reset all** si quieres empezar de cero en todas partes. +## Dónde se usa cada prompt + +Cada prompt de las tablas indica dónde se ejecuta y qué lo dispara. Revisa el disparador antes de editar, para saber qué flujo estás cambiando. + +### Git + +| Prompt | Dónde se ejecuta | Cuándo se dispara | +| --- | --- | --- | +| Generación de commit | El botón de generar en el cuadro de commit de la vista git, y la pantalla Changes en móvil | Generas un mensaje de commit. Se rellenan los archivos seleccionados y los asuntos de los commits recientes de la rama, para que el mensaje siga el estilo de tu repositorio. | +| Generación de PR | El formulario de creación de pull request en la pestaña PR de la vista git | Generas el título y el cuerpo de un PR. Se rellenan las ramas base y head, los commits y archivos cambiados entre ambas, tu contexto adicional y la plantilla de PR del repositorio si existe. | +| Resolución de conflicto de merge/rebase | El diálogo de conflictos en la vista git, cuando un merge o rebase se detiene por conflictos | Eliges "Resolve in current session" o "Resolve in new session". El agente lee los archivos en conflicto, propone una estrategia por archivo y espera tu confirmación antes de editar, hacer stage o continuar la operación. | +| Resolución de conflicto de cherry-pick | La sección "Re-integrate commits" de una sesión en worktree | Mover los commits de la sesión a la rama destino produce un conflicto y se lo pasas al agente. El agente resuelve dentro del worktree temporal, hace stage de los archivos y continúa el cherry-pick. | + +### GitHub + +| Prompt | Dónde se ejecuta | Cuándo se dispara | +| --- | --- | --- | +| Revisión de PR | El selector "Link GitHub PR" en el menú de adjuntos del composer, y el diálogo de nuevo worktree | Dos disparadores. Adjuntar un PR como contexto prepara las instrucciones, que se envían con tu siguiente mensaje. Crear una sesión de worktree desde un PR usa el prompt como primer mensaje de esa sesión, con el contexto completo del PR adjunto. | +| Revisión de issue | El diálogo de nuevo worktree, cuando el worktree parte de una issue | El primer mensaje de la nueva sesión revisa la issue, con su cuerpo y comentarios adjuntos como contexto. | +| Revisión de checks fallidos / comentarios de PR / comentario único de PR | — | Hoy no los envía ningún flujo. La vista de PR antes los disparaba con acciones de revisión de un clic; ahora los checks fallidos y los comentarios se fijan como borradores de contexto del chat. Siguen siendo editables para que las anulaciones existentes sigan funcionando. | + +### Planning + +| Prompt | Dónde se ejecuta | Cuándo se dispara | +| --- | --- | --- | +| Planificación desde todo | El panel Todos en la barra lateral del proyecto | Envías un todo a una sesión o a una nueva sesión en worktree. El texto del todo se convierte en el mensaje visible; las instrucciones lo convierten en un diálogo de planificación con preguntas en vez de saltar a implementar. | +| Mejorar plan | La acción "Improve" sobre un plan guardado en la vista Plans | Envías un plan guardado al flujo de mejora. El agente lee primero el archivo del plan, luego propone cambios basados en el estado actual del repositorio y se ofrece a editar ese mismo archivo. | +| Implementar plan | La acción "Implement" sobre un plan guardado | Envías un plan guardado al flujo de implementación. El agente lee el archivo del plan y lo implementa de principio a fin sin ampliar el alcance, y guarda ajustes del plan en el archivo cuando el propio plan resulta estar mal. | + +### Session + +La mayoría alimentan comandos de barra que se escriben en el composer. La mayoría también aparecen como chips de inicio en el borrador de una sesión nueva. + +| Prompt | Dónde se ejecuta | Cuándo se dispara | +| --- | --- | --- | +| Tour del código | `/explore` | Pides una orientación general del código. | +| Resumen de sesión | `/summary`, opcionalmente `/summary ` | Resumes la conversación hasta ahora, útil para pasar a una sesión nueva. Requiere una sesión existente. | +| Revisión del workspace | `/workspace-review` | Pides al agente revisar el diff actual del workspace en cuanto a intención, corrección y seguridad. | +| Planificación de feature | `/plan-feature` | Conviertes una idea rough de feature en un plan de implementación mediante un diálogo guiado de preguntas y respuestas. | +| Definir Goal | `/craft-goal`, opcionalmente `/craft-goal ` | Conviertes una idea en un objetivo Goal verificable para el diálogo de Goal. | +| Ponerse al día | `/catch-up` | Vuelves a un proyecto y preguntas en qué quedó y qué seguir. | +| Depuración | `/debug` | Investigas un bug: el agente forma hipótesis, confirma la causa raíz desde el código y solo entonces propone un arreglo. | +| Sopesar opciones | `/weigh` | Sabes qué construir pero no cómo. El agente compara dos o tres enfoques y recomienda uno. | +| Fusion | La acción "Run fusion" sobre un grupo de multi-run | Combinas los resultados de varias ejecuciones en una respuesta. Los resultados se añaden después de las instrucciones. | + +### Prompts sin página en Settings + +Algunos prompts se disparan automáticamente y no tienen página editable en Settings: + +| Prompt | Cuándo se dispara | +| --- | --- | +| Tarea programada | `/schedule-task`, opcionalmente con una idea inicial. Guía el diálogo que define una tarea programada. | +| Handoff de revisión | `/handoff-review`, o el botón Review en la vista de diff con el handoff activado. Genera el handoff en la sesión de trabajo. | +| Mensaje inicial de la sesión de revisión | El primer mensaje de la sesión de revisión generada, con el handoff cuando se produjo, o sin él. | +| Feedback de revisión / respuesta de implementación | Llevan mensajes entre las dos sesiones: el feedback del revisor vuelve a la sesión que implementa, y la respuesta del implementador regresa a la sesión de revisión. | + ## Relacionado - [Flujos de trabajo de Git y GitHub](/es/git/) — muchos de estos prompts impulsan los flujos de git +- [Notas, todos y planes](/es/notes-todos-plans/) — los todos y planes detrás de los prompts de Planning +- [Multi-run](/es/multi-run/) — grupos de ejecución y fusion diff --git a/packages/docs/content/docs/es/providers.mdx b/packages/docs/content/docs/es/providers.mdx index 24e5b94f..65526b23 100644 --- a/packages/docs/content/docs/es/providers.mdx +++ b/packages/docs/content/docs/es/providers.mdx @@ -45,6 +45,6 @@ Los inicios de sesión de los proveedores los guarda OpenCode, no OpenChamber, a ## Relacionado -- [Integraciones](/es/integrations/) — usa una suscripción de Claude, Command Code o Cursor como proveedor +- [Integraciones](/es/integrations/) — usa una suscripción de Claude o Cursor como proveedor - [Servidores MCP](/es/mcp/) — añade herramientas extra para los agentes - [Uso y cuotas](/es/usage/) — controla cuánto has consumido diff --git a/packages/docs/content/docs/es/skills-catalog.mdx b/packages/docs/content/docs/es/skills-catalog.mdx index eadec080..c29ffb71 100644 --- a/packages/docs/content/docs/es/skills-catalog.mdx +++ b/packages/docs/content/docs/es/skills-catalog.mdx @@ -12,7 +12,7 @@ Para escribir tus propias skills, consulta [Skills](/es/skills/). ## Instala una skill 1. Abre el catálogo. -2. Explora las fuentes integradas —el repositorio de skills de Anthropic y el registro comunitario de ClawHub— o busca. +2. Explora las fuentes integradas —como el repositorio de skills de Anthropic— o busca. 3. Elige una skill e instálala. 4. Elige dónde instalarla: para todo lo que hagas, o solo en el proyecto actual. diff --git a/packages/docs/content/docs/fr/integrations.mdx b/packages/docs/content/docs/fr/integrations.mdx index df347a34..2628a870 100644 --- a/packages/docs/content/docs/fr/integrations.mdx +++ b/packages/docs/content/docs/fr/integrations.mdx @@ -1,16 +1,17 @@ --- title: Intégrations -description: Utilise ton abonnement Claude, Command Code ou Cursor comme fournisseur. +description: Utilise ton abonnement Claude ou Cursor comme fournisseur. --- # Intégrations Une intégration est un petit plugin qui ajoute un fournisseur à OpenChamber à partir d'un abonnement que tu possèdes déjà. Tu les gères dans **Settings → Integrations**. +> **Fonctionnalité expérimentale.** Nous cherchons à respecter les règles des fournisseurs, mais les restrictions et suspensions de compte relèvent de leur décision. Utilise les intégrations à tes risques. + Intégrations disponibles : - **Claude Code** — ton plan Claude Pro ou Max, sans clés API -- **Command Code** — ton plan Command Code - **Cursor** — les limites de modèles de ton plan Cursor ## Installer une intégration @@ -29,19 +30,10 @@ Claude Code utilise ton plan Claude Pro ou Max — sans clés API et sans applic 1. Installe l'intégration (ci-dessus). 2. Choisis **Set up** et connecte-toi. Si tu n'as pas encore la CLI Claude Code, la configuration propose de l'installer d'abord, puis de te connecter. -Claude Code est la seule intégration ici qui exige que la CLI de son fournisseur soit installée et connectée. Command Code et Cursor n'exigent pas leurs CLIs. +Claude Code est la seule intégration ici qui exige que la CLI de son fournisseur soit installée et connectée. Cursor n'exige pas sa CLI. **Comment ton compte Claude reste protégé :** cette intégration utilise le Claude Agent SDK officiel d'Anthropic et ta CLI Claude Code installée. Elle ne détourne pas l'OAuth, n'extrait ni rejoue de tokens de navigateur, ne se fait pas passer pour un client non pris en charge et ne contourne pas l'authentification d'Anthropic. Elle reste sur la voie d'accès prise en charge par Anthropic et ne porte donc pas le risque de bannissement associé au détournement de tokens ou aux contournements d'authentification non autorisés. -## Command Code - -Command Code utilise ton plan Command Code. - -1. Installe l'intégration (ci-dessus). -2. Choisis **Set up** — une page s'ouvre dans le navigateur. Autorise l'accès, puis reviens dans OpenChamber. - -Sur une machine sans interface graphique ou en CI, définis la variable d'environnement `COMMAND_CODE_API_KEY` au lieu de te connecter via le navigateur. - ## Cursor Cursor rend disponibles dans OpenChamber les modèles inclus dans ton plan Cursor. diff --git a/packages/docs/content/docs/fr/magic-prompts.mdx b/packages/docs/content/docs/fr/magic-prompts.mdx index 2a7312e6..6314b953 100644 --- a/packages/docs/content/docs/fr/magic-prompts.mdx +++ b/packages/docs/content/docs/fr/magic-prompts.mdx @@ -21,6 +21,64 @@ Certains prompts ont une partie visible (le message que vous verriez) et une par Vous avez changé d’avis ? Chaque prompt possède **reset to default**, et il existe aussi **reset all** si vous voulez tout reprendre depuis le début. +## Où chaque prompt est utilisé + +Chaque prompt ci-dessous indique où il s’exécute et ce qui le déclenche. Vérifiez le déclencheur avant de modifier, pour savoir quel flux vous changez. + +### Git + +| Prompt | Où il s’exécute | Quand il se déclenche | +| --- | --- | --- | +| Génération de commit | Le bouton de génération dans la zone de commit de la vue git, et l’écran Changes sur mobile | Vous générez un message de commit. Les fichiers sélectionnés et les sujets des commits récents de la branche sont insérés, pour que le message respecte le style du dépôt. | +| Génération de PR | Le formulaire de création de pull request dans l’onglet PR de la vue git | Vous générez le titre et le corps d’une PR. Sont insérés les branches base et head, les commits et fichiers modifiés entre elles, votre contexte additionnel et le modèle de PR du dépôt s’il existe. | +| Résolution de conflit merge/rebase | Le dialogue de conflits dans la vue git, quand un merge ou un rebase s’arrête sur des conflits | Vous choisissez « Resolve in current session » ou « Resolve in new session ». L’agent lit les fichiers en conflit, propose une stratégie par fichier et attend votre confirmation avant de modifier, staging ou poursuivre l’opération. | +| Résolution de conflit cherry-pick | La section « Re-integrate commits » d’une session en worktree | Le déplacement des commits de la session vers la branche cible rencontre un conflit et vous le confiez à l’agent. L’agent résout dans le worktree temporaire, stage les fichiers et poursuit le cherry-pick. | + +### GitHub + +| Prompt | Où il s’exécute | Quand il se déclenche | +| --- | --- | --- | +| Relecture de PR | Le sélecteur « Link GitHub PR » dans le menu de pièces jointes du composer, et le dialogue de nouveau worktree | Deux déclencheurs. Attacher une PR comme contexte prépare les instructions, envoyées avec votre prochain message. Créer une session de worktree depuis une PR utilise le prompt comme premier message de la session, avec le contexte complet de la PR. | +| Relecture d’issue | Le dialogue de nouveau worktree, quand le worktree part d’une issue | Le premier message de la nouvelle session relit l’issue, avec son corps et ses commentaires attachés comme contexte. | +| Relecture de checks échoués / commentaires de PR / commentaire unique de PR | — | Aucun flux ne les envoie aujourd’hui. La vue PR les déclenchait avant via des actions de relecture en un clic ; désormais les checks échoués et les commentaires s’épinglent comme brouillons de contexte de chat. Ils restent modifiables pour que les overrides existants continuent de fonctionner. | + +### Planning + +| Prompt | Où il s’exécute | Quand il se déclenche | +| --- | --- | --- | +| Planification depuis un todo | Le panneau Todos dans la barre latérale du projet | Vous envoyez un todo vers une session ou une nouvelle session en worktree. Le texte du todo devient le message visible ; les instructions en font un dialogue de planification guidé par des questions plutôt qu’un passage direct à l’implémentation. | +| Améliorer un plan | L’action « Improve » sur un plan enregistré dans la vue Plans | Vous envoyez un plan enregistré dans le flux d’amélioration. L’agent lit d’abord le fichier du plan, propose ensuite des changements ancrés dans l’état actuel du dépôt et propose de modifier le même fichier. | +| Implémenter un plan | L’action « Implement » sur un plan enregistré | Vous envoyez un plan enregistré dans le flux d’implémentation. L’agent lit le fichier du plan et l’implémente de bout en bout sans élargir le périmètre, enregistrant les ajustements dans le fichier quand le plan lui-même s’avère erroné. | + +### Session + +La plupart alimentent des commandes slash saisies dans le composer. La plupart apparaissent aussi comme chips de départ sur le brouillon d’une nouvelle session. + +| Prompt | Où il s’exécute | Quand il se déclenche | +| --- | --- | --- | +| Tour du code | `/explore` | Vous demandez une vue d’ensemble du code. | +| Résumé de session | `/summary`, éventuellement `/summary ` | Vous résumez la conversation en cours — utile pour passer à une nouvelle session. Nécessite une session existante. | +| Relecture du workspace | `/workspace-review` | Vous demandez à l’agent de relire le diff actuel du workspace sous l’angle intention, correction et sécurité. | +| Planification de fonctionnalité | `/plan-feature` | Vous transformez une idée grossière de fonctionnalité en plan d’implémentation via un dialogue guidé de questions-réponses. | +| Formuler un Goal | `/craft-goal`, éventuellement `/craft-goal ` | Vous transformez une idée en objectif Goal vérifiable pour le dialogue Goal. | +| Se remettre dans le bain | `/catch-up` | Vous revenez sur un projet et demandez où en sont les choses et quoi reprendre. | +| Débogage | `/debug` | Vous investiguez un bug : l’agent forme des hypothèses, confirme la cause racine dans le code et seulement ensuite propose un correctif. | +| Peser les options | `/weigh` | Vous savez quoi construire mais pas comment. L’agent compare deux ou trois approches et en recommande une. | +| Fusion | L’action « Run fusion » sur un groupe multi-run | Vous combinez les sorties de plusieurs exécutions en une réponse. Les sorties des exécutions sont ajoutées après les instructions. | + +### Prompts sans page dans les Paramètres + +Quelques prompts se déclenchent automatiquement et n’ont pas de page modifiable dans les Paramètres : + +| Prompt | Quand il se déclenche | +| --- | --- | +| Tâche planifiée | `/schedule-task`, éventuellement avec une idée initiale. Guide le dialogue qui définit une tâche planifiée. | +| Handoff de relecture | `/handoff-review`, ou le bouton Review dans la vue diff avec handoff activé. Génère le handoff dans la session de travail. | +| Premier message de la session de relecture | Le message d’ouverture de la session de relecture générée — avec le handoff quand il a été produit, sans sinon. | +| Retour de relecture / réponse d’implémentation | Font circuler les messages entre les deux sessions : le retour du relecteur revient vers la session qui implémente, et la réponse de l’implémenteur repart vers la session de relecture. | + ## Pages liées - [Workflows Git et GitHub](/git/) — beaucoup de ces prompts alimentent les flux git +- [Notes, todos et plans](/notes-todos-plans/) — les todos et plans derrière les prompts Planning +- [Multi-run](/multi-run/) — groupes d’exécution et fusion diff --git a/packages/docs/content/docs/fr/providers.mdx b/packages/docs/content/docs/fr/providers.mdx index d79633d8..a3ce0149 100644 --- a/packages/docs/content/docs/fr/providers.mdx +++ b/packages/docs/content/docs/fr/providers.mdx @@ -45,6 +45,6 @@ Les connexions aux fournisseurs sont stockées par OpenCode, pas OpenChamber ; e ## Pages liées -- [Intégrations](/integrations/) — utiliser un abonnement Claude, Command Code ou Cursor comme fournisseur +- [Intégrations](/integrations/) — utiliser un abonnement Claude ou Cursor comme fournisseur - [Serveurs MCP](/mcp/) — ajouter des outils supplémentaires aux agents - [Utilisation et quotas](/usage/) — suivre votre consommation diff --git a/packages/docs/content/docs/fr/skills-catalog.mdx b/packages/docs/content/docs/fr/skills-catalog.mdx index 72123162..e6eb2323 100644 --- a/packages/docs/content/docs/fr/skills-catalog.mdx +++ b/packages/docs/content/docs/fr/skills-catalog.mdx @@ -12,7 +12,7 @@ Pour écrire vos propres skills, voir [Skills](/skills/). ## Installer un skill 1. Ouvrez le catalogue. -2. Parcourez les sources intégrées — le dépôt de skills Anthropic et le registre communautaire ClawHub — ou lancez une recherche. +2. Parcourez les sources intégrées — comme le dépôt de skills Anthropic — ou lancez une recherche. 3. Choisissez un skill et installez-le. 4. Choisissez où l’installer : pour tout ce que vous faites, ou seulement pour le projet actuel. diff --git a/packages/docs/content/docs/integrations.mdx b/packages/docs/content/docs/integrations.mdx index 14aee1f1..8aef2d38 100644 --- a/packages/docs/content/docs/integrations.mdx +++ b/packages/docs/content/docs/integrations.mdx @@ -1,16 +1,17 @@ --- title: Integrations -description: Use your Claude, Command Code, or Cursor subscription as a provider. +description: Use your Claude or Cursor subscription as a provider. --- # Integrations An integration is a small plugin that adds a provider to OpenChamber using a subscription you already have. You manage them at **Settings → Integrations**. +> **Experimental feature.** We aim to respect provider policies, but account restrictions and suspensions remain each provider's decision. Use integrations at your own risk. + Available integrations: - **Claude Code** — your Claude Pro or Max plan, no API keys -- **Command Code** — your Command Code plan - **Cursor** — the model limits of your Cursor plan ## Install an integration @@ -29,19 +30,10 @@ Claude Code uses your Claude Pro or Max plan — no API keys and no separate Cla 1. Install the integration (above). 2. Choose **Set up** and sign in. If you don't have the Claude Code CLI yet, setup offers to install it first and then sign you in. -Claude Code is the only integration here that requires its provider CLI to be installed and signed in. Command Code and Cursor do not require their CLIs. +Claude Code is the only integration here that requires its provider CLI to be installed and signed in. Cursor does not require its CLI. **How your Claude account stays safe:** this integration uses Anthropic's official Claude Agent SDK and your installed Claude Code CLI. It does not hijack OAuth, extract or replay browser tokens, impersonate an unsupported client, or bypass Anthropic's authentication flow. It stays on Anthropic's supported access path, so it does not carry the account-ban risk of token hijacking or unauthorized authentication workarounds. -## Command Code - -Command Code uses your Command Code plan. - -1. Install the integration (above). -2. Choose **Set up** — a browser page opens. Approve access, then return to OpenChamber. - -On a headless machine or in CI, set the `COMMAND_CODE_API_KEY` environment variable instead of signing in in the browser. - ## Cursor Cursor makes the models included in your Cursor plan available in OpenChamber. diff --git a/packages/docs/content/docs/ja/integrations.mdx b/packages/docs/content/docs/ja/integrations.mdx index 02fa8e36..b13ac0cd 100644 --- a/packages/docs/content/docs/ja/integrations.mdx +++ b/packages/docs/content/docs/ja/integrations.mdx @@ -1,16 +1,17 @@ --- title: 統合機能 -description: Claude、Command Code、Cursor のサブスクリプションをプロバイダーとして使う。 +description: Claude または Cursor のサブスクリプションをプロバイダーとして使う。 --- # 統合機能 統合機能(インテグレーション)は、すでに持っているサブスクリプションを使って OpenChamber にプロバイダーを追加する小さなプラグインです。**Settings → Integrations** で管理します。 +> **実験的な機能。** プロバイダーの方針を尊重するよう努めていますが、アカウントの制限や停止は各プロバイダーの判断に委ねられます。自己責任で連携を使用してください。 + 利用できる統合機能: - **Claude Code** — Claude Pro または Max プラン、API キー不要 -- **Command Code** — Command Code のプラン - **Cursor** — Cursor プランのモデル利用枠 ## 統合機能をインストールする @@ -29,19 +30,10 @@ Claude Code は Claude Pro または Max プランを使います — API キー 1. 統合機能をインストールします(上記)。 2. **Set up** を選んでサインインします。Claude Code CLI がまだない場合は、セットアップがまずインストールを提案し、その後サインインします。 -Claude Code は、ここで唯一プロバイダーの CLI のインストールとサインインを必要とする統合機能です。Command Code と Cursor は CLI を必要としません。 +Claude Code は、ここで唯一プロバイダーの CLI のインストールとサインインを必要とする統合機能です。Cursor は CLI を必要としません。 **Claude アカウントが守られる仕組み:** この統合機能は Anthropic の公式 Claude Agent SDK と、インストール済みの Claude Code CLI を使用します。OAuth の乗っ取り、ブラウザートークンの抽出や再生、未対応クライアントへの偽装、Anthropic の認証フローの回避は一切行いません。Anthropic がサポートする正規のアクセス経路を使うため、トークン乗っ取りや不正な認証の回避につきもののアカウント停止リスクはありません。 -## Command Code - -Command Code は Command Code のプランを使います。 - -1. 統合機能をインストールします(上記)。 -2. **Set up** を選ぶとブラウザーでページが開きます。アクセスを許可して OpenChamber に戻ります。 - -画面のないサーバーや CI では、ブラウザーでサインインする代わりに環境変数 `COMMAND_CODE_API_KEY` を設定してください。 - ## Cursor Cursor は Cursor プランに含まれるモデルを OpenChamber で使えるようにします。 diff --git a/packages/docs/content/docs/ja/magic-prompts.mdx b/packages/docs/content/docs/ja/magic-prompts.mdx index a31cc297..8209b16e 100644 --- a/packages/docs/content/docs/ja/magic-prompts.mdx +++ b/packages/docs/content/docs/ja/magic-prompts.mdx @@ -21,6 +21,64 @@ OpenChamber は、コミットメッセージの作成、PR の下書き、Issue 気が変わりましたか?各プロンプトには **reset to default** があり、すべてを最初からやり直したい場合は **reset all** もあります。 +## 各プロンプトが使われる場所 + +以下の表は、各プロンプトがどこで実行され、何がきっかけで動くかを示します。編集前にトリガーを確認し、どのフローを変えるのかを把握してください。 + +### Git + +| プロンプト | 実行される場所 | 動くタイミング | +| --- | --- | --- | +| コミット生成 | git ビューのコミット欄にある生成ボタン、およびモバイルの Changes 画面 | コミットメッセージを生成するとき。選択したファイルとブランチの直近コミットの件名が差し込まれ、メッセージがリポジトリの既存スタイルに合います。 | +| PR 生成 | git ビュー PR タブの pull request 作成フォーム | PR のタイトルと本文を生成するとき。base と head ブランチ、その間のコミットと変更ファイル、追加コンテキスト、リポジトリに PR テンプレートがあればそれも差し込まれます。 | +| merge/rebase コンフリクト解決 | merge や rebase がコンフリクトで止まったときの git ビューのコンフリクトダイアログ | "Resolve in current session" または "Resolve in new session" を選んだとき。エージェントはコンフリクトファイルを読み、ファイルごとの解決戦略を提案し、編集・stage・操作の再開の前に確認を待ちます。 | +| cherry-pick コンフリクト解決 | worktree セッションの "Re-integrate commits" セクション | セッションのコミットを対象ブランチへ移す途中でコンフリクトが起き、エージェントに任せたとき。エージェントは一時 worktree の中で解決し、ファイルを stage して cherry-pick を続けます。 | + +### GitHub + +| プロンプト | 実行される場所 | 動くタイミング | +| --- | --- | --- | +| PR レビュー | composer の添付メニューにある "Link GitHub PR" ピッカー、および新規 worktree ダイアログ | 2 つのトリガー。PR をコンテキストとして添付すると instructions が用意され、次のメッセージと一緒に送られます。PR から worktree セッションを始めると、このプロンプトがそのセッションの最初のメッセージになり、PR の完全なコンテキストが添付されます。 | +| Issue レビュー | Issue から worktree を作るときの新規 worktree ダイアログ | 新しいセッションの最初のメッセージが Issue をレビューし、本文とコメントがコンテキストとして添付されます。 | +| PR の失敗チェック / PR コメント / 個別 PR コメント | — | 現在はどのフローからも送信されません。以前は PR ビューのワンクリックレビューアクションから起動されましたが、今は失敗チェックとコメントがチャットコンテキストの下書きとしてピン留めされます。既存のオーバーライドが機能し続けるよう、編集可能なまま残っています。 | + +### Planning + +| プロンプト | 実行される場所 | 動くタイミング | +| --- | --- | --- | +| todo からの計画 | プロジェクトサイドバーの Todos パネル | todo をセッションまたは新しい worktree セッションへ送るとき。todo のテキストが見えるメッセージになり、instructions は実装へ飛ばず、質問主体の計画対話に変えます。 | +| 計画の改善 | Plans ビューの保存済み計画に対する "Improve" アクション | 保存済み計画を改善フローへ送るとき。エージェントはまず計画ファイルを読み、リポジトリの現在の状態に即した変更を提案し、同じファイルの編集を申し出ます。 | +| 計画の実装 | 保存済み計画に対する "Implement" アクション | 保存済み計画を実装フローへ送るとき。エージェントは計画ファイルを読み、スコープを広げずに最後まで実装し、計画自体に誤りが見つかった場合は調整を同じファイルへ保存します。 | + +### Session + +これらの多くは、composer に入力するスラッシュコマンドとして動きます。多くは新しいセッションの下書き画面でスターターチップとしても表示されます。 + +| プロンプト | 実行される場所 | 動くタイミング | +| --- | --- | --- | +| コードベースツアー | `/explore` | コードベースの概要を把握したいとき。 | +| セッション要約 | `/summary`、オプションで `/summary <トピック>` | ここまでの会話を要約します。新しいセッションへの引き継ぎに便利です。既存のセッションが必要です。 | +| ワークスペースレビュー | `/workspace-review` | 現在のワークスペース差分を意図・正確性・セキュリティの観点でレビューしてほしいとき。 | +| 機能計画 | `/plan-feature` | 大まかな機能アイデアを、質疑応答の対話を通じて実装計画に変えたいとき。 | +| Goal 作成 | `/craft-goal`、オプションで `/craft-goal <アイデア>` | アイデアを、Goal ダイアログで使える検証可能な Goal 目標に変えたいとき。 | +| キャッチアップ | `/catch-up` | プロジェクトに戻って、どこまで進んでいて次に何をするか知りたいとき。 | +| デバッグ | `/debug` | バグを調査するとき。エージェントは仮説を立て、コードから根本原因を確認してから修正を提案します。 | +| 選択肢の比較 | `/weigh` | 何を作るかは分かっているが作り方が分からないとき。エージェントが 2〜3 のアプローチを比較し、1 つを推奨します。 | +| Fusion | multi-run グループの "Run fusion" アクション | 複数ランの出力を 1 つの回答にまとめるとき。ランの出力は instructions の後に続けて添付されます。 | + +### Settings にページのないプロンプト + +一部のプロンプトは自動的に動き、Settings には編集ページがありません: + +| プロンプト | 動くタイミング | +| --- | --- | +| スケジュールタスク | `/schedule-task`、オプションで初期アイデアと一緒に。スケジュールタスクを定義する対話を進めます。 | +| レビュー用ハンドオフ | `/handoff-review`、またはハンドオフを有効にした diff ビューの Review ボタン。作業セッション内でハンドオフを生成します。 | +| レビューセッションの開始メッセージ | 生成されたレビューセッションの最初のメッセージ。ハンドオフが作られた場合はそれを含み、なければ含みません。 | +| レビューフィードバック / 実装応答 | 2 つのセッションの間でメッセージを運びます。レビュアーのフィードバックは実装セッションへ、実装者の応答はレビューセッションへ戻ります。 | + ## 関連 - [Git と GitHub ワークフロー](/git/) — これらのプロンプトの多くが Git フローを支えています +- [ノート、todo と計画](/notes-todos-plans/) — Planning プロンプトの背後にある todo と計画 +- [Multi-run](/multi-run/) — ラングループと fusion diff --git a/packages/docs/content/docs/ja/providers.mdx b/packages/docs/content/docs/ja/providers.mdx index 62f1c663..7e9c0525 100644 --- a/packages/docs/content/docs/ja/providers.mdx +++ b/packages/docs/content/docs/ja/providers.mdx @@ -45,6 +45,6 @@ OpenChamber が何かを行うには、少なくとも 1 つの AI プロバイ ## 関連 -- [統合機能](/integrations/) — Claude、Command Code、Cursor のサブスクリプションをプロバイダーとして使う +- [統合機能](/integrations/) — Claude または Cursor のサブスクリプションをプロバイダーとして使う - [MCP サーバー](/mcp/) — エージェントに追加ツールを加える - [使用量とクォータ](/usage/) — 使った量を追跡する diff --git a/packages/docs/content/docs/ja/skills-catalog.mdx b/packages/docs/content/docs/ja/skills-catalog.mdx index 8991f05f..029a0882 100644 --- a/packages/docs/content/docs/ja/skills-catalog.mdx +++ b/packages/docs/content/docs/ja/skills-catalog.mdx @@ -12,7 +12,7 @@ Skills Catalog では、自分で書く代わりに、他の人が公開した ## スキルをインストールする 1. カタログを開きます。 -2. 組み込みソース(Anthropic skills repo と ClawHub community registry)を閲覧するか、検索します。 +2. 組み込みソース(Anthropic skills repo など)を閲覧するか、検索します。 3. スキルを選び、インストールします。 4. インストール先を選びます。すべての作業で使うか、現在のプロジェクトだけで使うかです。 diff --git a/packages/docs/content/docs/ko/integrations.mdx b/packages/docs/content/docs/ko/integrations.mdx index f2589d50..0663879e 100644 --- a/packages/docs/content/docs/ko/integrations.mdx +++ b/packages/docs/content/docs/ko/integrations.mdx @@ -1,16 +1,17 @@ --- title: 통합 기능 -description: Claude, Command Code 또는 Cursor 구독을 공급자로 사용하세요. +description: Claude 또는 Cursor 구독을 공급자로 사용하세요. --- # 통합 기능 통합 기능(인테그레이션)은 이미 가지고 있는 구독을 사용해 OpenChamber에 공급자를 추가하는 작은 플러그인입니다. **Settings → Integrations**에서 관리합니다. +> **실험 단계 기능.** 프로바이더 정책을 존중하려 노력하지만, 계정 제한과 정지는 각 프로바이더의 결정입니다. 본인의 책임 아래 통합 기능을 사용하세요. + 사용 가능한 통합 기능: - **Claude Code** — Claude Pro 또는 Max 플랜, API 키 불필요 -- **Command Code** — Command Code 플랜 - **Cursor** — Cursor 플랜의 모델 한도 ## 통합 기능 설치 @@ -29,19 +30,10 @@ Claude Code는 Claude Pro 또는 Max 플랜을 사용합니다 — API 키도 1. 통합 기능을 설치합니다(위 참고). 2. **Set up**를 선택하고 로그인합니다. Claude Code CLI가 아직 없으면 설정에서 먼저 설치를 제안한 뒤 로그인을 진행합니다. -Claude Code는 여기에서 유일하게 공급자 CLI 설치와 로그인을 필요로 하는 통합 기능입니다. Command Code와 Cursor는 CLI가 필요 없습니다. +Claude Code는 여기에서 유일하게 공급자 CLI 설치와 로그인을 필요로 하는 통합 기능입니다. Cursor는 CLI가 필요 없습니다. **Claude 계정이 안전하게 유지되는 방식:** 이 통합 기능은 Anthropic의 공식 Claude Agent SDK와 설치된 Claude Code CLI를 사용합니다. OAuth 탈취, 브라우저 토큰 추출·재사용, 지원되지 않는 클라이언트로의 위장, Anthropic 인증 우회를 하지 않습니다. Anthropic이 지원하는 정상 경로를 사용하므로 토큰 탈취나 비인가 인증 우회에 따른 계정 정지 위험이 없습니다. -## Command Code - -Command Code는 Command Code 플랜을 사용합니다. - -1. 통합 기능을 설치합니다(위 참고). -2. **Set up**를 선택하면 브라우저에서 페이지가 열립니다. 접근을 승인한 뒤 OpenChamber로 돌아옵니다. - -화면이 없는 서버나 CI 환경에서는 브라우저 로그인 대신 `COMMAND_CODE_API_KEY` 환경 변수를 설정하세요. - ## Cursor Cursor는 Cursor 플랜에 포함된 모델을 OpenChamber에서 사용할 수 있게 합니다. diff --git a/packages/docs/content/docs/ko/magic-prompts.mdx b/packages/docs/content/docs/ko/magic-prompts.mdx index e826e7b9..e7f17689 100644 --- a/packages/docs/content/docs/ko/magic-prompts.mdx +++ b/packages/docs/content/docs/ko/magic-prompts.mdx @@ -21,6 +21,64 @@ OpenChamber는 커밋 메시지 작성, PR 초안 작성, 이슈 검토, 충돌 마음이 바뀌었나요? 각 프롬프트에는 **reset to default**가 있고, 모든 곳에서 처음부터 다시 시작하려면 **reset all**이 있습니다. +## 각 프롬프트가 사용되는 곳 + +아래 표의 각 프롬프트는 실행되는 위치와 실행을 일으키는 트리거를 나타냅니다. 편집하기 전에 트리거를 확인해 어떤 흐름을 바꾸는지 알아두세요. + +### Git + +| 프롬프트 | 실행 위치 | 트리거 시점 | +| --- | --- | --- | +| 커밋 생성 | git 뷰 커밋 상자의 생성 버튼, 모바일 Changes 화면 | 커밋 메시지를 생성할 때. 선택한 파일과 브랜치의 최근 커밋 제목이 채워져 메시지가 저장소 스타일을 따르게 됩니다. | +| PR 생성 | git 뷰 PR 탭의 pull request 생성 폼 | PR 제목과 본문을 생성할 때. base와 head 브랜치, 그 사이의 커밋과 변경 파일, 추가 컨텍스트, 저장소에 PR 템플릿이 있으면 그것까지 채워집니다. | +| merge/rebase 충돌 해결 | merge나 rebase가 충돌로 멈췄을 때 git 뷰의 충돌 대화상자 | "Resolve in current session" 또는 "Resolve in new session"을 선택할 때. 에이전트가 충돌 파일을 읽고 파일별 해결 전략을 제안하며, 편집·stage·계속 진행 전에 확인을 기다립니다. | +| cherry-pick 충돌 해결 | worktree 세션의 "Re-integrate commits" 섹션 | 세션의 커밋을 대상 브랜치로 옮기다 충돌이 나서 에이전트에 맡길 때. 에이전트가 임시 worktree에서 해결하고 파일을 stage한 뒤 cherry-pick을 계속합니다. | + +### GitHub + +| 프롬프트 | 실행 위치 | 트리거 시점 | +| --- | --- | --- | +| PR 검토 | 작성기 첨부 메뉴의 "Link GitHub PR" 선택기, 새 worktree 대화상자 | 두 가지 트리거. PR을 컨텍스트로 첨부하면 지침이 준비되어 다음 메시지와 함께 전송됩니다. PR에서 worktree 세션을 시작하면 이 프롬프트가 그 세션의 첫 메시지가 되고 전체 PR 컨텍스트가 첨부됩니다. | +| 이슈 검토 | worktree를 이슈에서 시작할 때의 새 worktree 대화상자 | 새 세션의 첫 메시지가 이슈를 검토하며, 본문과 댓글이 컨텍스트로 첨부됩니다. | +| PR 실패 검사 / PR 댓글 / 단일 PR 댓글 검토 | — | 현재 어떤 흐름도 이것들을 보내지 않습니다. PR 뷰가 이전에는 원클릭 검토 액션으로 실행했지만, 이제 실패한 검사와 댓글은 채팅 컨텍스트 초안으로 고정됩니다. 기존 재정의가 계속 동작하도록 편집 가능한 상태로 남습니다. | + +### Planning + +| 프롬프트 | 실행 위치 | 트리거 시점 | +| --- | --- | --- | +| todo 계획 | 프로젝트 사이드바의 Todos 패널 | todo를 세션 또는 새 worktree 세션으로 보낼 때. todo 텍스트가 보이는 메시지가 되고, 지침은 바로 구현으로 넘어가지 않고 질문 중심의 계획 대화로 만듭니다. | +| 계획 개선 | Plans 뷰에서 저장된 계획의 "Improve" 액션 | 저장된 계획을 개선 흐름으로 보낼 때. 에이전트가 먼저 계획 파일을 읽고, 저장소 현재 상태에 근거한 변경을 제안하며 같은 파일을 편집하겠다고 제안합니다. | +| 계획 구현 | 저장된 계획의 "Implement" 액션 | 저장된 계획을 구현 흐름으로 보낼 때. 에이전트가 계획 파일을 읽고 범위를 늘리지 않고 끝까지 구현하며, 계획 자체가 잘못된 것으로 밝혀지면 조정을 같은 파일에 저장합니다. | + +### Session + +대부분 작성기에 입력하는 슬래시 명령으로 동작합니다. 대부분 새 세션 초안 화면의 시작 칩으로도 나타납니다. + +| 프롬프트 | 실행 위치 | 트리거 시점 | +| --- | --- | --- | +| 코드베이스 투어 | `/explore` | 코드베이스의 전체 개요를 요청할 때. | +| 세션 요약 | `/summary`, 선택적으로 `/summary <주제>` | 지금까지의 대화를 요약할 때 — 새 세션으로 넘길 때 유용합니다. 기존 세션이 필요합니다. | +| 작업 공간 검토 | `/workspace-review` | 현재 작업 공간 diff를 의도, 정확성, 보안 관점에서 검토해 달라고 요청할 때. | +| 기능 계획 | `/plan-feature` | 거친 기능 아이디어를 안내된 질문-답변 대화를 통해 구현 계획으로 만들 때. | +| Goal 만들기 | `/craft-goal`, 선택적으로 `/craft-goal <아이디어>` | 아이디어를 Goal 대화상자에 쓸 수 있는 검증 가능한 Goal 목표로 바꿀 때. | +| 따라잡기 | `/catch-up` | 프로젝트로 돌아와 어디까지 진행됐고 다음에 무엇을 할지 물을 때. | +| 디버깅 | `/debug` | 버그를 조사할 때: 에이전트가 가설을 세우고 코드에서 근본 원인을 확인한 뒤에야 수정을 제안합니다. | +| 옵션 저울질 | `/weigh` | 무엇을 만들지는 알지만 어떻게 할지 모를 때. 에이전트가 두세 가지 접근을 비교하고 하나를 추천합니다. | +| Fusion | multi-run 그룹의 "Run fusion" 액션 | 여러 실행의 출력을 하나의 답변으로 합칠 때. 실행 출력은 지침 뒤에 추가됩니다. | + +### Settings에 페이지가 없는 프롬프트 + +일부 프롬프트는 자동으로 실행되며 Settings에 편집 가능한 페이지가 없습니다: + +| 프롬프트 | 트리거 시점 | +| --- | --- | +| 예약 작업 | `/schedule-task`, 선택적으로 초기 아이디어와 함께. 예약 작업을 정의하는 대화를 이끕니다. | +| 검토 핸드오프 | `/handoff-review`, 또는 핸드오프를 켜고 diff 뷰의 Review 버튼. 작업 세션에서 핸드오프를 생성합니다. | +| 검토 세션 시작 메시지 | 생성된 검토 세션의 첫 메시지 — 핸드오프가 만들어졌으면 포함, 아니면 제외. | +| 검토 피드백 / 구현 응답 | 두 세션 사이에서 메시지를 전달합니다: 검토자 피드백은 구현 세션으로, 구현자 응답은 검토 세션으로 돌아갑니다. | + ## 관련 항목 - [Git & GitHub Workflows](/ko/git/) — 이러한 프롬프트 중 다수가 git 흐름을 구동합니다 +- [노트, todo와 계획](/ko/notes-todos-plans/) — Planning 프롬프트 뒤에 있는 todo와 계획 +- [Multi-run](/ko/multi-run/) — 실행 그룹과 fusion diff --git a/packages/docs/content/docs/ko/providers.mdx b/packages/docs/content/docs/ko/providers.mdx index 11d9007f..98fb109d 100644 --- a/packages/docs/content/docs/ko/providers.mdx +++ b/packages/docs/content/docs/ko/providers.mdx @@ -45,6 +45,6 @@ OpenChamber가 무언가를 하려면 먼저 최소한 하나의 AI 공급자가 ## 관련 항목 -- [통합 기능](/ko/integrations/) — Claude, Command Code, Cursor 구독을 공급자로 사용 +- [통합 기능](/ko/integrations/) — Claude 또는 Cursor 구독을 공급자로 사용 - [MCP Servers](/ko/mcp/) — 에이전트에 추가 도구를 제공합니다 - [Usage & Quotas](/ko/usage/) — 사용량을 추적합니다 diff --git a/packages/docs/content/docs/ko/skills-catalog.mdx b/packages/docs/content/docs/ko/skills-catalog.mdx index ba16461a..d2cbb407 100644 --- a/packages/docs/content/docs/ko/skills-catalog.mdx +++ b/packages/docs/content/docs/ko/skills-catalog.mdx @@ -12,7 +12,7 @@ Skills Catalog를 사용하면 직접 작성하는 대신 다른 사람이 게 ## 스킬 설치하기 1. 카탈로그를 엽니다. -2. 내장된 소스(Anthropic 스킬 저장소와 ClawHub 커뮤니티 레지스트리)를 둘러보거나 검색합니다. +2. 내장된 소스(예: Anthropic 스킬 저장소)를 둘러보거나 검색합니다. 3. 스킬을 선택하고 설치합니다. 4. 설치 위치를 선택합니다. 모든 작업에 적용할지, 현재 프로젝트에만 적용할지 선택합니다. diff --git a/packages/docs/content/docs/magic-prompts.mdx b/packages/docs/content/docs/magic-prompts.mdx index ba830e09..93938ee5 100644 --- a/packages/docs/content/docs/magic-prompts.mdx +++ b/packages/docs/content/docs/magic-prompts.mdx @@ -21,6 +21,64 @@ Some prompts have a visible part (the message you'd see) and an instructions par Changed your mind? Each prompt has **reset to default**, and there's a **reset all** if you want to start over everywhere. +## Where each prompt is used + +Every prompt below lists where it runs and the trigger that fires it. Check the trigger before editing, so you know which flow you're changing. + +### Git + +| Prompt | Where it runs | When it fires | +| --- | --- | --- | +| Commit generation | The generate button in the git view's commit box, and the mobile Changes screen | You generate a commit message. The selected files and the branch's recent commit subjects are filled in, so the subject matches your repo's existing style. | +| PR generation | The create-pull-request form in the git view's PR tab | You generate a PR title and body. Filled with the base and head branches, the commits and changed files between them, your additional context, and the repo's PR template when one exists. | +| Merge/rebase conflict resolution | The conflicts dialog in the git view, when a merge or rebase stops on conflicts | You pick "Resolve in current session" or "Resolve in new session". The agent reads the conflicted files, proposes a per-file resolution strategy, and waits for your confirmation before editing, staging, or continuing the operation. | +| Cherry-pick conflict resolution | The "Re-integrate commits" section for a worktree session | Moving the session's commits onto the target branch hits a conflict and you hand it to the agent. The agent resolves inside the temporary worktree, stages the resolved files, and continues the cherry-pick. | + +### GitHub + +| Prompt | Where it runs | When it fires | +| --- | --- | --- | +| PR review | The "Link GitHub PR" picker in the composer's attach menu, and the new worktree dialog | Two triggers. Attaching a PR as context renders the instructions, which go out with your next message. Starting a worktree session from a PR uses the prompt as that session's opening message, with the full PR context attached. | +| Issue review | The new worktree dialog, when you start the worktree from an issue | The new session's opening message reviews the issue, with its body and comments attached as context. | +| PR failed checks / PR comments / single PR comment | — | Not sent by any flow today. The PR view used to fire these from one-click review actions; failed checks and comments now pin as chat-context drafts instead. They stay editable so existing overrides keep working. | + +### Planning + +| Prompt | Where it runs | When it fires | +| --- | --- | --- | +| Todo planning | The Todos panel in the project sidebar | You send a todo to a session or a new worktree session. The todo text becomes the visible message; the instructions turn it into a question-first planning dialogue instead of jumping straight to implementation. | +| Improve plan | The "Improve" action on a saved plan in the Plans view | You send a saved plan into an improve flow. The agent reads the plan file first, then proposes changes grounded in the current repo state and offers to edit the same file. | +| Implement plan | The "Implement" action on a saved plan | You send a saved plan into an implement flow. The agent reads the plan file and implements it end to end without expanding scope, saving plan adjustments back to the file when the plan itself turns out to be wrong. | + +### Session + +Most of these power slash commands typed in the composer. Most also appear as starter chips on a new-session draft. + +| Prompt | Where it runs | When it fires | +| --- | --- | --- | +| Codebase tour | `/explore` | You ask for a high-level orientation of the codebase. | +| Session summary | `/summary`, optionally `/summary ` | You summarize the conversation so far, useful for handing off to a new session. Needs an existing session. | +| Workspace review | `/workspace-review` | You ask the agent to review the current workspace diff for intent, correctness, and security. | +| Feature planning | `/plan-feature` | You turn a rough feature idea into an implementation plan through a guided question-and-answer dialogue. | +| Goal crafting | `/craft-goal`, optionally `/craft-goal ` | You turn an idea into a verifiable Goal objective for the Goal dialog. | +| Catch up | `/catch-up` | You return to a project and ask where things stand and what to pick up next. | +| Debugging | `/debug` | You investigate a bug: the agent forms hypotheses, confirms the root cause from the code, and only then proposes a fix. | +| Weigh options | `/weigh` | You know what you want to build but not how. The agent compares two or three approaches and recommends one. | +| Fusion | The "Run fusion" action on a multi-run group | You combine the outputs of several runs into one answer. The run outputs are appended after the instructions. | + +### Prompts without a Settings entry + +A few prompts fire automatically and have no editable page in Settings: + +| Prompt | When it fires | +| --- | --- | +| Scheduled task | `/schedule-task`, optionally with an initial idea. Guides the dialogue that defines a scheduled task. | +| Review handoff | `/handoff-review`, or the Review button in the diff view with handoff enabled. Generates the handoff in the working session. | +| Review session starter | The opening message of the generated review session, with the handoff when one was produced and without it otherwise. | +| Review feedback / implementation response | Shuttle messages between the two sessions: reviewer feedback goes back to the implementing session, and the implementer's response returns to the review session. | + ## Related - [Git & GitHub Workflows](/git/) — many of these prompts power the git flows +- [Notes, Todos & Plans](/notes-todos-plans/) — the todos and plans behind the Planning prompts +- [Multi-run](/multi-run/) — run groups and fusion diff --git a/packages/docs/content/docs/pl/integrations.mdx b/packages/docs/content/docs/pl/integrations.mdx index aca4b269..7e3ddf2c 100644 --- a/packages/docs/content/docs/pl/integrations.mdx +++ b/packages/docs/content/docs/pl/integrations.mdx @@ -1,16 +1,17 @@ --- title: Integracje -description: Używaj subskrypcji Claude, Command Code lub Cursor jako dostawcy. +description: Używaj subskrypcji Claude lub Cursor jako dostawcy. --- # Integracje Integracja to mała wtyczka, która dodaje dostawcę do OpenChamber na podstawie subskrypcji, którą już masz. Zarządzasz nimi w **Settings → Integrations**. +> **Funkcja eksperymentalna.** Staramy się przestrzegać zasad dostawców, ale ograniczenia i zawieszenia kont pozostają decyzją każdego dostawcy. Używaj integracji na własne ryzyko. + Dostępne integracje: - **Claude Code** — Twój plan Claude Pro lub Max, bez kluczy API -- **Command Code** — Twój plan Command Code - **Cursor** — limity modeli z Twojego planu Cursor ## Instalacja integracji @@ -29,19 +30,10 @@ Claude Code korzysta z Twojego planu Claude Pro lub Max — bez kluczy API i bez 1. Zainstaluj integrację (patrz wyżej). 2. Wybierz **Set up** i zaloguj się. Jeśli nie masz jeszcze Claude Code CLI, konfiguracja zaoferuje najpierw jego instalację, a potem logowanie. -Claude Code jest jedyną integracją tutaj, która wymaga zainstalowanego i zalogowanego CLI swojego dostawcy. Command Code i Cursor nie wymagają swoich CLI. +Claude Code jest jedyną integracją tutaj, która wymaga zainstalowanego i zalogowanego CLI swojego dostawcy. Cursor nie wymaga swojego CLI. **Jak chronione jest Twoje konto Claude:** ta integracja używa oficjalnego Claude Agent SDK od Anthropic i Twojego zainstalowanego Claude Code CLI. Nie przechwytuje OAuth, nie wyodrębnia ani nie odtwarza tokenów przeglądarki, nie podszywa się pod nieobsługiwany klient i nie omija uwierzytelniania Anthropic. Działa na obsługiwanej przez Anthropic ścieżce dostępu, więc nie niesie ryzyka zablokowania konta związanego z przechwytywaniem tokenów lub nieautoryzowanymi obejściami uwierzytelniania. -## Command Code - -Command Code korzysta z Twojego planu Command Code. - -1. Zainstaluj integrację (patrz wyżej). -2. Wybierz **Set up** — w przeglądarce otworzy się strona. Zatwierdź dostęp i wróć do OpenChamber. - -Na maszynie bez interfejsu graficznego lub w CI ustaw zmienną środowiskową `COMMAND_CODE_API_KEY` zamiast logowania w przeglądarce. - ## Cursor Cursor udostępnia w OpenChamber modele zawarte w Twoim planie Cursor. diff --git a/packages/docs/content/docs/pl/magic-prompts.mdx b/packages/docs/content/docs/pl/magic-prompts.mdx index c54be855..aada9935 100644 --- a/packages/docs/content/docs/pl/magic-prompts.mdx +++ b/packages/docs/content/docs/pl/magic-prompts.mdx @@ -21,6 +21,64 @@ Niektóre prompty mają część widoczną (wiadomość, którą zobaczysz) i cz Zmieniłeś zdanie? Każdy prompt ma **reset to default**, a jest też **reset all**, jeśli chcesz zacząć wszystko od nowa. +## Gdzie używany jest każdy prompt + +Dla każdego prompta poniżej podano, gdzie się wykonuje i co go uruchamia. Sprawdź wyzwalacz przed edycją, żeby wiedzieć, który przepływ zmieniasz. + +### Git + +| Prompt | Gdzie się wykonuje | Kiedy się uruchamia | +| --- | --- | --- | +| Generowanie commita | Przycisk generowania w polu commita w widoku git oraz ekran Changes na mobile | Generujesz komunikat commita. Wstawiane są wybrane pliki i tematy ostatnich commitów gałęzi, dzięki czemu komunikat trzyma styl twojego repozytorium. | +| Generowanie PR | Formularz tworzenia pull requesta w zakładce PR widoku git | Generujesz tytuł i treść PR. Wstawiane są gałęzie base i head, commity i zmienione pliki między nimi, twój dodatkowy kontekst oraz szablon PR repozytorium, jeśli istnieje. | +| Rozwiązywanie konfliktu merge/rebase | Okno konfliktów w widoku git, gdy merge lub rebase zatrzyma się na konfliktach | Wybierasz "Resolve in current session" albo "Resolve in new session". Agent czyta pliki z konfliktem, proponuje strategię dla każdego pliku i czeka na twoje potwierdzenie przed edycją, stage'owaniem lub kontynuowaniem operacji. | +| Rozwiązywanie konfliktu cherry-pick | Sekcja "Re-integrate commits" sesji w worktree | Przenoszenie commitów sesji na gałąź docelową trafia na konflikt i przekazujesz go agentowi. Agent rozwiązuje konflikty w tymczasowym worktree, robi stage plików i kontynuuje cherry-pick. | + +### GitHub + +| Prompt | Gdzie się wykonuje | Kiedy się uruchamia | +| --- | --- | --- | +| Review PR | Selektor "Link GitHub PR" w menu załączników kompozytora oraz okno nowego worktree | Dwa wyzwalacze. Przypięcie PR jako kontekstu przygotowuje instrukcje, które wychodzą z twoją następną wiadomością. Utworzenie sesji worktree z PR używa prompta jako pierwszej wiadomości tej sesji, z pełnym kontekstem PR w załączeniu. | +| Review issue | Okno nowego worktree, gdy worktree startuje z issue | Pierwsza wiadomość nowej sesji przegląda issue, z jej treścią i komentarzami jako kontekstem. | +| Review nieudanych checków PR / komentarzy PR / pojedynczego komentarza PR | — | Dziś żaden przepływ ich nie wysyła. Widok PR uruchamiał je kiedyś akcjami review jednym kliknięciem; teraz nieudane checki i komentarze są przypinane jako szkice kontekstu czatu. Zostają edytowalne, aby istniejące nadpisania dalej działały. | + +### Planning + +| Prompt | Gdzie się wykonuje | Kiedy się uruchamia | +| --- | --- | --- | +| Planowanie z todo | Panel Todos w pasku bocznym projektu | Wysyłasz todo do sesji albo nowej sesji w worktree. Tekst todo staje się widoczną wiadomością; instrukcje zamieniają go w planistyczny dialog oparty na pytaniach, zamiast skakać od razu do implementacji. | +| Ulepsz plan | Akcja "Improve" na zapisanym planie w widoku Plans | Wysyłasz zapisany plan do przepływu ulepszania. Agent najpierw czyta plik planu, potem proponuje zmiany zakorzenione w aktualnym stanie repozytorium i proponuje edycję tego samego pliku. | +| Zaimplementuj plan | Akcja "Implement" na zapisanym planie | Wysyłasz zapisany plan do przepływu implementacji. Agent czyta plik planu i implementuje go od początku do końca bez rozszerzania zakresu, zapisując korekty planu z powrotem do pliku, gdy sam plan okaże się błędny. | + +### Session + +Większość z nich zasila komendy z ukośnikiem wpisywane w kompozytorze. Większość pojawia się też jako startowe chipy na szkicu nowej sesji. + +| Prompt | Gdzie się wykonuje | Kiedy się uruchamia | +| --- | --- | --- | +| Tour po kodzie | `/explore` | Prosisz o ogólną orientację w bazie kodu. | +| Podsumowanie sesji | `/summary`, opcjonalnie `/summary ` | Podsumowujesz dotychczasową rozmowę — przydatne do przekazania do nowej sesji. Wymaga istniejącej sesji. | +| Review workspace | `/workspace-review` | Prosisz agenta o przegląd aktualnego diffu workspace pod kątem intencji, poprawności i bezpieczeństwa. | +| Planowanie funkcji | `/plan-feature` | Zamieniasz surowy pomysł na funkcję w plan implementacji przez prowadzony dialog pytań i odpowiedzi. | +| Formułowanie Goal | `/craft-goal`, opcjonalnie `/craft-goal ` | Zamieniasz pomysł w weryfikowalny cel Goal do okna Goal. | +| Nadrobienie bieżące | `/catch-up` | Wracasz do projektu i pytasz, na czym stanęło i co dalej. | +| Debugowanie | `/debug` | Badasz buga: agent stawia hipotezy, potwierdza przyczynę źródłową w kodzie i dopiero wtedy proponuje poprawkę. | +| Ważenie opcji | `/weigh` | Wiesz, co zbudować, ale nie jak. Agent porównuje dwa-trzy podejścia i poleca jedno. | +| Fusion | Akcja "Run fusion" na grupie multi-run | Łączysz wyniki kilku uruchomień w jedną odpowiedź. Wyniki uruchomień są doklejane po instrukcjach. | + +### Prompty bez strony w Settings + +Kilka promptów uruchamia się automatycznie i nie ma edytowalnej strony w Settings: + +| Prompt | Kiedy się uruchamia | +| --- | --- | +| Zaplanowane zadanie | `/schedule-task`, opcjonalnie z początkowym pomysłem. Prowadzi dialog, który definiuje zaplanowane zadanie. | +| Handoff do review | `/handoff-review` albo przycisk Review w widoku diff z włączonym handoffem. Generuje handoff w sesji roboczej. | +| Wiadomość startowa sesji review | Pierwsza wiadomość wygenerowanej sesji review — z handoffem, gdy powstał, bez niego w przeciwnym razie. | +| Feedback z review / odpowiedź implementacji | Przenoszą wiadomości między dwiema sesjami: feedback recenzenta wraca do sesji implementującej, a odpowiedź implementatora wraca do sesji review. | + ## Powiązane - [Przepływy Git i GitHub](/pl/git/) — wiele z tych promptów napędza przepływy git +- [Notatki, todo i plany](/pl/notes-todos-plans/) — todo i plany stojące za promptami Planning +- [Multi-run](/pl/multi-run/) — grupy uruchomień i fusion diff --git a/packages/docs/content/docs/pl/providers.mdx b/packages/docs/content/docs/pl/providers.mdx index 79d1ad41..4c777176 100644 --- a/packages/docs/content/docs/pl/providers.mdx +++ b/packages/docs/content/docs/pl/providers.mdx @@ -45,6 +45,6 @@ Logowania dostawców są przechowywane przez OpenCode, a nie OpenChamber, więc ## Powiązane -- [Integracje](/pl/integrations/) — używaj subskrypcji Claude, Command Code lub Cursor jako dostawcy +- [Integracje](/pl/integrations/) — używaj subskrypcji Claude lub Cursor jako dostawcy - [Serwery MCP](/pl/mcp/) — dodaj agentom dodatkowe narzędzia - [Zużycie i limity](/pl/usage/) — śledź, ile już wykorzystałeś diff --git a/packages/docs/content/docs/pl/skills-catalog.mdx b/packages/docs/content/docs/pl/skills-catalog.mdx index d3544fa1..d38a842f 100644 --- a/packages/docs/content/docs/pl/skills-catalog.mdx +++ b/packages/docs/content/docs/pl/skills-catalog.mdx @@ -12,7 +12,7 @@ Aby pisać własne skille, zobacz [Skille](/pl/skills/). ## Zainstaluj skill 1. Otwórz katalog. -2. Przeglądaj wbudowane źródła — repozytorium skilli Anthropic oraz rejestr społeczności ClawHub — albo wyszukaj. +2. Przeglądaj wbudowane źródła — na przykład repozytorium skilli Anthropic — albo wyszukaj. 3. Wybierz skill i zainstaluj go. 4. Wybierz, gdzie go zainstalować: dla wszystkiego, co robisz, albo tylko dla bieżącego projektu. diff --git a/packages/docs/content/docs/providers.mdx b/packages/docs/content/docs/providers.mdx index df7a7d1e..2505ced4 100644 --- a/packages/docs/content/docs/providers.mdx +++ b/packages/docs/content/docs/providers.mdx @@ -57,6 +57,6 @@ Provider sign-ins are stored by OpenCode, not OpenChamber, so they're shared wit ## Related -- [Integrations](/integrations/) — use a Claude, Command Code, or Cursor subscription as a provider +- [Integrations](/integrations/) — use a Claude or Cursor subscription as a provider - [MCP Servers](/mcp/) — add extra tools for agents - [Usage & Quotas](/usage/) — track how much you've used diff --git a/packages/docs/content/docs/pt-br/integrations.mdx b/packages/docs/content/docs/pt-br/integrations.mdx index 1d66cbda..82ed2e68 100644 --- a/packages/docs/content/docs/pt-br/integrations.mdx +++ b/packages/docs/content/docs/pt-br/integrations.mdx @@ -1,16 +1,17 @@ --- title: Integrações -description: Use sua assinatura Claude, Command Code ou Cursor como provedor. +description: Use sua assinatura Claude ou Cursor como provedor. --- # Integrações Uma integração é um pequeno plugin que adiciona um provedor ao OpenChamber usando uma assinatura que você já tem. Você as gerencia em **Settings → Integrations**. +> **Recurso experimental.** Buscamos respeitar as políticas dos provedores, mas restrições e suspensões de conta continuam sendo decisão de cada provedor. Use as integrações por sua conta e risco. + Integrações disponíveis: - **Claude Code** — seu plano Claude Pro ou Max, sem chaves de API -- **Command Code** — seu plano Command Code - **Cursor** — os limites de modelos do seu plano Cursor ## Instalar uma integração @@ -29,19 +30,10 @@ O Claude Code usa seu plano Claude Pro ou Max — sem chaves de API e sem um app 1. Instale a integração (acima). 2. Escolha **Set up** e faça login. Se você ainda não tem a CLI do Claude Code, a configuração oferece instalá-la primeiro e depois fazer login. -O Claude Code é a única integração aqui que exige que a CLI do provedor esteja instalada e autenticada. Command Code e Cursor não exigem suas CLIs. +O Claude Code é a única integração aqui que exige que a CLI do provedor esteja instalada e autenticada. Cursor não exige sua CLI. **Como sua conta Claude fica protegida:** esta integração usa o Claude Agent SDK oficial da Anthropic e a CLI do Claude Code instalada em sua máquina. Ela não sequestra OAuth, não extrai nem reproduz tokens do navegador, não se passa por um cliente não suportado e não contorna a autenticação da Anthropic. Ela permanece no caminho de acesso suportado pela Anthropic, portanto não traz o risco de banimento de conta associado a sequestro de tokens ou a contornos de autenticação não autorizados. -## Command Code - -O Command Code usa seu plano Command Code. - -1. Instale a integração (acima). -2. Escolha **Set up** — uma página abre no navegador. Autorize o acesso e volte ao OpenChamber. - -Em uma máquina sem interface gráfica ou em CI, defina a variável de ambiente `COMMAND_CODE_API_KEY` em vez de fazer login pelo navegador. - ## Cursor O Cursor torna disponíveis no OpenChamber os modelos incluídos no seu plano Cursor. diff --git a/packages/docs/content/docs/pt-br/magic-prompts.mdx b/packages/docs/content/docs/pt-br/magic-prompts.mdx index a4abf88d..2ef00ae1 100644 --- a/packages/docs/content/docs/pt-br/magic-prompts.mdx +++ b/packages/docs/content/docs/pt-br/magic-prompts.mdx @@ -21,6 +21,64 @@ Alguns prompts têm uma parte visível (a mensagem que você veria) e uma parte Mudou de ideia? Cada prompt tem **reset to default**, e há um **reset all** se você quiser recomeçar em tudo. +## Onde cada prompt é usado + +Cada prompt nas tabelas abaixo indica onde ele roda e o que o dispara. Confira o gatilho antes de editar, para saber qual fluxo você está mudando. + +### Git + +| Prompt | Onde roda | Quando dispara | +| --- | --- | --- | +| Geração de commit | O botão de gerar na caixa de commit da vista git, e a tela Changes no mobile | Você gera uma mensagem de commit. São preenchidos os arquivos selecionados e os assuntos dos commits recentes do branch, para a mensagem seguir o estilo do seu repositório. | +| Geração de PR | O formulário de criação de pull request na aba PR da vista git | Você gera título e corpo de um PR. São preenchidos os branches base e head, os commits e arquivos alterados entre eles, o contexto adicional que você escreveu e o template de PR do repositório, quando existe. | +| Resolução de conflito de merge/rebase | O diálogo de conflitos na vista git, quando um merge ou rebase para em conflitos | Você escolhe "Resolve in current session" ou "Resolve in new session". O agente lê os arquivos em conflito, propõe uma estratégia por arquivo e aguarda sua confirmação antes de editar, fazer stage ou continuar a operação. | +| Resolução de conflito de cherry-pick | A seção "Re-integrate commits" de uma sessão em worktree | Mover os commits da sessão para o branch de destino esbarra em um conflito e você passa para o agente. O agente resolve dentro do worktree temporário, faz stage dos arquivos e continua o cherry-pick. | + +### GitHub + +| Prompt | Onde roda | Quando dispara | +| --- | --- | --- | +| Revisão de PR | O seletor "Link GitHub PR" no menu de anexos do composer, e o diálogo de novo worktree | Dois gatilhos. Anexar um PR como contexto renderiza as instruções, que saem com a sua próxima mensagem. Criar uma sessão de worktree a partir de um PR usa o prompt como primeira mensagem dessa sessão, com o contexto completo do PR anexado. | +| Revisão de issue | O diálogo de novo worktree, quando o worktree parte de uma issue | A primeira mensagem da nova sessão revisa a issue, com o corpo e os comentários anexados como contexto. | +| Revisão de checks falhos / comentários de PR / comentário único de PR | — | Hoje nenhum fluxo os envia. A vista de PR antes os disparava com ações de revisão de um clique; agora checks falhos e comentários são fixados como rascunhos de contexto do chat. Continuam editáveis para que overrides existentes mantenham efeito. | + +### Planning + +| Prompt | Onde roda | Quando dispara | +| --- | --- | --- | +| Planejamento a partir de todo | O painel Todos na barra lateral do projeto | Você envia um todo para uma sessão ou uma nova sessão em worktree. O texto do todo vira a mensagem visível; as instruções transformam isso em um diálogo de planejamento guiado por perguntas, em vez de pular direto para a implementação. | +| Melhorar plano | A ação "Improve" sobre um plano salvo na vista Plans | Você envia um plano salvo para o fluxo de melhoria. O agente lê primeiro o arquivo do plano, propõe mudanças ancoradas no estado atual do repositório e se oferece para editar o mesmo arquivo. | +| Implementar plano | A ação "Implement" sobre um plano salvo | Você envia um plano salvo para o fluxo de implementação. O agente lê o arquivo do plano e o implementa do início ao fim sem ampliar o escopo, salvando ajustes do plano no arquivo quando o próprio plano se mostra errado. | + +### Session + +A maioria alimenta comandos de barra digitados no composer. A maioria também aparece como chips de partida no rascunho de nova sessão. + +| Prompt | Onde roda | Quando dispara | +| --- | --- | --- | +| Tour pelo código | `/explore` | Você pede uma orientação geral do código. | +| Resumo de sessão | `/summary`, opcionalmente `/summary ` | Você resume a conversa até aqui — útil para passar para uma nova sessão. Precisa de uma sessão existente. | +| Revisão do workspace | `/workspace-review` | Você pede ao agente para revisar o diff atual do workspace quanto a intenção, correção e segurança. | +| Planejamento de feature | `/plan-feature` | Você transforma uma ideia grosseira de feature em um plano de implementação por um diálogo guiado de perguntas e respostas. | +| Construir Goal | `/craft-goal`, opcionalmente `/craft-goal ` | Você transforma uma ideia em um objetivo Goal verificável para o diálogo de Goal. | +| Retomar o fio | `/catch-up` | Você volta a um projeto e pergunta onde as coisas pararam e o que fazer a seguir. | +| Depuração | `/debug` | Você investiga um bug: o agente levanta hipóteses, confirma a causa raiz no código e só então propõe uma correção. | +| Pesar opções | `/weigh` | Você sabe o que construir, mas não como. O agente compara duas ou três abordagens e recomenda uma. | +| Fusion | A ação "Run fusion" em um grupo de multi-run | Você combina as saídas de várias execuções em uma resposta. As saídas das execuções são anexadas depois das instruções. | + +### Prompts sem página no Settings + +Alguns prompts disparam automaticamente e não têm página editável no Settings: + +| Prompt | Quando dispara | +| --- | --- | +| Tarefa agendada | `/schedule-task`, opcionalmente com uma ideia inicial. Conduz o diálogo que define uma tarefa agendada. | +| Handoff de revisão | `/handoff-review`, ou o botão Review na vista de diff com handoff ativado. Gera o handoff na sessão de trabalho. | +| Mensagem inicial da sessão de revisão | A primeira mensagem da sessão de revisão gerada — com o handoff quando um foi produzido, sem ele caso contrário. | +| Feedback de revisão / resposta de implementação | Levam mensagens entre as duas sessões: o feedback do revisor volta para a sessão que implementa, e a resposta do implementador retorna à sessão de revisão. | + ## Relacionado - [Fluxos de Git e GitHub](/pt-br/git/) — muitos desses prompts alimentam os fluxos de git +- [Notas, todos e planos](/pt-br/notes-todos-plans/) — os todos e planos por trás dos prompts de Planning +- [Multi-run](/pt-br/multi-run/) — grupos de execução e fusion diff --git a/packages/docs/content/docs/pt-br/providers.mdx b/packages/docs/content/docs/pt-br/providers.mdx index b05dc222..70ece24f 100644 --- a/packages/docs/content/docs/pt-br/providers.mdx +++ b/packages/docs/content/docs/pt-br/providers.mdx @@ -45,6 +45,6 @@ Os logins de provedores são armazenados pelo OpenCode, não pelo OpenChamber, e ## Relacionado -- [Integrações](/pt-br/integrations/) — use uma assinatura Claude, Command Code ou Cursor como provedor +- [Integrações](/pt-br/integrations/) — use uma assinatura Claude ou Cursor como provedor - [Servidores MCP](/pt-br/mcp/) — adicione ferramentas extras para os agentes - [Uso e Cotas](/pt-br/usage/) — acompanhe quanto você já usou diff --git a/packages/docs/content/docs/pt-br/skills-catalog.mdx b/packages/docs/content/docs/pt-br/skills-catalog.mdx index eea841f4..b28c44f3 100644 --- a/packages/docs/content/docs/pt-br/skills-catalog.mdx +++ b/packages/docs/content/docs/pt-br/skills-catalog.mdx @@ -12,7 +12,7 @@ Para escrever suas próprias skills, veja [Skills](/pt-br/skills/). ## Instalar uma skill 1. Abra o catálogo. -2. Navegue pelas fontes integradas — o repositório de skills da Anthropic e o registro comunitário ClawHub — ou pesquise. +2. Navegue pelas fontes integradas — como o repositório de skills da Anthropic — ou pesquise. 3. Escolha uma skill e instale-a. 4. Escolha onde instalá-la: para tudo o que você faz, ou apenas no projeto atual. diff --git a/packages/docs/content/docs/skills-catalog.mdx b/packages/docs/content/docs/skills-catalog.mdx index 9ddcb131..e223a459 100644 --- a/packages/docs/content/docs/skills-catalog.mdx +++ b/packages/docs/content/docs/skills-catalog.mdx @@ -12,7 +12,7 @@ For writing your own skills, see [Skills](/skills/). ## Install a skill 1. Open the catalog. -2. Browse the built-in sources — the Anthropic skills repo and the ClawHub community registry — or search. +2. Browse the built-in sources — like the Anthropic skills repo — or search. 3. Pick a skill and install it. 4. Choose where to install it: for everything you do, or just the current project. diff --git a/packages/docs/content/docs/uk/integrations.mdx b/packages/docs/content/docs/uk/integrations.mdx index b68da46c..d6d075f4 100644 --- a/packages/docs/content/docs/uk/integrations.mdx +++ b/packages/docs/content/docs/uk/integrations.mdx @@ -1,16 +1,17 @@ --- title: Інтеграції -description: Використовуйте підписки Claude, Command Code або Cursor як провайдерів. +description: Використовуйте підписки Claude або Cursor як провайдерів. --- # Інтеграції Інтеграція — це невеликий плагін, що додає провайдера до OpenChamber на основі підписки, яка в вас уже є. Керувати ними можна в **Settings → Integrations**. +> **Експериментальна функція.** Ми прагнемо дотримуватися політик провайдерів, але обмеження та блокування облікових записів залишаються рішенням кожного провайдера. Використовуйте інтеграції на власний ризик. + Доступні інтеграції: - **Claude Code** — ваша підписка Claude Pro або Max, без API-ключів -- **Command Code** — ваша підписка Command Code - **Cursor** — ліміти моделей вашої підписки Cursor ## Встановлення інтеграції @@ -29,19 +30,10 @@ Claude Code використовує вашу підписку Claude Pro або 1. Встановіть інтеграцію (вище). 2. Натисніть **Set up** і увійдіть. Якщо у вас ще немає Claude Code CLI, програма встановлення спершу запропонує його встановити, а потім виконає вхід. -Claude Code — єдина інтеграція тут, яка вимагає встановленого та залогіненого CLI свого провайдера. Для Command Code і Cursor їхні CLI не потрібні. +Claude Code — єдина інтеграція тут, яка вимагає встановленого та залогіненого CLI свого провайдера. Для Cursor CLI не потрібен. **Як захищається ваш обліковий запис Claude:** ця інтеграція використовує офіційний Claude Agent SDK від Anthropic і ваш встановлений Claude Code CLI. Вона не перехоплює OAuth, не витягує й не відтворює браузерні токени, не видає себе за непідтримуваний клієнт і не обходить процес автентифікації Anthropic. Усе працює через підтримуваний Anthropic шлях доступу, тож інтеграція не несе ризику блокування облікового запису, пов'язаного з перехопленням токенів або несанкціонованими способами автентифікації. -## Command Code - -Command Code використовує вашу підписку Command Code. - -1. Встановіть інтеграцію (вище). -2. Натисніть **Set up** — відкриється сторінка в браузері. Підтвердьте доступ і поверніться до OpenChamber. - -На сервері без графічного інтерфейсу або в CI замість входу через браузер задайте змінну середовища `COMMAND_CODE_API_KEY`. - ## Cursor Cursor робить доступними в OpenChamber моделі, що входять у вашу підписку Cursor. diff --git a/packages/docs/content/docs/uk/magic-prompts.mdx b/packages/docs/content/docs/uk/magic-prompts.mdx index 7141a0f1..84622208 100644 --- a/packages/docs/content/docs/uk/magic-prompts.mdx +++ b/packages/docs/content/docs/uk/magic-prompts.mdx @@ -21,6 +21,64 @@ OpenChamber використовує вбудовані промпти за ла Передумали? Кожен промпт має **reset to default**, а ще є **reset all**, якщо хочете почати спочатку всюди. +## Де використовується кожен промпт + +Для кожного промпту нижче вказано, де він виконується та який тригер його запускає. Перевірте тригер перед редагуванням, щоб розуміти, який процес ви змінюєте. + +### Git + +| Промпт | Де виконується | Коли спрацьовує | +| --- | --- | --- | +| Генерація коміту | Кнопка генерації в полі коміту у git-поданні та на мобільному екрані Changes | Ви генеруєте повідомлення коміту. Підставляються вибрані файли та теми нещодавніх комітів гілки, щоб стиль відповідав вашому репозиторію. | +| Генерація PR | Форма створення pull request у вкладці PR git-подання | Ви генеруєте заголовок і опис PR. Підставляються базова та головна гілки, коміти та змінені файли між ними, ваш додатковий контекст і PR-шаблон репозиторію, якщо він є. | +| Розв'язання конфліктів merge/rebase | Діалог конфліктів у git-поданні, коли merge або rebase зупинився на конфліктах | Ви обираєте "Resolve in current session" або "Resolve in new session". Агент читає конфліктні файли, пропонує стратегію розв'язання для кожного файлу та чекає на ваше підтвердження перед редагуванням, індексацією чи продовженням операції. | +| Розв'язання конфліктів cherry-pick | Розділ "Re-integrate commits" для сесії у worktree | Перенесення комітів сесії на цільову гілку впирається в конфлікт, і ви передаєте його агентові. Агент розв'язує конфлікти в тимчасовому worktree, індексує файли та продовжує cherry-pick. | + +### GitHub + +| Промпт | Де виконується | Коли спрацьовує | +| --- | --- | --- | +| Рев'ю PR | Пікер "Link GitHub PR" у меню вкладень композера та діалог нового worktree | Два тригери. Прикріплення PR як контексту готує інструкції, які надсилаються разом із вашим наступним повідомленням. Створення сесії у worktree з PR використовує промпт як перше повідомлення сесії з повним контекстом PR. | +| Рев'ю issue | Діалог нового worktree, коли worktree створюється з issue | Перше повідомлення нової сесії рев'ю issue, з тілом і коментарями як контекстом. | +| Рев'ю провалених перевірок PR / коментарів PR / окремого коментаря PR | — | Нині не надсилаються жодним процесом. Подання PR раніше запускало їх кнопками швидкого рев'ю; тепер провалені перевірки й коментарі прикріплюються як чернетки контексту чату. Вони лишаються редагованими, щоб наявні перевизначення продовжували працювати. | + +### Planning + +| Промпт | Де виконується | Коли спрацьовує | +| --- | --- | --- | +| Планування з todo | Панель Todos у проєктній бічній панелі | Ви надсилаєте todo в сесію або нову сесію у worktree. Текст todo стає видимим повідомленням; інструкції перетворюють його на планувальний діалог із питаннями, а не стрибок одразу в імплементацію. | +| Покращення плану | Дія "Improve" для збереженого плану у поданні Plans | Ви надсилаєте збережений план у потік покращення. Агент спершу читає файл плану, потім пропонує зміни на основі поточного стану репозиторію та пропонує відредагувати той самий файл. | +| Імплементація плану | Дія "Implement" для збереженого плану | Ви надсилаєте збережений план у потік імплементації. Агент читає файл плану та імплементує його від початку до кінця без розширення обсягу, зберігаючи корективи плану назад у файл, якщо сам план виявився хибним. | + +### Session + +Більшість із них живлять слеш-команди, які вводяться в композері. Більшість також доступні як стартові чіпи на чернетці нової сесії. + +| Промпт | Де виконується | Коли спрацьовує | +| --- | --- | --- | +| Тур кодовою базою | `/explore` | Ви просите загальний огляд кодової бази. | +| Підсумок сесії | `/summary`, необов'язково `/summary <тема>` | Ви підсумовуєте поточну розмову — зручно для передачі в нову сесію. Потрібна наявна сесія. | +| Рев'ю робочої області | `/workspace-review` | Ви просите агента переглянути поточний diff робочої області на намір, коректність і безпеку. | +| Планування фічі | `/plan-feature` | Ви перетворюєте грубу ідею фічі на план імплементації через керований діалог питань і відповідей. | +| Формулювання Goal | `/craft-goal`, необов'язково `/craft-goal <ідея>` | Ви перетворюєте ідею на перевірювану ціль Goal для діалогу Goal. | +| Catch up | `/catch-up` | Ви повертаєтеся до проєкту й питаєте, на чому зупинилися і що робити далі. | +| Дебаг | `/debug` | Ви досліджуєте баг: агент формує гіпотези, підтверджує кореневу причину з коду і лише тоді пропонує виправлення. | +| Зважування варіантів | `/weigh` | Ви знаєте, що будувати, але не як. Агент порівнює два-три підходи та рекомендує один. | +| Fusion | Дія "Run fusion" на групі multi-run | Ви об'єднуєте виводи кількох запусків в одну відповідь. Виводи запусків додаються після інструкцій. | + +### Промпти без сторінки в Settings + +Кілька промптів спрацьовують автоматично й не мають редагованих сторінок у Settings: + +| Промпт | Коли спрацьовує | +| --- | --- | +| Заплановане завдання | `/schedule-task`, необов'язково з початковою ідеєю. Веде діалог, який визначає заплановане завдання. | +| Handoff для рев'ю | `/handoff-review` або кнопка Review у поданні diff із увімкненим handoff. Генерує handoff у робочій сесії. | +| Стартове повідомлення сесії рев'ю | Перше повідомлення згенерованої сесії рев'ю — з handoff, якщо він створений, або без нього. | +| Відгук рев'ю / відповідь імплементатора | Переносять повідомлення між двома сесіями: відгук рев'юера повертається в сесію імплементації, а відповідь імплементатора — назад у сесію рев'ю. | + ## Пов'язане - [Робочі процеси Git і GitHub](/uk/git/) — багато з цих промптів живлять git-процеси +- [Нотатки, todo та плани](/uk/notes-todos-plans/) — todo і плани за промптами групи Planning +- [Multi-run](/uk/multi-run/) — групи запусків і fusion diff --git a/packages/docs/content/docs/uk/providers.mdx b/packages/docs/content/docs/uk/providers.mdx index 4fb61d65..9f2777e5 100644 --- a/packages/docs/content/docs/uk/providers.mdx +++ b/packages/docs/content/docs/uk/providers.mdx @@ -45,6 +45,6 @@ description: Підключайте AI-провайдерів, обирайте ## Пов'язане -- [Інтеграції](/uk/integrations/) — використовуйте підписки Claude, Command Code або Cursor як провайдерів +- [Інтеграції](/uk/integrations/) — використовуйте підписки Claude або Cursor як провайдерів - [MCP Servers](/uk/mcp/) — додайте агентам додаткові інструменти - [Використання та квоти](/uk/usage/) — відстежуйте, скільки ви витратили diff --git a/packages/docs/content/docs/uk/skills-catalog.mdx b/packages/docs/content/docs/uk/skills-catalog.mdx index 78c0c1d6..585ad77a 100644 --- a/packages/docs/content/docs/uk/skills-catalog.mdx +++ b/packages/docs/content/docs/uk/skills-catalog.mdx @@ -12,7 +12,7 @@ description: Переглядайте та встановлюйте готові ## Встановлення навички 1. Відкрийте каталог. -2. Перегляньте вбудовані джерела — репозиторій навичок Anthropic та спільнотний реєстр ClawHub — або скористайтеся пошуком. +2. Перегляньте вбудовані джерела — наприклад репозиторій навичок Anthropic — або скористайтеся пошуком. 3. Оберіть навичку й установіть її. 4. Виберіть, куди встановити: для всього, що ви робите, чи лише для поточного проєкту. diff --git a/packages/docs/content/docs/zh-cn/integrations.mdx b/packages/docs/content/docs/zh-cn/integrations.mdx index 94daf884..a5686e3c 100644 --- a/packages/docs/content/docs/zh-cn/integrations.mdx +++ b/packages/docs/content/docs/zh-cn/integrations.mdx @@ -1,16 +1,17 @@ --- title: 集成 -description: 将你的 Claude、Command Code 或 Cursor 订阅用作提供商。 +description: 将你的 Claude 或 Cursor 订阅用作提供商。 --- # 集成 集成是一个小型插件,它使用你已有的订阅为 OpenChamber 添加一个提供商。你可以在 **Settings → Integrations** 中管理它们。 +> **实验性功能。**我们力求遵守提供商的政策,但帐户限制和暂停仍由各提供商决定。请自行承担使用集成的风险。 + 可用的集成: - **Claude Code** — 你的 Claude Pro 或 Max 套餐,无需 API 密钥 -- **Command Code** — 你的 Command Code 套餐 - **Cursor** — 你的 Cursor 套餐的模型额度 ## 安装集成 @@ -29,19 +30,10 @@ Claude Code 使用你的 Claude Pro 或 Max 套餐 — 无需 API 密钥,也 1. 安装集成(见上文)。 2. 选择 **Set up** 并登录。如果你还没有 Claude Code CLI,安装向导会先提供安装,然后再登录。 -Claude Code 是这里唯一要求安装并登录其提供商 CLI 的集成。Command Code 和 Cursor 不需要它们的 CLI。 +Claude Code 是这里唯一要求安装并登录其提供商 CLI 的集成。Cursor 不需要其 CLI。 **你的 Claude 账户如何受到保护:** 此集成使用 Anthropic 官方的 Claude Agent SDK 和你已安装的 Claude Code CLI。它不会劫持 OAuth,不会提取或重放浏览器令牌,不会冒充不受支持的客户端,也不会绕过 Anthropic 的身份验证。它始终运行在 Anthropic 支持的访问路径上,因此不会带来与令牌劫持或未授权身份验证变通手段相关的封号风险。 -## Command Code - -Command Code 使用你的 Command Code 套餐。 - -1. 安装集成(见上文)。 -2. 选择 **Set up** — 浏览器中会打开一个页面。授权访问,然后返回 OpenChamber。 - -在没有图形界面的服务器或 CI 环境中,请设置环境变量 `COMMAND_CODE_API_KEY` 来代替浏览器登录。 - ## Cursor Cursor 让你的 Cursor 套餐中包含的模型可以在 OpenChamber 中使用。 diff --git a/packages/docs/content/docs/zh-cn/magic-prompts.mdx b/packages/docs/content/docs/zh-cn/magic-prompts.mdx index 65d17398..b291644e 100644 --- a/packages/docs/content/docs/zh-cn/magic-prompts.mdx +++ b/packages/docs/content/docs/zh-cn/magic-prompts.mdx @@ -21,6 +21,64 @@ description: 自定义 OpenChamber 自动化流程背后的内置提示词。 改主意了?每个提示词都有 **reset to default**,如果你想在所有地方重新开始,还有一个 **reset all**。 +## 每个提示词在哪里使用 + +下表列出每个提示词的运行位置和触发时机。编辑前先看清触发条件,你就知道自己在改哪个流程。 + +### Git + +| 提示词 | 运行位置 | 触发时机 | +| --- | --- | --- | +| 提交信息生成 | git 视图提交框中的生成按钮,以及移动端 Changes 页面 | 你生成提交信息时。会填入选中的文件和分支最近的提交主题,让提交信息符合仓库的现有风格。 | +| PR 生成 | git 视图 PR 标签页中的创建 pull request 表单 | 你生成 PR 标题和正文时。会填入 base 和 head 分支、两者之间的提交和变更文件、你补充的附加上下文,以及仓库的 PR 模板(如果存在)。 | +| merge/rebase 冲突解决 | git 视图中的冲突对话框,当 merge 或 rebase 因冲突停止时 | 你选择 "Resolve in current session" 或 "Resolve in new session"。智能体会阅读冲突文件,为每个文件提出解决策略,并等你确认后才编辑、暂存或继续操作。 | +| cherry-pick 冲突解决 | worktree 会话的 "Re-integrate commits" 区域 | 把会话的提交迁移到目标分支时遇到冲突并交给智能体。智能体在临时 worktree 中解决冲突、暂存文件并继续 cherry-pick。 | + +### GitHub + +| 提示词 | 运行位置 | 触发时机 | +| --- | --- | --- | +| PR 审阅 | 输入框附件菜单中的 "Link GitHub PR" 选择器,以及新 worktree 对话框 | 两个触发点。把 PR 附为上下文时会渲染指令,并随你的下一条消息发出。从 PR 创建 worktree 会话时,该提示词成为会话的开场消息,并附上完整 PR 上下文。 | +| issue 审阅 | 新 worktree 对话框,当你从 issue 创建 worktree 时 | 新会话的开场消息会审阅该 issue,并将其正文和评论附为上下文。 | +| PR 失败检查 / PR 评论 / 单条 PR 评论 | — | 目前没有任何流程发送它们。PR 视图过去通过一键审阅操作触发这些提示词;现在失败的检查和评论会改为固定为聊天上下文草稿。保留它们是为了让已有的覆盖配置继续生效。 | + +### Planning + +| 提示词 | 运行位置 | 触发时机 | +| --- | --- | --- | +| todo 规划 | 项目侧边栏中的 Todos 面板 | 你把一个 todo 发送到会话或新的 worktree 会话。todo 文本成为可见消息;指令会把它变成先提问的规划对话,而不是直接开始实现。 | +| 改进计划 | Plans 视图中已保存计划上的 "Improve" 操作 | 你把已保存的计划送入改进流程。智能体先读取计划文件,再基于仓库当前状态提出修改,并主动提出编辑同一个文件。 | +| 实现计划 | 已保存计划上的 "Implement" 操作 | 你把已保存的计划送入实现流程。智能体读取计划文件并端到端地实现它,不扩大范围;当计划本身有问题时,会把计划调整保存回该文件。 | + +### Session + +其中大多数由在输入框中输入的斜杠命令驱动。大多数也会以启动芯片的形式出现在新会话草稿页上。 + +| 提示词 | 运行位置 | 触发时机 | +| --- | --- | --- | +| 代码库导览 | `/explore` | 你想要一份代码库的高层次概览。 | +| 会话总结 | `/summary`,可选 `/summary <主题>` | 你总结目前的对话 — 适合移交给新会话。需要已存在的会话。 | +| 工作区审阅 | `/workspace-review` | 你让智能体从意图、正确性和安全性角度审阅当前的工作区 diff。 | +| 功能规划 | `/plan-feature` | 你通过引导式问答对话,把粗略的功能想法变成实现计划。 | +| Goal 制定 | `/craft-goal`,可选 `/craft-goal <想法>` | 你把一个想法变成可用于 Goal 对话框的可验证 Goal 目标。 | +| 快速追平 | `/catch-up` | 你回到一个项目,想知道进展如何、接下来做什么。 | +| 调试 | `/debug` | 你调查一个 bug:智能体提出假设、从代码确认根因,然后才提出修复方案。 | +| 权衡选项 | `/weigh` | 你知道要做什么,但不知道怎么做。智能体会比较两三种方案并推荐其一。 | +| Fusion | multi-run 组上的 "Run fusion" 操作 | 你把多次运行的输出合并成一个答案。运行输出会附加在指令之后。 | + +### 没有 Settings 页面的提示词 + +少数提示词会自动触发,在 Settings 中没有可编辑的页面: + +| 提示词 | 触发时机 | +| --- | --- | +| 计划任务 | `/schedule-task`,可选附带初始想法。引导完成定义计划任务的对话。 | +| 审阅交接 | `/handoff-review`,或 diff 视图中启用交接时的 Review 按钮。在当前工作会话中生成交接内容。 | +| 审阅会话开场消息 | 生成的审阅会话的开场消息 — 生成了交接内容时包含它,否则不包含。 | +| 审阅反馈 / 实现响应 | 在两个会话之间传递消息:审阅者的反馈回到实现会话,实现者的响应返回审阅会话。 | + ## 相关内容 - [Git 与 GitHub 工作流](/zh-cn/git/) — 其中许多提示词为 git 流程提供动力 +- [笔记、todo 与计划](/zh-cn/notes-todos-plans/) — Planning 提示词背后的 todo 和计划 +- [Multi-run](/zh-cn/multi-run/) — 运行组与 fusion diff --git a/packages/docs/content/docs/zh-cn/providers.mdx b/packages/docs/content/docs/zh-cn/providers.mdx index 62cf4019..5e81e7d3 100644 --- a/packages/docs/content/docs/zh-cn/providers.mdx +++ b/packages/docs/content/docs/zh-cn/providers.mdx @@ -45,6 +45,6 @@ description: 连接 AI 提供商、选择模型并设置智能体。 ## 相关内容 -- [集成](/zh-cn/integrations/) — 将 Claude、Command Code 或 Cursor 订阅用作提供商 +- [集成](/zh-cn/integrations/) — 将 Claude 或 Cursor 订阅用作提供商 - [MCP Servers](/zh-cn/mcp/) — 为智能体添加额外工具 - [用量与配额](/zh-cn/usage/) — 跟踪你已使用的量 diff --git a/packages/docs/content/docs/zh-cn/skills-catalog.mdx b/packages/docs/content/docs/zh-cn/skills-catalog.mdx index f393944e..a3d982b1 100644 --- a/packages/docs/content/docs/zh-cn/skills-catalog.mdx +++ b/packages/docs/content/docs/zh-cn/skills-catalog.mdx @@ -12,7 +12,7 @@ Skills 目录让你能够安装其他人发布的 skill,而不必自己编写 ## 安装 skill 1. 打开目录。 -2. 浏览内置来源 — Anthropic skills 仓库和 ClawHub 社区注册表 — 或进行搜索。 +2. 浏览内置来源 — 例如 Anthropic skills 仓库 — 或进行搜索。 3. 选择一个 skill 并安装它。 4. 选择安装位置:用于你的所有工作,或仅用于当前项目。 diff --git a/packages/electron/README.md b/packages/electron/README.md index a7763d48..7a306894 100644 --- a/packages/electron/README.md +++ b/packages/electron/README.md @@ -143,6 +143,7 @@ Use an explicit override when testing a different OpenCode CLI build or when a u ## Native Features Owned Here - Floating Mini Chat windows. +- New Mini Chat windows default to the managed Chats target. Explicit project/worktree drafts retain their target, existing managed chat sessions reopen in their own directory, and the compact header omits project/branch metadata for Chats. Opening a managed draft back in the main window preserves that target. - Multiple native windows. - Native notifications. - User-confirmed local folder selection. The shared UI supplies the requested directory as the picker `defaultPath`; confirmation is required before filesystem access is retried. diff --git a/packages/electron/main.mjs b/packages/electron/main.mjs index c6fe8edb..49c2ba22 100644 --- a/packages/electron/main.mjs +++ b/packages/electron/main.mjs @@ -11,6 +11,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'; import { promisify } from 'node:util'; import updaterPkg from 'electron-updater'; import { ElectronSshManager } from './ssh-manager.mjs'; +import { replaceFileWithRetry } from './windows-file-replace.mjs'; import { createTrayController } from './tray.mjs'; import { resolveManagedOpenCodeCwd } from './opencode-cwd.mjs'; import { resolveStartupUrlProbePlan, shouldIgnoreLoopbackConnectionLimit } from './startup-url-selection.mjs'; @@ -32,6 +33,7 @@ import { } from './linux-autostart.mjs'; import { unsupportedAppSpecificOpenError, validateLocalPath } from './path-open-utils.mjs'; import { shouldAllowBrowserPanelCertificateError } from './browser-panel-security.mjs'; +import { createRendererRecoveryPolicy } from './renderer-recovery.mjs'; import { mintOutsideFileGrant } from '@openchamber/web/server/lib/fs/routes.js'; const execFileAsync = promisify(execFile); @@ -560,10 +562,15 @@ const writeJsonFile = async (filePath, data) => { // Atomic: write to a temp file then rename. Readers never see a partial // JSON file that could parse-error and get coerced to {}. const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; - await fsp.writeFile(tmp, JSON.stringify(data, null, 2), { encoding: 'utf8', mode: 0o600 }); - if (process.platform !== 'win32') await fsp.chmod(tmp, 0o600); - await fsp.rename(tmp, filePath); - if (process.platform !== 'win32') await fsp.chmod(filePath, 0o600); + try { + await fsp.writeFile(tmp, JSON.stringify(data, null, 2), { encoding: 'utf8', mode: 0o600 }); + if (process.platform !== 'win32') await fsp.chmod(tmp, 0o600); + await replaceFileWithRetry(tmp, filePath); + if (process.platform !== 'win32') await fsp.chmod(filePath, 0o600); + } catch (error) { + await fsp.rm(tmp, { force: true }).catch(() => {}); + throw error; + } }; const readSettingsRoot = () => { @@ -1234,7 +1241,15 @@ const registerPackagedUiProtocol = () => { if (filePath.endsWith('.html')) { const html = await fsp.readFile(filePath, 'utf8'); const body = injectRuntimeConfigIntoHtml(html); - return new Response(body, { headers: { 'Content-Type': 'text/html; charset=utf-8' } }); + // index.html must never be cached: it names the hashed asset + // bundles, and a cached copy keeps a freshly installed build + // loading the previous version's UI from the renderer disk cache. + return new Response(body, { + headers: { + 'Content-Type': 'text/html; charset=utf-8', + 'Cache-Control': 'no-store', + }, + }); } return electronNet.fetch(pathToFileURL(filePath).toString()); } @@ -1355,7 +1370,7 @@ const maybeShowNativeNotification = (rawInput) => { notification.on('click', () => { focusForegroundWindow(); if (sessionId) { - emitToAllWindows('openchamber:open-session', { sessionId, directory }); + emitToPrimaryWindow('openchamber:open-session', { sessionId, directory }); } release(); }); @@ -1756,6 +1771,15 @@ const computeBootOutcome = ({ envTargetUrl, probe, config, localAvailable }) => : probe?.status === 'wrong-service' ? 'wrong-service' : 'ok'; + // A relay-capable host is not a recovery case just because its stored + // direct URL failed the http probe — that URL is often the pairing + // creator's own loopback (unreachable here, or worse, someone else's + // service). The relay leg is activated in the renderer's relay restore, + // which cannot run from a recovery screen: boot to main on the local + // substrate and let it pick direct-or-relay. + if (status !== 'ok' && sanitizeHostRelayForStorage(host.relay)) { + return { target: 'remote', status: 'ok', hostId: host.id, url: host.apiUrl || host.url, ...availability }; + } return { target: 'remote', status, hostId: host.id, url: host.apiUrl || host.url, ...availability }; }; @@ -1983,6 +2007,18 @@ const emitToAllWindows = (event, detail) => { } }; +// Session navigation must land in ONE window. Broadcasting it makes every +// open window adopt the same session, hijacking whatever the other windows +// were doing. +const emitToPrimaryWindow = (event, detail) => { + const windows = BrowserWindow.getAllWindows().filter((window) => !window.isDestroyed()); + if (windows.length === 0) return; + const target = (state.mainWindow && !state.mainWindow.isDestroyed()) + ? state.mainWindow + : windows.find((window) => window.isFocused()) || windows.find((window) => window.isVisible()) || windows[0]; + emitToWindow(target, event, detail); +}; + const setTaskbarProgress = (value) => { if (process.platform !== 'win32') return; for (const browserWindow of BrowserWindow.getAllWindows()) { @@ -2264,7 +2300,7 @@ const dispatchDeepLink = (link) => { } if (link.type === 'session' && link.value) { - emitToAllWindows('openchamber:open-session', { sessionId: link.value }); + emitToPrimaryWindow('openchamber:open-session', { sessionId: link.value }); return; } if (link.type === 'host' && link.value) { @@ -2461,10 +2497,14 @@ const createBrowserWindow = ({ label, restoreGeometry, url, runtimeConfig = {} } }; const browserWindow = new BrowserWindow(options); + const rendererRecoveryPolicy = createRendererRecoveryPolicy(); browserWindow.__ocLabel = label || nextWindowLabel(); browserWindow.__ocRuntimeConfig = { apiBaseUrl: desktopApiBaseUrl, clientToken: desktopClientToken, requestHeaders: desktopRequestHeaders }; browserWindow.__ocInitScript = buildInitScript(desktopLocalOrigin, state.bootOutcome, desktopApiBaseUrl, desktopClientToken, desktopRequestHeaders); browserWindow.__ocTitleBarOverlayEnabled = titleBarOverlayEnabled; + browserWindow.on('app-command', (event, command) => { + if (command === 'browser-backward') event.preventDefault(); + }); if (useSaved && saved.maximized) { browserWindow.maximize(); @@ -2618,6 +2658,19 @@ const createBrowserWindow = ({ label, restoreGeometry, url, runtimeConfig = {} } browserWindow.webContents.on('zoom-changed', () => { browserWindow.webContents.setZoomFactor(1); }); + browserWindow.webContents.on('render-process-gone', (_event, details) => { + if (!rendererRecoveryPolicy.shouldReload(details.reason)) return; + log.warn('[electron] renderer exited unexpectedly; reloading window', { + label: browserWindow.__ocLabel, + reason: details.reason, + exitCode: details.exitCode, + }); + setTimeout(() => { + if (!browserWindow.isDestroyed()) { + browserWindow.webContents.reload(); + } + }, 100); + }); browserWindow.webContents.on('dom-ready', () => { if (browserWindow.__ocLabel === 'main') { @@ -2996,12 +3049,24 @@ const resolveInitialUrl = async () => { } } + const defaultHostRelayCapable = Boolean( + config.defaultHostId + && config.defaultHostId !== LOCAL_HOST_ID + && sanitizeHostRelayForStorage(config.hosts.find((entry) => entry.id === config.defaultHostId)?.relay), + ); if (apiBaseUrl && apiBaseUrl !== localUrl) { remoteProbe = await probeHostWithTimeout(apiBaseUrl, 2_000, clientToken, requestHeaders); - if (remoteProbe.status === 'unreachable') { + if (remoteProbe.status === 'unreachable' && !defaultHostRelayCapable) { remoteProbe = await probeHostWithTimeout(apiBaseUrl, 10_000, clientToken, requestHeaders); } - if (remoteProbe.status === 'unreachable') { + // The renderer's relay restore owns transport selection for relay-capable + // hosts; any failed direct probe falls back to the local substrate. + if (remoteProbe.status !== 'ok' && defaultHostRelayCapable) { + apiBaseUrl = localUrl || ''; + clientToken = localUrl ? readDesktopLocalClientToken() : ''; + requestHeaders = {}; + initialUrl = localUiUrl; + } else if (remoteProbe.status === 'unreachable') { state.unreachableHosts.add(apiBaseUrl); apiBaseUrl = localUrl || ''; clientToken = localUrl ? readDesktopLocalClientToken() : ''; diff --git a/packages/electron/package.json b/packages/electron/package.json index 8432487f..da27bdd8 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -1,6 +1,6 @@ { "name": "@openchamber/electron", - "version": "1.19.0", + "version": "1.21.0", "private": true, "description": "Electron desktop runtime for OpenChamber", "author": "OpenChamber", diff --git a/packages/electron/renderer-recovery.mjs b/packages/electron/renderer-recovery.mjs new file mode 100644 index 00000000..77037210 --- /dev/null +++ b/packages/electron/renderer-recovery.mjs @@ -0,0 +1,30 @@ +const RECOVERY_WINDOW_MS = 60_000; +const MAX_RECOVERY_ATTEMPTS = 3; + +const RECOVERABLE_REASONS = new Set([ + 'abnormal-exit', + 'crashed', + 'oom', + 'memory-eviction', +]); + +export const createRendererRecoveryPolicy = (now = Date.now) => { + let windowStartedAt = 0; + let attempts = 0; + + return { + shouldReload: (reason) => { + if (!RECOVERABLE_REASONS.has(reason)) return false; + + const currentTime = now(); + if (currentTime - windowStartedAt >= RECOVERY_WINDOW_MS) { + windowStartedAt = currentTime; + attempts = 0; + } + if (attempts >= MAX_RECOVERY_ATTEMPTS) return false; + + attempts += 1; + return true; + }, + }; +}; diff --git a/packages/electron/renderer-recovery.test.mjs b/packages/electron/renderer-recovery.test.mjs new file mode 100644 index 00000000..a0240051 --- /dev/null +++ b/packages/electron/renderer-recovery.test.mjs @@ -0,0 +1,34 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { createRendererRecoveryPolicy } from './renderer-recovery.mjs'; + +test('allows a bounded number of reloads for recoverable renderer failures', () => { + const policy = createRendererRecoveryPolicy(() => 1_000); + + assert.equal(policy.shouldReload('crashed'), true); + assert.equal(policy.shouldReload('oom'), true); + assert.equal(policy.shouldReload('abnormal-exit'), true); + assert.equal(policy.shouldReload('memory-eviction'), false); +}); + +test('ignores clean and externally killed renderer exits', () => { + const policy = createRendererRecoveryPolicy(() => 1_000); + + assert.equal(policy.shouldReload('clean-exit'), false); + assert.equal(policy.shouldReload('killed'), false); + assert.equal(policy.shouldReload('launch-failed'), false); +}); + +test('resets the recovery budget after the recovery window', () => { + let currentTime = 1_000; + const policy = createRendererRecoveryPolicy(() => currentTime); + + assert.equal(policy.shouldReload('crashed'), true); + assert.equal(policy.shouldReload('crashed'), true); + assert.equal(policy.shouldReload('crashed'), true); + assert.equal(policy.shouldReload('crashed'), false); + + currentTime += 60_000; + assert.equal(policy.shouldReload('crashed'), true); +}); diff --git a/packages/electron/scripts/electron-dev.mjs b/packages/electron/scripts/electron-dev.mjs index a4ee60e4..c8ef57a6 100644 --- a/packages/electron/scripts/electron-dev.mjs +++ b/packages/electron/scripts/electron-dev.mjs @@ -220,7 +220,7 @@ async function main() { }); } - const electron = spawnProcess('npx', ['electron', './main.mjs'], { + const electron = spawnProcess('bun', ['x', 'electron', './main.mjs'], { cwd: electronDir, env: { ...process.env, diff --git a/packages/electron/ssh-manager.mjs b/packages/electron/ssh-manager.mjs index 5c0b52ba..390c2a38 100644 --- a/packages/electron/ssh-manager.mjs +++ b/packages/electron/ssh-manager.mjs @@ -5,9 +5,30 @@ import os from 'node:os'; import path from 'node:path'; import { spawn } from 'node:child_process'; +import { replaceFileWithRetry } from './windows-file-replace.mjs'; + const LOCAL_HOST_ID = 'local'; const DEFAULT_CONNECTION_TIMEOUT_SEC = 60; const DEFAULT_LOCAL_BIND_HOST = '127.0.0.1'; +// Global npm prefixes are root-owned on most distributions, so `npm install -g` +// fails with EACCES for a normal SSH user. Everything we install goes to a +// prefix inside the user's home instead. +const REMOTE_USER_PREFIX = '$HOME/.openchamber/npm-global'; +const REMOTE_BUN_CANDIDATE = '"${BUN_INSTALL:-$HOME/.bun}/bin/bun"'; +// The opencode CLI usually installs into the user's home, which an SSH login +// shell does not have on PATH. The remote server only looks at OPENCODE_BINARY +// and PATH, so resolve the CLI here and hand it over explicitly. +const REMOTE_OPENCODE_CANDIDATES = [ + '"$HOME/.opencode/bin/opencode"', + '"${BUN_INSTALL:-$HOME/.bun}/bin/opencode"', + '"$HOME/.local/bin/opencode"', + '"$HOME/.openchamber/npm-global/bin/opencode"', +]; +const REMOTE_PATH_PREFIX = '$HOME/.opencode/bin:${BUN_INSTALL:-$HOME/.bun}/bin:$HOME/.local/bin:$HOME/.openchamber/npm-global/bin'; +const REMOTE_BIN_CANDIDATES = [ + '"$HOME/.openchamber/npm-global/bin/openchamber"', + '"${BUN_INSTALL:-$HOME/.bun}/bin/openchamber"', +]; const DEFAULT_CONTROL_PERSIST_SEC = 300; const DEFAULT_READY_TIMEOUT_SEC = 30; const DEFAULT_RECONNECT_MAX_ATTEMPTS = 5; @@ -77,10 +98,15 @@ const writeJsonRoot = async (settingsFilePath, root) => { await fsp.mkdir(path.dirname(settingsFilePath), { recursive: true }); // Atomic write: concurrent readers (main.mjs, web server) would otherwise // see partial JSON and readJsonRoot()'s catch would silently coerce to {}, - // causing the next read-modify-write to wipe the entire settings file. + // causing the next read-modify-write wipe the entire settings file. const tmp = `${settingsFilePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; - await fsp.writeFile(tmp, JSON.stringify(root, null, 2)); - await fsp.rename(tmp, settingsFilePath); + try { + await fsp.writeFile(tmp, JSON.stringify(root, null, 2)); + await replaceFileWithRetry(tmp, settingsFilePath); + } catch (error) { + await fsp.rm(tmp, { force: true }).catch(() => {}); + throw error; + } }; const defaultTrue = () => true; @@ -578,6 +604,7 @@ export class ElectronSshManager { localUrl: null, localPort: null, remotePort: null, + remoteBinPath: null, startedByUs: false, retryAttempt: 0, requiresUserAction: false, @@ -804,9 +831,10 @@ export class ElectronSshManager { mode: instance?.remoteOpenchamber?.mode === 'external' ? 'external' : 'managed', keepRunning: instance?.remoteOpenchamber?.keepRunning !== false, ...(Number.isFinite(instance?.remoteOpenchamber?.preferredPort) ? { preferredPort: Number(instance.remoteOpenchamber.preferredPort) } : {}), - installMethod: ['npm', 'bun', 'download_release', 'upload_bundle'].includes(instance?.remoteOpenchamber?.installMethod) + installMethod: ['auto', 'npm', 'bun'].includes(instance?.remoteOpenchamber?.installMethod) ? instance.remoteOpenchamber.installMethod - : 'bun', + : 'auto', + bindHost: instance?.remoteOpenchamber?.bindHost === '0.0.0.0' ? '0.0.0.0' : '127.0.0.1', uploadBundleOverSsh: Boolean(instance?.remoteOpenchamber?.uploadBundleOverSsh), }, localForward: { @@ -981,38 +1009,77 @@ export class ElectronSshManager { return secret?.enabled && typeof secret.value === 'string' && secret.value.trim() ? secret.value.trim() : null; } - async remoteCommandExists(parsed, controlPath, commandName) { - try { - const output = await this.runRemoteCommand(parsed, controlPath, `command -v ${commandName} >/dev/null 2>&1 && echo yes || echo no`); - return output.trim() === 'yes'; - } catch { - return false; - } - } + // A login shell over SSH does not source the user's interactive rc files, so + // tools installed into a home directory (bun above all) are missing from PATH + // even when they exist. Look at their known install locations too. + async resolveRemoteTool(parsed, controlPath, commandName, extraCandidates = []) { + const candidateList = [...extraCandidates, `"$(command -v ${commandName} 2>/dev/null)"`].join(' '); + const script = [ + `for candidate in ${candidateList}; do`, + ' [ -n "$candidate" ] || continue;', + ' [ -x "$candidate" ] || continue;', + ` printf '%s' "$candidate";`, + ' exit 0;', + 'done', + ].join(' '); - async currentRemoteOpenChamberVersion(parsed, controlPath) { try { - const output = await this.runRemoteCommand(parsed, controlPath, 'openchamber --version 2>/dev/null || true'); - return parseVersionToken(output); + const output = await this.runRemoteCommand(parsed, controlPath, script); + return output.trim() || null; } catch { return null; } } - async installOpenChamberManaged(parsed, controlPath, version, preferred) { - const hasBun = await this.remoteCommandExists(parsed, controlPath, 'bun'); - const hasNpm = await this.remoteCommandExists(parsed, controlPath, 'npm'); - const commands = []; + // Every place OpenChamber may live on the remote host, with the version each + // one reports. Installs land in the user prefix while an older copy can still + // sit on PATH, so the caller picks by version instead of trusting PATH order. + async remoteOpenChamberCandidates(parsed, controlPath) { + const script = [ + `for candidate in ${REMOTE_BIN_CANDIDATES.join(' ')} "$(command -v openchamber 2>/dev/null)"; do`, + ' [ -n "$candidate" ] || continue;', + ' [ -x "$candidate" ] || continue;', + ` printf '%s\t%s\n' "$candidate" "$("$candidate" --version 2>/dev/null | head -n 1)";`, + 'done', + ].join(' '); - if (preferred === 'bun') { - if (hasBun) commands.push(`bun add -g @openchamber/web@${version}`); - if (hasNpm) commands.push(`npm install -g @openchamber/web@${version}`); - } else if (preferred === 'npm') { - if (hasNpm) commands.push(`npm install -g @openchamber/web@${version}`); - if (hasBun) commands.push(`bun add -g @openchamber/web@${version}`); + let output = ''; + try { + output = await this.runRemoteCommand(parsed, controlPath, script); + } catch { + return []; + } + + const candidates = []; + const seen = new Set(); + for (const line of output.split(/\r?\n/)) { + const [binPath, versionRaw] = line.split('\t'); + const trimmed = (binPath || '').trim(); + if (!trimmed || seen.has(trimmed)) continue; + seen.add(trimmed); + candidates.push({ binPath: trimmed, version: parseVersionToken(versionRaw || '') }); + } + return candidates; + } + + async installOpenChamberManaged(parsed, controlPath, version, preferred) { + const bunPath = await this.resolveRemoteTool(parsed, controlPath, 'bun', [REMOTE_BUN_CANDIDATE]); + const npmPath = await this.resolveRemoteTool(parsed, controlPath, 'npm'); + + // bun's global install already targets ~/.bun; npm is pinned to a prefix in + // the user's home so it never touches the root-owned global directory. + const bunCommand = bunPath ? `${shellQuote(bunPath)} add -g @openchamber/web@${version}` : null; + const npmCommand = npmPath + ? `mkdir -p "${REMOTE_USER_PREFIX}" && ${shellQuote(npmPath)} install -g --prefix "${REMOTE_USER_PREFIX}" @openchamber/web@${version}` + : null; + + const commands = []; + if (preferred === 'npm') { + if (npmCommand) commands.push(npmCommand); + if (bunCommand) commands.push(bunCommand); } else { - if (hasBun) commands.push(`bun add -g @openchamber/web@${version}`); - if (hasNpm) commands.push(`npm install -g @openchamber/web@${version}`); + if (bunCommand) commands.push(bunCommand); + if (npmCommand) commands.push(npmCommand); } if (commands.length === 0) { @@ -1072,24 +1139,35 @@ export class ElectronSshManager { } } - async startRemoteServerManaged(parsed, controlPath, instance, desiredPort) { - let envPrefix = 'OPENCHAMBER_RUNTIME=ssh-remote'; + async startRemoteServerManaged(parsed, controlPath, instance, desiredPort, binPath) { + const opencodePath = await this.resolveRemoteTool(parsed, controlPath, 'opencode', REMOTE_OPENCODE_CANDIDATES); + if (!opencodePath) { + throw new Error('The opencode CLI is not installed on the remote machine. Install it there, then connect again'); + } + const secret = this.configuredOpenChamberPassword(instance); + const remoteBindHost = instance.remoteOpenchamber?.bindHost === '0.0.0.0' ? '0.0.0.0' : '127.0.0.1'; + // Binding the remote server to every interface publishes its UI to the + // remote machine's whole network, so it may not run without a password. + if (remoteBindHost === '0.0.0.0' && !secret) { + throw new Error('Exposing the remote server to its network requires a UI password'); + } + + let envPrefix = `PATH="${REMOTE_PATH_PREFIX}:$PATH" OPENCODE_BINARY=${shellQuote(opencodePath)} OPENCHAMBER_RUNTIME=ssh-remote`; if (secret) { envPrefix += ` OPENCHAMBER_UI_PASSWORD=${shellQuote(secret)}`; } - const output = await this.runRemoteCommand(parsed, controlPath, `${envPrefix} openchamber serve --hostname 127.0.0.1 --port ${desiredPort}`); + const output = await this.runRemoteCommand(parsed, controlPath, `${envPrefix} ${shellQuote(binPath)} serve --hostname ${remoteBindHost} --port ${desiredPort}`); const port = output.split(/\s+/).map((token) => Number.parseInt(token, 10)).find((value) => Number.isFinite(value)); return port || desiredPort; } - async stopRemoteServerBestEffort(parsed, controlPath, remotePort) { + // `openchamber stop` owns the daemon lifecycle. The HTTP shutdown route sits + // behind UI authentication, so it cannot stop a password-protected server. + async stopRemoteServerBestEffort(parsed, controlPath, remotePort, remoteBinPath) { + if (!remoteBinPath) return; try { - await this.runRemoteCommand( - parsed, - controlPath, - `if command -v curl >/dev/null 2>&1; then curl -fsS -X POST http://127.0.0.1:${remotePort}/api/system/shutdown >/dev/null 2>&1 || true; elif command -v wget >/dev/null 2>&1; then wget -qO- --method=POST http://127.0.0.1:${remotePort}/api/system/shutdown >/dev/null 2>&1 || true; fi`, - ); + await this.runRemoteCommand(parsed, controlPath, `${shellQuote(remoteBinPath)} stop --port ${remotePort}`); } catch { } } @@ -1143,17 +1221,27 @@ export class ElectronSshManager { const port = instance.remoteOpenchamber.preferredPort; this.setStatus(instance.id, 'server_detecting', 'Probing external OpenChamber server', null, null, port, false, 0, false); await this.probeRemoteSystemInfo(parsed, controlPath, port, this.configuredOpenChamberPassword(instance)); - return { remotePort: port, startedByUs: false }; + return { remotePort: port, startedByUs: false, remoteBinPath: null }; } this.setStatus(instance.id, 'remote_probe', 'Checking remote OpenChamber installation'); - const installedVersion = await this.currentRemoteOpenChamberVersion(parsed, controlPath); - if (!installedVersion) { - this.setStatus(instance.id, 'installing', 'Installing OpenChamber on remote host'); - await this.installOpenChamberManaged(parsed, controlPath, this.appVersion, instance.remoteOpenchamber.installMethod); - } else if (installedVersion !== this.appVersion) { - this.setStatus(instance.id, 'updating', `Updating remote OpenChamber from ${installedVersion} to ${this.appVersion}`); + const installed = await this.remoteOpenChamberCandidates(parsed, controlPath); + let binary = installed.find((candidate) => candidate.version === this.appVersion) || null; + + if (!binary) { + const existing = installed[0] || null; + if (existing) { + this.setStatus(instance.id, 'updating', `Updating remote OpenChamber from ${existing.version || 'unknown'} to ${this.appVersion}`); + } else { + this.setStatus(instance.id, 'installing', 'Installing OpenChamber on remote host'); + } await this.installOpenChamberManaged(parsed, controlPath, this.appVersion, instance.remoteOpenchamber.installMethod); + + const afterInstall = await this.remoteOpenChamberCandidates(parsed, controlPath); + binary = afterInstall.find((candidate) => candidate.version === this.appVersion) || afterInstall[0] || existing; + if (!binary) { + throw new Error('OpenChamber was installed on the remote host but no openchamber binary could be found'); + } } this.setStatus(instance.id, 'server_detecting', 'Detecting managed OpenChamber server'); @@ -1165,13 +1253,13 @@ export class ElectronSshManager { if (!remotePort) { this.setStatus(instance.id, 'server_starting', 'Starting managed OpenChamber server'); const desiredPort = instance.remoteOpenchamber.preferredPort || randomPortCandidate(instance.id); - remotePort = await this.startRemoteServerManaged(parsed, controlPath, instance, desiredPort); + remotePort = await this.startRemoteServerManaged(parsed, controlPath, instance, desiredPort, binary.binPath); startedByUs = true; } if (!(await this.remoteServerRunning(parsed, controlPath, remotePort, this.configuredOpenChamberPassword(instance)))) { throw new Error('Managed OpenChamber server failed to become reachable'); } - return { remotePort, startedByUs }; + return { remotePort, startedByUs, remoteBinPath: binary.binPath }; } async disconnectInternal(id, reportIdle) { @@ -1186,7 +1274,7 @@ export class ElectronSshManager { if (session) { if (session.startedByUs && session.remotePort && session.instance.remoteOpenchamber.mode === 'managed' && !session.instance.remoteOpenchamber.keepRunning) { - await this.stopRemoteServerBestEffort(session.parsed, session.controlPath, session.remotePort); + await this.stopRemoteServerBestEffort(session.parsed, session.controlPath, session.remotePort, session.remoteBinPath); } await this.stopControlMasterBestEffort(session.parsed, session.controlPath); const auth = this.sshAuth.get(session.parsed); @@ -1262,9 +1350,10 @@ export class ElectronSshManager { throw new Error(`Unsupported remote OS: ${remoteOs}`); } - const { remotePort, startedByUs } = await this.ensureRemoteServer(instance, parsed, controlPath); + const { remotePort, startedByUs, remoteBinPath } = await this.ensureRemoteServer(instance, parsed, controlPath); session.remotePort = remotePort; session.startedByUs = startedByUs; + session.remoteBinPath = remoteBinPath; this.setStatus(id, 'forwarding', 'Setting up port forwards', null, null, remotePort, startedByUs, 0, false); const bindHost = sanitizeBindHost(instance.localForward?.bindHost); diff --git a/packages/electron/ssh-manager.test.mjs b/packages/electron/ssh-manager.test.mjs index eb5d1af7..fdbf84ea 100644 --- a/packages/electron/ssh-manager.test.mjs +++ b/packages/electron/ssh-manager.test.mjs @@ -289,4 +289,161 @@ describe('ElectronSshManager', () => { }); expect(settings.desktopHosts).toEqual([{ id: 'ssh-1', label: 'SSH Host', url: localUrl, apiUrl: localUrl, clientToken: 'ssh-client-token' }]); }); + test('installs OpenChamber into a home-owned npm prefix instead of the root-owned global one', async () => { + const commands = []; + const manager = new ElectronSshManager({ + settingsFilePath: path.join(os.tmpdir(), 'unused-settings.json'), + appVersion: '1.2.3', + emit: () => undefined, + }); + manager.resolveRemoteTool = async (_parsed, _controlPath, name) => (name === 'npm' ? '/usr/bin/npm' : null); + manager.runRemoteCommand = async (_parsed, _controlPath, script) => { + commands.push(script); + return ''; + }; + + await manager.installOpenChamberManaged({ destination: 'user@example.test', args: [] }, '/tmp/control.sock', '1.2.3', 'auto'); + + expect(commands).toHaveLength(1); + expect(commands[0]).toContain('--prefix "$HOME/.openchamber/npm-global"'); + expect(commands[0]).not.toMatch(/npm install -g @openchamber/); + }); + + test('lists every remote OpenChamber binary with its reported version', async () => { + const manager = new ElectronSshManager({ + settingsFilePath: path.join(os.tmpdir(), 'unused-settings.json'), + appVersion: '1.2.3', + emit: () => undefined, + }); + manager.runRemoteCommand = async () => [ + '/home/pi/.openchamber/npm-global/bin/openchamber\t1.2.3', + '/usr/bin/openchamber\t0.9.0', + '', + ].join('\n'); + + const candidates = await manager.remoteOpenChamberCandidates({ destination: 'user@example.test', args: [] }, '/tmp/control.sock'); + + expect(candidates).toEqual([ + { binPath: '/home/pi/.openchamber/npm-global/bin/openchamber', version: '1.2.3' }, + { binPath: '/usr/bin/openchamber', version: '0.9.0' }, + ]); + }); + + test('starts the resolved OpenChamber binary rather than whatever PATH exposes', async () => { + let started = ''; + const manager = new ElectronSshManager({ + settingsFilePath: path.join(os.tmpdir(), 'unused-settings.json'), + appVersion: '1.2.3', + emit: () => undefined, + }); + manager.resolveRemoteTool = async () => '/home/pi/.opencode/bin/opencode'; + manager.runRemoteCommand = async (_parsed, _controlPath, script) => { + started = script; + return '4321\n'; + }; + + const instance = { id: 'ssh-1', auth: {}, remoteOpenchamber: { mode: 'managed' } }; + const port = await manager.startRemoteServerManaged( + { destination: 'user@example.test', args: [] }, + '/tmp/control.sock', + instance, + 4321, + '/home/pi/.openchamber/npm-global/bin/openchamber', + ); + + expect(port).toBe(4321); + expect(started).toContain("'/home/pi/.openchamber/npm-global/bin/openchamber' serve"); + expect(started).toContain("OPENCODE_BINARY='/home/pi/.opencode/bin/opencode'"); + expect(started).toContain('$HOME/.opencode/bin:'); + }); + + test('refuses to start when the remote machine has no opencode CLI', async () => { + const manager = new ElectronSshManager({ + settingsFilePath: path.join(os.tmpdir(), 'unused-settings.json'), + appVersion: '1.2.3', + emit: () => undefined, + }); + manager.resolveRemoteTool = async () => null; + manager.runRemoteCommand = async () => { + throw new Error('should not start the server without a CLI'); + }; + + await expect(manager.startRemoteServerManaged( + { destination: 'user@example.test', args: [] }, + '/tmp/control.sock', + { id: 'ssh-1', auth: {}, remoteOpenchamber: { mode: 'managed' } }, + 4321, + '/home/pi/.bun/bin/openchamber', + )).rejects.toThrow(/opencode CLI is not installed/); + }); + test('prefers a bun that only exists in the home directory over npm', async () => { + const commands = []; + const manager = new ElectronSshManager({ + settingsFilePath: path.join(os.tmpdir(), 'unused-settings.json'), + appVersion: '1.2.3', + emit: () => undefined, + }); + // A login shell over SSH does not put ~/.bun/bin on PATH. + manager.resolveRemoteTool = async (_parsed, _controlPath, name) => + (name === 'bun' ? '/home/pi/.bun/bin/bun' : '/usr/bin/npm'); + manager.runRemoteCommand = async (_parsed, _controlPath, script) => { + commands.push(script); + return ''; + }; + + await manager.installOpenChamberManaged({ destination: 'user@example.test', args: [] }, '/tmp/control.sock', '1.2.3', 'auto'); + + expect(commands).toEqual(["'/home/pi/.bun/bin/bun' add -g @openchamber/web@1.2.3"]); + }); + test('stops a remote server it started through the CLI, not the authenticated HTTP route', async () => { + const scripts = []; + const manager = new ElectronSshManager({ + settingsFilePath: path.join(os.tmpdir(), 'unused-settings.json'), + appVersion: '1.2.3', + emit: () => undefined, + }); + manager.runRemoteCommand = async (_parsed, _controlPath, script) => { + scripts.push(script); + return ''; + }; + + await manager.stopRemoteServerBestEffort( + { destination: 'user@example.test', args: [] }, + '/tmp/control.sock', + 41777, + '/home/pi/.bun/bin/openchamber', + ); + + expect(scripts).toEqual(["'/home/pi/.bun/bin/openchamber' stop --port 41777"]); + }); + test('publishes the remote server to its network only with a UI password', async () => { + const manager = new ElectronSshManager({ + settingsFilePath: path.join(os.tmpdir(), 'unused-settings.json'), + appVersion: '1.2.3', + emit: () => undefined, + }); + manager.resolveRemoteTool = async () => '/home/pi/.opencode/bin/opencode'; + let started = ''; + manager.runRemoteCommand = async (_parsed, _controlPath, script) => { + started = script; + return '4321\n'; + }; + + const parsed = { destination: 'user@example.test', args: [] }; + const exposed = { + id: 'ssh-1', + auth: {}, + remoteOpenchamber: { mode: 'managed', bindHost: '0.0.0.0' }, + }; + + await expect(manager.startRemoteServerManaged(parsed, '/tmp/control.sock', exposed, 4321, '/bin/openchamber')) + .rejects.toThrow(/requires a UI password/); + + const secured = { + ...exposed, + auth: { openchamberPassword: { enabled: true, value: 'remote-secret', store: 'settings' } }, + }; + await manager.startRemoteServerManaged(parsed, '/tmp/control.sock', secured, 4321, '/bin/openchamber'); + expect(started).toContain('--hostname 0.0.0.0'); + }); }); diff --git a/packages/electron/windows-file-replace.mjs b/packages/electron/windows-file-replace.mjs new file mode 100644 index 00000000..b43ad904 --- /dev/null +++ b/packages/electron/windows-file-replace.mjs @@ -0,0 +1,28 @@ +import fsp from 'node:fs/promises'; + +const WINDOWS_RETRY_DELAYS_MS = [50, 100, 200, 400, 800, 1_000, 1_000]; + +const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)); + +const isTransientWindowsFileError = (error, platform) => { + if (platform !== 'win32') return false; + const code = error?.code; + return code === 'EPERM' || code === 'EACCES' || code === 'EBUSY'; +}; + +export const replaceFileWithRetry = async (source, target, options = {}) => { + const platform = options.platform ?? process.platform; + const rename = options.rename ?? fsp.rename; + const wait = options.wait ?? sleep; + + for (let attempt = 0; ; attempt += 1) { + try { + await rename(source, target); + return; + } catch (error) { + const delay = WINDOWS_RETRY_DELAYS_MS[attempt]; + if (delay === undefined || !isTransientWindowsFileError(error, platform)) throw error; + await wait(delay); + } + } +}; diff --git a/packages/electron/windows-file-replace.test.mjs b/packages/electron/windows-file-replace.test.mjs new file mode 100644 index 00000000..a860037d --- /dev/null +++ b/packages/electron/windows-file-replace.test.mjs @@ -0,0 +1,81 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +import { replaceFileWithRetry } from './windows-file-replace.mjs'; + +const fileError = (code = 'EPERM') => Object.assign(new Error(code), { code }); + +test('retries transient Windows rename failures until replacement succeeds', async () => { + const delays = []; + let attempts = 0; + + await replaceFileWithRetry('settings.tmp', 'settings.json', { + platform: 'win32', + rename: async () => { + attempts += 1; + if (attempts < 4) throw fileError(); + }, + wait: async (delay) => delays.push(delay), + }); + + assert.equal(attempts, 4); + assert.deepEqual(delays, [50, 100, 200]); +}); + +test('does not retry rename errors that are not transient Windows locks', async () => { + let attempts = 0; + const error = fileError('ENOENT'); + + await assert.rejects( + replaceFileWithRetry('settings.tmp', 'settings.json', { + platform: 'win32', + rename: async () => { + attempts += 1; + throw error; + }, + wait: async () => assert.fail('unexpected wait'), + }), + error, + ); + + assert.equal(attempts, 1); +}); + +test('does not retry transient error codes outside Windows', async () => { + let attempts = 0; + const error = fileError(); + + await assert.rejects( + replaceFileWithRetry('settings.tmp', 'settings.json', { + platform: 'linux', + rename: async () => { + attempts += 1; + throw error; + }, + wait: async () => assert.fail('unexpected wait'), + }), + error, + ); + + assert.equal(attempts, 1); +}); + +test('returns the final Windows lock error after the retry window', async () => { + const delays = []; + let attempts = 0; + + await assert.rejects( + replaceFileWithRetry('settings.tmp', 'settings.json', { + platform: 'win32', + rename: async () => { + attempts += 1; + throw fileError(); + }, + wait: async (delay) => delays.push(delay), + }), + { code: 'EPERM' }, + ); + + assert.equal(attempts, 8); + assert.deepEqual(delays, [50, 100, 200, 400, 800, 1_000, 1_000]); +}); diff --git a/packages/mobile/android/app/src/main/AndroidManifest.xml b/packages/mobile/android/app/src/main/AndroidManifest.xml index dba5d6b5..4b21d928 100644 --- a/packages/mobile/android/app/src/main/AndroidManifest.xml +++ b/packages/mobile/android/app/src/main/AndroidManifest.xml @@ -1,17 +1,12 @@ - + + + + + + + + diff --git a/packages/ui/package.json b/packages/ui/package.json index 2b224dcb..6eeb6f0a 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@openchamber/ui", - "version": "1.19.0", + "version": "1.21.0", "private": true, "type": "module", "main": "src/main.tsx", @@ -43,8 +43,9 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@legendapp/list": "3.3.8", "@lezer/highlight": "^1.2.3", - "@opencode-ai/sdk": "1.18.18", + "@opencode-ai/sdk": "1.18.23", "@pierre/diffs": "1.3.0-beta.6", "@replit/codemirror-vim": "^6.4.0", "@simplewebauthn/browser": "13.3.0", @@ -66,6 +67,7 @@ "http-proxy-middleware": "^3.0.5", "katex": "^0.17.0", "marked": "^17.0.3", + "marked-linkify-it": "^4.0.2", "morphdom": "^2.7.7", "motion": "^12.23.24", "next-themes": "^0.4.6", @@ -103,6 +105,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.5.0", "globals": "^16.3.0", + "happy-dom": "^18.0.1", "nodemon": "^3.1.7", "tailwindcss": "^4.0.0", "tsx": "^4.20.6", diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index ff0ffe7c..cf81988b 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -1,16 +1,19 @@ import React from 'react'; import { MainLayout } from '@/components/layout/MainLayout'; import { ChatView } from '@/components/views/ChatView'; +import { AppLinkConfirmDialog } from '@/components/chat/AppLinkConfirmDialog'; import { FireworksProvider } from '@/contexts/FireworksContext'; import { Toaster } from '@/components/ui/sonner'; import { Button } from '@/components/ui/button'; import { MemoryDebugPanel } from '@/components/ui/MemoryDebugPanel'; import { setStreamPerfEnabled } from '@/stores/utils/streamDebug'; +import { setRequestsInFlightTrackingEnabled } from '@/stores/utils/requestsInFlight'; import { ErrorBoundary } from '@/components/ui/ErrorBoundary'; // useEventStream removed — replaced by SyncProvider + SyncBridge import { useMenuActions } from '@/hooks/useMenuActions'; import { useSessionStatusBootstrap } from '@/hooks/useSessionStatusBootstrap'; import { useTraySync } from '@/hooks/useTraySync'; +import { useGlobalSessionsPolling } from '@/hooks/useGlobalSessionsPolling'; import { useRouter } from '@/hooks/useRouter'; import { usePushVisibilityBeacon } from '@/hooks/usePushVisibilityBeacon'; import { useWebNotificationStream } from '@/hooks/useWebNotificationStream'; @@ -18,7 +21,6 @@ import { useAgentMemorySync } from '@/hooks/useAgentMemorySync'; import { usePwaInstallPrompt } from '@/hooks/usePwaInstallPrompt'; import { useWindowTitle } from '@/hooks/useWindowTitle'; import { useConfigStore } from '@/stores/useConfigStore'; -import { hasModifier } from '@/lib/utils'; import { isDesktopLocalOriginActive, isDesktopShell, restartDesktopApp, invokeDesktop } from '@/lib/desktop'; import { getInjectedBootOutcome, @@ -33,7 +35,6 @@ import type { RecoveryVariant } from '@/components/onboarding/DesktopConnectionR import { useSessionUIStore } from '@/sync/session-ui-store'; import { markSessionViewed } from '@/sync/notification-store'; import { useDirectoryStore } from '@/stores/useDirectoryStore'; -import { useProjectsStore } from '@/stores/useProjectsStore'; import { opencodeClient } from '@/lib/opencode/client'; import { runtimeFetch } from '@/lib/runtime-fetch'; import { getRuntimeKey, subscribeRuntimeEndpointChanged } from '@/lib/runtime-switch'; @@ -279,6 +280,13 @@ function App({ apis }: AppProps) { }; }, [showMemoryDebug]); + React.useEffect(() => { + setRequestsInFlightTrackingEnabled(showMemoryDebug); + return () => { + setRequestsInFlightTrackingEnabled(false); + }; + }, [showMemoryDebug]); + React.useEffect(() => { applyMobileKeyboardMode(mobileKeyboardMode); }, [mobileKeyboardMode]); @@ -625,7 +633,6 @@ function App({ apis }: AppProps) { const directory = typeof detail?.directory === 'string' && detail.directory.trim().length > 0 ? detail.directory.trim() : null; - useUIStore.getState().setActiveMainTab('chat'); void useSessionUIStore.getState().setCurrentSession(sessionId, directory); }; @@ -639,12 +646,9 @@ function App({ apis }: AppProps) { React.useEffect(() => { if (typeof window === 'undefined') return; const onOpenMiniChat = () => { - const currentDir = useDirectoryStore.getState().currentDirectory; - const { activeProjectId, projects } = useProjectsStore.getState(); - const activeProject = projects.find((p) => p.id === activeProjectId) ?? null; void invokeDesktop('desktop_open_draft_mini_chat_window', { - directory: currentDir || activeProject?.path || '', - projectId: activeProject?.id ?? null, + directory: '', + projectId: null, }); }; window.addEventListener('openchamber:open-mini-chat', onOpenMiniChat); @@ -676,11 +680,12 @@ function App({ apis }: AppProps) { const projectId = typeof detail?.projectId === 'string' && detail.projectId.trim().length > 0 ? detail.projectId.trim() : null; - useUIStore.getState().setActiveMainTab('chat'); + const hasProjectTarget = Boolean(directory || projectId); useUIStore.getState().setSessionSwitcherOpen(false); useSessionUIStore.getState().openNewSessionDraft({ - selectedProjectId: projectId, - directoryOverride: directory, + target: hasProjectTarget ? 'project' : 'chat', + selectedProjectId: hasProjectTarget ? projectId : null, + directoryOverride: hasProjectTarget ? directory : null, preserveDirectoryOverride: Boolean(directory), }); }; @@ -721,28 +726,16 @@ function App({ apis }: AppProps) { useMenuActions(handleToggleMemoryDebug); useTraySync(); + useGlobalSessionsPolling(!embeddedSessionChat); useSessionStatusBootstrap({ enabled: embeddedBackgroundWorkEnabled }); + // Palette-only action: the memory debug panel has no keyboard shortcut. React.useEffect(() => { - if (embeddedSessionChat) { - return; - } - - const handleKeyDown = (e: KeyboardEvent) => { - const isDebugShortcut = hasModifier(e) - && e.shiftKey - && !e.altKey - && (e.code === 'KeyD' || e.key.toLowerCase() === 'd'); - - if (isDebugShortcut) { - e.preventDefault(); - setShowMemoryDebug(prev => !prev); - } - }; - - window.addEventListener('keydown', handleKeyDown, true); - return () => window.removeEventListener('keydown', handleKeyDown, true); + if (embeddedSessionChat) return; + const handleToggle = () => setShowMemoryDebug((previous) => !previous); + window.addEventListener('openchamber:memory-debug-toggle', handleToggle); + return () => window.removeEventListener('openchamber:memory-debug-toggle', handleToggle); }, [embeddedSessionChat]); React.useEffect(() => { @@ -908,6 +901,7 @@ function App({ apis }: AppProps) { isVSCodeRuntime={isVSCodeRuntime} embeddedBackgroundWorkEnabled={embeddedBackgroundWorkEnabled} /> + @@ -951,6 +945,7 @@ function App({ apis }: AppProps) { + {!isBootShell && ( <> diff --git a/packages/ui/src/apps/ElectronMiniChatApp.tsx b/packages/ui/src/apps/ElectronMiniChatApp.tsx index 10a993d8..7aed5ba0 100644 --- a/packages/ui/src/apps/ElectronMiniChatApp.tsx +++ b/packages/ui/src/apps/ElectronMiniChatApp.tsx @@ -5,6 +5,7 @@ import { registerRuntimeAPIs } from '@/contexts/runtimeAPIRegistry'; import { TooltipProvider } from '@/components/ui/tooltip'; import { Toaster } from '@/components/ui/sonner'; import { MiniChatLayout } from '@/components/mini-chat/MiniChatLayout'; +import { AppLinkConfirmDialog } from '@/components/chat/AppLinkConfirmDialog'; import { usePushVisibilityBeacon } from '@/hooks/usePushVisibilityBeacon'; import { useWindowTitle } from '@/hooks/useWindowTitle'; import { opencodeClient } from '@/lib/opencode/client'; @@ -25,6 +26,7 @@ import { worktreeMapsEqual, } from '@/lib/worktrees/worktreeManager'; import type { WorktreeMetadata } from '@/types/worktree'; +import { CHAT_DRAFT_PROJECT_ID } from '@/lib/chatDirectories'; const MINI_CHAT_PRESENCE_CHANNEL = 'openchamber:mini-chat-presence'; @@ -153,9 +155,9 @@ const MiniChatBootstrap: React.FC<{ config: MiniChatConfig }> = ({ config }) => const sessionId = typeof detail?.sessionId === 'string' ? detail.sessionId.trim() : ''; if (!sessionId) return; if (useSessionUIStore.getState().currentSessionId === sessionId) return; - const directory = typeof detail?.directory === 'string' && detail.directory.trim().length > 0 - ? detail.directory.trim() - : (sessions.find((entry) => entry.id === sessionId) as { directory?: string | null } | undefined)?.directory ?? null; + const sessionDirectory = (sessions.find((entry) => entry.id === sessionId) as { directory?: string | null } | undefined)?.directory?.trim(); + const directory = sessionDirectory + || (typeof detail?.directory === 'string' && detail.directory.trim().length > 0 ? detail.directory.trim() : null); void sync.ensureSessionRenderable(sessionId); setCurrentSession(sessionId, directory); sessionBootstrappedRef.current = true; @@ -166,9 +168,11 @@ const MiniChatBootstrap: React.FC<{ config: MiniChatConfig }> = ({ config }) => React.useEffect(() => { if (config.mode !== 'draft' || draftOpen || currentSessionId) return; + const hasProjectTarget = Boolean(config.projectId || config.directory); openNewSessionDraft({ - selectedProjectId: config.projectId, - directoryOverride: config.directory, + target: hasProjectTarget ? 'project' : 'chat', + selectedProjectId: hasProjectTarget ? config.projectId : CHAT_DRAFT_PROJECT_ID, + directoryOverride: hasProjectTarget ? config.directory : null, preserveDirectoryOverride: Boolean(config.directory), }); }, [config, currentSessionId, draftOpen, openNewSessionDraft]); @@ -278,10 +282,11 @@ const MiniChatPresencePublisher: React.FC = () => { const useSessionUnavailable = (config: MiniChatConfig): boolean => { const sessions = useSessions(); const currentSessionId = useSessionUIStore((state) => state.currentSessionId); + const draftOpen = useSessionUIStore((state) => state.newSessionDraft.open); const [timedOut, setTimedOut] = React.useState(false); React.useEffect(() => { - if (config.mode !== 'session' || !config.sessionId || currentSessionId === config.sessionId) { + if (draftOpen || config.mode !== 'session' || !config.sessionId || currentSessionId) { setTimedOut(false); return; } @@ -291,7 +296,7 @@ const useSessionUnavailable = (config: MiniChatConfig): boolean => { } const timeout = window.setTimeout(() => setTimedOut(true), 5000); return () => window.clearTimeout(timeout); - }, [config.mode, config.sessionId, currentSessionId, sessions]); + }, [config.mode, config.sessionId, currentSessionId, draftOpen, sessions]); return timedOut; }; @@ -321,6 +326,7 @@ export function ElectronMiniChatApp({ apis }: ElectronMiniChatAppProps) {
+
diff --git a/packages/ui/src/apps/MobileApp.tsx b/packages/ui/src/apps/MobileApp.tsx index b790270c..9178e36b 100644 --- a/packages/ui/src/apps/MobileApp.tsx +++ b/packages/ui/src/apps/MobileApp.tsx @@ -9,8 +9,10 @@ import { OpenChamberLogo } from '@/components/ui/OpenChamberLogo'; import { ChatView } from '@/components/views/ChatView'; import { PlanView } from '@/components/views/PlanView'; import { SettingsView } from '@/components/views/SettingsView'; +import { AppLinkConfirmDialog } from '@/components/chat/AppLinkConfirmDialog'; import { ErrorBoundary } from '@/components/ui/ErrorBoundary'; import { RuntimeAPIProvider } from '@/contexts/RuntimeAPIProvider'; +import { useAuthSessionStore } from '@/lib/runtime-auth-expiry'; import { registerRuntimeAPIs } from '@/contexts/runtimeAPIRegistry'; import { TooltipProvider } from '@/components/ui/tooltip'; import { Toaster } from '@/components/ui/sonner'; @@ -20,6 +22,7 @@ import { useUpdatePolling } from '@/hooks/useUpdatePolling'; import { useWindowTitle } from '@/hooks/useWindowTitle'; import { opencodeClient } from '@/lib/opencode/client'; import type { RuntimeAPIs } from '@/lib/api/types'; +import type { ProjectRef } from '@/lib/projectContextApi'; import { readTabletLayout, useOrientation, useTabletLayout } from '@/lib/device'; import { useHardwareKeyboard } from '@/lib/hardwareKeyboard'; import { useI18n } from '@/lib/i18n'; @@ -109,7 +112,7 @@ const MobileShell: React.FC<{ onActiveConnectionDeleted: () => void }> = ({ onAc const [workspaceTab, setWorkspaceTab] = React.useState('changes'); // A plan opened from the workspace drawer's Notes tab, shown as a fullscreen // layer on top of it (back returns to the notes). - const [openPlan, setOpenPlan] = React.useState<{ id: string; title: string } | null>(null); + const [openPlan, setOpenPlan] = React.useState<{ id: string; title: string; projectRef: ProjectRef } | null>(null); const [settingsInitialMobileStage, setSettingsInitialMobileStage] = React.useState<'nav' | 'page-content'>('nav'); // When set, the Changes surface opens directly into the per-file diff for this path. const [pendingChangesDiff, setPendingChangesDiff] = React.useState<{ path: string; staged: boolean } | null>(null); @@ -540,7 +543,7 @@ const MobileShell: React.FC<{ onActiveConnectionDeleted: () => void }> = ({ onAc > { closeSurface(); closeWorkspace(); @@ -771,6 +774,23 @@ export function MobileApp({ apis }: MobileAppProps) { }; }, [isNativeMobileApp, handleNativeResume]); + // A confirmed mid-session auth expiry (classified centrally from live 401 + // traffic) runs the same seq-guarded re-probe the resume path uses: it ends + // in needs-login → the native welcome screen with the auth-expired notice. + // The shared web banner never renders on native (the session gate is not + // mounted here), so this is the only surface reacting to the signal. + React.useEffect(() => { + if (!isNativeMobileApp) return; + return useAuthSessionStore.subscribe((store, previous) => { + if (store.state === 'expired' && previous.state !== 'expired') { + handleNativeResume(); + // The probe ladder owns the outcome from here; the shared store goes + // back to 'ok' so a later expiry can signal again. + useAuthSessionStore.getState().markAuthenticated(); + } + }); + }, [isNativeMobileApp, handleNativeResume]); + React.useEffect(() => { registerRuntimeAPIs(apis); return () => registerRuntimeAPIs(null); @@ -1258,6 +1278,7 @@ export function MobileApp({ apis }: MobileAppProps) { switchRuntimeEndpoint({ apiBaseUrl: '', clientToken: null, runtimeKey: 'mobile-disconnected' }); setConnectionEpoch((value) => value + 1); }} /> + {isInitialized ? : null} diff --git a/packages/ui/src/apps/MobileSessionSwitcher.tsx b/packages/ui/src/apps/MobileSessionSwitcher.tsx index c05b9ecf..fdaf436f 100644 --- a/packages/ui/src/apps/MobileSessionSwitcher.tsx +++ b/packages/ui/src/apps/MobileSessionSwitcher.tsx @@ -3,7 +3,7 @@ import type { Session } from '@opencode-ai/sdk/v2'; import { SessionActivityDuration } from '@/components/session/SessionActivityDuration'; import { formatSessionCompactDateLabel } from '@/components/session/sidebar/utils'; -import { useSwitcherItems } from '@/components/session/sidebar/hooks/useSwitcherItems'; +import { useSwitcherItems } from '@/components/session/sidebar/shell/useSwitcherItems'; import { useTabletLayout } from '@/lib/device'; import { useI18n } from '@/lib/i18n'; import { cn } from '@/lib/utils'; diff --git a/packages/ui/src/apps/MobileSessionsSheet.tsx b/packages/ui/src/apps/MobileSessionsSheet.tsx index 43a28f32..a64d94f0 100644 --- a/packages/ui/src/apps/MobileSessionsSheet.tsx +++ b/packages/ui/src/apps/MobileSessionsSheet.tsx @@ -43,6 +43,7 @@ import { useThemeSystem } from '@/contexts/useThemeSystem'; import { getProjectLabel, normalizePath } from './mobilePaths'; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import { useI18n } from '@/lib/i18n'; +import { matchesRankQuery, rankByQuery } from '@/lib/search/fuzzySearch'; import { PROJECT_COLOR_MAP, PROJECT_ICON_MAP, ProjectIconImage } from '@/lib/projectMeta'; import { cn } from '@/lib/utils'; import { @@ -188,11 +189,8 @@ const findExactProjectMatch = (projects: ProjectMeta[], directory: string): Proj return projects.find((project) => projectMatchesExactDirectory(project, normalizedDirectory)) ?? null; }; -const sessionMatchesQuery = (session: Session, projectLabel: string, query: string): boolean => { - if (!query) return true; - const haystack = `${session.title ?? ''} ${session.id} ${getSessionDirectory(session)} ${projectLabel}`.toLowerCase(); - return haystack.includes(query); -}; +const sessionMatchesQuery = (session: Session, projectLabel: string, query: string): boolean => + matchesRankQuery([session.title, session.id, getSessionDirectory(session), projectLabel], query); const MobileProjectIcon: React.FC<{ project: Pick; @@ -1355,7 +1353,7 @@ export const MobileSessionsSheet: React.FC = ({ open, const filteredNodes = React.useMemo(() => { if (!normalizedQuery) return projectNodes; return projectNodes.filter((node) => { - if (`${node.project.label} ${node.project.path}`.toLowerCase().includes(normalizedQuery)) return true; + if (matchesRankQuery([node.project.label, node.project.path], normalizedQuery)) return true; return node.buckets.some((bucket) => bucket.sessions.some((session) => sessionMatchesQuery(session, node.project.label, normalizedQuery)), ); @@ -1385,8 +1383,7 @@ export const MobileSessionsSheet: React.FC = ({ open, const searchProjectMatches = React.useMemo(() => { if (!normalizedQuery) return [] as Array; - return projectsMeta - .filter((project) => `${project.label} ${project.path}`.toLowerCase().includes(normalizedQuery)) + return rankByQuery(projectsMeta, normalizedQuery, (project) => [project.label, project.path]) .map((project) => ({ ...project, sessionCount: sessions.filter((session) => { diff --git a/packages/ui/src/apps/MobileWorkspaceDrawer.tsx b/packages/ui/src/apps/MobileWorkspaceDrawer.tsx index f0847aa1..40a3649a 100644 --- a/packages/ui/src/apps/MobileWorkspaceDrawer.tsx +++ b/packages/ui/src/apps/MobileWorkspaceDrawer.tsx @@ -9,6 +9,7 @@ import { ErrorBoundary } from '@/components/ui/ErrorBoundary'; import { SortableTabsStrip, type SortableTabsStripItem } from '@/components/ui/sortable-tabs-strip'; import { TerminalView } from '@/components/views/TerminalView'; import { useI18n } from '@/lib/i18n'; +import type { ProjectRef } from '@/lib/projectContextApi'; import { cn } from '@/lib/utils'; import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { useMcpConfigStore } from '@/stores/useMcpConfigStore'; @@ -105,7 +106,7 @@ export const MobileWorkspaceDrawer: React.FC<{ /** When set, the Changes tab opens directly into the per-file diff. */ pendingChangesDiff: { path: string; staged: boolean } | null; /** Notes tab: opens a plan fullscreen (layered above the drawer). */ - onOpenPlan: (plan: { id: string; title: string }) => void; + onOpenPlan: (plan: { id: string; title: string; projectRef: ProjectRef }) => void; /** MCP tab: jump to the MCP settings page pre-seeded with a new server draft. */ onOpenMcpSettings: () => void; variant?: 'drawer' | 'panel'; diff --git a/packages/ui/src/apps/VSCodeApp.tsx b/packages/ui/src/apps/VSCodeApp.tsx index 47b8086c..737a0239 100644 --- a/packages/ui/src/apps/VSCodeApp.tsx +++ b/packages/ui/src/apps/VSCodeApp.tsx @@ -8,8 +8,10 @@ import { Toaster } from '@/components/ui/sonner'; import { ConfigUpdateOverlay } from '@/components/ui/ConfigUpdateOverlay'; import { ErrorBoundary } from '@/components/ui/ErrorBoundary'; import { OpenCodeUpdateToast } from '@/components/update/OpenCodeUpdateToast'; +import { AppLinkConfirmDialog } from '@/components/chat/AppLinkConfirmDialog'; import { VSCodeLayout } from '@/components/layout/VSCodeLayout'; import { usePushVisibilityBeacon } from '@/hooks/usePushVisibilityBeacon'; +import { useGlobalSessionsPolling } from '@/hooks/useGlobalSessionsPolling'; import { useRouter } from '@/hooks/useRouter'; import { useWindowTitle } from '@/hooks/useWindowTitle'; import { opencodeClient } from '@/lib/opencode/client'; @@ -56,6 +58,7 @@ export function VSCodeApp({ apis }: VSCodeAppProps) { usePushVisibilityBeacon({ enabled: true }); useWindowTitle(); useRouter(); + useGlobalSessionsPolling(panelType !== 'agentManager'); React.useEffect(() => { document.documentElement.classList.toggle('wide-chat-layout', wideChatLayoutEnabled); @@ -108,6 +111,7 @@ export function VSCodeApp({ apis }: VSCodeAppProps) {
+
@@ -127,6 +131,7 @@ export function VSCodeApp({ apis }: VSCodeAppProps) {
+ diff --git a/packages/ui/src/apps/runtimeEndpointReset.ts b/packages/ui/src/apps/runtimeEndpointReset.ts index 39c325fd..cd1f33ce 100644 --- a/packages/ui/src/apps/runtimeEndpointReset.ts +++ b/packages/ui/src/apps/runtimeEndpointReset.ts @@ -6,7 +6,6 @@ import { useProjectsStore } from '@/stores/useProjectsStore'; import { useProjectContextStore } from '@/stores/useProjectContextStore'; import { useGlobalSessionsStore } from '@/stores/useGlobalSessionsStore'; import { useAutoReviewStore } from '@/stores/useAutoReviewStore'; -import { useUIStore } from '@/stores/useUIStore'; import { usePermissionStore } from '@/stores/permissionStore'; import { useFileSearchStore } from '@/stores/useFileSearchStore'; import { useGitStore } from '@/stores/useGitStore'; @@ -16,7 +15,7 @@ import { useFilesViewTabsStore } from '@/stores/useFilesViewTabsStore'; import { useTerminalStore } from '@/stores/useTerminalStore'; import { useSessionUIStore } from '@/sync/session-ui-store'; import { resetStreamingState } from '@/sync/streaming'; -import { useGlobalSessionStatusStore } from '@/sync/global-session-status'; +import { replaceGlobalSessionStatusById } from '@/sync/global-session-status'; import { resetSessionOrdering } from '@/sync/session-ordering'; import { resetSessionActivityTiming } from '@/sync/session-activity-timing'; import { syncDesktopSettings } from '@/lib/persistence'; @@ -37,7 +36,6 @@ export const reconnectAppForTransportSwitch = (): void => { export const resetAppForRuntimeEndpointChange = (detail: RuntimeEndpointChangedDetail): void => { useSessionUIStore.getState().prepareForRuntimeSwitch(detail.previousRuntimeKey); - useUIStore.getState().prepareForRuntimeSwitch(detail.previousRuntimeKey); if (detail.previousRuntimeKey) { useAutoReviewStore.getState().stopRunningRunsForRuntime(detail.previousRuntimeKey); } @@ -59,7 +57,7 @@ export const resetAppForRuntimeEndpointChange = (detail: RuntimeEndpointChangedD // Cross-project session list (mobile sessions sheet & co) belongs to the // previous instance — drop it so stale sessions can't linger after a switch. useGlobalSessionsStore.getState().resetForRuntimeSwitch(); - useGlobalSessionStatusStore.setState({ statusById: new Map() }); + replaceGlobalSessionStatusById(new Map()); resetSessionOrdering(); // Turn timings belong to the previous instance's sessions, and the reset also // restarts the resume window so the switch is treated as a fresh load. @@ -71,7 +69,6 @@ export const resetAppForRuntimeEndpointChange = (detail: RuntimeEndpointChangedD useSessionFoldersStore.getState().resetForRuntimeSwitch(detail.runtimeKey); useFilesViewTabsStore.getState().resetForRuntimeSwitch(detail.runtimeKey); useSessionUIStore.getState().restoreForRuntimeSwitch(detail.runtimeKey); - useUIStore.getState().restoreForRuntimeSwitch(detail.runtimeKey); resetStreamingState(); queueMicrotask(() => void syncDesktopSettings()); }; diff --git a/packages/ui/src/components/auth/AuthExpiredBanner.tsx b/packages/ui/src/components/auth/AuthExpiredBanner.tsx new file mode 100644 index 00000000..986b7484 --- /dev/null +++ b/packages/ui/src/components/auth/AuthExpiredBanner.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { Button } from '@/components/ui/button'; +import { Icon } from '@/components/icon/Icon'; +import { useI18n } from '@/lib/i18n'; +import { useAuthSessionStore } from '@/lib/runtime-auth-expiry'; + +/** + * Non-blocking notice that the OpenChamber session expired mid-work. It never + * takes the screen on its own: work stays visible and interactive, and only + * the explicit "Log in" click hands control to the session gate's full login + * flow (password, passkey, desktop shell — all already there). + */ +export const AuthExpiredBanner: React.FC = () => { + const { t } = useI18n(); + const authState = useAuthSessionStore((store) => store.state); + const markReauthenticating = useAuthSessionStore((store) => store.markReauthenticating); + + if (authState !== 'expired') { + return null; + } + + return ( + // Below the header on purpose: the header row can be a window-drag region + // on desktop, where nothing under the cursor is clickable. +
+
+ + {t('sessionAuth.expired.banner')} + +
+
+ ); +}; diff --git a/packages/ui/src/components/auth/SessionAuthGate.tsx b/packages/ui/src/components/auth/SessionAuthGate.tsx index 553804cc..702d879b 100644 --- a/packages/ui/src/components/auth/SessionAuthGate.tsx +++ b/packages/ui/src/components/auth/SessionAuthGate.tsx @@ -12,6 +12,8 @@ import { OpenChamberLogo } from '@/components/ui/OpenChamberLogo'; import { Icon } from "@/components/icon/Icon"; import { useI18n } from '@/lib/i18n'; import { runtimeFetch } from '@/lib/runtime-fetch'; +import { installAuthSessionFocusWatch, useAuthSessionStore } from '@/lib/runtime-auth-expiry'; +import { AuthExpiredBanner } from './AuthExpiredBanner'; import { getRuntimeExtraHeadersSync } from '@/lib/runtime-auth'; import { getRuntimeApiBaseUrl, getRuntimeKey, subscribeRuntimeEndpointChanged, switchRuntimeEndpoint } from '@/lib/runtime-switch'; import { desktopHostsGet, desktopHostsSet, getDesktopHostApiUrl, normalizeHostUrl } from '@/lib/desktopHosts'; @@ -351,6 +353,7 @@ export const SessionAuthGate: React.FC = ({ const [activePasskeyAction, setActivePasskeyAction] = React.useState<'auth' | 'register' | null>(null); const passwordInputRef = React.useRef(null); const hasResyncedRef = React.useRef(skipAuth); + const hasBootstrapResyncedRef = React.useRef(skipAuth); React.useEffect(() => { if (typeof window === 'undefined') { @@ -557,6 +560,27 @@ export const SessionAuthGate: React.FC = ({ } }, [skipAuth, state]); + // Mid-session expiry: the banner asks for a re-login by flipping the shared + // auth store to 'reauthenticating'; the gate answers with its own status + // check, which lands in the full 'locked' flow on a genuine 401. A + // successful login resolves the store back to 'ok'. + const authSessionState = useAuthSessionStore((store) => store.state); + React.useEffect(() => { + if (!skipAuth) installAuthSessionFocusWatch(); + }, [skipAuth]); + React.useEffect(() => { + if (skipAuth) return; + if (authSessionState === 'reauthenticating') { + void checkStatusRef.current?.(); + } + }, [authSessionState, skipAuth]); + React.useEffect(() => { + if (skipAuth) return; + if (state === 'authenticated' && useAuthSessionStore.getState().state !== 'ok') { + useAuthSessionStore.getState().markAuthenticated(); + } + }, [skipAuth, state]); + React.useEffect(() => { if (state === 'locked' && passwordInputRef.current) { passwordInputRef.current.focus(); @@ -570,10 +594,18 @@ export const SessionAuthGate: React.FC = ({ } if (state === 'authenticated' && !hasResyncedRef.current) { hasResyncedRef.current = true; + // First authentication of this page load is bootstrap: adopt the + // persisted workspace pointers. A re-login after mid-session expiry is + // not — this window already has its own workspace, and the shared + // settings document may carry another window's pointers. + const isBootstrapResync = !hasBootstrapResyncedRef.current; + hasBootstrapResyncedRef.current = true; void (async () => { await initializeAppearancePreferences(); - await syncDesktopSettings(); - await applyPersistedDirectoryPreferences(); + await syncDesktopSettings({ adoptWorkspace: isBootstrapResync }); + if (isBootstrapResync) { + await applyPersistedDirectoryPreferences(); + } })(); } }, [skipAuth, state]); @@ -983,5 +1015,10 @@ export const SessionAuthGate: React.FC = ({ ); } - return <>{children}; + return ( + <> + {skipAuth ? null : } + {children} + + ); }; diff --git a/packages/ui/src/components/chat/AppLinkConfirmDialog.test.tsx b/packages/ui/src/components/chat/AppLinkConfirmDialog.test.tsx new file mode 100644 index 00000000..ceb2624e --- /dev/null +++ b/packages/ui/src/components/chat/AppLinkConfirmDialog.test.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { beforeEach, describe, expect, mock, test } from 'bun:test'; +import { renderToStaticMarkup } from 'react-dom/server'; + +import { I18nProvider } from '@/lib/i18n'; + +mock.module('@/components/ui/dialog', () => ({ + Dialog: ({ children }: React.PropsWithChildren) => <>{children}, + DialogContent: ({ children }: React.PropsWithChildren) =>
{children}
, + DialogDescription: ({ children }: React.PropsWithChildren) =>

{children}

, + DialogFooter: ({ children }: React.PropsWithChildren) =>
{children}
, + DialogHeader: ({ children }: React.PropsWithChildren) =>
{children}
, + DialogTitle: ({ children }: React.PropsWithChildren) =>

{children}

, +})); + +const { AppLinkConfirmDialog } = await import('./AppLinkConfirmDialog'); +const { + getAppLinkConfirmationSnapshot, + openAppLinkWithConfirmation, + settleAppLinkConfirmation, +} = await import('./appLinkConfirmation'); + +describe('AppLinkConfirmDialog', () => { + beforeEach(() => { + if (getAppLinkConfirmationSnapshot()) { + settleAppLinkConfirmation('cancel'); + } + }); + + test('keeps cancel visible and focused beside both open choices', () => { + void openAppLinkWithConfirmation('obsidian://open?vault=Notebook'); + + const markup = renderToStaticMarkup( + + + , + ); + + expect(markup).toContain('>Cancel'); + expect(markup).toContain('autofocus=""'); + expect(markup).toContain('>Open once'); + expect(markup).toContain('>Trust and open'); + + settleAppLinkConfirmation('cancel'); + }); +}); diff --git a/packages/ui/src/components/chat/AppLinkConfirmDialog.tsx b/packages/ui/src/components/chat/AppLinkConfirmDialog.tsx new file mode 100644 index 00000000..73960a9e --- /dev/null +++ b/packages/ui/src/components/chat/AppLinkConfirmDialog.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; + +import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { useI18n } from '@/lib/i18n'; +import { getUrlScheme } from '@/lib/url'; + +import { + getAppLinkConfirmationSnapshot, + settleAppLinkConfirmation, + subscribeAppLinkConfirmation, + type AppLinkConfirmationChoice, +} from './appLinkConfirmation'; + +/** + * App-level dialog confirming application deep links (obsidian://, vscode://, + * ...) rendered in chat markdown before the OS is asked to open them. + * Dismissing via the close button, Escape, or the backdrop cancels the open. + */ +export const AppLinkConfirmDialog = () => { + const { t } = useI18n(); + const request = React.useSyncExternalStore( + subscribeAppLinkConfirmation, + getAppLinkConfirmationSnapshot, + getAppLinkConfirmationSnapshot, + ); + + const url = request?.url ?? ''; + const scheme = getUrlScheme(url) ?? ''; + + const settle = React.useCallback((choice: AppLinkConfirmationChoice) => { + settleAppLinkConfirmation(choice); + }, []); + + return ( + { + if (!open) { + settle('cancel'); + } + }} + > + + + {t('chat.appLink.confirm.title')} + + {scheme + ? t('chat.appLink.confirm.description', { scheme: `${scheme}://` }) + : t('chat.appLink.confirm.descriptionPlain')} + + +
+ {url} +
+ + + + + +
+
+ ); +}; diff --git a/packages/ui/src/components/chat/ChatContainer.tsx b/packages/ui/src/components/chat/ChatContainer.tsx index a50ea6e7..d3c71b23 100644 --- a/packages/ui/src/components/chat/ChatContainer.tsx +++ b/packages/ui/src/components/chat/ChatContainer.tsx @@ -18,8 +18,9 @@ import { StatusRowContainer } from './StatusRowContainer'; import { SessionRecapNote } from '@/components/chat/SessionRecapSpacer'; import ScrollToBottomButton from './components/ScrollToBottomButton'; import { PromptNavigatorRail } from './components/PromptNavigatorRail'; -import { ScrollShadow } from '@/components/ui/ScrollShadow'; -import { useChatAutoFollow, type AnimationHandlers, type ContentChangeReason } from '@/hooks/useChatAutoFollow'; +import { useAuthSessionStore } from '@/lib/runtime-auth-expiry'; +import { useScrollShadow } from '@/components/ui/useScrollShadow'; +import { useChatTimelineScroll, type TimelineListHandle } from '@/hooks/useChatTimelineScroll'; import { useChatTimelineController } from './hooks/useChatTimelineController'; import { TimelineDialog } from './TimelineDialog'; import { useChatTurnNavigation } from './hooks/useChatTurnNavigation'; @@ -60,11 +61,14 @@ import { findShellCommandForMessage, isUserShellMarkerMessage } from './lib/shel import { resolveChatPromptReadOnly } from './chatPromptReadOnly'; import { getRuntimeKey } from '@/lib/runtime-switch'; import { createFirstVisibleSessionPerformanceTracker } from '@/sync/session-load-performance'; +import { isChatDirectoryPath } from '@/lib/chatDirectories'; const EMPTY_MESSAGES: Array<{ info: Message; parts: Part[] }> = []; const IDLE_SESSION_STATUS = { type: 'idle' as const }; const CHAT_FORCE_SCROLL_BOTTOM_EVENT = 'openchamber:chat-force-scroll-bottom'; const DEFAULT_RETRY_MESSAGE = 'Quota limit reached. Retrying automatically.'; +const DRAFT_EXIT_DURATION_MS = 120; +const COMPOSER_MOVE_DURATION_MS = 180; const CHAT_SCROLL_STYLE = { overflowAnchor: 'none', overscrollBehavior: 'contain', @@ -148,11 +152,15 @@ type ChatViewportProps = { currentSessionKey: string; isDesktopExpandedInput: boolean; isMobile: boolean; - stickyUserHeader: boolean; directory?: string; scrollRef: React.RefObject; messageListRef: React.RefObject; - pendingRevealWork: boolean; + registerList: (list: TimelineListHandle | null) => void; + anchorMessageId: string | null; + onAnchorReady: (messageId: string, anchorIndex: number) => void; + onAnchorSizeChanged: (messageId: string) => void; + onIsAtEndChange: (isAtEnd: boolean) => void; + onTimelineDataChange: () => void; renderedMessages: SessionMessageRecord[]; isLoadingOlder: boolean; sessionIsWorking: boolean; @@ -164,10 +172,11 @@ type ChatViewportProps = { confirmedAt?: number; fallbackTimestamp?: number; } | null; - handleMessageContentChange: (reason?: ContentChangeReason) => void; - getAnimationHandlers: (messageId: string) => AnimationHandlers; - handleHistoryScroll: () => void; scrollToBottom: () => void; + endPinningReleased: boolean; + // One-shot fade for content that replaced the hydration skeleton; + // cached sessions render instantly without it. + revealContent: boolean; sessionQuestions: QuestionRequest[]; sessionPermissions: PermissionRequest[]; isProgrammaticFollowActive: boolean; @@ -187,21 +196,24 @@ const ChatViewport = React.memo(({ currentSessionKey, isDesktopExpandedInput, isMobile, - stickyUserHeader, directory, scrollRef, messageListRef, - pendingRevealWork, + registerList, + anchorMessageId, + onAnchorReady, + onAnchorSizeChanged, + onIsAtEndChange, + onTimelineDataChange, renderedMessages, isLoadingOlder, sessionIsWorking, streamingMessageId, activeStreamingPhase, retryOverlay, - handleMessageContentChange, - getAnimationHandlers, - handleHistoryScroll, scrollToBottom, + endPinningReleased, + revealContent, sessionQuestions, sessionPermissions, isProgrammaticFollowActive, @@ -312,83 +324,96 @@ const ChatViewport = React.memo(({ scrollRef.current?.focus({ preventScroll: true }); }, [scrollRef]); + // Everything that used to sit beside the list inside the scroll container + // now renders as the list's header/footer, so it keeps scrolling with the + // rows exactly as before. + const listHeader = React.useMemo(() => ( + showLoadOlderButton ? ( +
+ +
+ ) : null + ), [isLoadingOlder, onLoadOlder, showLoadOlderButton, t]); + + const listFooter = React.useMemo(() => ( + <> + {(sessionQuestions.length > 0 || sessionPermissions.length > 0) && ( +
+ {sessionQuestions.map((question) => ( + + ))} + {sessionPermissions.map((permission) => ( + + ))} +
+ )} + + + +