Repository navigation
Conversation
…um] (#1) Model: gpt-5 Co-authored-by: Lody Dev <zx@loro.dev>
Model: gpt-5 Co-authored-by: Lody Dev <zx@loro.dev>
* feat(cli): update built-in ACP runtimes [risk:high] (#27) * feat: update built-in ACP runtimes [risk:high] Model: GPT-5.6 * chore: refresh public dependency lock [risk:high] Model: GPT-5.6 * fix: gate provider-neutral goal controls [risk:high] Validate neutral goal snapshots strictly, normalize limited status for mixed-version history, and keep non-Codex goals read-only until the ACP extension control method is routed through Lody. Model: GPT-5.6 * fix: separate goal and prompt activity [risk:high] Treat active goals as persistent session state rather than live turn presence, keep quiescent sessions directly dispatchable, and preserve completion notifications and working indicators around actual prompt activity. Model: GPT-5.6 * fix: preserve goal activity helper API [risk:high] Keep a deprecated isSessionGoalWorking alias for external consumers while documenting that it reports persistent goal state rather than live prompt activity. Model: GPT-5.6 * docs: changelog * fix(components): close empty session side panel [risk:medium] Model: gpt-5 --------- Co-authored-by: Zixuan Chen <me@zxch3n.com> Co-authored-by: Lody Dev <zx@loro.dev>
Model: gpt-5 Co-authored-by: Lody Dev <zx@loro.dev>
Show the sidebar update prompt while the update is still downloading, and open the changelog in an app dialog instead of a hardcoded lody.ai link. Release notes render as sanitized Markdown (raw HTML off); the localized website changelog stays as the no-notes fallback. Model: claude-opus-5[1m]
Match the agreed updater contract: the changelog text and the fallback website link both follow i18n.resolvedLanguage instead of the stored language preference. Model: claude-opus-5[1m]
Bind embedded localized notes to the exact update version and keep the external changelog fallback limited to missing in-app content. Model: GPT-5
Model: gpt-5.6-sol
fix(site-docs): restore release validation
feat: electron update changelog
…um] (#8) Inviting a member to a paid workspace adds a billed seat, and Stripe invoices the prorated difference the moment the invitation is accepted. The dialog only asked for an email, so the payer learned about the charge from the next invoice. Redesign the invite dialog as one shared component for the desktop and mobile account settings, and give it a seat-cost block: how much is charged on acceptance, the per-seat price and that it is prorated, and the recurring total from the next renewal. Free workspaces and gift/enterprise entitlements are not billed per seat and say so (or say nothing) instead of quoting a number. Model: claude-opus-5[1m] Co-authored-by: Lody Dev <zx@loro.dev>
Model: gpt-5 Co-authored-by: Leeeon233 <leeeon233@gmail.com>
Model: gpt-5.6-sol
Model: gpt-5.6-sol
Bumps the catalog entry and the pinned CLI runtime dependency from 1.14.0 to 1.14.1. loro-crdt 1.14.1 makes `importBatch` atomic: the batch now runs in an OpLog rollback scope, so a blob that decodes but fails state validation returns an error with the document still attached instead of trapping in WASM and leaving the document permanently detached. It also drops the quadratic preflight rescan for out-of-order updates (~3.4s -> ~0.45s for 12000 updates). The lockfile also picks up the `apps/cli` -> `tsx@^4.23.5` importer entry that was missing from the public lockfile; no package versions change from it. Model: claude-opus-5[1m] Co-authored-by: Zixuan Chen <me@zxch3n.com>
Bumps the catalog entry and the pinned CLI runtime dependency from 1.14.0 to 1.14.1. loro-crdt 1.14.1 makes `importBatch` atomic: the batch now runs in an OpLog rollback scope, so a blob that decodes but fails state validation returns an error with the document still attached instead of trapping in WASM and leaving the document permanently detached. It also drops the quadratic preflight rescan for out-of-order updates (~3.4s -> ~0.45s for 12000 updates). The lockfile also picks up the `apps/cli` -> `tsx@^4.23.5` importer entry that was missing from the public lockfile; no package versions change from it. Model: claude-opus-5[1m] Co-authored-by: Zixuan Chen <me@zxch3n.com>
Model: gpt-5.6-sol
Keep Grok interaction_mode in the unified run-config selectors instead of consuming it as the legacy standalone mode control.\n\nModel: gpt-5.6-sol
Localize the Lody-owned Grok interaction and permission compatibility selectors while preserving their ACP wire values and upstream labels for other providers.\n\nModel: gpt-5.6-sol
Model: gpt-5.6-sol Co-authored-by: Lody Dev <zx@loro.dev>
* feat(components): gate App Store review prompts [risk:medium] Model: GPT-5 * fix(components): keep App Store review policy shared [risk:medium] Keep eligibility persistence and policy evaluation in the shared prompt hook, while the mobile app exposes only the StoreKit request bridge. Model: GPT-5 * fix(components): harden App Store review prompting [risk:medium] Baseline only after session history sync, exclude canceled and failed turns, and avoid repeated local storage writes during streaming updates. Model: GPT-5 * fix(components): preserve App Store review idle timer [risk:medium] Decouple outcome persistence from the prompt timer so identity-only history updates cannot consume and cancel an eligible turn. Model: GPT-5 --------- Co-authored-by: Leeeon233 <leeeon233@gmail.com>
* fix(cli): keep direct local sessions branchless [risk:medium] Avoid capturing a branch for direct local-project sessions so delayed dispatches do not attempt a dirty-worktree checkout.\n\nModel: gpt-5.6-sol * fix(cli): preserve current branch for legacy local sessions [risk:medium] Treat stored branches on existing direct local sessions as historical metadata when ACP must be initialized again, so dirty worktrees are never checked out. Model: gpt-5.6-sol
Model: gpt-5.6-sol Co-authored-by: Lody Dev <62133302+wibus-wee@users.noreply.github.com>
Model: gpt-5 Co-authored-by: Leeeon233 <leeeon233@gmail.com>
Model: gpt-5.6-sol
refactor: react 19
* docs: add agent role design proposal Model: gpt-5 * feat: resolve Agent Roles in session creation Model: gpt-5 * docs: record Agent Role operation invariant Model: gpt-5 * fix: preserve GitHub context for Role sessions Model: gpt-5 * feat(components): add Agent Role settings and mentions [risk:medium] Agent Role V1, UI side only: authoring, sharing, and mentioning a reusable Session-creation preset. CLI/MCP Session orchestration is a separate task. Shared contract: - `AgentRole`, `AgentRoleInvocationSnapshot`, validators, and the visibility, availability, and work-context rules every surface must reuse. - One `agentRole` row family in the existing workspace Flock document, so sharing is an ordinary update of `visibility` rather than a move between two catalogs. - `agentRoleInvocations` on the Turn input config, plus the `agent_role` message text span. - A Role stores no secret: `isSensitiveAgentRoleConfigOptionKey` is applied on read as well as on write, because a workspace row reaches every member. Settings → Agent Roles: - List, create, edit, duplicate, delete, and a default-off workspace share toggle; unavailable and saved-but-unsynced states name their exact reason. - Machine, agent config, model, reasoning, and run options are generated from the selected agent's published capabilities; nothing is offered for an agent whose capabilities are unknown, and an incompatible saved value is reported rather than replaced. Mentions: - Agent Roles category, `agent_role` range/span/chip, and draft hydration. - Candidates pass visibility, executability, then work context: a GitHub composer may reach any authorized machine, a local one is pinned to its own. - Sending freezes the picked Roles into the Turn as `agentRoleInvocations`, from the same committed ranges the prompt rewrite uses. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(shared): declare Agent Role provenance on SessionMeta [risk:low] The CLI already writes a Session's originating Role, but `SessionMeta` had no such field, so `session create` carried a local intersection type for it. Move the two fields into the shared contract that both sides read: - `SessionMeta.agentRoleId` / `agentRoleRevision`, documented as provenance only — execution, recovery, and retry read the already-frozen dispatch config and Turn input config, never the mutable Role catalog. - Cover `agentRoleInvocations` in `normalizeSessionTurnInputConfig`: the CLI authorizes an `agentRoleId` solely against that field of the driving Turn, so it has to survive the same normalization the Turn does — including dropping a secret-shaped option key out of a stored run config. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(components): simplify Agent Role authoring and mention copy [risk:medium] Review feedback on the Agent Role UI, taken before release so no stored Role has to be migrated. - One label. The mention token is derived from the name (`getAgentRoleMentionSlug`) instead of a second authored "mention name" field, so renaming a Role renames its mention and uniqueness is checked on the token two names would share. The description field is gone with it. - A Role carries an optional emoji, shown before its name in the mention menu and in the settings row. - No "Agent default" entry. Selecting an agent config seeds the agent's own published defaults into the unset run-config fields, so every control shows a concrete value; a stored selection is never overwritten, which is what keeps an incompatible one visible instead of silently replaced. - Duplicating a Role, and the banner for a delete that is durable locally but not yet synced, are both removed: the delete syncs on its own and there was nothing for the user to act on. - The message copy button collapses an `agent_role` span back to `@Name`. Its rewritten region is an instruction addressed to this agent and means nothing pasted elsewhere. Edit-and-resend still reads the expanded text, because a token with no committed range would reach the agent as a word. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(components): pick an Agent Role emoji with a real picker [risk:low] Typing an emoji into a text field means knowing the OS shortcut, and an empty slot makes "no emoji" read as an unfinished form. So the field is now a filled button that opens a picker, the way a Notion page icon works. - Adds the shadcn `frimousse` emoji picker (`npx shadcn@latest add https://frimousse.liveblocks.io/r/emoji-picker`) as `ui/emoji-picker.tsx`, minus the `"use client"` banner this repo has no boundary for, with its two visible strings on i18n and its locale following the product language rather than the host OS. - The picker is a Popover portalled into the settings dialog content, like `option-selector.tsx`: a body-level portal sits outside the dialog's scroll lock, and this popover's whole content is a scrolling list. - `DEFAULT_AGENT_ROLE_EMOJI` is the shared fallback, so the button, the settings row, and the mention candidate all show one glyph for a Role whose owner never picked one, and choosing is a change rather than a prerequisite. `pnpm-lock.yaml` and the attribution artifacts carry only the frimousse entries: regenerating them wholesale in this worktree also swept in unrelated resolution drift from the acp-extension submodules. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(components): bundle the emoji dataset with the app [risk:low] `frimousse` fetches `${emojibaseUrl}/${locale}/{data,messages}.json` from a public CDN by default, so on a desktop or mobile app with no network the picker spins forever. Lody is local-first; its emoji list should not need the internet. - `vite-emojibase-assets.ts` emits `emojibase/{en,zh}/{data,messages}.json` into the host build and serves the same paths in dev. It has to be a plugin rather than a `?url` import: the library builds those paths at runtime, and a hashed asset name cannot satisfy a URL contract. - The picker reads `getBundledEmojibaseUrl()`, resolved against `document.baseURI` because the Electron renderer and the mobile shell both load from a scheme where `/…` is not the app root. - `apps/electron` registers the plugin; a private mobile/web host adds the same one line. Locales are limited to the product's own languages — the dataset is ~750 KB each. Verified against a real `electron-vite build --mode oss`: the four files land in `out/renderer/emojibase/` and `index.html` keeps its relative base. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(components): serve the bundled emoji data from the app root [risk:low] The picker failed to open in dev with `Unexpected token '<', "<!doctype "…`: the dataset URL was resolved against `document.baseURI`, and the router uses browser history over http, so a deep route asked for `…/settings/emojibase/en/data.json`. The dev server answered with its SPA fallback and the picker parsed HTML as JSON. - Anchor the URL on the Vite base instead. A relative base (the packaged Electron renderer) still resolves against the document, which is right there: it loads `index.html` over `file:` and switches to hash history, so the document path stays the entry file. - Skip `emitFile` while serving; it is unsupported in that mode and logged a "not vite-compatible" warning on every reload. Settings, same pass: - The Role's emoji and name are shown as themselves — no "Identity" card, no "mentioned as @…" hint under them. - "Prompt prefix" is now the "Default instruction" and sits above where the Role runs, since it is part of what the Role is rather than of its binding. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(components): group Agent Roles by machine and show their marks [risk:low] - The list groups by machine, with the machine on a pill above its group. A Role binds one machine exactly, so it is what the list is grouped BY rather than a fact repeated on every row. - A row no longer prints `@token`: it is derived from the very name beside it, so showing both said one thing twice. - The second line is now what the Role will RUN — the agent's icon, then model, reasoning, and whatever else it pins (`buildAgentRoleRunConfigSummary`). A boolean that is off is dropped; an option left at the agent's own default is not, because a Role pins concrete values and hiding one would make two different Roles read the same. - The committed mention in the composer carries the Role's own emoji instead of the generic glyph. The composer wraps the caller's chip resolver to do it: a range carries only the Role id, and only the composer holds the live catalog. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(components): show an Agent Role by its own emoji everywhere [risk:low] The Role's mark now stands in for the generic category glyph on all three surfaces, so one Role looks like one object from the menu to the sent message. - Menu rows carry `MentionCandidate.iconEmoji` INSTEAD of the category glyph: the category header above already says these are Agent Roles, so a second generic icon only crowded out the mark. The detail pane has no icon slot, so there the mark stays in the title. - Sent messages read `MessageTextSpan.mark`, frozen with the span at send time rather than resolved when the bubble renders. A sent message shows the Role as it was, so renaming or re-marking it later cannot repaint history, and painting a bubble never waits on the mutable catalog. `sanitizeMessageTextSpans` gates it like every other span field: one short glyph or nothing. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(shared): declare the span mark on the strict send-path schema [risk:medium] Sending a message that mentioned an Agent Role answered with "Please enter something to discuss" and dropped the turn. `MessageTextSpanSchema` is `.strict()`, so the `mark` I added to the span in the previous commit made `SessionInputBlocksSchema` reject the whole block list. `normalizeSessionInputBlocks` then fell back to its empty prompt, and the composer reported an empty message — one unknown key on one span, and the whole send is refused. The bar now lives in one place (`MAX_MESSAGE_TEXT_SPAN_MARK_LENGTH`) and both readers use it. The regression test walks a marked span through the strict schema AND `normalizeSessionInputBlocks`, which is the pair that has to agree; it fails against the previous commit. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(components): close the Agent Role editor on durability, not on upload [risk:medium] Creating a Role first reported "another role already uses this name", then a second or two later closed the dialog and showed the Role created. Both halves came from the same cause: the editor awaited the UPLOAD, not the write. The local row lands in milliseconds and the catalog room publishes it immediately, so the still-open create form found the Role it had just written and failed its own name check — while the dialog waited out the round trip. - `upsert` now resolves on durability and hands the upload back as `uploaded`. The editor closes as soon as the row is durable; a failed upload is reported afterwards as a toast — "saved on this device, not yet synced" — never as a failed save, and never rolled back. - `resolveAgentRoleNameCheckExemption` exempts the id an in-flight save claimed, the same way an edit's own Role has always been exempt. Claimed BEFORE the write, so the row cannot appear while the form still counts it as a stranger. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(components): stop reporting catalog uploads in settings [risk:medium] Saving an Agent Role or an MCP server flashed "saved on this device but not yet synced" and then cleared itself seconds later. The message was neither actionable nor dismissible, and it described a write that had already succeeded. Every catalog mutation now resolves on the local Flock write and lets the upload run on its own: - No surface waits for the upload, so a dialog closes the moment the row exists rather than after a round trip. - No surface reports it. The row is durable, and the joined catalog room carries the document when a one-shot upload cannot; a failed upload is logged. - What stays forbidden is the opposite — reporting a durable write as failed, or rolling one back, because the upload did not go through. The CLI still reports its own sync result to the terminal. The test hangs the upload and asserts the write resolves anyway, which is the property the editors depend on. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(components): show the Role instruction, drop the duplicated offline note [risk:low] - The Settings row no longer repeats "its machine is offline": Roles are grouped by machine and the group's pill carries that status, so the sentence was printed once per row under it. The pill keeps a screen-reader equivalent now that the dot is the whole signal. Reasons about the Role's own binding — a deleted agent config, a machine the user cannot reach — still appear. - The mention detail pane shows the default instruction itself instead of a "Prompt" badge saying one exists: what it SAYS is what decides whether this is the Role you meant. It renders through a new neutral `MentionCandidateDetail.body` field, in its own capped scroll area above the rows, so an instruction of any length cannot push machine/agent/model off the pane. - The Role editor states that memory is not supported: a Role reads like a standing assistant, so its owner has to be told that every session it creates starts fresh. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(components): drop the memory icon, mark the instruction optional [risk:low] - The memory notice is text only. Its glyph decorated a sentence that already reads as a note, next to a share row that carries no icon either. - The instruction section says "(optional)": every other section in the editor is required to make a Role run, and this one is the exception. The textarea's accessible name stays the plain field name. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: reuse shared rules across the Agent Role surfaces [risk:medium] Quality pass over the Agent Role work. No intended behaviour change. - The MCP server re-declared what an invocation snapshot is: a local zod schema plus a local type shadowing the exported one. The Turn's input config already arrives through `normalizeSessionTurnInputConfig`, so the field is typed, validated, deduped on `roleId`, and stripped of secret-shaped option keys. Reading it directly drops the schema, the `as Record<string, unknown>` cast, and an ambiguity branch upstream deduplication made unreachable. The local copy was `.passthrough()` — the side that ENFORCES the rule was the one not applying it. - `AGENT_ROLE_MANUAL_OVERRIDE_FIELDS` is derived from `SessionRunConfigInputShape` instead of hand-copied. A field added to the shape is now a field the mutual-exclusion guard rejects, rather than one the schema accepts and `resolveMcpSessionCreate` silently drops. - Ranking and slug hydration were duplicated verbatim between the session and Agent Role mention sources, including the rule that a token a file path claims is left alone. Both now share `mention-rank.ts` and `hydrateSlugMentionsFromText`, so that precedence has one definition. - `Section`/`Field` were a third copy of the settings form grammar; they move to `settings/form-primitives.tsx`. The Role editor's option-value check reuses `isConfigOptionValueValid`, and the copy path's splice loop reuses `applyTextRewrites` — whose whole reason to exist is not chaining offset math. - The Roles editor derives the defaults-filled value at render instead of writing it back through an effect whose dep array contained the state it set, and an `add` carries its id from the moment the form opens. That deletes `savingRoleId` and `resolveAgentRoleNameCheckExemption`: the window they covered — the local write landing while the dialog is open — no longer exists. - Availability selects the loaded-machine KEY SET out of the machine-flock atom rather than subscribing to the whole row map, which changed identity on every rate-limit or launch-config row of every machine and rebuilt the entire Role pipeline each time. Agent config names are indexed once instead of scanned per Role, the catalog room reuses a family's array when its rows did not move so a Role edit stops invalidating the MCP memos, and the direct create path only walks the transcript when a Role is actually involved. - `frimousse` moves behind a lazy boundary: Settings is reachable from the app shell, so a static import parsed the whole picker library at renderer startup for a popover that opens inside one editor. - Dead on arrival, now removed: `getAgentRoleFormMentionSlug`, `isValidAgentRoleMentionSlug`, `getWorkspaceAgentRoleCatalog`, `WorkspaceFlockRowFamily`, and the availability resolver's unread `context`. The emojibase asset directory and bundled-locale list stop being declared in both the lib and the Vite plugin, kept in step by a comment and a test. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Leeeon233 <leeeon233@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Archive, restore, and delete the complete session lifecycle subtree across child tabs and MCP-opened sessions. Preserve the opened-by hierarchy in the archive list while keeping child tabs inside their owner session. Model: gpt-5.6-sol Co-authored-by: Leeeon233 <leeeon233@gmail.com>
Model: gpt-5 Co-authored-by: Leeeon233 <leeeon233@gmail.com>
Route same-machine Electron file previews through local IPC only and allow readonly previews outside the workspace. Model: gpt-5.6
* fix(cli): keep direct local sessions branchless [risk:medium] Avoid capturing a branch for direct local-project sessions so delayed dispatches do not attempt a dirty-worktree checkout.\n\nModel: gpt-5.6-sol * fix(cli): preserve current branch for legacy local sessions [risk:medium] Treat stored branches on existing direct local sessions as historical metadata when ACP must be initialized again, so dirty worktrees are never checked out. Model: gpt-5.6-sol * fix(cli): preserve explicit local session branch [risk:medium] Treat a direct local branch as historical only after ACP session creation proves the session has executed, preserving explicit branches during fresh creation. Model: gpt-5.6-sol
This was referenced Sep 9, 2026
ladydd
added a commit
to ladydd/Lody
that referenced
this pull request
Sep 11, 2026
Pin acp-extension-codex to a67f231 (LodyAI/acp-extension-codex#38, rebased onto current adapter main / 5f0aab0) so compaction waiters reject when the Codex process exits. Other submodules stay on current main. Depends on adapter LodyAI#38. Do not ship a desktop release until that merge. Closes LodyAI#550
ladydd
added a commit
to ladydd/Lody
that referenced
this pull request
Sep 11, 2026
Agent Note for pinning acp-extension-codex a67f231 (adapter LodyAI#38) so /compact does not hang when the process exits after start.
This was referenced Sep 11, 2026
ladydd
added a commit
to ladydd/Lody
that referenced
this pull request
Sep 12, 2026
Pin acp-extension-codex to a67f231 (LodyAI/acp-extension-codex#38, rebased onto current adapter main / 5f0aab0) so compaction waiters reject when the Codex process exits. Other submodules stay on current main. Depends on adapter LodyAI#38. Do not ship a desktop release until that merge. Closes LodyAI#550
ladydd
added a commit
to ladydd/Lody
that referenced
this pull request
Sep 12, 2026
Agent Note for pinning acp-extension-codex a67f231 (adapter LodyAI#38) so /compact does not hang when the process exits after start.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.