From 1d11fa0192b9abd80e0e5196471abd60630c6c58 Mon Sep 17 00:00:00 2001 From: Jason Mulligan Date: Tue, 23 Jun 2026 07:52:33 -0400 Subject: [PATCH 1/4] feat: fix searchFiles ReferenceError on stdout?.trim() - Replace unsafe optional chaining with nullish coalescing fallback - Ensures output is always a string before calling split() - Handles undefined, null, and empty string cases for stdout --- .../.openspec.yaml | 2 ++ .../fix-searchfiles-stdout-handling/design.md | 26 +++++++++++++++++++ .../proposal.md | 23 ++++++++++++++++ .../specs/searchFiles/spec.md | 16 ++++++++++++ .../fix-searchfiles-stdout-handling/tasks.md | 6 +++++ 5 files changed, 73 insertions(+) create mode 100644 openspec/changes/fix-searchfiles-stdout-handling/.openspec.yaml create mode 100644 openspec/changes/fix-searchfiles-stdout-handling/design.md create mode 100644 openspec/changes/fix-searchfiles-stdout-handling/proposal.md create mode 100644 openspec/changes/fix-searchfiles-stdout-handling/specs/searchFiles/spec.md create mode 100644 openspec/changes/fix-searchfiles-stdout-handling/tasks.md diff --git a/openspec/changes/fix-searchfiles-stdout-handling/.openspec.yaml b/openspec/changes/fix-searchfiles-stdout-handling/.openspec.yaml new file mode 100644 index 0000000..a4ac4d7 --- /dev/null +++ b/openspec/changes/fix-searchfiles-stdout-handling/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-23 diff --git a/openspec/changes/fix-searchfiles-stdout-handling/design.md b/openspec/changes/fix-searchfiles-stdout-handling/design.md new file mode 100644 index 0000000..6e4f7e9 --- /dev/null +++ b/openspec/changes/fix-searchfiles-stdout-handling/design.md @@ -0,0 +1,26 @@ +## Context + +The searchFiles tool in `src/tools/filesystem.js` uses `execFile` to run the ripgrep (`rg`) CLI. When `stdout` is undefined (e.g., due to encoding issues or edge cases), the current code `stdout?.trim() ?? stdout` evaluates to `undefined`, causing a TypeError on the subsequent `output.split("\n")` call. + +## Goals / Non-Goals + +**Goals:** +- Fix the undefined stdout handling to prevent ReferenceError/TypeError +- Ensure `output` is always a string before calling `split()` + +**Non-Goals:** +- No changes to search logic, pattern matching, or tool schema +- No changes to error handling for ENOENT or timeout cases (already handled) + +## Decisions + +**Decision: Use `(stdout ?? "").trim()` instead of `stdout?.trim() ?? stdout`** +- Rationale: The nullish coalescing operator `??` ensures we always have a string to call `.trim()` on. An empty string is already handled by the existing `if (!output)` check. +- Alternatives considered: + - `stdout ? stdout.trim() : ""` — equivalent but more verbose + - `String(stdout ?? "").trim()` — also works but `String()` on null/undefined produces "null"/"undefined" strings, which is not desired + +## Risks / Trade-offs + +**Risk:** None significant. This is a minimal, surgical fix. +**Trade-off:** None. The fix only changes error handling behavior for an edge case that previously crashed. \ No newline at end of file diff --git a/openspec/changes/fix-searchfiles-stdout-handling/proposal.md b/openspec/changes/fix-searchfiles-stdout-handling/proposal.md new file mode 100644 index 0000000..2177127 --- /dev/null +++ b/openspec/changes/fix-searchfiles-stdout-handling/proposal.md @@ -0,0 +1,23 @@ +## Why + +The searchFiles tool throws a ReferenceError/TypeError when `stdout` from the `execFile` call is undefined. The current code uses `stdout?.trim() ?? stdout` which evaluates to `undefined` when stdout is undefined, causing a subsequent TypeError on `output.split("\n")`. This breaks the searchFiles tool in edge cases where stdout is not a string. + +## What Changes + +- Replace `const output = stdout?.trim() ?? stdout;` with `const output = (stdout ?? "").trim();` in `src/tools/filesystem.js` line 436 +- Ensure `output` is always a string before calling `split()` +- Handle undefined, null, and empty string cases for stdout gracefully + +## Capabilities + +### New Capabilities + + +### Modified Capabilities +- `searchFiles`: Fix stdout handling to prevent ReferenceError when stdout is undefined + +## Impact + +- `src/tools/filesystem.js` — searchFilesImpl function (line 436) +- No API changes, no dependency changes +- Existing tests should continue to pass; the fix only changes error handling behavior \ No newline at end of file diff --git a/openspec/changes/fix-searchfiles-stdout-handling/specs/searchFiles/spec.md b/openspec/changes/fix-searchfiles-stdout-handling/specs/searchFiles/spec.md new file mode 100644 index 0000000..be99fdf --- /dev/null +++ b/openspec/changes/fix-searchfiles-stdout-handling/specs/searchFiles/spec.md @@ -0,0 +1,16 @@ +## ADDED Requirements + +### Requirement: searchFiles must handle undefined stdout safely +The searchFiles tool MUST ensure that stdout from the execFile call is always a string before calling string methods on it. If stdout is undefined or null, the tool MUST treat it as an empty string and return "No matches found." + +#### Scenario: stdout is undefined +- **WHEN** the execFile call returns undefined stdout +- **THEN** the tool treats output as an empty string and returns "No matches found." + +#### Scenario: stdout is null +- **WHEN** the execFile call returns null stdout +- **THEN** the tool treats output as an empty string and returns "No matches found." + +#### Scenario: stdout is a valid string +- **WHEN** the execFile call returns a valid string stdout +- **THEN** the tool trims and splits the output as expected \ No newline at end of file diff --git a/openspec/changes/fix-searchfiles-stdout-handling/tasks.md b/openspec/changes/fix-searchfiles-stdout-handling/tasks.md new file mode 100644 index 0000000..fd215d3 --- /dev/null +++ b/openspec/changes/fix-searchfiles-stdout-handling/tasks.md @@ -0,0 +1,6 @@ +## 1. Fix stdout handling in searchFiles + +- [ ] 1.1 Replace `const output = stdout?.trim() ?? stdout;` with `const output = (stdout ?? "").trim();` in `src/tools/filesystem.js` line 436 +- [ ] 1.2 Verify the fix handles undefined, null, and empty string cases for stdout +- [ ] 1.3 Run tests to ensure no regressions: `npm run test` +- [ ] 1.4 Run lint to ensure code quality: `npm run lint` \ No newline at end of file From 4dde80f47b50a4163e738cf8a2618dfa93bbbf21 Mon Sep 17 00:00:00 2001 From: Jason Mulligan Date: Tue, 23 Jun 2026 08:02:32 -0400 Subject: [PATCH 2/4] fix: handle undefined stdout in searchFiles --- .../.openspec.yaml | 2 ++ .../design.md | 26 +++++++++++++++++++ .../proposal.md | 23 ++++++++++++++++ .../specs/searchFiles/spec.md | 16 ++++++++++++ .../tasks.md | 6 +++++ openspec/specs/searchFiles/spec.md | 20 ++++++++++++++ src/tools/filesystem.js | 2 +- 7 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/.openspec.yaml create mode 100644 openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/design.md create mode 100644 openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/proposal.md create mode 100644 openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/specs/searchFiles/spec.md create mode 100644 openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/tasks.md create mode 100644 openspec/specs/searchFiles/spec.md diff --git a/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/.openspec.yaml b/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/.openspec.yaml new file mode 100644 index 0000000..a4ac4d7 --- /dev/null +++ b/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-23 diff --git a/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/design.md b/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/design.md new file mode 100644 index 0000000..6e4f7e9 --- /dev/null +++ b/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/design.md @@ -0,0 +1,26 @@ +## Context + +The searchFiles tool in `src/tools/filesystem.js` uses `execFile` to run the ripgrep (`rg`) CLI. When `stdout` is undefined (e.g., due to encoding issues or edge cases), the current code `stdout?.trim() ?? stdout` evaluates to `undefined`, causing a TypeError on the subsequent `output.split("\n")` call. + +## Goals / Non-Goals + +**Goals:** +- Fix the undefined stdout handling to prevent ReferenceError/TypeError +- Ensure `output` is always a string before calling `split()` + +**Non-Goals:** +- No changes to search logic, pattern matching, or tool schema +- No changes to error handling for ENOENT or timeout cases (already handled) + +## Decisions + +**Decision: Use `(stdout ?? "").trim()` instead of `stdout?.trim() ?? stdout`** +- Rationale: The nullish coalescing operator `??` ensures we always have a string to call `.trim()` on. An empty string is already handled by the existing `if (!output)` check. +- Alternatives considered: + - `stdout ? stdout.trim() : ""` — equivalent but more verbose + - `String(stdout ?? "").trim()` — also works but `String()` on null/undefined produces "null"/"undefined" strings, which is not desired + +## Risks / Trade-offs + +**Risk:** None significant. This is a minimal, surgical fix. +**Trade-off:** None. The fix only changes error handling behavior for an edge case that previously crashed. \ No newline at end of file diff --git a/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/proposal.md b/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/proposal.md new file mode 100644 index 0000000..2177127 --- /dev/null +++ b/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/proposal.md @@ -0,0 +1,23 @@ +## Why + +The searchFiles tool throws a ReferenceError/TypeError when `stdout` from the `execFile` call is undefined. The current code uses `stdout?.trim() ?? stdout` which evaluates to `undefined` when stdout is undefined, causing a subsequent TypeError on `output.split("\n")`. This breaks the searchFiles tool in edge cases where stdout is not a string. + +## What Changes + +- Replace `const output = stdout?.trim() ?? stdout;` with `const output = (stdout ?? "").trim();` in `src/tools/filesystem.js` line 436 +- Ensure `output` is always a string before calling `split()` +- Handle undefined, null, and empty string cases for stdout gracefully + +## Capabilities + +### New Capabilities + + +### Modified Capabilities +- `searchFiles`: Fix stdout handling to prevent ReferenceError when stdout is undefined + +## Impact + +- `src/tools/filesystem.js` — searchFilesImpl function (line 436) +- No API changes, no dependency changes +- Existing tests should continue to pass; the fix only changes error handling behavior \ No newline at end of file diff --git a/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/specs/searchFiles/spec.md b/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/specs/searchFiles/spec.md new file mode 100644 index 0000000..be99fdf --- /dev/null +++ b/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/specs/searchFiles/spec.md @@ -0,0 +1,16 @@ +## ADDED Requirements + +### Requirement: searchFiles must handle undefined stdout safely +The searchFiles tool MUST ensure that stdout from the execFile call is always a string before calling string methods on it. If stdout is undefined or null, the tool MUST treat it as an empty string and return "No matches found." + +#### Scenario: stdout is undefined +- **WHEN** the execFile call returns undefined stdout +- **THEN** the tool treats output as an empty string and returns "No matches found." + +#### Scenario: stdout is null +- **WHEN** the execFile call returns null stdout +- **THEN** the tool treats output as an empty string and returns "No matches found." + +#### Scenario: stdout is a valid string +- **WHEN** the execFile call returns a valid string stdout +- **THEN** the tool trims and splits the output as expected \ No newline at end of file diff --git a/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/tasks.md b/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/tasks.md new file mode 100644 index 0000000..1802e8e --- /dev/null +++ b/openspec/changes/archive/2026-06-23-fix-searchfiles-stdout-handling/tasks.md @@ -0,0 +1,6 @@ +## 1. Fix stdout handling in searchFiles + +- [x] 1.1 Replace `const output = stdout?.trim() ?? stdout;` with `const output = (stdout ?? "").trim();` in `src/tools/filesystem.js` line 436 +- [x] 1.2 Verify the fix handles undefined, null, and empty string cases for stdout +- [x] 1.3 Run tests to ensure no regressions: `npm run test` +- [x] 1.4 Run lint to ensure code quality: `npm run lint` \ No newline at end of file diff --git a/openspec/specs/searchFiles/spec.md b/openspec/specs/searchFiles/spec.md new file mode 100644 index 0000000..24cf30a --- /dev/null +++ b/openspec/specs/searchFiles/spec.md @@ -0,0 +1,20 @@ +# searchFiles Specification + +## Purpose +TBD - created by archiving change fix-searchfiles-stdout-handling. Update Purpose after archive. +## Requirements +### Requirement: searchFiles must handle undefined stdout safely +The searchFiles tool MUST ensure that stdout from the execFile call is always a string before calling string methods on it. If stdout is undefined or null, the tool MUST treat it as an empty string and return "No matches found." + +#### Scenario: stdout is undefined +- **WHEN** the execFile call returns undefined stdout +- **THEN** the tool treats output as an empty string and returns "No matches found." + +#### Scenario: stdout is null +- **WHEN** the execFile call returns null stdout +- **THEN** the tool treats output as an empty string and returns "No matches found." + +#### Scenario: stdout is a valid string +- **WHEN** the execFile call returns a valid string stdout +- **THEN** the tool trims and splits the output as expected + diff --git a/src/tools/filesystem.js b/src/tools/filesystem.js index 2f42dad..3caef39 100644 --- a/src/tools/filesystem.js +++ b/src/tools/filesystem.js @@ -433,7 +433,7 @@ export async function searchFilesImpl(input, options) { resolved.path, ].filter(Boolean); const { stdout } = await execFile("rg", rgArgs, { timeout: 10000, encoding: "utf-8" }); - const output = stdout?.trim() ?? stdout; + const output = (stdout ?? "").trim(); if (!output) { return "No matches found."; From c121e80711ad900a2952433f6fba0ad562710a72 Mon Sep 17 00:00:00 2001 From: Jason Mulligan Date: Tue, 23 Jun 2026 08:06:31 -0400 Subject: [PATCH 3/4] chore: remove duplicate unarchived searchFiles change --- .../.openspec.yaml | 2 -- .../fix-searchfiles-stdout-handling/design.md | 26 ------------------- .../proposal.md | 23 ---------------- .../specs/searchFiles/spec.md | 16 ------------ .../fix-searchfiles-stdout-handling/tasks.md | 6 ----- 5 files changed, 73 deletions(-) delete mode 100644 openspec/changes/fix-searchfiles-stdout-handling/.openspec.yaml delete mode 100644 openspec/changes/fix-searchfiles-stdout-handling/design.md delete mode 100644 openspec/changes/fix-searchfiles-stdout-handling/proposal.md delete mode 100644 openspec/changes/fix-searchfiles-stdout-handling/specs/searchFiles/spec.md delete mode 100644 openspec/changes/fix-searchfiles-stdout-handling/tasks.md diff --git a/openspec/changes/fix-searchfiles-stdout-handling/.openspec.yaml b/openspec/changes/fix-searchfiles-stdout-handling/.openspec.yaml deleted file mode 100644 index a4ac4d7..0000000 --- a/openspec/changes/fix-searchfiles-stdout-handling/.openspec.yaml +++ /dev/null @@ -1,2 +0,0 @@ -schema: spec-driven -created: 2026-06-23 diff --git a/openspec/changes/fix-searchfiles-stdout-handling/design.md b/openspec/changes/fix-searchfiles-stdout-handling/design.md deleted file mode 100644 index 6e4f7e9..0000000 --- a/openspec/changes/fix-searchfiles-stdout-handling/design.md +++ /dev/null @@ -1,26 +0,0 @@ -## Context - -The searchFiles tool in `src/tools/filesystem.js` uses `execFile` to run the ripgrep (`rg`) CLI. When `stdout` is undefined (e.g., due to encoding issues or edge cases), the current code `stdout?.trim() ?? stdout` evaluates to `undefined`, causing a TypeError on the subsequent `output.split("\n")` call. - -## Goals / Non-Goals - -**Goals:** -- Fix the undefined stdout handling to prevent ReferenceError/TypeError -- Ensure `output` is always a string before calling `split()` - -**Non-Goals:** -- No changes to search logic, pattern matching, or tool schema -- No changes to error handling for ENOENT or timeout cases (already handled) - -## Decisions - -**Decision: Use `(stdout ?? "").trim()` instead of `stdout?.trim() ?? stdout`** -- Rationale: The nullish coalescing operator `??` ensures we always have a string to call `.trim()` on. An empty string is already handled by the existing `if (!output)` check. -- Alternatives considered: - - `stdout ? stdout.trim() : ""` — equivalent but more verbose - - `String(stdout ?? "").trim()` — also works but `String()` on null/undefined produces "null"/"undefined" strings, which is not desired - -## Risks / Trade-offs - -**Risk:** None significant. This is a minimal, surgical fix. -**Trade-off:** None. The fix only changes error handling behavior for an edge case that previously crashed. \ No newline at end of file diff --git a/openspec/changes/fix-searchfiles-stdout-handling/proposal.md b/openspec/changes/fix-searchfiles-stdout-handling/proposal.md deleted file mode 100644 index 2177127..0000000 --- a/openspec/changes/fix-searchfiles-stdout-handling/proposal.md +++ /dev/null @@ -1,23 +0,0 @@ -## Why - -The searchFiles tool throws a ReferenceError/TypeError when `stdout` from the `execFile` call is undefined. The current code uses `stdout?.trim() ?? stdout` which evaluates to `undefined` when stdout is undefined, causing a subsequent TypeError on `output.split("\n")`. This breaks the searchFiles tool in edge cases where stdout is not a string. - -## What Changes - -- Replace `const output = stdout?.trim() ?? stdout;` with `const output = (stdout ?? "").trim();` in `src/tools/filesystem.js` line 436 -- Ensure `output` is always a string before calling `split()` -- Handle undefined, null, and empty string cases for stdout gracefully - -## Capabilities - -### New Capabilities - - -### Modified Capabilities -- `searchFiles`: Fix stdout handling to prevent ReferenceError when stdout is undefined - -## Impact - -- `src/tools/filesystem.js` — searchFilesImpl function (line 436) -- No API changes, no dependency changes -- Existing tests should continue to pass; the fix only changes error handling behavior \ No newline at end of file diff --git a/openspec/changes/fix-searchfiles-stdout-handling/specs/searchFiles/spec.md b/openspec/changes/fix-searchfiles-stdout-handling/specs/searchFiles/spec.md deleted file mode 100644 index be99fdf..0000000 --- a/openspec/changes/fix-searchfiles-stdout-handling/specs/searchFiles/spec.md +++ /dev/null @@ -1,16 +0,0 @@ -## ADDED Requirements - -### Requirement: searchFiles must handle undefined stdout safely -The searchFiles tool MUST ensure that stdout from the execFile call is always a string before calling string methods on it. If stdout is undefined or null, the tool MUST treat it as an empty string and return "No matches found." - -#### Scenario: stdout is undefined -- **WHEN** the execFile call returns undefined stdout -- **THEN** the tool treats output as an empty string and returns "No matches found." - -#### Scenario: stdout is null -- **WHEN** the execFile call returns null stdout -- **THEN** the tool treats output as an empty string and returns "No matches found." - -#### Scenario: stdout is a valid string -- **WHEN** the execFile call returns a valid string stdout -- **THEN** the tool trims and splits the output as expected \ No newline at end of file diff --git a/openspec/changes/fix-searchfiles-stdout-handling/tasks.md b/openspec/changes/fix-searchfiles-stdout-handling/tasks.md deleted file mode 100644 index fd215d3..0000000 --- a/openspec/changes/fix-searchfiles-stdout-handling/tasks.md +++ /dev/null @@ -1,6 +0,0 @@ -## 1. Fix stdout handling in searchFiles - -- [ ] 1.1 Replace `const output = stdout?.trim() ?? stdout;` with `const output = (stdout ?? "").trim();` in `src/tools/filesystem.js` line 436 -- [ ] 1.2 Verify the fix handles undefined, null, and empty string cases for stdout -- [ ] 1.3 Run tests to ensure no regressions: `npm run test` -- [ ] 1.4 Run lint to ensure code quality: `npm run lint` \ No newline at end of file From 0108e0fda479f829e33104de8306e02a6a0f761d Mon Sep 17 00:00:00 2001 From: Jason Mulligan Date: Tue, 23 Jun 2026 08:08:23 -0400 Subject: [PATCH 4/4] cleanup --- .../.openspec.yaml | 2 -- .../fix-toolmessage-loss-compaction/design.md | 36 ------------------- .../proposal.md | 24 ------------- .../specs/toolmessage-compaction/spec.md | 16 --------- .../fix-toolmessage-loss-compaction/tasks.md | 17 --------- 5 files changed, 95 deletions(-) delete mode 100644 openspec/changes/fix-toolmessage-loss-compaction/.openspec.yaml delete mode 100644 openspec/changes/fix-toolmessage-loss-compaction/design.md delete mode 100644 openspec/changes/fix-toolmessage-loss-compaction/proposal.md delete mode 100644 openspec/changes/fix-toolmessage-loss-compaction/specs/toolmessage-compaction/spec.md delete mode 100644 openspec/changes/fix-toolmessage-loss-compaction/tasks.md diff --git a/openspec/changes/fix-toolmessage-loss-compaction/.openspec.yaml b/openspec/changes/fix-toolmessage-loss-compaction/.openspec.yaml deleted file mode 100644 index 38f7628..0000000 --- a/openspec/changes/fix-toolmessage-loss-compaction/.openspec.yaml +++ /dev/null @@ -1,2 +0,0 @@ -schema: spec-driven -created: 2026-06-22 diff --git a/openspec/changes/fix-toolmessage-loss-compaction/design.md b/openspec/changes/fix-toolmessage-loss-compaction/design.md deleted file mode 100644 index dffcf55..0000000 --- a/openspec/changes/fix-toolmessage-loss-compaction/design.md +++ /dev/null @@ -1,36 +0,0 @@ -## Context - -The ReAct agent in `src/agent/react.js` uses conversation compaction to handle context length limits. When a conversation exceeds the token limit, `compactConversation` is called and the resulting compacted messages are rebuilt into LangChain message instances. The rebuild logic currently handles "system" and "user" roles but falls through to `AIMessage` for all other roles, including "tool". This causes ToolMessage instances (which contain tool execution results) to be lost, breaking the agent's ability to continue conversations that depend on prior tool outputs. - -## Goals / Non-Goals - -**Goals:** -- Preserve ToolMessage instances during conversation compaction in both `callReactAgent` and `callReactAgentStreaming` -- Ensure tool results are correctly reconstructed using `new ToolMessage(m.content)` - -**Non-Goals:** -- Changes to the compaction algorithm itself -- Changes to other message types (FunctionMessage, etc.) -- Changes to other agents or the compaction utility - -## Decisions - -1. **Add explicit "tool" case in rebuild logic** — Rather than using a generic fallback, add an explicit case for `m.role === "tool"`. This is more maintainable and makes the intent clear. - -2. **Use `new ToolMessage(m.content)`** — ToolMessage is already imported and used elsewhere in the file. The content field is the primary field needed for reconstruction. - -3. **Apply fix to both functions** — Both `callReactAgent` and `callReactAgentStreaming` have identical rebuild logic. Both must be fixed to maintain consistency. - -## Risks / Trade-offs - -- **Risk:** ToolMessage may have additional fields beyond `content` that are not preserved. **Mitigation:** The compacted message structure only includes `role` and `content` fields, so this is the best we can do without changing the compaction format. -- **Risk:** Other message types (e.g., FunctionMessage) may have the same issue. **Mitigation:** Out of scope for this fix. Can be addressed in a follow-up if needed. - -## Migration Plan - -No migration needed. This is a code-only fix that will take effect immediately on the next deployment. - -## Open Questions - -- Should we also handle FunctionMessage and other message types in a follow-up? -- Does the compaction format need to be extended to preserve additional ToolMessage fields? \ No newline at end of file diff --git a/openspec/changes/fix-toolmessage-loss-compaction/proposal.md b/openspec/changes/fix-toolmessage-loss-compaction/proposal.md deleted file mode 100644 index 1cc9f46..0000000 --- a/openspec/changes/fix-toolmessage-loss-compaction/proposal.md +++ /dev/null @@ -1,24 +0,0 @@ -## Why - -ToolMessage instances are incorrectly converted to AIMessage instances during conversation compaction in the ReAct agent. When the conversation exceeds context length and `compactConversation` is called, the message rebuild logic in both `callReactAgent` and `callReactAgentStreaming` only handles "system" and "user" roles explicitly — all other roles (including "tool") fall through to `new AIMessage(m.content)`. This causes tool results to be permanently lost, breaking the agent's ability to continue a conversation that depends on prior tool outputs. - -## What Changes - -- Add a `case` for `m.role === "tool"` in the message rebuild logic at lines 190-197 in `callReactAgent` (`src/agent/react.js`) -- Add a `case` for `m.role === "tool"` in the message rebuild logic at lines 494-501 in `callReactAgentStreaming` (`src/agent/react.js`) -- ToolMessage instances will be reconstructed using `new ToolMessage(m.content)` when the role is "tool" - -## Capabilities - -### New Capabilities - - -### Modified Capabilities - - -## Impact - -- `src/agent/react.js` — two message rebuild sections (callReactAgent and callReactAgentStreaming) -- `@langchain/core/messages` — ToolMessage already imported, no new dependencies -- No API changes, no breaking changes -- Compacted conversations will now correctly preserve tool results across compaction cycles \ No newline at end of file diff --git a/openspec/changes/fix-toolmessage-loss-compaction/specs/toolmessage-compaction/spec.md b/openspec/changes/fix-toolmessage-loss-compaction/specs/toolmessage-compaction/spec.md deleted file mode 100644 index 022cb65..0000000 --- a/openspec/changes/fix-toolmessage-loss-compaction/specs/toolmessage-compaction/spec.md +++ /dev/null @@ -1,16 +0,0 @@ -## ADDED Requirements - -### Requirement: ToolMessage preservation during compaction -The ReAct agent SHALL preserve ToolMessage instances during conversation compaction. When `compactConversation` is called and the resulting messages are rebuilt, any message with role "tool" MUST be reconstructed as a `ToolMessage` instance, not an `AIMessage` instance. - -#### Scenario: ToolMessage preserved in callReactAgent -- **WHEN** a conversation exceeds context length and compaction is triggered in `callReactAgent` -- **THEN** the rebuilt messages include ToolMessage instances for all messages with role "tool" - -#### Scenario: ToolMessage preserved in callReactAgentStreaming -- **WHEN** a conversation exceeds context length and compaction is triggered in `callReactAgentStreaming` -- **THEN** the rebuilt messages include ToolMessage instances for all messages with role "tool" - -#### Scenario: Tool results available after compaction -- **WHEN** the agent continues execution after compaction -- **THEN** tool results from before compaction are available in the message history \ No newline at end of file diff --git a/openspec/changes/fix-toolmessage-loss-compaction/tasks.md b/openspec/changes/fix-toolmessage-loss-compaction/tasks.md deleted file mode 100644 index 6b9fed8..0000000 --- a/openspec/changes/fix-toolmessage-loss-compaction/tasks.md +++ /dev/null @@ -1,17 +0,0 @@ -## 1. Fix message rebuild in callReactAgent - -- [x] 1.1 Add case for m.role === "tool" in message rebuild logic (lines 190-197) to return new ToolMessage(m.content) - -## 2. Fix message rebuild in callReactAgentStreaming - -- [x] 2.1 Add case for m.role === "tool" in message rebuild logic (lines 494-501) to return new ToolMessage(m.content) - -## 3. Write unit tests - -- [x] 3.1 Write test verifying ToolMessage instances are preserved through compaction in callReactAgent -- [x] 3.2 Write test verifying ToolMessage instances are preserved through compaction in callReactAgentStreaming - -## 4. Verify - -- [x] 4.1 Run existing test suite to ensure no regressions -- [x] 4.2 Run lint to ensure code quality \ No newline at end of file