feat(kap-server): add page mode, updated_before, and batch archive/restore to v2 sessions - #2983
Conversation
The v2 session list gains a stateless 1-based `page` parameter beside the opaque page_token cursor for admin-style lists that jump arbitrarily: each request stays a full independent snapshot, no token is minted, and `page` + `page_token` together fail 40001. Every response now carries `total` (the filtered/sorted set size) in both pagination modes.
Symmetric with meta.updated_after (inclusive boundary, Unix ms), applied at the edge over the drained set and bound into the page_token query fingerprint like every other condition.
…h endpoints
Batch archive/restore for session-management views: { ids } (non-empty,
≤5000 unique after dedup) answers per-item results in input order with
succeeded/failed counts — only a body validation failure fails the whole
request, and an unknown id folds into its own item as 40401.
The live/cold split keeps the batch cheap: a session with a live handle
goes through the full ISessionLifecycleService chain (agents drain,
scope teardown, mirror drain), while a cold session is never
materialized — the new setColdSessionArchived helper in agent-core-v2
patches the persisted state.json (archived/archivedAt, updatedAt
preserved, mirroring setArchived's touchUpdatedAt: false semantics),
mirrors the flipped summary into the read-model queue, and republishes
the same event.session.archived bus event the live lifecycle emits
(:restore publishes nothing, matching the live restore). Hot items run
with bounded concurrency and the batch ends with one shared
ISessionIndexMirror.drain().
…re, and batch archive/restore
|
|
❌ Nix build failed |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6ae1b2f0e6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…ssions route CI's tsgo/rolldown (Linux) fail to bind liveHandlerForSession and IWorkspaceLifecycleService through the agent-core-v2 package-root barrel even though it re-exports them; the same files use the established deep-import pattern already used for the git domain.
commit: |
The previous deep imports still fail to resolve on CI's Linux toolchain (tsgo TS2307, rolldown MISSING_EXPORT) while every other module path from the same package binds fine. Keep the route self-contained: the hot-path lookup is a five-line loop over IWorkspaceLifecycleService's handlers (mirrors agent-core-v2's liveHandlerForSession), and the tests assert non-materialization behaviorally via the live map instead of importing the same two symbols for spies.
The phantom only hits the workspaceLifecycle-group symbols in these two files on CI's Linux toolchain; getLiveSessionById is observed to bind fine there. It returns the session's live scope directly (no resume), which is exactly what the batch hot path needs.
setSessionArchivedBatch owns the split next to the cold patch: live sessions go through the full lifecycle chain via the workspace handler accessor (the v1-proven resolution path), cold sessions through the direct write. The route becomes a thin wire-code adapter, and the batch tests assert the live chain behaviorally (disposal, events, index) instead of spying through scope accessors.
…rchive The '#/app/workspaceLifecycle/*' specifier resolves from src/ and src/app/* files on CI's Linux toolchain but not from src/workspace/sessionLifecycle/ (tsgo TS2307, rolldown follows); a relative import bypasses the package-imports mapping.
Main's workspace/session DI refactor removed the workspaceLifecycle lookup modules; the live branch now goes through the App-level ISessionManager (the same entry the v1 action route uses post-refactor) with getLiveSessionById from the new sessionManager lookup.
…sessions
fields=id,archived trims each item to { id, archived } for
select-all-matching flows (the session admin page's Gmail-style
select-all). Only that projection gets the relaxed page_size ceiling
(10000); unknown fields, non-pair subsets, and include=git combinations
are 40001, and the projection binds into the page_token fingerprint so
shapes never flip mid-pagination.
…resumes Codex review on #2983: while a resume is in flight the live registry hides the handle, so the batch route could classify the session as cold and its direct write would race the materializing metadata service (its stale in-memory document wins the next write, silently un-archiving the session after the endpoint reported success). The batch now settles the resume first: SessionManager registers the whole resume promise synchronously at the App level (controllerForSession is async, so the controller's own resuming map learns about it a few microtasks late) and whenResumeSettled awaits it before classification — a settled resume lands the item on the live chain, a failed one falls back to the cold path. Also folds the module header down to the package's external-role comment convention.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 63a8130a7b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…cycle transitions
|
@codex review |
1 similar comment
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 46db11d6fd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 46db11d6fd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@codex review |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e825bc2bc8
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aa05630e2e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…d batch sections unguarded ops
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 46d88dedb1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| } | ||
|
|
||
| async whenResumeSettled(sessionId: string): Promise<void> { | ||
| await this.pendingResumes.get(sessionId)?.catch(() => undefined); |
There was a problem hiding this comment.
Propagate resume failures before cold classification
When a concurrent resume fails after materializeSession() has inserted the handle into the workspace handler but before announceCreated() registers it with SessionManager (for example, main-agent creation fails), this catch suppresses the failure and the batch sees no live session through the App registry. It then performs a cold metadata write and reports success without archiving or disposing the handler-owned live scope, leaving the two lifecycle registries inconsistent. Propagate the resume rejection so this batch item becomes an internal error rather than continuing to cold classification.
AGENTS.md reference: packages/agent-core-v2/AGENTS.md:L7-L7
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2c0e315daa
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // A fresh attempt supersedes any earlier failure record. | ||
| this.resumeFailures.delete(sessionId); |
There was a problem hiding this comment.
Reconcile the orphaned handle before clearing resume failure
When the first resume fails during main-agent creation, materializeSession() has already inserted the handle into the workspace controller, but announceCreated() has not populated the App registry. A normal retry reaches this line, clears the recorded failure, and then the controller returns that existing handle without announcing it; the retry therefore reports success while owners remains empty. A subsequent batch operation sees neither a failure nor an App-visible live session and performs a cold write against the still-live scope. Fresh evidence beyond the earlier failed-resume comment is that the newly added “fresh attempt supersedes” path clears the only guard without reconciling this controller-owned handle.
Useful? React with 👍 / 👎.
…ils mid-materialization
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 165fdf37c7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const raw = await docs.get<SessionMeta>(metaScope, 'state.json'); | ||
| if (raw === undefined) return 'not_found'; |
There was a problem hiding this comment.
Read the legacy metadata location before returning not found
When an older v2 session still stores metadata only at <sessionDir>/session-meta/state.json, ISessionIndex.get() finds it because sessionIndexSource.ts explicitly falls back to that legacy location, but this read checks only <sessionDir>/state.json. Batch archive/restore therefore returns a per-item 40401 and leaves the archive state unchanged for a session that remains visible in session listings; use the authoritative reader's fallback and then write or migrate the same document before classifying the session as missing.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b0c43b6e3c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const applyOne = async (id: string): Promise<SessionArchiveBatchItemOutcome> => { | ||
| try { | ||
| const manager = accessor.get(ISessionManager); | ||
| return await manager.withLifecycleSerialization(id, async (unguarded) => { |
There was a problem hiding this comment.
Serialize target creation with batch archive operations
When an in-process caller creates a session with an explicit sessionId while a batch archives that same ID, this critical section does not exclude creation because SessionManager.create() bypasses the per-session chain. After creation has written and mirrored initial metadata but before announceCreated() populates the App registry, the batch classifies the session as cold and writes archived: true; the still-live metadata service retains archived: false, so a subsequent metadata update can overwrite the archive while the endpoint has already reported success. Queue creation on the target session ID's chain as well.
AGENTS.md reference: packages/agent-core-v2/AGENTS.md:L7-L7
Useful? React with 👍 / 👎.
# Conflicts: # packages/agent-core-v2/src/workspace/sessionLifecycle/sessionLifecycleService.ts # packages/kap-server/src/routes/v2/sessions.ts # packages/kap-server/test/v2Sessions.test.ts
…ifecycle chain create() with a caller-supplied sessionId bypassed the per-session chain, so a concurrent batch archive could classify the half-created session as cold and write archived state that the live metadata service later overwrites. Creation now queues on the target id's chain whenever an explicit id is present. Also type the resume-failure maps as Error and normalize at the catch site, satisfying only-throw-error.
…comments convention
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b079259c5b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…he lifecycle chain fork() and createChild() with a newSessionId locked only the source id, so a batch archive of the target could slip into the creation window: the index already knows the half-created session, the batch writes archived state to its document, and the fork's in-memory metadata later overwrites it. Both operations now acquire the deduped, sorted key set so multi-key sections always take locks in one deterministic order.
|
@codex review |
|
Codex Review: Didn't find any major issues. You're on a roll. Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
Related Issue
No tracking issue — the problem is explained below (this PR accompanies the session admin page in the desktop client, code-app PR #221).
Problem
The desktop client's new session admin page (an admin-style management view with numbered pagination, an updated-at range filter, and batch complete/reopen) needs three capabilities the v2 sessions API does not offer today:
GET /api/v2/sessionsonly supports opaque keyset cursors — a management table cannot render "共 N 条 / page k of M" or jump to an arbitrary page. The route already drains the (workspace/archive-filtered) set into memory and sorts it there, so a total and offset slicing are free.meta.updated_afterexists; a date-range filter needsmeta.updated_before(a symmetric edge filter — the gap is a missing requirement, not a performance concern).resume()s the session first — becausearchive()only operates on live handles (a cold session is a silent no-op). Batch-archiving hundreds of cold sessions through that path would fully materialize every one of them (session scope + main agent + MCP seed) for no benefit.What changed
GET /api/v2/sessions— page-number mode +totaltotal(the filtered/sorted set size; cursor mode benefits too).page(1-based): a stateless slice over the in-memory sorted set — no pageToken issued, no fingerprint checked, since every request carries its full conditions.pageandpage_tokenare mutually exclusive (40001).GET /api/v2/sessions—meta.updated_beforeupdated_after(inclusive bound), wired into the normalized query and the query fingerprint.POST /api/v2/sessions:archiveandPOST /api/v2/sessions:restore{ ids }(non-empty, ≤ 5000 unique), per-item results in input order plus succeeded/failed counts; only body validation fails the whole request, unknown ids fold into their own item (40401).archive()/restore()); cold sessions are patched on disk directly — atomicstate.jsonrewrite preserving every field incl.updatedAt, read-model mirror, and the sameevent.session.archived(restore publishes none, matching the live path) — without materializing anything. Bounded concurrency for live items, oneISessionIndexMirror.drain()at the end.Tests: 12 new cases in
packages/kap-server/test/v2Sessions.test.ts(page mode shape/exclusivity,updated_beforebounds + fingerprint drift, cold archive equivalence without materialization, hot path, mixed batches, validation) — full kap-server (1071) and agent-core-v2 (5067) suites green. API reference updated (docs/{en,zh}/reference/server-api.md).Checklist
gen-changesetsskill, or this PR needs no changeset. — One changeset for the agent-core-v2 behavior (cold-session-batch-archive.md, patch); the pure protocol additions (page mode / total / updated_before) ship no changeset per the skill's bundled-web-UI rule, same as the v2 API introduction.gen-docsskill, or this PR needs no doc update. —server-api.mdupdated in en + zh.