Skip to content

feat(agent): add session tape memory#1669

Merged
zerob13 merged 3 commits into
devfrom
feat/deepchat-tape-memory
May 25, 2026
Merged

feat(agent): add session tape memory#1669
zerob13 merged 3 commits into
devfrom
feat/deepchat-tape-memory

Conversation

@yyhhyyyyyy
Copy link
Copy Markdown
Collaborator

@yyhhyyyyyy yyhhyyyyyy commented May 25, 2026

Adds SQLite-backed, session-scoped tape memory for DeepChat agents, inspired by Bub's tape model; for more on the underlying tape architecture, see tape.systems.

Summary by CodeRabbit

  • New Features
    • Added a session "tape" system for recording conversation history with anchors, handoffs, search, and fork/merge/discard workflows; new presenter and tool APIs expose tape info, search, anchors, and handoff.
  • Documentation
    • Expanded the acknowledgements to include additional third‑party projects.
  • Tests
    • Added extensive tests for tape behavior, search, anchors, handoffs, fork lifecycle, and compaction/resume scenarios.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 25, 2026

📝 Walkthrough

Walkthrough

Adds an append-only session tape: DB schema and table, tape service, fact writers/readers and effective view, integration into message/session/compaction flows, presenter/tool APIs and MCP tools, fork/handoff management, tests, and README acknowledgments.

Changes

Tape Core Implementation

Layer / File(s) Summary
Tape Database Schema and SQLite Wiring
src/main/presenter/sqlitePresenter/tables/deepchatTapeEntries.ts, src/main/presenter/sqlitePresenter/index.ts, src/main/presenter/sqlitePresenter/schemaCatalog.ts
Defines deepchat_tape_entries table schema, provenance/search helpers, indexes, and wires the table into SQLitePresenter with migration and cleanup support.
Tape Service and Session Management
src/main/presenter/agentRuntimePresenter/tapeService.ts, src/main/presenter/agentRuntimePresenter/sessionStore.ts
Implements DeepChatTapeService for backfill/bootstrap, handoff/anchors, forks (create/merge/discard), info/search, and legacy summary migration; session store now resolves and manages summary state from tape anchors and bootstraps tape anchors on create/delete.
Message Fact Recording and Effective View
src/main/presenter/agentRuntimePresenter/tapeFacts.ts, src/main/presenter/agentRuntimePresenter/tapeEffectiveView.ts
Writes message/tool facts (live/replacement/retraction/tool_call/tool_result), builds an effective tape projection with dedupe/ranking, search helpers, and readers that map tape entries into ChatMessageRecord objects.
Message Store Tape Integration
src/main/presenter/agentRuntimePresenter/messageStore.ts
Appends tape facts for user/assistant/compaction messages, content updates, and deletions; adds transactional helpers and conditional live-fact append when tape is present.

Tape Integration with Session State and Compaction

Layer / File(s) Summary
Session Store Tape Support
src/main/presenter/agentRuntimePresenter/sessionStore.ts
Adds reconstruction-anchor prompt state types, prefers reconstruction anchors for summary resolution, updates compare-and-set/reset to optionally append tape anchors inside transactions, and bootstraps/deletes tape anchors with sessions.
Compaction Service Tape Integration
src/main/presenter/agentRuntimePresenter/compactionService.ts
Compaction intents accept optional historyRecords, include anchorName, compute summaryRange/sourceMessageIds/summaryableTurnCount, and persist expanded summary anchor payloads; adds appendReconstructionAnchorStateSection to serialize visible anchor state into prompts.
Context Builder and AgentRuntimePresenter Tape Wiring
src/main/presenter/agentRuntimePresenter/index.ts, src/main/presenter/agentRuntimePresenter/contextBuilder.ts
Presenter uses DeepChatTapeService.ensureSessionTapeReady as history source across processing, compaction, recovery, and resume flows, threads tapeReady.historyRecords into builders, and wraps system prompt assembly with reconstruction-anchor serialization.

Tape Public APIs and Tool Integration

Layer / File(s) Summary
Session Presenter and Runtime Port Tape Methods
src/main/presenter/agentSessionPresenter/index.ts, src/main/presenter/toolPresenter/runtimePorts.ts
Adds presenter methods getTapeInfo, searchTape, listTapeAnchors, handoffTape, mergeSubagentTape, discardSubagentTape and extends runtime port types with optional tape APIs.
Agent Interface and Presenter Type Definitions
src/shared/types/agent-interface.d.ts, src/shared/types/presenters/agent-session.presenter.d.ts
Adds tape types (AgentTapeInfo, search/anchor shapes) and extends IAgentImplementation/IAgentSessionPresenter with tape method signatures.
Tape Tools Handler and Integration
src/main/presenter/toolPresenter/agentTools/agentTapeTools.ts, src/main/presenter/toolPresenter/agentTools/agentToolManager.ts, src/main/presenter/toolPresenter/index.ts
Implements AgentTapeToolHandler with Zod-validated tool schemas (tape_info, tape_search, tape_anchors, tape_handoff), integrates tape tools into tool manager routing and system prompts, and reserves tape tool names.
Subagent Orchestrator Tape Finalization
src/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.ts
Tracks per-task tapeFinalized and tapeFinalizeError, idempotently finalizes subagent tapes by calling merge or discard on terminal task outcomes, and surfaces finalization errors in run/task results.
Presenter Public Wiring and Security
src/main/presenter/index.ts, src/main/presenter/databaseSecurityPresenter/index.ts
Exposes tape operations via agentToolRuntime pass-throughs and includes deepchat_tape_entries in database migration validation.

Tape Tests and Documentation

Layer / File(s) Summary
Tape Table, Service, and State Tests
test/main/presenter/sqlitePresenter/deepchatTapeEntriesTable.test.ts, test/main/presenter/agentRuntimePresenter/tapeService.test.ts, test/main/presenter/agentRuntimePresenter/sessionStoreTape.test.ts
Unit and integration tests covering table behavior, idempotent backfill, effective view/search, handoff enrichment, legacy summary migration, fork lifecycle, and session-store anchor resolution.
Compaction, Presenter, and Tool Tests
test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts, test/main/presenter/agentRuntimePresenter/compactionService.test.ts, test/main/presenter/toolPresenter/agentTools/agentTapeTools.test.ts, test/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.test.ts, test/main/presenter/sqlitePresenter.migrationSqlSplit.test.ts
Adds presenter mock with stateful tape table, tightens compaction CAS assertions, tests reconstruction-anchor prompt serialization, validates tape tool exposure and negative handoff input, and covers subagent finalization retry/failure behavior.
MessageStore, Deletion, and Replacement Tests
test/main/presenter/agentRuntimePresenter/messageStore.test.ts
Adds tests for transactional delete failure behavior, compaction message updates appended to tape, and update/delete workflows.
Tool Prompt and System Docs
test/main/presenter/toolPresenter/toolPresenter.test.ts, README.md, README.jp.md, README.zh.md
Verifies tape tool help appears only for enabled tools and updates README acknowledgments to include Bub (noting tape model inspiration).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

codex

Suggested reviewers

  • zerob13

Poem

"🐇 I stitched a tape of every chat and art,

Anchors planted like seeds to restart,
Forks and handoffs recorded with care,
Bub whispered a model — I borrowed a share,
Hop, hop, the session's story lives on, everywhere."

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/deepchat-tape-memory

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/presenter/agentRuntimePresenter/messageStore.ts (2)

166-183: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Record compaction message updates in tape as well.

This only captures the initial compaction message state. If the row is created as 'compacting' and later updated to 'compacted' in updateCompactionMessage(), tape-backed history stays on the stale version and reconstruction can surface the wrong compaction status. Add a replacement/live tape append after the update path too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/presenter/agentRuntimePresenter/messageStore.ts` around lines 166 -
183, createCompactionMessage currently appends the new compaction row to the
live tape but updateCompactionMessage does not, leaving tape with a stale
'compacting' record; modify updateCompactionMessage (the function that updates
the deepchatMessagesTable row to 'compacted') to also call appendLiveTapeFacts
with the updated message id (or append a replacement event) after the DB update
so the tape reflects the new content/metadata built via buildCompactionBlocks
and buildCompactionMetadata; ensure you use the same id and include updated JSON
content and metadata in the replacement entry so reconstruction reads the latest
compaction status.

371-387: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make tape retractions and row deletes atomic.

Both delete paths append the retraction fact before deleting the message rows and related secondary tables. If any later delete fails, tape says the message was removed while SQLite still contains it, which breaks the tape-as-source-of-truth model introduced by this PR. Wrap the tape append and all related deletes in one SQLite transaction.

Also applies to: 390-412

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/presenter/agentRuntimePresenter/messageStore.ts` around lines 371 -
387, The deleteMessage path currently calls appendMessageRetractionToTape(...)
and then performs multiple deletes on sqlitePresenter.deepchat* tables
(deepchatSearchDocumentsTable, deepchatAssistantBlocksTable,
deepchatUserMessageLinksTable, deepchatUserMessageFilesTable,
deepchatUserMessagesTable, deepchatMessageTracesTable,
deepchatMessageSearchResultsTable, deepchatMessagesTable) separately, which can
leave tape inconsistent if a later delete fails; modify deleteMessage to perform
the appendMessageRetractionToTape call and all subsequent deletes inside a
single SQLite transaction (use the sqlitePresenter's existing
transaction/connection API—e.g. begin/commit or a runInTransaction helper) so
that either all operations commit or all roll back; apply the same transactional
change to the other deletion path referenced around lines 390-412.
src/main/presenter/agentRuntimePresenter/sessionStore.ts (1)

203-240: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Preserve compare-and-set semantics when no tape anchor is passed.

The optional tapeAnchor creates a broken success path here: without it, this does a non-atomic read/compare/write and only updates deepchatSessionsTable, while getSummaryState() still prefers the previous tape anchor. That means applied: true can be returned even though later reads keep surfacing the old summary state, and concurrent callers can both win. Either require an anchor for every successful update, or restore the DB-side CAS and keep tape state in sync in the same transaction.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/presenter/agentRuntimePresenter/sessionStore.ts` around lines 203 -
240, The compareAndSetSummaryState implementation currently only runs the DB
transaction when a tapeAnchor is provided, causing non-atomic read/compare/write
when tapeAnchor is absent; fix by always running applyUpdate inside a DB
transaction when a database is available (use this.sqlitePresenter.getDatabase
and db.transaction(applyUpdate)()), and only fall back to non-transactional
applyUpdate when no DB is present; keep conditional appendAnchor logic
(this.sqlitePresenter.deepchatTapeEntriesTable.appendAnchor) inside applyUpdate
so tape updates happen in the same transaction when present, and ensure the
returned applied/currentState value is based on the transaction result.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/presenter/agentRuntimePresenter/tapeFacts.ts`:
- Around line 287-307: The appended "message/retracted" event is including a
source object which auto-generates a provenance key and can cause
unique-provenance collisions for rapid non-idempotent retractions; in the call
to table.appendEvent (the event with name 'message/retracted' and idempotent:
false), remove the source property (do not supply source) so no provenance is
auto-generated for this non-idempotent retraction. Ensure the rest of the
payload (sessionId, data, meta, idempotent: false) remains unchanged.

In `@src/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.ts`:
- Around line 388-403: The code sets task.tapeFinalized = true before calling
the runtime APIs (this.runtimePort.mergeSubagentTape / discardSubagentTape),
which will suppress retry if those calls fail; move the assignment so it only
happens after the awaited mergeSubagentTape or discardSubagentTape resolves
successfully (i.e., set task.tapeFinalized = true inside the try block after the
await completes for either the completed or non-completed branch) so failed
merges/discards leave tapeFinalized false and can be retried; update references
in subagentOrchestratorTool (task, runtimePort.mergeSubagentTape,
runtimePort.discardSubagentTape, parentSessionId, task.sessionId, meta)
accordingly.

In `@src/main/presenter/toolPresenter/index.ts`:
- Around line 638-650: The buildTapePrompt function currently unconditionally
documents `tape_info`, `tape_search`, and `tape_handoff`; change it to inspect
the provided tools array (MCPToolDefinition[] tools) and only include
descriptions for the tools that are present (use tool.function.name to detect
each). Build the `names` list from tools as you do now, then conditionally
append the specific descriptive lines for `tape_info`, `tape_search`, and
`tape_handoff` only when a matching tool name exists in tools; keep the header
("## Tape Tools") and join the resulting lines into the final string returned by
buildTapePrompt.

In `@test/main/presenter/agentRuntimePresenter/tapeService.test.ts`:
- Around line 129-133: The mock search implementation (vi.fn named search) trims
the incoming query into normalizedQuery but still proceeds to filter entries
even when normalizedQuery is empty; update the mock so that after computing
normalizedQuery it immediately returns an empty array when normalizedQuery ===
'' to match the real table contract, preserving the existing options.limit
handling and the subsequent entries.filter logic for non-empty queries
(references: search, normalizedQuery, entries).

---

Outside diff comments:
In `@src/main/presenter/agentRuntimePresenter/messageStore.ts`:
- Around line 166-183: createCompactionMessage currently appends the new
compaction row to the live tape but updateCompactionMessage does not, leaving
tape with a stale 'compacting' record; modify updateCompactionMessage (the
function that updates the deepchatMessagesTable row to 'compacted') to also call
appendLiveTapeFacts with the updated message id (or append a replacement event)
after the DB update so the tape reflects the new content/metadata built via
buildCompactionBlocks and buildCompactionMetadata; ensure you use the same id
and include updated JSON content and metadata in the replacement entry so
reconstruction reads the latest compaction status.
- Around line 371-387: The deleteMessage path currently calls
appendMessageRetractionToTape(...) and then performs multiple deletes on
sqlitePresenter.deepchat* tables (deepchatSearchDocumentsTable,
deepchatAssistantBlocksTable, deepchatUserMessageLinksTable,
deepchatUserMessageFilesTable, deepchatUserMessagesTable,
deepchatMessageTracesTable, deepchatMessageSearchResultsTable,
deepchatMessagesTable) separately, which can leave tape inconsistent if a later
delete fails; modify deleteMessage to perform the appendMessageRetractionToTape
call and all subsequent deletes inside a single SQLite transaction (use the
sqlitePresenter's existing transaction/connection API—e.g. begin/commit or a
runInTransaction helper) so that either all operations commit or all roll back;
apply the same transactional change to the other deletion path referenced around
lines 390-412.

In `@src/main/presenter/agentRuntimePresenter/sessionStore.ts`:
- Around line 203-240: The compareAndSetSummaryState implementation currently
only runs the DB transaction when a tapeAnchor is provided, causing non-atomic
read/compare/write when tapeAnchor is absent; fix by always running applyUpdate
inside a DB transaction when a database is available (use
this.sqlitePresenter.getDatabase and db.transaction(applyUpdate)()), and only
fall back to non-transactional applyUpdate when no DB is present; keep
conditional appendAnchor logic
(this.sqlitePresenter.deepchatTapeEntriesTable.appendAnchor) inside applyUpdate
so tape updates happen in the same transaction when present, and ensure the
returned applied/currentState value is based on the transaction result.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 204ee85e-048e-41ff-bdc2-f6e9a6da5fb0

📥 Commits

Reviewing files that changed from the base of the PR and between d5bae13 and 1127faf.

📒 Files selected for processing (33)
  • README.jp.md
  • README.md
  • README.zh.md
  • src/main/presenter/agentRuntimePresenter/compactionService.ts
  • src/main/presenter/agentRuntimePresenter/contextBuilder.ts
  • src/main/presenter/agentRuntimePresenter/index.ts
  • src/main/presenter/agentRuntimePresenter/messageStore.ts
  • src/main/presenter/agentRuntimePresenter/sessionStore.ts
  • src/main/presenter/agentRuntimePresenter/tapeEffectiveView.ts
  • src/main/presenter/agentRuntimePresenter/tapeFacts.ts
  • src/main/presenter/agentRuntimePresenter/tapeService.ts
  • src/main/presenter/agentSessionPresenter/index.ts
  • src/main/presenter/databaseSecurityPresenter/index.ts
  • src/main/presenter/index.ts
  • src/main/presenter/sqlitePresenter/index.ts
  • src/main/presenter/sqlitePresenter/schemaCatalog.ts
  • src/main/presenter/sqlitePresenter/tables/deepchatTapeEntries.ts
  • src/main/presenter/toolPresenter/agentTools/agentTapeTools.ts
  • src/main/presenter/toolPresenter/agentTools/agentToolManager.ts
  • src/main/presenter/toolPresenter/agentTools/index.ts
  • src/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.ts
  • src/main/presenter/toolPresenter/index.ts
  • src/main/presenter/toolPresenter/runtimePorts.ts
  • src/shared/types/agent-interface.d.ts
  • src/shared/types/presenters/agent-session.presenter.d.ts
  • test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts
  • test/main/presenter/agentRuntimePresenter/compactionService.test.ts
  • test/main/presenter/agentRuntimePresenter/sessionStoreTape.test.ts
  • test/main/presenter/agentRuntimePresenter/tapeService.test.ts
  • test/main/presenter/sqlitePresenter.migrationSqlSplit.test.ts
  • test/main/presenter/sqlitePresenter/deepchatTapeEntriesTable.test.ts
  • test/main/presenter/toolPresenter/agentTools/agentTapeTools.test.ts
  • test/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.test.ts

Comment thread src/main/presenter/agentRuntimePresenter/compactionService.ts Outdated
Comment thread src/main/presenter/agentRuntimePresenter/tapeFacts.ts
Comment thread src/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.ts Outdated
Comment thread src/main/presenter/toolPresenter/index.ts Outdated
Comment thread test/main/presenter/agentRuntimePresenter/tapeService.test.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/main/presenter/agentRuntimePresenter/index.ts (1)

261-261: 💤 Low value

Potential stale entry in activeSteerPendingInputIds after cancellation.

The activeSteerPendingInputIds map is cleaned up in destroySession and after successful drain in drainPendingQueueIfPossible, but not in cancelGeneration. If a user cancels generation while a steer input is queued but not yet consumed, the map entry will remain stale until the session is destroyed.

Consider adding cleanup in cancelGeneration:

♻️ Proposed fix
 async cancelGeneration(sessionId: string): Promise<void> {
   const activeGeneration = this.activeGenerations.get(sessionId)
   if (activeGeneration) {
     // ... existing code ...
   } else {
     const controller = this.abortControllers.get(sessionId)
     if (controller) {
       controller.abort()
       this.abortControllers.delete(sessionId)
     }
   }
   this.abortDeferredToolAbortControllers(sessionId)
   this.clearActiveProviderPermissionsForSession(sessionId)
+  this.activeSteerPendingInputIds.delete(sessionId)
   this.setSessionStatus(sessionId, 'idle')
 }

Also applies to: 421-421

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/presenter/agentRuntimePresenter/index.ts` at line 261, The map
activeSteerPendingInputIds can retain stale entries when a generation is
cancelled; update cancelGeneration to remove any associated steer pending id(s)
from activeSteerPendingInputIds (the same cleanup that exists in destroySession
and drainPendingQueueIfPossible). Locate cancelGeneration and ensure it deletes
the entry keyed by the generation/input id (or any mapping used there) from
activeSteerPendingInputIds and/or runs the same cleanup helper used elsewhere so
queued steer inputs are removed immediately on cancellation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/main/presenter/agentRuntimePresenter/index.ts`:
- Line 261: The map activeSteerPendingInputIds can retain stale entries when a
generation is cancelled; update cancelGeneration to remove any associated steer
pending id(s) from activeSteerPendingInputIds (the same cleanup that exists in
destroySession and drainPendingQueueIfPossible). Locate cancelGeneration and
ensure it deletes the entry keyed by the generation/input id (or any mapping
used there) from activeSteerPendingInputIds and/or runs the same cleanup helper
used elsewhere so queued steer inputs are removed immediately on cancellation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 26a32d4b-028e-477f-979e-09d616ca02b8

📥 Commits

Reviewing files that changed from the base of the PR and between 1127faf and 29f3e5e.

📒 Files selected for processing (4)
  • src/main/presenter/agentRuntimePresenter/index.ts
  • src/main/presenter/index.ts
  • src/main/presenter/toolPresenter/agentTools/agentToolManager.ts
  • test/main/presenter/agentRuntimePresenter/agentRuntimePresenter.test.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/main/presenter/agentRuntimePresenter/compactionService.ts (1)

173-185: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cap serialized handoff state before appending it to the system prompt.

Allowlisting fixed field leakage, but a very large summary or reason can still bloat prompt tokens and destabilize context assembly. Add a hard size cap (and truncation marker) before buildUntrustedPromptBlock.

💡 Proposed fix
+const MAX_RECONSTRUCTION_STATE_CHARS = 4000
+
 export function appendReconstructionAnchorStateSection(
   systemPrompt: string,
   anchor: ReconstructionAnchorPromptState | null | undefined
 ): string {
@@
-  const stateJson = JSON.stringify(
+  const rawStateJson = JSON.stringify(
     {
       anchor: anchor.name,
       state: visibleState
     },
     null,
     2
   )
+  const stateJson =
+    rawStateJson.length > MAX_RECONSTRUCTION_STATE_CHARS
+      ? `${rawStateJson.slice(0, MAX_RECONSTRUCTION_STATE_CHARS)}\n... [TRUNCATED]`
+      : rawStateJson
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/presenter/agentRuntimePresenter/compactionService.ts` around lines
173 - 185, The serialized handoff state (stateJson) can be arbitrarily large;
before calling buildUntrustedPromptBlock include a hard size cap and truncation
marker: serialize visibleState as stateJson, check its length, and if it exceeds
the cap (e.g., N chars) truncate it and append an explicit marker like
"…[TRUNCATED]" so the anchorSection uses the capped string; update the code
around stateJson / buildUntrustedPromptBlock (and any helper that consumes it)
to ensure composeSections still receives the truncated payload while preserving
anchor.name and the rest of the structure.
🧹 Nitpick comments (1)
test/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.test.ts (1)

309-340: ⚡ Quick win

Restore console.warn spies via shared cleanup to avoid leak on failed assertions.

If an assertion fails before warnSpy.mockRestore(), the spy can bleed into later tests. Prefer afterEach(() => vi.restoreAllMocks()) for deterministic cleanup.

♻️ Proposed cleanup
-import { describe, expect, it, vi } from 'vitest'
+import { afterEach, describe, expect, it, vi } from 'vitest'
@@
 describe('SubagentOrchestratorTool', () => {
+  afterEach(() => {
+    vi.restoreAllMocks()
+  })
@@
-    warnSpy.mockRestore()
@@
-    warnSpy.mockRestore()
@@
-    warnSpy.mockRestore()

Also applies to: 380-459, 473-562

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@test/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.test.ts`
around lines 309 - 340, The test creates a console.warn spy (warnSpy) and
restores it inline, which can leak if assertions fail; replace per-test inline
restores with deterministic shared cleanup by adding an afterEach hook that
calls vi.restoreAllMocks() (or vi.resetAllMocks()) so all spies including
warnSpy are restored automatically, and ensure the tests referencing
SubagentOrchestratorTool.finalizeTaskTape and the mergeSubagentTape mock remain
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/main/presenter/agentRuntimePresenter/compactionService.ts`:
- Around line 173-185: The serialized handoff state (stateJson) can be
arbitrarily large; before calling buildUntrustedPromptBlock include a hard size
cap and truncation marker: serialize visibleState as stateJson, check its
length, and if it exceeds the cap (e.g., N chars) truncate it and append an
explicit marker like "…[TRUNCATED]" so the anchorSection uses the capped string;
update the code around stateJson / buildUntrustedPromptBlock (and any helper
that consumes it) to ensure composeSections still receives the truncated payload
while preserving anchor.name and the rest of the structure.

---

Nitpick comments:
In
`@test/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.test.ts`:
- Around line 309-340: The test creates a console.warn spy (warnSpy) and
restores it inline, which can leak if assertions fail; replace per-test inline
restores with deterministic shared cleanup by adding an afterEach hook that
calls vi.restoreAllMocks() (or vi.resetAllMocks()) so all spies including
warnSpy are restored automatically, and ensure the tests referencing
SubagentOrchestratorTool.finalizeTaskTape and the mergeSubagentTape mock remain
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a14df6b8-de16-422f-9c72-4e51479bfb53

📥 Commits

Reviewing files that changed from the base of the PR and between 29f3e5e and 644cd08.

📒 Files selected for processing (14)
  • src/main/presenter/agentRuntimePresenter/compactionService.ts
  • src/main/presenter/agentRuntimePresenter/messageStore.ts
  • src/main/presenter/agentRuntimePresenter/sessionStore.ts
  • src/main/presenter/agentRuntimePresenter/tapeFacts.ts
  • src/main/presenter/toolPresenter/agentTools/agentTapeTools.ts
  • src/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.ts
  • src/main/presenter/toolPresenter/index.ts
  • test/main/presenter/agentRuntimePresenter/compactionService.test.ts
  • test/main/presenter/agentRuntimePresenter/messageStore.test.ts
  • test/main/presenter/agentRuntimePresenter/sessionStoreTape.test.ts
  • test/main/presenter/agentRuntimePresenter/tapeService.test.ts
  • test/main/presenter/toolPresenter/agentTools/agentTapeTools.test.ts
  • test/main/presenter/toolPresenter/agentTools/subagentOrchestratorTool.test.ts
  • test/main/presenter/toolPresenter/toolPresenter.test.ts

@zerob13 zerob13 merged commit 01af7ab into dev May 25, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants