-
Notifications
You must be signed in to change notification settings - Fork 2
Keep new agent chats as drafts until the first message is sent #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sambitcreate
wants to merge
2
commits into
main
Choose a base branch
from
feature/draft-agent-chats
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Draft agent chats | ||
|
|
||
| Status: Implemented; pull-request CI and merge pending. | ||
|
|
||
| Ordinary desktop workspace chats stay transient until the first user message is durably saved. Opening New Agent, entering an empty workspace, or opening a fresh worktree must not install an empty chat or sidebar history entry. Leaving an unsent draft discards it. A committed message remains saved even if generation fails. | ||
|
|
||
| ## Implementation | ||
|
|
||
| - Use an explicit renderer draft record with a stable future chat ID. Keep it outside persisted chat lists and query caches. Model, workspace, title, Computer Use, attachments and composer options remain local until Send. | ||
| - Freeze first-send input and settings while saving. Commit a complete nonempty chat through an additive desktop IPC, preserving existing workspace authority, attachment quotas, skill leases, turn admission and durability recovery. | ||
| - Store a private first-message receipt. Matching retries confirm the same message; mismatched identifier reuse fails. Reconciliation prevents a lost or uncertain receipt from duplicating a chat. | ||
| - Promote the draft in place using the same chat/component identity. Publish one sidebar entry and start generation once. Navigation wins over late completion; release a pending turn if its conversation is no longer open. | ||
| - Preserve existing Bot, Assistant, scheduled, Telegram and runtime child-agent lifecycles. Remote HTTP contracts remain unchanged; inspect native consumers and validate applicable shared contracts. | ||
|
|
||
| ## Authorized legacy migration | ||
|
|
||
| The owner authorized deleting existing empty chats in this migration. At startup, after recovery and before clients can write, snapshot readable zero-message chat identities and fingerprints before consulting workspace, schedule, artifact, or private-history eligibility stores. Only eligible ordinary chats in registered workspaces are deleted. Exclude Bot/Assistant/Telegram conversations, scheduled task and run references, unreadable records, and chats with staged artifacts or private execution history. A message object counts as history even if its text is empty. Validated header-only Pi journals created by the old Todo snapshot read, including completed empty v3-to-v4 promotions with matching backup/receipt and migration-only scaffolding, do not count as history; body records and uncertain journal state remain protected. | ||
|
|
||
| Persist the exact candidate set and per-chat fingerprints before deletion, recheck each candidate and fingerprint, and remove via the existing cross-store deletion service. Checkpoint progress and mark completion once. Interrupted migration resumes only original candidates; subsequent launches never sweep newer empty chats created through unchanged remote APIs. Unknown/corrupt migration state fails closed. If the initial index enumeration or snapshot save fails, startup stops before admitting writers. Unreadable payloads and uncertain eligibility preserve the affected candidates and allow checkpointed completion; they never cause a later resweep. The final cross-store deletion assertion checks only the frozen chat fingerprint and zero-message state, without reopening already-deleted private stores. | ||
|
|
||
| ## Verification gates | ||
|
|
||
| - Draft abandonment through all creation paths creates no chat payload or sidebar row. | ||
| - First send failure preserves composer payload; successful promotion persists one message and runs once. | ||
| - Duplicate submission, changed-payload retry, navigation during saving, workspace removal, document invalidation and post-install storage failure are covered. | ||
| - Migration deletes empty payload/index entries, preserves real history and special conversations, resumes safely, and runs once across restarts. | ||
| - Focused unit/contract tests, Electron draft/migration acceptance, TypeScript, lint and PR exact-head CI must pass before delivery. | ||
|
|
||
| No new setup capability or onboarding asset is introduced; this corrects the existing new-chat lifecycle. | ||
|
|
||
| ## Local verification | ||
|
|
||
| The Electron acceptance tests cover: draft abandonment and in-place promotion, one-time migration across restart, definite first-save failure and retry, a delayed receipt after navigation, and corruption-safe migration completion. Shared Remote tests and a generic iOS build-for-testing also pass. Native iOS devices were offline and the local Android toolchain was unavailable; no physical-device acceptance is claimed. The PR checks are the source of truth for final exact-head CI. |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import { parseChatAppend, type ParsedChatAppend } from "./chat-append-params.js"; | ||
| import { parseChatCreate } from "./chat-create-params.js"; | ||
| import { ASSISTANT_WORKSPACE_ID } from "../../renderer/shared/assistant.js"; | ||
|
|
||
| const KEYS = new Set([ | ||
| "draftId", "workspaceId", "providerId", "model", "computerUseEnabled", "title", | ||
| "turnId", "message", "skillInvocation", | ||
| ]); | ||
|
|
||
| export interface ParsedChatFirstMessage extends ParsedChatAppend { | ||
| title?: string; | ||
| workspaceId: string; | ||
| computerUseEnabled: boolean; | ||
| } | ||
|
|
||
| /** Project the complete untrusted envelope before retaining it across awaits. */ | ||
| export function parseChatFirstMessage(input: unknown): ParsedChatFirstMessage { | ||
| if (!input || typeof input !== "object" || Array.isArray(input)) { | ||
| throw new Error("Invalid first-message request."); | ||
| } | ||
| const record = input as Record<string, unknown>; | ||
| for (const key in record) { | ||
| if (!Object.prototype.hasOwnProperty.call(record, key)) continue; | ||
| if (!KEYS.has(key)) throw new Error("Invalid first-message fields."); | ||
| } | ||
| // A dedicated UUID namespace prevents a draft from impersonating bot, | ||
| // Assistant, scheduled, or imported chat identities. | ||
| if (typeof record.draftId !== "string" || | ||
| !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(record.draftId)) { | ||
| throw new Error("Invalid draft identifier."); | ||
| } | ||
| const create = parseChatCreate({ | ||
| title: record.title, | ||
| workspaceId: record.workspaceId, | ||
| providerId: record.providerId, | ||
| model: record.model, | ||
| }); | ||
| if (create.workspaceId === ASSISTANT_WORKSPACE_ID) { | ||
| throw new Error("Assistant chats require the Assistant chat creation path."); | ||
| } | ||
| if (record.computerUseEnabled !== undefined && typeof record.computerUseEnabled !== "boolean") { | ||
| throw new Error("Invalid Computer Use setting."); | ||
| } | ||
| const append = parseChatAppend(record.draftId, record.message, { | ||
| turnId: record.turnId, | ||
| providerId: create.providerId, | ||
| model: create.model, | ||
| autoTitle: true, | ||
| skillInvocation: record.skillInvocation, | ||
| }); | ||
| if (!append.content.trim() && !append.attachments?.length) { | ||
| throw new Error("Add a message or attachment before sending."); | ||
| } | ||
| return { | ||
| ...append, | ||
| title: create.title, | ||
| workspaceId: create.workspaceId, | ||
| computerUseEnabled: record.computerUseEnabled === true, | ||
| retainedBytes: append.retainedBytes + Buffer.byteLength(create.workspaceId, "utf8") + Buffer.byteLength(create.title ?? "", "utf8") + 64, | ||
| }; | ||
| } |
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
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
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.