Skip to content

feat: add durable agent activity preambles#27

Merged
steipete merged 25 commits into
openclaw:mainfrom
ragesaq:pr/agent-activity-cohesion
Jul 1, 2026
Merged

feat: add durable agent activity preambles#27
steipete merged 25 commits into
openclaw:mainfrom
ragesaq:pr/agent-activity-cohesion

Conversation

@ragesaq

@ragesaq ragesaq commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Persist bot-authored commentary and tool activity as durable channel/DM messages, grouped by bot and turn into one collapsible preamble above the final answer.
  • Keep live agent-responding state separate from history; stale live state expires, while durable preambles survive reloads.
  • Add independent commentary/tool visibility preferences and configurable self-message alignment.
  • Document the API-first integration model and regenerate the OpenAPI TypeScript surface.

This keeps the strongest bounded part of the contributor proposal. The unrelated model/reasoning runtime-control plane was removed: ClickClack transports and renders agent activity, while LLM runtime selection remains owned by the external agent runtime.

Security and invariants

  • New agent_activity:write bot-token scope; deliberately excluded from default bot scope bundles.
  • Human sessions and bot tokens without the explicit scope cannot create activity rows.
  • Activity is supported for channels and DMs, not thread replies.
  • Activity rows do not create unread counts, search results, or browser/server notifications.
  • Nonce replay identity includes message kind and turn ID in SQLite and Postgres.
  • Coalescing keys include conversation, author, and turn so separate bots cannot merge activity.

Verification

  • pnpm check
  • go test ./apps/api/internal/store/sqlite ./apps/api/internal/store/postgres ./apps/api/internal/httpapi
  • CI=1 pnpm exec playwright test --reporter=line --workers=1 — 41/41 passed
  • Production build rendered in the existing Chrome profile; collapsed/expanded preamble and tool details verified
  • Autoreview clean with no accepted/actionable findings

Thanks @ragesaq for the original agent-activity design and implementation.

ragesaq added 7 commits June 15, 2026 21:25
Add a message `kind` discriminator ('message', 'agent_commentary',
'agent_tool') and a nullable `turn_id` correlation column so a bot bridge
can persist agent commentary/tool output in the normal message stream
instead of ephemeral frames.

Activity rows ride the ordinary message.created fan-out and inherit
channel_seq for scrollback, but are excluded from full-text search
(sqlite FTS triggers gated on kind='message'; postgres partial GIN index
+ search query filter) and from unread/notification accounting (unread
subqueries and push notify path skip activity kinds).

Creating a message with kind != 'message' requires a bot token carrying
the new agent_activity:write scope. The scope is registered in both bot
stores' allowed-scope validation but deliberately EXCLUDED from the bot:*
bundles, so existing deployments gain no new capability. Human sessions
get 403, bots without the scope get 403, unknown kinds get 400. Targets
remain single-channel/single-DM via the existing create endpoints.

migrations: sqlite 0017, postgres 0011.

Tests: authz matrix (403 human, 403 missing scope, 400 unknown kind,
success for scoped bot, bundle-exclusion, membership privacy) and store
round-trip + unread exemption + search exclusion.
Render durable agent activity messages (kind agent_commentary / agent_tool)
inline in the normal message flow with a theme-tied accent rail and a small
commentary/tool badge so operators can tell intermediate output from the
final answer. The accent comes from a new --activity-accent token (blue on
light theme, amber on dark) driven by the existing prefers-color-scheme
override, not hardcoded hex.

Add a client-only "Show agent activity" preference (default on) in account
settings that filters activity kinds from the rendered stream, persisted in
localStorage like other client prefs. The client unread bump path also skips
activity kinds to match server accounting.
Replace the flat one-row-per-activity rendering (a badged row per
agent_commentary/agent_tool message) with a single coalesced preamble
block per agent turn, matching the clickglass guidestone:

- Consecutive same-turn activity rows (grouped by turn_id) collapse into
  one block: incrementing commentary prose plus a collapsed-by-default,
  expandable list of tool calls. Done entirely at render time; no schema
  or bridge change (rows already carry turn_id).
- The block opens while the turn is live (pulsing LIVE pill) and
  collapses to a single expandable line once the turn ends (DONE).
- Tool sub-items render as tool name + derived action + truncated arg
  detail (one row per call), not the full coalesced step chain.
- Replace the single show-agent-activity toggle with two independent
  operator flags: hide-commentary and hide-tool-calls (localStorage,
  with migration from the legacy key). Both set omits the block.

New: lib/chat/agent-activity.ts (coalescer), lib/chat/tool-detail.ts
(glyph/action resolver), components/messages/PreambleBlock.svelte.

Also embed dist with all: so underscore-prefixed SvelteKit chunks are
not silently dropped from the binary.
Rework the coalesced agent preamble block to the clickglass display
contract, per operator review:

- Commentary prose and tool calls now interleave in arrival order
  (commentary, tool, commentary, tool) instead of prose-then-tool-list.
- Each tool call is its own row, collapsed by default to one line
  (glyph + action + tool name + truncated arg detail) on a light blue
  wash; clicking expands it to the full stored body (step-chain head
  rendered as plain monospace, output as markdown).
- Grouping is by turn_id across the whole list, not adjacency: the
  bridge's debounced trailing commentary can land after the final
  answer, which previously split the block and visually swallowed the
  final message. The final answer now always renders as a clearly
  separate ordinary message below the block.
- The block collapses to a single line when the final answer lands
  (same-author ordinary message after the turn opened), when anything
  follows the turn's last activity row, or when the turn goes stale
  (3 min without frames) so orphaned turns don't pulse LIVE forever.
- Preamble wash lightened; tool rows use new --tool-call-bg tokens
  (light blue on both themes).

Both operator flags re-verified across all four states; hide-both
omits blocks entirely. Embedded web dist rebuilt.
…collapsed)

The pill previously read "done" for any finished turn regardless of
whether the block was open. Now it mirrors what the toggle will do:
"collapsed" when closed, "expanded" when open, "live" while streaming.
Includes embedded dist rebuild.
Within an agent message group the synthetic preamble row is immediately
followed by the same author final answer. Tag the paired rows
(before-final-message / after-preamble) and draw them as a single
bordered card with a flat internal seam, mirroring the ClawCanvas inline
model so the activity log and the answer read as one unit. Scoped to the
paired rows only; a standalone or still-live preamble keeps its
full-bleed wash and gutter accent.
Copilot AI review requested due to automatic review settings June 15, 2026 21:35
@ragesaq

ragesaq commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@clawsweeper this is the durable follow-up to #24 (now merged). It turns the ephemeral activity strip into a persistent, collapsible preamble block: coalesced commentary + per-tool rows (detail collapsed by default), visibility-state pill, and the preamble boxed together with the final answer into one cohesive card, plus a profile toggle.

Green gate on post-merge main: web typecheck 0, build:web 0, embed 0, go build ./... 0, go test -run AgentActivity|AgentProgress PASS. No e2e removal, no lockfile churn, dist regenerates reproducibly.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds support for durable “agent activity” messages (commentary + tool calls) that are persisted server-side, authorized via a dedicated bot scope, and rendered client-side as a coalesced “preamble block” UI.

Changes:

  • Introduces kind + turn_id message fields, DB migrations (SQLite + Postgres), and scope-gated HTTP create paths for agent activity rows.
  • Adds a client-side coalescing pipeline to collapse per-turn agent activity rows into a single preamble block with per-tool expand/collapse UI.
  • Updates styling/theme variables and fixes Go embed behavior to include underscore/dot-prefixed asset files.

Reviewed changes

Copilot reviewed 31 out of 48 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
apps/web/src/styles/messages.css Adds styling for the new preamble block and tool-call subrows.
apps/web/src/styles/base.css Introduces theme variables for activity accents and tool-call row backgrounds.
apps/web/src/lib/types.ts Extends message/event types to include kind, turn_id, and client-only preamble_block.
apps/web/src/lib/chat/tool-detail.ts Adds tool name → glyph/action/detail mapping for preamble tool summaries.
apps/web/src/lib/chat/agent-activity.ts Implements coalescing of agent activity rows into a single synthetic preamble message per turn.
apps/web/src/components/profile/ProfileSettingsModal.svelte Adds user toggles to hide commentary and/or tool calls.
apps/web/src/components/messages/PreambleBlock.svelte New component rendering the collapsible preamble + per-tool expansion.
apps/web/src/components/messages/MessageRow.svelte Renders PreambleBlock when preamble_block is present and adds pairing classes.
apps/web/src/components/messages/MessageGroup.svelte Passes adjacent messages into MessageRow for preamble/answer pairing logic.
apps/web/src/ChatApp.svelte Applies coalescing to the message stream and persists hide flags in localStorage.
apps/api/internal/webassets/webassets.go Switches to //go:embed all:dist to include underscore/dot-prefixed asset files.
apps/api/internal/webassets/dist/index.html Updates built web asset references for the new build output.
apps/api/internal/webassets/dist/200.html Updates built web asset references for the new build output.
apps/api/internal/webassets/dist/_app/immutable/nodes/5.kueAycLe.js Build artifact updated for new chunk references.
apps/api/internal/webassets/dist/_app/immutable/nodes/4.Z19KiFas.js Build artifact updated for new chunk references.
apps/api/internal/webassets/dist/_app/immutable/nodes/3.C3tghYkn.js Removes old hashed build artifact.
apps/api/internal/webassets/dist/_app/immutable/nodes/3.B-d9fl0n.js Adds new hashed build artifact.
apps/api/internal/webassets/dist/_app/immutable/nodes/2.BQWrx22g.js Build artifact updated for new chunk references.
apps/api/internal/webassets/dist/_app/immutable/entry/app.Xpli9ePf.js Removes old hashed build artifact (entry).
apps/api/internal/webassets/dist/_app/immutable/assets/0.DeZxIIxo.css Removes old hashed build artifact (CSS).
apps/api/internal/store/types.go Adds message kind constants, normalization/validation, and new agent_activity:write scope.
apps/api/internal/store/sqlite/sqlite.go Stores normalized kind + turn_id and includes them in realtime event payloads.
apps/api/internal/store/sqlite/sqlc/schema.sql Adds kind and turn_id columns to SQLite messages schema.
apps/api/internal/store/sqlite/sqlc/queries.sql Excludes activity kinds from unread counts and inserts kind/turn_id on message writes.
apps/api/internal/store/sqlite/migrations/0017_agent_activity.sql SQLite migration adding columns, index, and FTS trigger filtering for kind=message.
apps/api/internal/store/sqlite/helpers.go Includes kind and turn_id in message selects/scans.
apps/api/internal/store/sqlite/dms.go Stores normalized kind + turn_id for direct messages and includes them in events.
apps/api/internal/store/sqlite/bots.go Makes new scope grantable but not inherited by bot bundles.
apps/api/internal/store/sqlite/agent_activity_test.go Adds SQLite tests for kind/turn_id round-trip, unread exemption, and search exclusion.
apps/api/internal/store/postgres/sqlc/schema.sql Adds kind/turn_id columns and makes search index partial on kind=message.
apps/api/internal/store/postgres/sqlc/queries.sql Excludes activity kinds from unread counts and inserts kind/turn_id on message writes.
apps/api/internal/store/postgres/search.go Filters search results to kind=message.
apps/api/internal/store/postgres/postgres.go Stores normalized kind + turn_id and includes them in realtime event payloads.
apps/api/internal/store/postgres/migrations/0011_agent_activity.sql Postgres migration adding columns/index and rebuilding partial search index.
apps/api/internal/store/postgres/helpers.go Includes kind and turn_id in message selects/scans.
apps/api/internal/store/postgres/dms.go Stores normalized kind + turn_id for direct messages and includes them in events.
apps/api/internal/store/postgres/bots.go Makes new scope grantable but not inherited by bot bundles.
apps/api/internal/httpapi/server.go Adds kind/turn_id to create-message requests and enforces activity scope for activity kinds.
apps/api/internal/httpapi/features.go Adds kind/turn_id to create-DM requests and enforces activity scope for activity kinds.
apps/api/internal/httpapi/agent_activity_test.go Adds API-level tests for authz, privacy, and non-inheritance of the new scope.
Files not reviewed (4)
  • apps/api/internal/store/postgres/storedb/models.go: Generated file
  • apps/api/internal/store/postgres/storedb/queries.sql.go: Generated file
  • apps/api/internal/store/sqlite/storedb/models.go: Generated file
  • apps/api/internal/store/sqlite/storedb/queries.sql.go: Generated file
Comments suppressed due to low confidence (4)

apps/web/src/components/messages/PreambleBlock.svelte:1

  • class:open is missing its boolean expression, which is inconsistent with the rest of the file and will fail to compile in Svelte versions that require class:name={expr}. Bind it to the open boolean (e.g., class:open={open}) so the chevron rotates only when expanded.
    apps/web/src/components/messages/MessageRow.svelte:1
  • The is-preamble-collapsed class name implies “currently collapsed”, but it is being driven by block.final. Final blocks can be manually expanded in PreambleBlock, so this CSS class will be applied even when the preamble is open, causing “collapsed” spacing/styling to be incorrectly applied. Consider moving the collapsed/expanded styling decisions into PreambleBlock.svelte (where preambleOpen is known) by toggling a class there, or by exposing/binding the open state to the parent so MessageRow can reflect the actual UI state.
    apps/web/src/lib/chat/agent-activity.ts:1
  • Finality detection can become O(T * N) due to scanning from firstIndex+1 to the end for every turn. On long timelines with many turns, this can noticeably impact scroll/render performance. A more efficient approach is a single reverse pass to precompute “is there a later same-author ordinary message after index i” (e.g., track seenOrdinaryByAuthor while iterating from the end), or compute per-index “next ordinary message author” so each turn checks finality in O(1).
    apps/web/src/lib/types.ts:1
  • EventPayload.kind is typed as a plain string, even though Message.kind is already a constrained union ("message" | "agent_commentary" | "agent_tool"). Typing EventPayload.kind to reuse the same union (or Message["kind"]) would prevent typos and keep client logic like handleUnreadBump aligned with server-supported values.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/api/internal/httpapi/server.go Outdated
ragesaq added 6 commits June 15, 2026 22:13
The preamble now reads clearly yellow while the turn streams (growing
bubble), and collapses to a distinct yellow bar capping the blue final
answer block. Retunes --activity-accent to yellow on both themes
(#d49415 light, #e8b23e dark), strengthens the streaming wash to 17%,
and gives the collapsed boxed-pair bar a 22% wash + warm bottom divider
so it stands apart from the blue delivery. Rebuilds embedded web dist.
…wcanvas)

Per-channel runtime snapshot (model, thinking, context used/limit) served from
clickclack's own SQLite/Postgres store, fed by the agent-bridge reading the
gateway directly. No /api/live proxy, no ClawCanvas dependency.

- migration: channel_runtime table (sqlite 0018 + postgres 0012)
- store: GetChannelRuntime / UpsertChannelRuntime / SetChannelRuntimeOverride
  with disjoint bridge-snapshot vs picker-override columns (neither clobbers
  the other), raw SQL in both backends
- api: GET (session read) / PUT (bot agent_activity:write stamp) / PATCH
  (session override) /api/channels/{id}/runtime
- test: snapshot/override separation + empty-record-on-unknown-channel
- docs: RUNTIME_CONTROLS_INDEPENDENT_PLANE.md (data plane + per-field sourcing)

Increment 1 of 4. Next: bridge stamp, frontend port (picker+meter), deploy.
…point

Increment 2/3 of the composer runtime controls: port the model picker and
context meter into the composer utility bar, fed by clickclack's own
/api/channels/{id}/runtime endpoint (the increment-1 data plane), with no
ClawCanvas dependency.

- channel-runtime.ts: runtime resolver + provider/model catalogs (frontend only)
- channel-runtime-store.svelte.ts: narrow runtime store; loadChannelRuntime()
  reads the native endpoint, persistRuntimeOverride() PATCHes picker picks,
  seedDemoChannelRuntime() keeps the chrome populated until the bridge stamp lands
- ComposerModelPicker / ModelPickerMenu / ComposerContextMeter: ported components
- ChatComposer: composer-utility row hosts the meter + picker (showModelPicker)
- ChatApp: seed on boot, load on channel switch, override handlers persist via PATCH
- rebuilt embedded web dist

Demoed live on the :8100 stock instance against a real runtime row; meter/picker
update end-to-end when the DB row changes. PR intentionally not opened yet.
Bundle the remaining composer/runtime-controls UI work onto the PR:

- User (self) messages render in their own accent-tinted bubble that
  mirrors the agent delivery bubble's weight (7x11 padding, 11px radius,
  3px rail) using a single theme-tied token (--user-bubble-soft =
  color-mix of --accent), so the dark theme override flows automatically.
  Bubble hugs content (fit-content, capped) so alignment is visible.
- Configurable self-message alignment (left default / right) via a new
  "Your message alignment" select in Account settings. Persisted in
  localStorage (clickclack:user-align:v1) and applied as a
  data-user-align root attribute; messages.css mirrors the self group
  (avatar gutter, header, bubble) without prop drilling.
- MessageGroup gains currentUserID -> is-self; bots never match the
  current user id, so an agent group is never marked self even in a DM.
- AgentResponding: compact "agent is responding" status above the
  composer, driven by any unfinalized tracked turn line.
- Context-meter detail popover + model-picker refinements.
- Drop the dead "Preamble" title (markup + .preamble-title CSS); the
  preamble header now reads chevron + state pill + action only.
…ontrols

# Conflicts:
#	apps/api/internal/webassets/dist/200.html
#	apps/api/internal/webassets/dist/_app/immutable/entry/app.Dphjwod_.js
#	apps/api/internal/webassets/dist/_app/immutable/entry/app.Xpli9ePf.js
#	apps/api/internal/webassets/dist/_app/immutable/entry/app.inV1wtz9.js
#	apps/api/internal/webassets/dist/_app/immutable/nodes/2.BQWrx22g.js
#	apps/api/internal/webassets/dist/_app/immutable/nodes/2.C6_wREb2.js
#	apps/api/internal/webassets/dist/_app/immutable/nodes/2.DF-TCSVN.js
#	apps/api/internal/webassets/dist/_app/immutable/nodes/4.CsfpOieR.js
#	apps/api/internal/webassets/dist/_app/immutable/nodes/5.BR4pOwJp.js
#	apps/api/internal/webassets/dist/_app/immutable/nodes/5.ClAa746V.js
#	apps/api/internal/webassets/dist/_app/immutable/nodes/5.kueAycLe.js
#	apps/api/internal/webassets/dist/index.html
#	apps/web/src/ChatApp.svelte
#	apps/web/src/components/profile/ProfileSettingsModal.svelte
@ragesaq ragesaq changed the title feat(web): durable, cohesive agent activity preamble block feat(web): cohesive agent-activity preamble + composer runtime controls + self-message bubbles Jun 16, 2026
On a ~360px viewport the three full-width runtime widgets (markdown
toolbar, context meter, model picker) shared one fixed 1fr/auto/1fr grid
and overlapped into an unreadable jumble. Apply the learned mobile
treatment below 520px:

- composer-runtime-controls drops to two rows: context meter + model
  picker on top (space-between), markdown glyph strip beneath.
- ContextMeter pill collapses to the fill bar + percentage + caret;
  token counts and cache-hit detail move to the one-tap inspector.
- ModelPicker pill collapses to dot + model name (truncated) + caret;
  provider prefix and thinking/speed labels live in the picker menu.
- Right-adjusted self bubbles use the 44px mobile avatar gutter instead
  of the desktop 56px column for list consistency.

Rebuilt embedded web dist.
@ragesaq

ragesaq commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

Live demo is up in the PR description. Recaptured against the stock validation instance so the chat actually moves: the composer types and sends, the agent works live in-channel (commentary + per-tool rows stream in), the trail folds into the real COLLAPSED gold preamble pill, then expand → collapse, plus the model picker / context meter and a mobile pass. No caption overlays, no frozen frames.

ClickClack composer + preamble live demo

Full-quality MP4 (1600×1000 · 30 fps · 83 s): https://github.com/ragesaq/clickclack/raw/media/composer-demo-video/.proof/video/clickclack-composer-demo.mp4

@clawsweeper

clawsweeper Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 23, 2026, 7:53 AM ET / 11:53 UTC.

Summary
This PR adds durable agent-activity preambles, composer runtime controls, self-message bubbles/alignment, channel-runtime storage/API, tests, docs, and rebuilt embedded web assets.

Reproducibility: not applicable. as a bug reproduction: this is a feature PR. The maintainer-review concern is source-verifiable through the runtime PATCH endpoint, generated sqlc merge ref, and accompanying tests.

Review metrics: 3 noteworthy metrics.

  • Changed files: 83 files affected. The PR spans API, persistent storage, generated sqlc output, web UI, e2e tests, docs, and embedded assets, so maintainers should review it as a broad feature branch.
  • Runtime routes: 3 routes added. GET, PUT, and PATCH for channel runtime create a new API contract with separate read, bridge-stamp, and member override semantics.
  • Storage migrations: 4 migrations added. SQLite and Postgres each gain agent-activity and channel-runtime migrations, making upgrade behavior a merge consideration.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster ✨ media proof bonus
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Get maintainer approval for channel-member model/reasoning overrides.
  • Optionally rerun final merge-ref sqlc/build/e2e before landing.

Mantis proof suggestion
A real visual pass would materially help confirm the broad chat/composer UI behavior on the merge result. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis visual task: verify durable preamble expand/collapse, self-message alignment, composer model picker, and context meter on the PR merge result.

Risk before merge

  • [P1] Merging intentionally expands persistent storage/API surface for agent activity and channel runtime state, so maintainers should be comfortable with the upgrade path even though the inspected merge ref preserves current-main sqlc changes.
  • [P1] The runtime PATCH endpoint intentionally lets a channel member with messages:write set model/reasoning overrides that the bridge later consumes; that policy may be right, but it should be explicitly accepted by maintainers before merge.

Maintainer options:

  1. Approve channel-scoped runtime overrides (recommended)
    Maintainers can accept that channel members with messages:write may set next-turn model/reasoning overrides while bridge-stamped effective runtime remains bot-only.
  2. Narrow the override permission
    If that policy is too broad, gate PATCH behind a stricter role/scope or default-off setting and keep the bridge PUT contract unchanged.
  3. Require final upgrade proof
    Before merge, maintainers can ask for one final current-main merge-ref run of sqlc/build/e2e to confirm the persistent migrations and embedded assets match the landing branch.

Next step before merge

  • [P2] The remaining action is maintainer policy and ownership review, not an autonomous code repair.

Security
Cleared: No concrete security or supply-chain defect was found; the runtime override surface is tracked as a maintainer auth-provider/product risk instead.

Review details

Best possible solution:

Merge after maintainers explicitly accept the channel-scoped runtime override policy and are satisfied with the inspected merge-ref storage/API result; otherwise narrow PATCH authorization or make the override behavior opt-in before landing.

Do we have a high-confidence way to reproduce the issue?

Not applicable as a bug reproduction: this is a feature PR. The maintainer-review concern is source-verifiable through the runtime PATCH endpoint, generated sqlc merge ref, and accompanying tests.

Is this the best way to solve the issue?

Unclear as a product choice: the implementation is technically coherent and tested, but the channel-writer model/reasoning override policy needs maintainer acceptance before it is clearly the best permanent contract.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 9d29bc6356f2.

Label changes

Label justifications:

  • P2: This is a normal-priority feature PR with broad but bounded web/API/storage impact.
  • merge-risk: 🚨 compatibility: The PR changes persistent migrations, generated store code, embedded assets, and upgrade-sensitive message/runtime data shapes.
  • merge-risk: 🚨 auth-provider: The PR adds model/reasoning override routing and bot-scoped runtime/activity authorization surfaces.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • feature: ✨ showcase: ClawSweeper spotlight: unusually compelling feature idea for maintainer attention. Durable agent preambles plus composer runtime controls are a substantial, cohesive agent-chat workflow improvement worth maintainer attention.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (recording): The PR body and comments contain linked live recording proof plus copied terminal/test output for the changed UI/API behavior; local prepared media extraction failed because ffprobe is unavailable.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body and comments contain linked live recording proof plus copied terminal/test output for the changed UI/API behavior; local prepared media extraction failed because ffprobe is unavailable.
  • proof: 🎥 video: Contributor real behavior proof includes video or recording evidence. The PR body and comments contain linked live recording proof plus copied terminal/test output for the changed UI/API behavior; local prepared media extraction failed because ffprobe is unavailable.
Evidence reviewed

What I checked:

Likely related people:

  • Peter Steinberger: Current-main blame shows the base HTTP create-message path, migration runner, and much of ChatApp originated in the initial scaffold commit. (role: introduced API/store and web app baseline; confidence: high; commits: e0de6adf9cf0; files: apps/api/internal/httpapi/server.go, apps/api/internal/store/sqlite/sqlite.go, apps/api/internal/store/postgres/postgres.go)
  • Vincent Koc: Recent current-main commits changed API/store platform features and hardened client runtime edges that overlap the PR merge surface. (role: recent area contributor; confidence: high; commits: 9d29bc6356f2, 95008dc407a1, 31b23105c3b3; files: apps/web/src/lib/api.ts, apps/api/internal/store/types.go, apps/api/internal/httpapi/server.go)
  • ragesaq: The merged agent.progress web consumer on current main introduced the AgentProgress surface this PR extends into durable preambles. (role: feature predecessor author; confidence: medium; commits: 1b6e5c71b8d0; files: apps/web/src/ChatApp.svelte, apps/web/src/components/messages/AgentProgress.svelte)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

…ime handlers

Reject a non-empty turn_id on an ordinary 'message' row (400,
ErrTurnIDNotAllowed) at the shared resolveMessageKind choke point used by
both the channel and direct-message create paths. turn_id correlates agent
activity rows only; an ordinary message carrying one violated the documented
'Empty for ordinary messages' contract (Copilot review, server.go:655).
Activity kinds (agent_commentary/agent_tool) may still carry a turn_id.

Add runtime_test.go covering the previously 0%-covered composer runtime
handlers (get/put/patchChannelRuntime) plus the turn_id contract on both
create paths. Lifts go coverage 84.3% -> 86.5%, clearing the 85% gate.
@ragesaq

ragesaq commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

Pushed 4733232 resolving both open follow-ups on this PR.

1. turn_id contract enforcement (Copilot review, server.go:655)
The shared kind/authorization helper (resolveActivityKindresolveMessageKind) now also validates turn_id: an ordinary kind="message" carrying a non-empty turn_id is rejected with 400 (store.ErrTurnIDNotAllowed); activity kinds (agent_commentary/agent_tool) may carry one. Enforced at the single choke point both createMessage and createDirectMessage already use, so the documented "Empty for ordinary messages" invariant now holds on both paths. Fail-closed (reject) over silent-clear so a client bug surfaces. Review thread resolved.

2. Go coverage gate (the unstable check)
The red check was the coverage gate (84.3% vs 85.0%), not a test failure. The gap was this PR's own composer-runtime handlers — getChannelRuntime/putChannelRuntime/patchChannelRuntime in runtime.go shipped at 0% coverage. New runtime_test.go covers all three (bridge PUT scope gate, picker PATCH override, composer GET, 404 on unknown channel) plus the turn_id contract on channel + DM paths.

Proof (local, go1.26):

  • go test ./apps/api/... → all ok
  • coverage gate (CLICKCLACK_GO_COVERAGE_MIN=85 scripts/check-go-coverage.sh) → 84.3% → 86.5%, exit 0
  • gofmt -l clean, go vet clean

Diff: +1 sentinel error, helper signature extended (turn_id in/out), both call sites, +1 test file. No behavior change for existing valid callers.

Address Clarity review on PR openclaw#27:
- turn_id field doc now states both halves of the contract: must be
  empty for ordinary messages (kind=message) with a 400
  ErrTurnIDNotAllowed, optional for agent activity kinds.
- TestMessageTurnIDContract now asserts the 400 response body carries
  the ErrTurnIDNotAllowed sentinel text (channel + DM paths), proving
  the failure reason reaches the client rather than an opaque 400.
@clawsweeper clawsweeper Bot added proof: 🎥 video Contributor real behavior proof includes video or recording evidence. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. labels Jun 16, 2026
The composer-runtime-controls work wraps the composer in a .composer-dock
that reserves fixed internal chrome above the input card: an always-present
agent-responding band plus the runtime-controls row (model picker + context
meter). That chrome is intentional dead space, not distance between the
newest message and the composer area.

expectMessageNearComposer measured the gap to the inner .composer-card, so
the new ~28px chrome pushed it past the 24px budget and failed three
auto-scroll assertions (unread-divider jump, live-channel pin, escape-to-
latest). Re-anchor the measurement to .composer-dock (the composer area
boundary) so the assertion stays about "is the newest message pinned to the
composer?" rather than tracking composer-internal chrome height, and keep the
same 24px slack budget. Falls back to .composer-card when no dock is present.

Full Playwright suite: 37 passed.
ragesaq added 2 commits June 16, 2026 06:26
The history-paging test scrolled to the bottom of a search-jump window and
asserted exactly one newer page fetched (toHaveLength(1)). The page count is
not an invariant: it depends on how many newer rows fit above the prefetch
sentinel before it leaves the trigger margin. A single 50-row page sits right
at one viewport, so the agent-activity/composer layout changes on this branch
pushed it to the edge where scroll-to-bottom occasionally needs a second
sequential page (after_seq=100 then after_seq=150) to fill. That is benign
forward fill, confirmed by the sequential cursors, not a runaway to the live
tail.

This made the test ~37% flaky on this branch (3/8 local repeats) while it is
deterministic on main, because the layout changes here moved the fill to the
boundary. Assert the real invariant instead: newer-paging engaged
(length >= 1) and stayed incremental (history-msg-149 visible, live-tail
history-msg-259 still absent). The 259-absence check is the runaway guard.

Verified: test 1087 12/12 green after the change (was 3/8); full Playwright
suite 37 passed.
Closes the two open HyperReview gates on the runtime cleanup and the
stale-carryover bug the new test caught.

- web: ComposerContextMeter renders the pill only when the channel reports
  context-window accounting (context_used/limit). Empty and override-only
  channels show no meter instead of a meaningless "?/?" placeholder. This
  also tears the pill down on an in-app channel switch, so the previous
  channel's meter cannot linger after the picker has already cleared.
- api: patchChannelRuntime validates the picker override at the boundary:
  thinking must be off|minimal|low|medium|high|xhigh|adaptive (empty clears),
  model is length-bounded (<=256, no control chars). A channel member can no
  longer persist an arbitrary/oversized string the bridge would apply to a
  gateway session. Bridge PUT is unchanged (bot + agent_activity:write).
- test(e2e): add an in-app sidebar-click channel switch (not page.goto) that
  asserts the override channel's model chrome and context meter both clear
  when switching to an empty channel.
- test(api): cover cookie+CSRF PATCH (403 without header, 200 with) and the
  new override validation rejections.
- docs: document the meter render-gate and the PATCH validation contract.
@ragesaq

ragesaq commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

Pushed f95564a resolving the three runtime-control findings from the review, plus the real-behavior proof you asked for.

  • Demo seed removed (seedDemoChannelRuntime gone) — empty channels render unknown state, not fabricated values.
  • Stale-clear fixed — store clears before the fetch, guarded by a serial + selected-channel re-check; loadChannelRuntime returns {} for empty channels; transient failure preserves cleared (not stale) chrome.
  • CSRF write path fixed — persistRuntimeOverride/loadChannelRuntime now route through the api() helper, so a normal cookie session adds X-ClickClack-CSRF: 1 and the override persists.

On top of the review: API-boundary validation on the PATCH (thinking allowlist, model length-bound), and a context-meter render-gate fix for a stale Context ?/? pill that a new in-app (sidebar-click) channel-switch E2E caught.

Proof (network/API behavior):

  • go test ./apps/api/internal/httpapi/...PASS: cookie PATCH w/o CSRF → 403, with CSRF → 200 and override_* persisted, bridge/effective fields unchanged, invalid thinking/oversized model rejected.
  • Full Playwright suite — 38/38 passed, including the in-app channel-switch stale-carryover test.

PR body updated with the full breakdown and the policy note that channel-scoped override (messages:write + membership) is product-approved for this PR.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

Re-review progress:

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. feature: ✨ showcase ClawSweeper spotlight: unusually compelling feature idea for maintainer attention. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 16, 2026
@shakkernerd shakkernerd self-assigned this Jun 16, 2026
@steipete steipete changed the title feat(web): cohesive agent-activity preamble + composer runtime controls + self-message bubbles feat: add durable agent activity preambles Jul 1, 2026
@steipete

steipete commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Maintainer closeout for af57acf4166408ffc19f39bbe8313cd21da8ff10:

  • Exact-head CI: https://github.com/openclaw/clickclack/actions/runs/28501841719 — Go, TypeScript, Docker Image, and Playwright E2E all passed.
  • Local gates: pnpm check; focused SQLite/Postgres/HTTP API tests; production E2E 41/41 passed serially.
  • Rendered proof: fresh production build/server opened in the existing Chrome profile; durable preamble collapse/expand, commentary, tool detail, and final-answer cohesion verified.
  • Security: activity writes require the dedicated agent_activity:write bot scope; ordinary users and unscoped bots are denied; activity remains outside unread, search, and notification paths.
  • Product fit: retained durable agent activity and UI preferences; removed the unrelated runtime model/reasoning control plane.
  • Autoreview: clean, no accepted/actionable findings.

Thanks @ragesaq for the original design and implementation.

@steipete
steipete merged commit 933f6d7 into openclaw:main Jul 1, 2026
4 checks passed
@ragesaq
ragesaq deleted the pr/agent-activity-cohesion branch July 1, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature: ✨ showcase ClawSweeper spotlight: unusually compelling feature idea for maintainer attention. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. proof: 🎥 video Contributor real behavior proof includes video or recording evidence. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants