Commit Graph
88 Commits
Author SHA1 Message Date
5353241743 fix(chat): warn before regenerate, resend or edit deletes later messages; offer fork (#6225)
* fix(chat): warn before "regenerate" permanently deletes later messages

Both "regenerate" entry points — the per-message footer's ↻ "Regenerate
from here" button, and the vision editor's "Regenerate message" button —
call POST /api/session/{id}/truncate, which does a real, permanent SQL
delete of every message after the point clicked, then generates a new
reply. Clicking either on anything but the very last exchange did this with
zero warning: the button's own label doesn't say it also erases the rest
of the conversation, and neither button visually signals it's destructive
differently from any other message-footer action.

Caught live: a mis-click destroyed a real conversation with no way to
recover it (no backup covered the affected window). This adds a
confirmation prompt — via the existing uiModule.styledConfirm — whenever
the action would delete more than just the message(s) being regenerated,
stating the exact count of messages that would be lost. Regenerating the
most recent exchange (the everyday case, nothing exists after it) is
unaffected and still proceeds with zero friction.

New test: tests/test_regenerate_truncate_warning_js.py — drives the real
regenerateFrom/resendUserMessage functions under Node with a fake message
list DOM, confirming: the last-exchange case never prompts and truncates
directly; an earlier case prompts with the correct count; cancelling
aborts before any network call; confirming proceeds with the correct
keep_count. Also fixed a pre-existing, unrelated gap this surfaced while
verifying the full suite: test_pr6020_browser_review_regressions.py's
Node harness never stubbed `navigator` at all (sessions.js reads
navigator.platform at module load) — a gap on origin/dev itself, not
caused by this branch's diff, using the same Object.defineProperty fix
already applied on #6019/#6148 for the sibling issue there (Node 21+'s own
getter-only navigator global, not relevant to this specific failure but
the same defensive pattern).

Verified: full pytest suite in-container, 5798 passed / 11 pre-existing
unrelated failures (README/docs/agent-loop baseline) / 0 new.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(chat): cover plain resend after dev's replace-by-default change

dev made plain resend (no options) replace from the clicked turn by
default, with append-only resend opt-in via { append: true } (see
test_resend_message_nondestructive.py). The footer "Resend message"
button calls resendUserMessage(el) with no options, so it now truncates
everything after the clicked turn and goes through this PR's warning.
Replace the old "plain resend never prompts" test, which assumed the
previous append-by-default behavior, with one asserting that plain
resend on an earlier pair prompts with the right count, and one
asserting that explicit append-only resend never truncates or prompts.

Also resolve ui.js/sessions.js through the same cache-busting query
string chat.js imports them with. ES modules cache by full URL, so the
harness's unversioned import of ui.js was a second module instance and
its uiMod.styledConfirm stub never reached the instance chat.js calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(chat): warn before an edit deletes later messages; offer to fork instead

Sending an edited user message truncates the chat at that message, the
same unrecoverable server-side delete as regenerate and resend, but did
it with no prompt. It now goes through the same confirmation, worded for
the action ("Sending this edit will also permanently delete the N
messages after it"). Cancelling leaves the editor open with the draft.

The confirmation for all three actions (regenerate, resend, edit) also
offers "Fork from here": it forks the chat just before the user turn,
switches to the new chat, and replays the action there (the same message,
or the edited text), leaving the original chat untouched. forkFrom now
shares the same fork helper.

Tests drive the real editUserMessage Send handler under Node and cover
the edit prompt (count, cancel, last-message no-prompt) and the fork
choice for each action (fork keep_count, no truncate, replayed text).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(chat): cut and fork a chat by message id, not loaded-page position

Regenerate and edit sent keep_count, the clicked message's position among
the messages loaded in the page. A long chat loads only its newest messages
until the user scrolls up, so that position is smaller than the real one and
the truncate deleted real messages above the clicked one (regenerating the
latest reply in a ~50-message chat removed about a dozen earlier messages).
Resend already sent before_msg_id; regenerate and edit now do the same.

The fork options this PR adds had the same flaw. Fork now accepts
before_msg_id (fork before that message) and through_msg_id (include it),
resolved against the stored messages; keep_count stays as the fallback for a
message that has no id yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(chat): keep a regenerated turn's message id current; stop on a refused cut

Regenerate keeps the original user bubble on screen and resubmits its text
without drawing a new bubble. The resubmitted message is a new DB row, but
the bubble kept the deleted row's id, so the next regenerate's cut was
refused (404) and the page sent the turn anyway, stacking a second reply.

The stream now reports the saved user message's id (user_message_saved),
which the page attaches to the user bubble, or to the bubble a regenerate
kept. Regenerate and edit stop with an error when the server refuses the
cut instead of sending.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(chat): name the buttons in words instead of symbols

CONTRIBUTING asks for no Unicode emoji in UI or code; the test comments
used the regenerate and pencil glyphs to name buttons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(chat): share the fork-and-send step across edit, resend and regenerate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(chat): pass the regenerate fork its attachment ids explicitly

Resend already hands its collected file ids to _forkAndSend; regenerate
relied on the earlier _pendingRegenAttachments assignment. Pass _regenIds
too so both fork paths read the same, and check the hand-off in the
regenerate tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(chat): assert fork choices keep the user message's attachments

Run regenerate and resend's "Fork from here" against a user message with
an uploaded file and check that the send fired in the fork carries its
file id, replacing the source-text check. chat.js exposes a read-only
_pendingRegenAttachmentIds() for this, like cookbook-hwfit.js's test hooks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(chat): preserve attachments across edit and regeneration

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Alexandre Teixeira <general@alexandreteixeira.dev>
2026-11-01 02:47:32 +00:00
Alexandre Teixeira 72ba37af6b merge(dev): integrate reviewed preview UI changes 2026-10-29 01:12:08 +00:00
pewdiepie-archdaemon da8ba69c74 Keep streamed-chat module identity consistently cache-busted 2026-10-26 16:57:01 +00:00
pewdiepie-archdaemon 3246ae5d9c Keep streamed-chat module identity consistently cache-busted 2026-10-26 16:57:01 +00:00
pewdiepie-archdaemon 871c0c0687 Align the shared stream watchdog with the UI review 2026-10-26 16:36:59 +00:00
pewdiepie-archdaemon 76ab72412d Preserve completion gating and handle explicit idle stream status safely 2026-10-26 16:34:56 +00:00
pewdiepie-archdaemon 5c6bb9798b Preserve completion gating and handle explicit idle stream status safely 2026-10-26 16:34:56 +00:00
pewdiepie-archdaemon e461a6b5c5 Keep chat module URLs consistent across imports and precache 2026-10-26 16:29:17 +00:00
pewdiepie-archdaemon ace6be44f3 Merge current dev and refresh PR #6740 from the 7011 integration 2026-10-26 16:21:34 +00:00
Gr33nbl00d b2613b2106 fix(ui): respect iOS safe areas, screen height and landscape layout 2026-10-19 19:43:53 +02:00
Beyrem WeslatiandAlexandre Teixeira 8feb50e894 feat(notes): attach selected Notes/Todos as Agent context (#6495)
* feat: add Notes and Todos as agent chat context

- Send selected Note/Todo IDs from the Notes UI
- Load selected notes with owner scoping in chat routes
- Format note content and checklist items for the model
- Inject selected notes as untrusted chat context
- Add a selected-context indicator beside Shell Access
- Keep selected context associated with the active chat session

* Improved selected notes context handling and discouragin redundant note lookup and permission requests

* Fix selected notes UI when switching chats

* fix(notes): harden selected context lifecycle and validation

---------

Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
2026-10-13 18:57:19 +01:00
pewdiepie-archdaemonandClaude Opus 5.5 8fda203d28 feat(ui): chat frontend, model picker, streaming, admin, a11y
Preview/UI work: chat renderer, streaming/think-trace, model picker
(instant-open/refresh-backoff), admin panel, a11y, gallery landing.

Source: vendor/photocraft (ad1cff44) static files, 3-way merged onto
upstream/dev (a8c147b2). Resolution policy in pr_work/D_conflicts.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:42:33 +00:00
pewdiepie-archdaemon 2e8413a54a Preserve preview harness, editor, email and task improvements
Snapshot current maintainer-preview application changes and regression fixtures for integration into lab. Excludes local runtime data, evaluation outputs and source backups. Focused Python regression selection: 140 passed; full suite not certified.
2026-10-01 01:34:26 +00:00
Alexandre Teixeira 411559913c feat(ui): refine subscription model and usage controls 2026-09-22 13:12:19 +01:00
Alexandre Teixeira ed7ccfd584 feat(provider): support multiple ChatGPT subscriptions with usage 2026-09-22 13:12:19 +01:00
pewdiepie-archdaemon 297ad19248 Harden maintainer-preview harness and review fixes
Unify conversational domain routing, preserve artifact completion evidence, replace provisional tool-round prose with terminal synthesis, and resolve verified maintainer review findings across search, frontend module identity, path policy, configuration, and built-in skill startup.
2026-09-21 06:54:03 +00:00
pewdiepie-archdaemon 218d762427 Consolidate Odysseus agent harness and tool contracts 2026-09-17 10:07:40 +00:00
pewdiepie-archdaemon 84aa9a91de Squash Odysseus development history 2026-09-11 06:04:19 +00:00
RaresKeYandLéo 981652358e fix(agent): allow remaining actions for an approved task (#6113)
* fix(agent): allow remaining actions for an approved task

* fix(agent): make approval continuation control-only

* fix(ci): preserve approval taint and cache-buster contract

* fix(ui): keep tool approvals in current chat

* fix(ui): route tool approvals through chat submit

* test(ui): pin approval submit routing

* fix(agent): complete approval denial flow

* fix(ui): avoid duplicate ask-user close icon

* fix(agent): retain approved tool in continuation set

* revert(ui): keep PR 6113 scoped to approval continuation

* fix(agent): add task and chat approval scopes

* fix(ui): prevent duplicate ask-user close icon

* feat(ui): add ask-user option shortcuts

* fix(compare): route ask-user choices per pane

* fix(agent): keep skill-test approvals to a single action

The chat card now reuses the wire value `approve` to mean chat-session
scope, and `consume()` returned `allow_remaining_actions=True` for it
unconditionally. The skill-test approval route was never updated: it still
sends `approve` meaning "once", and its button still reads "Allow once",
but the grant it got back set `approval_gate_bypassed` for the rest of the
resumed run. That surface wraps the skill body and every transcript byte
as untrusted context, so it is the last place where one click should
ungate everything that follows.

Give `consume()` an explicit `allow_continuation` flag. Callers that own a
resumable chat keep the scope the user picked; callers that do not — the
skill tester, unattended audits — get SINGLE_ACTION and the gate re-arms
behind the sealed action, which is what their label promises.

* fix(ui): cache-bust every module the approval click depends on

chatStream.js, compare/index.js and compare/stream.js all changed
behaviour but kept their old `?v=`, while chat.js and chatRenderer.js were
bumped. A returning browser therefore serves the new chat.js — which now
deliberately leaves the composer empty and clicks the send button — next to
the cached chatStream.js that has no interceptor. With an empty composer
that button sits at `data-mode="newchat"`, so the click opens a new chat
and the approval is dropped.

Bump the three, and version compare/stream.js's chatRenderer import to
match everyone else's so the ask_user keydown listener binds to one module
instance instead of two.

* fix(ui): keep the digit shortcuts off tool approval cards

With an approval card on screen and focus anywhere outside an input, a bare
`1` fired `approve_task` — the widest of the three grants — with no
modifier and no confirmation. That card is the one control whose entire
purpose is deliberate consent after untrusted context influenced the run,
and Deny sits at 3.

Label the card with its kind and skip the shortcut for approvals. Ordinary
ask_user questions keep 1-3.

* fix(compare): restore a pane's ask_user card instead of dropping the choice

renderAskUserCard removes the card as soon as onSubmit accepts, but the
resume loop gave up silently after 10s if the originating stream still owned
the pane. The user saw the click land, the card vanish, and nothing happen,
with no way to get it back.

Re-render the card on that deadline and say why. The reroll case still
returns without sending — that choice belongs to a stream that no longer
exists.

* refactor(chat): drop the unreachable deny branch

`if decision != "deny"` is always true — the deny path returns a
StreamingResponse a few lines above. It reads as if deny still falls
through to the toggle restore.

---------

Co-authored-by: Léo <leograndcontact@gmail.com>
2026-08-19 08:01:34 -06:00
Léo 895bf896e3 refactor(static): load the image editor on first use (#6074)
galleryEditor.js and its js/editor/ graph are 54 modules / 576 KB, and
gallery.js imported them statically. Every page load paid for the whole
image editor even though most sessions never open the Edit tab: 54 of the
173 JS files on a cold load, and 576 KB of the decoded JS, were for a
panel that was never displayed.

Add a small panel-loader registry (static/js/panels.js) that imports a
panel's module on first use and memoises the promise, so a double-click
cannot start two loads and a failed load can still be retried. Convert
the image editor to it, and route the two existing dynamic imports in
chat.js and chatRenderer.js through the same entry so all three call
sites share one module instance instead of two.

closeEditor() and isEditorOpen() stay synchronous: if the module was
never loaded there is no edit session to close and none can be open.

The service worker keeps precaching the editor, in a separate
PANEL_PRECACHE list, so the panel stays available offline even though
index.html no longer loads it. The two lists now serve different
purposes and the header comment says so.
2026-08-16 17:54:24 +01:00
RaresKeY 2b72531eaa fix(agent): harden approval lifecycle 2026-08-15 06:52:44 +00:00
RaresKeY 58b2a4bfa9 fix(agent): close approval continuation gaps 2026-08-15 06:14:37 +00:00
RaresKeY fd50561af6 fix(ui): complete exact approval continuation 2026-08-15 05:51:54 +00:00
RaresKeY 1b09c568d8 fix(agent): authorize exact actions after untrusted context 2026-08-15 05:37:47 +00:00
RaresKeY 05442a9945 fix(agent): close external-context gate gaps 2026-08-15 03:52:23 +00:00
c4369305f0 refactor(model-routing): centralize explicit foreground fallback policy (#6020)
* refactor(model-routing): centralize explicit foreground fallback policy

Make foreground fallback an explicit per-user, availability-only policy shared by streaming Chat, non-stream Chat, and Agent runs.

Preserve strict defaults, owner/model and credential boundaries, pinned Agent routes, and truthful per-round provenance/accounting. Carry provider-reported model identifiers through native streaming adapters, non-stream responses, and caches, and keep legacy default_model_fallbacks as tombstoned raw storage that generic settings APIs and agent tools cannot expose or mutate.

* fix(agent-loop): restore rebase-dropped qwen routing, workspace prompt, and temperature clamp

* fix(model-routing): thread selected endpoint identity, fix cost classification and fallback eligibility

* fix(chat): restore stream helpers and harden run stop lifecycle

* fix(model-routing): let numeric provider codes win over symbolic rate-limit statuses

* fix(agent-loop): apply qwen temperature and notes-tool clamps per fallback candidate

* fix(chat): honor queued stop across resend and reload canonical terminal on EOF

* fix(chat): track stop queue and cleanup ownership by per-send generation

* fix(agent-loop): preserve requested temperature for non-qwen fallback candidates

* fix(chat): reserve send ownership before any await and scope stop to the current send

* fix(chat): clear the previous run identity at send reservation

---------

Co-authored-by: RaresKeY <158580472+RaresKeY@users.noreply.github.com>
Co-authored-by: StressTestor <212606152+StressTestor@users.noreply.github.com>
2026-08-14 08:10:30 +01:00
RaresKeYandLéo 651bf714de perf(chat): batch live thinking rendering and bound timer updates (#5931)
* perf(chat): batch live thinking DOM updates

* test(chat): cover live thinking scheduler lifecycle

* fix(chat): guard background stop-state, restore live thinking text, drop source-text tests

- _closeOpenThinkingMarkup no longer overwrites currentAccumulated for
  backgrounded streams. It now mirrors the guard the delta path already uses
  (`if (!_isBg) currentAccumulated = accumulated`). Without it a backgrounded
  stream's text is written into the foreground session's stop-state, which
  abortCurrentRequest and detachCurrentStream then put in the wrong bubble.

- Split _extractLiveThinkingText into _liveThinkingText (strip every think tag)
  and _closedThinkingText (via extractThinkingBlocks). Slicing from the first
  <think> to the first </think> pinned the live box to "The" for the rest of the
  stream on the `<think>The</think>` + untagged-thinking pattern that the
  hasUnclosedThink detection deliberately keeps streaming through.

- The background transition now flushes with rich:true, so a stream that
  backgrounds mid-thinking isn't left as pre-wrap plain text permanently.

- Move the throttle to static/js/liveThinkingThrottle.js and import it. The
  .mjs suite imports the module instead of slicing it out of chat.js with
  vm.runInNewContext and marker comments.

- Replace the source-text assertions in tests/test_live_thinking_scheduler_js.py
  with behavioral coverage, per tests/TESTING_STANDARD.md. The .mjs suite grows
  from 3 to 6 cases.

- Collapse the duplicated tool_start/agent_step finalizers into one
  _endLiveThinkingSection().

* fix(chat): hoist thinking teardown out of the try block so catch can reach it

In an ES module a function declared inside `try { }` is scoped to that block,
and `catch` is a sibling scope rather than a nested one. _closeOpenThinkingMarkup
was declared inside the try and called from catch, so the call threw
ReferenceError and killed the rest of the error path: the stream never
finalized and the thinking block was never torn down.

Declare _closeOpenThinkingMarkup and a new _endThinkingOnTerminalPath next to
the existing _flushLiveThinking / _cancelLiveThinkingWork outer lets and assign
them inside the try, which is the pattern those two already use for exactly
this reason.

Verified against a live stream in a browser: before, clicking stop mid-thinking
logged "_closeOpenThinkingMarkup is not defined" and left no finalized thinking
section; after, the block collapses to "View thinking process" correctly.

* perf(chat): extract live thinking at commit cadence

* fix(chat): bound live thinking work

* test(chat): update stream invariant assertions

---------

Co-authored-by: Léo <leograndcontact@gmail.com>
2026-08-10 20:11:48 +01:00
Samyandsamy f06a0a30a8 fix(session): restore session URL hash writes (removed in cf4e240a) (#5872)
* Fix: restore session URL hash writes (removed in cf4e240a)

Restores history.replaceState() calls in selectSession() and
materializePendingSession() that were dropped during the July 23 merge.
Without these, chat URLs never update the address bar hash, making
sessions unshareable and causing bare-URL reloads to land on the
welcome screen instead of restoring the last active chat.

Root cause: selectSession() had its hash-write deliberately removed;
materializePendingSession() lost its during a larger refactor that
added the stale-response and incognito guards.

Fixes #5870 (upstream)

* fix: session URL hash lost when sending message mid-stream

Two independent bugs caused the session hash to disappear from the URL:

Bug 1 — ReferenceError in catch block silently killed error recovery
  In handleChatSubmit, two const variables (streamingTTS at line 1922 and
  abortCtrl at line 1741) were declared inside the try block but referenced
  in the catch block. Since const is block-scoped in JavaScript, they were
  undefined in catch, causing a ReferenceError that silently aborted the
  error handler. This prevented materializePendingSession() from ever being
  called, so no hash was written to the URL.
  Fix: Hoisted both as let declarations before the try { block.

Bug 2 — Dual sessions.js ES module instances with mismatched state
  app.js imported sessions.js with a version query string
  (?v=20260722ctxheader4) while every other module imported ./sessions.js
  without one. The browser treated them as different URLs, creating two
  separate module instances with independent _pendingChat and
  currentSessionId state. createDirectChat() set pending on one instance
  while handleChatSubmit() checked hasPendingChat() on the other — so the
  pending session never materialized.
  Fix: Removed the version query string from the sessions.js import in
  app.js and from the modulepreload + script tags in index.html. All
  modules now share a single sessions.js instance.

Bonus guard: _adoptOpenedSessionBeforeAutoCreate() now checks
hasPendingChat() before adopting a stale DOM-active session, preventing
the send path from landing in the wrong session when a New Chat is pending.

---------

Co-authored-by: samy <samy@users.noreply.github.com>
2026-08-07 22:04:53 +02:00
Jakub Grula 36d4098421 fix: Edit box formatting was removing triple tick boxes (#5737) 2026-08-07 19:15:50 +02:00
pewdiepie-archdaemon d8a2059df8 Merge verified Odysseus fixes 2026-07-23 14:49:02 +00:00
falabellamichael c2d2075833 fix(stabilization): harden attachment lifecycle and agent guard signals (#5420)
* fix: harden stabilization attachment and agent guards

* fix(uploads): preserve durable references during cleanup

* fix(uploads): close cleanup and compaction races
2026-07-11 15:14:14 +01:00
pewdiepie-archdaemon a1a14bd5c9 Checkpoint Odysseus local update 2026-07-07 00:50:07 +00:00
pewdiepie-archdaemon 25ba07a5b1 Open documents from native tool outputs 2026-07-03 02:20:23 +00:00
pewdiepie-archdaemon 5bcd22873f Fix stale streams and cookbook task controls 2026-07-03 00:45:43 +00:00
pewdiepie-archdaemon 2918ef71ea Support mobile enter for queued agent prompts 2026-07-01 14:42:52 +00:00
pewdiepie-archdaemon 0b4ef7187f Stabilize chat and cookbook workflows 2026-07-01 10:09:25 +00:00
pewdiepie-archdaemon a1b317df87 Write email replies into open composer 2026-06-30 10:48:08 +00:00
pewdiepie-archdaemon affc8b1c37 Improve document agent streaming and chat metrics 2026-06-30 05:14:41 +00:00
pewdiepie-archdaemon edbc9131b7 Fix incognito agent mode and cookbook tmux preview 2026-06-30 01:47:48 +00:00
pewdiepie-archdaemon 757ebd8795 Keep open editor drafts in chat context 2026-06-29 03:10:42 +00:00
pewdiepie-archdaemon ef6cda29f6 Link gallery uploads back to chat 2026-06-29 02:34:11 +00:00
pewdiepie-archdaemon 9401fd2870 Allow stalled chat uploads to be cancelled 2026-06-29 02:06:39 +00:00
pewdiepie-archdaemon 9d576dc9ee Clear stale chat stream indicators 2026-06-28 22:06:20 +00:00
pewdiepie-archdaemon 38d5e65e36 Merge dev into main for testing 2026-06-28 14:07:23 +00:00
pewdiepie-archdaemon 1e4d06e6c5 Reduce cookbook startup polling 2026-06-27 13:50:21 +00:00
pewdiepie-archdaemon 4ab68b6566 Polish mobile UI and editor workflows 2026-06-27 13:05:44 +00:00
Gabriel Peña 476c250fa7 fix ask-user choices across reloads (#4669) 2026-06-22 20:49:49 +02:00
pewdiepie-archdaemon c20535f1ad Cookbook model workflow fixes 2026-06-21 11:02:35 +00:00
pewdiepie-archdaemon 2aa0ff7173 Chat: first-token wait timer cleanup so per-pane timeouts dont leak when a response finishes mid-wait 2026-06-19 00:34:47 +00:00
pewdiepie-archdaemon d31674e7ff Merge remote-tracking branch 'origin/dev' into test-main-dev-merge-20260615
# Conflicts:
#	src/tool_implementations.py
#	static/js/research/panel.js
2026-06-15 21:20:15 +09:00