Skip to content

feat(sessions): grouping, unread state, and full session controls on web, iOS, and Android#100814

Merged
steipete merged 1 commit into
mainfrom
claude/friendly-germain-e9501b
Jul 6, 2026
Merged

feat(sessions): grouping, unread state, and full session controls on web, iOS, and Android#100814
steipete merged 1 commit into
mainfrom
claude/friendly-germain-e9501b

Conversation

@steipete

@steipete steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Session management is uneven across OpenClaw clients (#100739):

  • The web sidebar only offers hover pin/archive; custom-group sections exist only on the dedicated Sessions page.
  • The iOS session list (Command Center) is read-only — no rename, pin, archive, delete, group, or fork controls.
  • The Android session list has sort/filter pills but no per-session controls.
  • The gateway has no unread concept, so no client can show "this session has new activity since you looked", and there is no one-step way to fork a session's transcript into a new session.

Why This Change Was Made

Brings the session list on web, iOS, and Android to parity with modern session managers: grouped sections, unread indicators, and a full per-session control menu, on top of two small additive protocol changes.

Gateway (additive, no protocol version bump):

  • SessionEntry gains lastReadAt, markedUnreadAt, lastActivityAt (epoch ms), all registered as core-reserved slot keys.
  • Session rows expose derived unread: explicitly marked unread, or last read before the latest activity (max(lastInteractionAt, lastActivityAt)). Sessions never marked read stay unread: false, so existing installs do not light up on upgrade.
  • lastActivityAt is stamped in the canonical post-run store update (updateSessionStoreAfterAgentRun). Cron runs count as activity; heartbeat and internal-event turns and preserved-state runs do not, so background heartbeats can never re-flag a session unread. Metadata patches never touch it.
  • sessions.patch gains unread?: boolean (false = record read, true = mark unread; allowed on archived sessions).
  • sessions.create gains fork?: boolean (requires parentSessionKey): branches the parent transcript into the new session via the existing fork runtime, under the parent's exclusive lifecycle-mutation lock, refusing active or concurrently-changed parents, with cross-agent transcript placement in the child store and forkedFromParent + fresh token counters on the child.
  • Swift protocol models regenerated (pnpm protocol:gen:swift).

Web (Control UI):

  • Sidebar sessions now render as Pinned → one section per custom group (category, alphabetical) → Ungrouped.
  • Session rows get an unread dot plus a kebab/right-click context menu: Pin/Unpin, Mark as unread/read, Rename, Fork, Move to group (existing groups / New group / Remove from group), Archive, Delete (confirm; falls back to the agent main session when deleting the selected session).
  • Opening a session marks it read once per unread episode; new activity while the session stays open re-acknowledges without patch loops (server-side timestamps make this convergent).
  • Sessions page rows show the unread dot and gain Mark unread/read + Fork actions; the custom-groups localStorage helpers are now shared (ui/src/lib/sessions/custom-groups.ts) instead of page-local.
  • New strings in en.ts; locale bundles regenerated via pnpm ui:i18n:sync.

iOS:

  • OpenClawChatSessionEntry decodes label, category, pinned, archived, unread, lastReadAt, lastActivityAt.
  • New transport calls: patchSession (double-optional String?? for explicit JSON-null label/category clearing), deleteSession, forkSession, and listSessions(archived:); protocol extension defaults keep other OpenClawChatTransport conformers source-compatible.
  • Command Center sessions screen gets Pinned/category/Ungrouped sections, unread dots, pin indicators, a Show Archived toggle, and a reusable context menu (commandSessionActions) with the full control set including rename/new-group alerts and a destructive delete confirmation. The 3-row preview orders pinned first and carries the same menu. Opening an unread session acknowledges the read.

Android:

  • ChatSessionEntry carries the new fields; parsing and partial-event merging preserve them.
  • ChatController gains patchSession (explicit JSON null for clears), deleteSession, forkSession (+ archived listing with a request-sequence race guard), exposed through NodeRuntime/MainViewModel.
  • SessionsScreen gets Pinned/category/Ungrouped headers, unread dots, pin indicators, an Archived filter pill, long-press menu with the full control set, rename/new-group dialogs, delete confirmation, and mark-read on open.

Out of scope: "Open PR" / "Open in" from the desktop reference UI do not map to OpenClaw sessions.

User Impact

Operators get consistent session organization and triage on all three clients: group sessions into named sections, spot background sessions with new output at a glance (unread dots survive across devices because read state is gateway-owned), and manage any session in place — pin, rename, fork, move between groups, archive, or delete — without visiting the web Sessions page.

Evidence

  • Gateway: pnpm test src/gateway/sessions-patch.test.ts src/gateway/session-utils.test.ts src/gateway/server.sessions.create.test.ts src/gateway/server.sessions.list-changed.test.ts src/agents/command/session-store.test.ts — all pass (unread patch transitions incl. archived sessions, table-driven unread derivation, fork transcript-copy/validation/active-parent/cross-agent coverage, heartbeat/preserved-state runs proven not to bump lastActivityAt).
  • Web: pnpm test ui/src/lib/sessions ui/src/pages/sessions ui/src/components — pass (sidebar grouping pure function, unread patch guard incl. re-acknowledge episode semantics, fork wire params, sessions view render contract).
  • Android: new unit coverage for grouping sections, new-field parsing, partial-entry merge preservation, and mutation/fork/archive/unread wire contracts.
  • iOS: unit coverage for grouping/preview ordering, Codable decoding of new fields, patch/fork/null-encoding params, and typography guards for new controls.
  • Path-scoped pnpm check:changed lanes green on Blacksmith Testbox (typecheck core caught and fixed the reserved-slot-key exhaustiveness guard for the new fields).
  • pnpm build, pnpm ui:i18n:sync, pnpm native:i18n:sync, pnpm protocol:gen:swift, pnpm docs:map:gen all clean.
  • Docs updated: docs/reference/session-management-compaction.md (store fields, unread semantics, operator forks), docs/web/control-ui.md (sidebar groups/unread/menu, Sessions page actions).

Closes #100739

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation app: android App: android app: ios App: ios app: web-ui App: web-ui gateway Gateway runtime agents Agent runtime and tooling size: XL maintainer Maintainer-authored PR labels Jul 6, 2026
@blacksmith-sh

This comment has been minimized.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ce0fa2e043

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +421 to +424
if (entry?.unread == true && !unreadPatchRequested) {
unreadPatchRequested = true
_sessions.value = _sessions.value.map { if (it.key == key) it.copy(unread = false) else it }
scope.launch { patchSession(key = key, unread = false) }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Reset the read-ack guard after a confirmed read

When an operator opens an unread session, this path sets unreadPatchRequested = true and never clears it when the sessions list/history later observes unread=false. If another run completes while that same session remains selected, the gateway derives unread=true again (lastActivityAt > lastReadAt), but switchSession does not run and even another call would skip the patch, so the active Android chat can stay marked unread until the user switches away or manually marks it read. Reset the guard when the current row becomes read, or run the same episode guard from session updates.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 3693d7b: acknowledgeUnreadIfNeeded (ChatController.kt) now resets the pending flag when a server-confirmed read (unread=false) is observed and re-acknowledges when the open session flips back to unread — wired into switchSession, event upserts, and full list refreshes. Covered by ChatControllerCommandControlsTest.openSessionReacknowledgesUnreadOncePerEpisode.

Comment on lines +2283 to +2285
if unread {
self.acknowledgeChatSessionReadIfNeeded(activeKey)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Re-acknowledge the visible chat after new activity

This only acknowledges unread state when openChat is called with unread=true. If the session is already open and a subsequent run completes, the gateway derives unread=true from lastActivityAt > lastReadAt, but no code calls acknowledgeChatSessionReadIfNeeded for the updated current session, leaving the visible iOS chat marked unread until it is reopened or manually marked read. Hook the episode guard to session updates for the active key, as the web client does.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 3693d7b: NodeAppModel.reconcileChatSessionReadState clears the acknowledgement guard when a fresh snapshot confirms the read and re-acknowledges when the focused open session shows unread again; both Command Center session fetch sites feed it. Server-side lastReadAt stamping keeps the exchange convergent.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 529bee65fa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +229 to +232
onSetArchived = { archived ->
coroutineScope.launch { viewModel.patchChatSession(key = session.key, archived = archived) }
},
onDelete = { deleteSessionKey = session.key },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Reset the active chat after archive/delete

When the row is the current chat, these callbacks only mutate the gateway and refresh the list. If the operator archives the current non-main session, Android leaves chatSessionKey on that archived key, and subsequent chat.send is rejected by the gateway's resolveSessionWorkStartError path for archived sessions; delete has the same stale-active-key problem after the row is removed. Web and iOS reset the active session for these destructive actions, so Android should switch to the main/current fallback after a successful archive/delete of the selected session.

Useful? React with 👍 / 👎.

Comment on lines +2221 to +2222
label = next.label ?: existing.label,
category = next.category ?: existing.category,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Preserve explicit null clears in session event merges

When another client clears a label or group, the gateway sends label: null / category: null in sessions.changed, but parseSessionEntry converts that to null and this merge treats null as “field absent”. Android therefore keeps showing the old label/category after cross-client clears until a full sessions.list refresh happens. Since grouping and renaming are now multi-client session controls, the event merge needs to distinguish explicit clears from missing fields.

Useful? React with 👍 / 👎.

@clawsweeper

clawsweeper Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Codex review: stale review; fresh review needed.

Summary
The latest durable ClawSweeper review was for head cc58f9d0456b9c1c4448ed431da44e09d53862dd, but the PR head is now e0852f05e3209b226eadd14ddda4164d0f38c035. Its old verdict and PR readiness labels are no longer current.

Next step
Run or wait for a fresh ClawSweeper review on the current PR head.

Review history (2 earlier review cycles)
  • reviewed 2026-07-06T10:47:33.455Z sha 529bee6 :: needs real behavior proof before merge. :: [P2] Reset Android after active archive/delete | [P2] Preserve explicit null clears in Android merges
  • reviewed 2026-07-06T12:06:58.737Z sha 324f811 :: needs real behavior proof before merge. :: [P2] Preserve explicit null clears in Android merges | [P2] Thread the owner agent through Android global controls

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels Jul 6, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f98b764a9b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

try {
val params =
buildJsonObject {
put("key", JsonPrimitive(sessionKey))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Preserve the agent scope for global session controls

When the displayed row key is global for a non-default agent, this request sends only the key. The gateway's sessions.patch contract requires agentId to disambiguate selected-global rows; without it, unscoped global resolves to the default agent, while Android does not retain an owner agent on ChatSessionEntry. As a result, rename/group/pin/unread actions on a non-default global row mutate the default global row or appear to do nothing; thread the row's agent id into these session-control RPCs, or scope the list to one agent.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 324f8112aa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ui/src/components/app-sidebar.ts Outdated
type="button"
class="sidebar-session-menu__item sidebar-session-menu__item--destructive"
role="menuitem"
?disabled=${!this.connected}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Disable delete for agent main sessions

When this menu is opened for an agent main row such as agent:work:main, the new destructive action stays enabled even though the archive action above uses canArchiveSessionRow to keep main/global rows reachable. The delete RPC only rejects the default main key, so a non-default agent main can be removed and then deleteSession() replaces the UI with that same now-deleted key, leaving the next chat focused on a missing session. Gate Delete with the same main/global/unknown check (or route this to reset) before exposing it here.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. label Jul 6, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e9fca63094

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +491 to +493
SessionMenuItem("Delete…") {
menuExpanded = false
onDelete()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Gate Android delete for agent main sessions

When the combined session list includes an agent main row such as agent:work:main, this menu still exposes Delete. The gateway only blocks deletion of the default main session, so a non-default agent main row can be removed from Android and the app then falls back to the main alias, leaving the operator focused on a missing anchor session. Apply the same main/global/unknown guard used for non-destructive reachability before offering this action.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a68ab5e23f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +598 to +602
const agentId = this.sessionAgentId(key);
const forkedKey = await context.sessions.create({
parentSessionKey: key,
fork: true,
...(agentId ? { agentId } : {}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Fork sessions under their parent agent

When the Sessions page forks a normal non-default agent row such as agent:work:dashboard:123, this.sessionAgentId(key) returns no agent id because it only scopes global/main aliases, so this sessions.create call omits agentId. The gateway then copies the parent transcript but creates the new dashboard session under the default agent, unlike the sidebar/iOS/Android fork paths that parse the parent key's agent, so the fork lands in the wrong agent and uses the wrong agent defaults.

Useful? React with 👍 / 👎.

@steipete steipete self-assigned this Jul 6, 2026
@steipete
steipete force-pushed the claude/friendly-germain-e9501b branch from 4875918 to cc58f9d Compare July 6, 2026 12:57
@clawsweeper clawsweeper Bot removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jul 6, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cc58f9d045

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

?disabled=${props.loading}
@click=${(event: MouseEvent) => {
event.stopPropagation();
props.onPatch(row.key, { unread: row.unread !== true });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Scope global unread actions to the row owner

When the Sessions page is listing combined agent stores, loadCombinedSessionStoreForGateway canonicalizes per-agent global rows to the single key global, so the visible row does not carry its owner agent. This new unread toggle passes only row.key; SessionsPage.patchSession then derives agentId from the currently selected agent via sessionAgentId, and sessions.patch resolves an unscoped global to that/default global row. If the displayed global row is from another agent, or the user changes the selected agent after loading, Mark read/unread mutates the wrong global session while the shown row stays unchanged.

Useful? React with 👍 / 👎.

…web, iOS, and Android

Gateway (additive, no protocol version bump): SessionEntry gains
lastReadAt/markedUnreadAt/lastActivityAt; session rows expose a derived
unread flag (explicit mark, or last read before latest activity; never-read
sessions stay read so upgrades do not light up). lastActivityAt is stamped
in the canonical post-run store update - user, channel, and cron runs count
as activity; heartbeat, internal-event, and preserved-state runs do not.
sessions.patch gains unread; sessions.create gains fork (transcript fork
from parentSessionKey under the parent lifecycle lock, refusing active,
concurrently-changed, and oversized parents, cross-agent aware).

Web sidebar: Pinned/custom-group/Ungrouped sections, unread dots, kebab and
right-click context menu (pin, mark unread/read, rename, fork, move to group,
archive, delete guarded for agent main sessions and active runs), mark-read
on view with loop-safe re-acknowledgement and failure retry; sessions page
gets unread + fork actions and shared custom-group helpers.

iOS Command Center: grouped sections, unread/pin indicators, Show Archived
gated on per-entry state, full context menu with rename/new-group alerts and
delete confirmation, current-session preview guarantee, read-episode
re-acknowledgement; new patch/delete/fork transport calls; Swift protocol
models regenerated.

Android SessionsScreen: grouped headers, unread/pin indicators, Archived
filter gated on per-entry state, long-press menu with the full control set,
agent-scoped forks, explicit label/category clears from session events,
main-session fallback when archiving/deleting the open chat, read-episode
re-acknowledgement with failure retry.

Closes #100739
@steipete
steipete force-pushed the claude/friendly-germain-e9501b branch from cc58f9d to e0852f0 Compare July 6, 2026 13:16

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e0852f05e3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

),
};
}
const fork = await forkSessionFromParent({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Preserve user-only transcripts when forking sessions

When an operator forks a completed parent whose transcript has user/system entries but no assistant message (for example a run that failed after the prompt was recorded), this new sessions.create fork path delegates to forkSessionFromParent, whose runtime falls back to writing only a header unless hasAssistantEntry(source.branchEntries) is true. The resulting child session opens with no copied visible messages even though the UI action is presented as a transcript fork; the explicit operator fork path should force the branch entries to be written or otherwise reject this case instead of silently creating an empty child.

Useful? React with 👍 / 👎.

@steipete
steipete merged commit 88f1ec3 into main Jul 6, 2026
100 checks passed
@steipete
steipete deleted the claude/friendly-germain-e9501b branch July 6, 2026 13:30
steipete added a commit that referenced this pull request Jul 6, 2026
…tion (#100964)

sessions.patch becomes a params-aware dynamic-scope method: operator.write
now authorizes patches that touch only user-level chat-organization fields
(label, category, pinned, archived, unread); every other field, mixed
patches, and unknown keys keep requiring operator.admin (fail closed).
This unblocks the session controls shipped in #100814 for the Android app,
whose bounded operator session intentionally never requests operator.admin.

Also: session list ordering gains a deterministic key tiebreaker for equal
pinnedAt/updatedAt (stable offset paging and prompt-cache friendliness), a
user-assigned label now beats stored channel-derived display names in the
row projection so renames survive refreshes, and subagent spawn pins its
gateway calls to admin via the params-aware least-privilege resolver
instead of the static admin-only method check (#59428 contract preserved).

Refs #100712
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 7, 2026
…web, iOS, and Android (openclaw#100814)

Gateway (additive, no protocol version bump): SessionEntry gains
lastReadAt/markedUnreadAt/lastActivityAt; session rows expose a derived
unread flag (explicit mark, or last read before latest activity; never-read
sessions stay read so upgrades do not light up). lastActivityAt is stamped
in the canonical post-run store update - user, channel, and cron runs count
as activity; heartbeat, internal-event, and preserved-state runs do not.
sessions.patch gains unread; sessions.create gains fork (transcript fork
from parentSessionKey under the parent lifecycle lock, refusing active,
concurrently-changed, and oversized parents, cross-agent aware).

Web sidebar: Pinned/custom-group/Ungrouped sections, unread dots, kebab and
right-click context menu (pin, mark unread/read, rename, fork, move to group,
archive, delete guarded for agent main sessions and active runs), mark-read
on view with loop-safe re-acknowledgement and failure retry; sessions page
gets unread + fork actions and shared custom-group helpers.

iOS Command Center: grouped sections, unread/pin indicators, Show Archived
gated on per-entry state, full context menu with rename/new-group alerts and
delete confirmation, current-session preview guarantee, read-episode
re-acknowledgement; new patch/delete/fork transport calls; Swift protocol
models regenerated.

Android SessionsScreen: grouped headers, unread/pin indicators, Archived
filter gated on per-entry state, long-press menu with the full control set,
agent-scoped forks, explicit label/category clears from session events,
main-session fallback when archiving/deleting the open chat, read-episode
re-acknowledgement with failure retry.

Closes openclaw#100739
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 7, 2026
…tion (openclaw#100964)

sessions.patch becomes a params-aware dynamic-scope method: operator.write
now authorizes patches that touch only user-level chat-organization fields
(label, category, pinned, archived, unread); every other field, mixed
patches, and unknown keys keep requiring operator.admin (fail closed).
This unblocks the session controls shipped in openclaw#100814 for the Android app,
whose bounded operator session intentionally never requests operator.admin.

Also: session list ordering gains a deterministic key tiebreaker for equal
pinnedAt/updatedAt (stable offset paging and prompt-cache friendliness), a
user-assigned label now beats stored channel-derived display names in the
row projection so renames survive refreshes, and subagent spawn pins its
gateway calls to admin via the params-aware least-privilege resolver
instead of the static admin-only method check (openclaw#59428 contract preserved).

Refs openclaw#100712
giodl73-repo pushed a commit to giodl73-repo/openclaw that referenced this pull request Jul 8, 2026
…web, iOS, and Android (openclaw#100814)

Gateway (additive, no protocol version bump): SessionEntry gains
lastReadAt/markedUnreadAt/lastActivityAt; session rows expose a derived
unread flag (explicit mark, or last read before latest activity; never-read
sessions stay read so upgrades do not light up). lastActivityAt is stamped
in the canonical post-run store update - user, channel, and cron runs count
as activity; heartbeat, internal-event, and preserved-state runs do not.
sessions.patch gains unread; sessions.create gains fork (transcript fork
from parentSessionKey under the parent lifecycle lock, refusing active,
concurrently-changed, and oversized parents, cross-agent aware).

Web sidebar: Pinned/custom-group/Ungrouped sections, unread dots, kebab and
right-click context menu (pin, mark unread/read, rename, fork, move to group,
archive, delete guarded for agent main sessions and active runs), mark-read
on view with loop-safe re-acknowledgement and failure retry; sessions page
gets unread + fork actions and shared custom-group helpers.

iOS Command Center: grouped sections, unread/pin indicators, Show Archived
gated on per-entry state, full context menu with rename/new-group alerts and
delete confirmation, current-session preview guarantee, read-episode
re-acknowledgement; new patch/delete/fork transport calls; Swift protocol
models regenerated.

Android SessionsScreen: grouped headers, unread/pin indicators, Archived
filter gated on per-entry state, long-press menu with the full control set,
agent-scoped forks, explicit label/category clears from session events,
main-session fallback when archiving/deleting the open chat, read-episode
re-acknowledgement with failure retry.

Closes openclaw#100739
giodl73-repo pushed a commit to giodl73-repo/openclaw that referenced this pull request Jul 8, 2026
…tion (openclaw#100964)

sessions.patch becomes a params-aware dynamic-scope method: operator.write
now authorizes patches that touch only user-level chat-organization fields
(label, category, pinned, archived, unread); every other field, mixed
patches, and unknown keys keep requiring operator.admin (fail closed).
This unblocks the session controls shipped in openclaw#100814 for the Android app,
whose bounded operator session intentionally never requests operator.admin.

Also: session list ordering gains a deterministic key tiebreaker for equal
pinnedAt/updatedAt (stable offset paging and prompt-cache friendliness), a
user-assigned label now beats stored channel-derived display names in the
row projection so renames survive refreshes, and subagent spawn pins its
gateway calls to admin via the params-aware least-privilege resolver
instead of the static admin-only method check (openclaw#59428 contract preserved).

Refs openclaw#100712
Rorqualx pushed a commit to Rorqualx/cortex that referenced this pull request Jul 20, 2026
…session-row lifecycle, readiness draining)

Net-new tsgo:core divergences (vs pre-merge baseline) cleared in 3 clusters:

A. infra/sim (fork-only sim harness): upstream renamed packages/llm-runtime ->
   packages/ai and refactored api-registry into a factory with module-level
   convenience fns re-exported from @openclaw/ai/internal/runtime. Repointed
   record-replay/scripted-model-provider/cron-simulation.demo imports; mapped the
   removed ApiProviderInternal type -> RegisteredApiProvider (getApiProvider's
   return). Also sourced Api from the ../../llm/types facade + dropped an unused
   AssistantMessageEvent import in the block being rewritten.

B. session-row wire schema: upstream's session-event-payload projection (openclaw#100814
   grouping/unread/pin/archive) reads archived/pinned/unread/category/*At off
   GatewaySessionRow; the merge=ours-adjacent fork schema lacked them. Added the 8
   optional lifecycle fields so the adopted projection compiles and emits safe
   defaults. Full buildGatewaySessionRow<-SessionEntry population deferred.

C. server.impl readiness: fork readiness.ts supports getGatewayDraining (reports
   failing:["gateway-draining"]) but the fork call site never passed it (masked
   divergence); wired getGatewayDraining: isGatewayDraining and dropped the
   genuinely-unused PluginRuntime type import.

Local tsgo:core: 35 -> 14 unique (remaining 14 are pre-existing fork known-red:
exec-policy-cli, grounding/verifier, infra/sim not-exported/unused, deferred-UI).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: android App: android app: ios App: ios app: web-ui App: web-ui docs Improvements or additions to documentation gateway Gateway runtime maintainer Maintainer-authored PR P2 Normal backlog priority with limited blast radius. size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Session grouping, unread state, and full session controls on web, iOS, and Android

1 participant