feat(FN-4062): add global thinking log setting to persist agent reasoning a
Adds a global `thinkingLogEnabled` setting that gates AI thinking log persistence across the engine (executor, reviewer, merger, triage, step-session) and exposes the control in the dashboard Settings modal, with tests verifying settings parity and modal behavior. Fusion-Task-Id: FN-4062
This commit is contained in:
5
.changeset/fn-4062-thinking-log-setting.md
Normal file
5
.changeset/fn-4062-thinking-log-setting.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add a global setting, `persistAgentThinkingLog` (default `false`), to control whether agent thinking/reasoning log rows are persisted. Tool output persistence remains separately controlled by `persistAgentToolOutput`.
|
||||||
@@ -82,6 +82,7 @@ In **Settings → Notifications**, use **Test message notification** to exercise
|
|||||||
> Mesh lifecycle note: settings sync is executed by the process-level `PeerExchangeService` started by `fn serve`/`fn dashboard`. `InProcessRuntime` does not instantiate settings-sync mesh services per project.
|
> Mesh lifecycle note: settings sync is executed by the process-level `PeerExchangeService` started by `fn serve`/`fn dashboard`. `InProcessRuntime` does not instantiate settings-sync mesh services per project.
|
||||||
| `dashboardCurrentProjectIdByNode` | `Record<string, string>` | `undefined` | Map of node ID to last-selected project ID. Use key `"local"` for the local node. Persists project context across browser restarts and PWA sessions. |
|
| `dashboardCurrentProjectIdByNode` | `Record<string, string>` | `undefined` | Map of node ID to last-selected project ID. Use key `"local"` for the local node. Persists project context across browser restarts and PWA sessions. |
|
||||||
| `persistAgentToolOutput` | `boolean` | `true` | Controls whether detailed `detail` payloads are persisted for `tool`, `tool_result`, and `tool_error` agent log entries. When disabled, tool timeline rows are still recorded, but verbose payloads are omitted. |
|
| `persistAgentToolOutput` | `boolean` | `true` | Controls whether detailed `detail` payloads are persisted for `tool`, `tool_result`, and `tool_error` agent log entries. When disabled, tool timeline rows are still recorded, but verbose payloads are omitted. |
|
||||||
|
| `persistAgentThinkingLog` | `boolean` | `false` | Controls whether `thinking`/reasoning agent log entries are persisted. When disabled (default), only persisted `thinking` rows are suppressed; normal assistant text output and tool rows are unchanged. |
|
||||||
| `researchGlobalDefaults` | `ResearchGlobalDefaults` | `{ searchProvider: "builtin", synthesisProvider: undefined, synthesisModelId: undefined, enabledSources: { webSearch: true, pageFetch: true, github: false, localDocs: true, llmSynthesis: true }, maxSourcesPerRun: 20, defaultExportFormat: "markdown" }` | Global Research defaults shared by all projects. Web search defaults to the built-in WebSearch/WebFetch-backed provider; project overrides come from `researchSettings`. |
|
| `researchGlobalDefaults` | `ResearchGlobalDefaults` | `{ searchProvider: "builtin", synthesisProvider: undefined, synthesisModelId: undefined, enabledSources: { webSearch: true, pageFetch: true, github: false, localDocs: true, llmSynthesis: true }, maxSourcesPerRun: 20, defaultExportFormat: "markdown" }` | Global Research defaults shared by all projects. Web search defaults to the built-in WebSearch/WebFetch-backed provider; project overrides come from `researchSettings`. |
|
||||||
| `researchGlobalEnabled` | `boolean` | `true` | Enable or disable the research subsystem globally. When false, dashboard/API/CLI/agent entrypoints reject new runs. |
|
| `researchGlobalEnabled` | `boolean` | `true` | Enable or disable the research subsystem globally. When false, dashboard/API/CLI/agent entrypoints reject new runs. |
|
||||||
| `researchGlobalMaxConcurrentRuns` | `number` | `3` | Maximum concurrent research runs across all projects. |
|
| `researchGlobalMaxConcurrentRuns` | `number` | `3` | Maximum concurrent research runs across all projects. |
|
||||||
|
|||||||
@@ -59,9 +59,16 @@ describe("settings key parity", () => {
|
|||||||
expect(isGlobalSettingsKey("remoteAccess")).toBe(true);
|
expect(isGlobalSettingsKey("remoteAccess")).toBe(true);
|
||||||
expect(isGlobalSettingsKey("persistAgentToolOutput")).toBe(true);
|
expect(isGlobalSettingsKey("persistAgentToolOutput")).toBe(true);
|
||||||
expect(isProjectSettingsKey("persistAgentToolOutput")).toBe(false);
|
expect(isProjectSettingsKey("persistAgentToolOutput")).toBe(false);
|
||||||
|
expect(isGlobalSettingsKey("persistAgentThinkingLog")).toBe(true);
|
||||||
|
expect(isProjectSettingsKey("persistAgentThinkingLog")).toBe(false);
|
||||||
|
expect(isGlobalOnlySettingsKey("persistAgentThinkingLog")).toBe(true);
|
||||||
expect(isGlobalSettingsKey("researchSettings")).toBe(false);
|
expect(isGlobalSettingsKey("researchSettings")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("defaults persisted thinking logs to disabled", () => {
|
||||||
|
expect(DEFAULT_GLOBAL_SETTINGS.persistAgentThinkingLog).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("includes heartbeatMultiplier in project defaults", () => {
|
it("includes heartbeatMultiplier in project defaults", () => {
|
||||||
expect(DEFAULT_PROJECT_SETTINGS.heartbeatMultiplier).toBe(1);
|
expect(DEFAULT_PROJECT_SETTINGS.heartbeatMultiplier).toBe(1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ export const DEFAULT_GLOBAL_SETTINGS = {
|
|||||||
vitestKillThresholdPct: 90,
|
vitestKillThresholdPct: 90,
|
||||||
// Agent log persistence controls
|
// Agent log persistence controls
|
||||||
persistAgentToolOutput: true,
|
persistAgentToolOutput: true,
|
||||||
|
persistAgentThinkingLog: false,
|
||||||
researchGlobalDefaults: {
|
researchGlobalDefaults: {
|
||||||
searchProvider: undefined,
|
searchProvider: undefined,
|
||||||
synthesisProvider: undefined,
|
synthesisProvider: undefined,
|
||||||
|
|||||||
@@ -1811,8 +1811,13 @@ export interface GlobalSettings {
|
|||||||
* logs for `tool`, `tool_result`, and `tool_error` entries. Very large tool
|
* logs for `tool`, `tool_result`, and `tool_error` entries. Very large tool
|
||||||
* payloads may still be clipped server-side to keep dashboard log reads
|
* payloads may still be clipped server-side to keep dashboard log reads
|
||||||
* responsive. When false, tool timeline rows are still stored, but their
|
* responsive. When false, tool timeline rows are still stored, but their
|
||||||
* verbose `detail` payload is omitted to reduce log size/noise. */
|
* verbose `detail` payload is omitted to reduce log size/noise. Distinct
|
||||||
|
* from `persistAgentThinkingLog`, which controls `thinking` rows. */
|
||||||
persistAgentToolOutput?: boolean;
|
persistAgentToolOutput?: boolean;
|
||||||
|
/** When true, persist `thinking` log entries from agent reasoning deltas.
|
||||||
|
* Default: false (suppressed). This only affects persisted `thinking` rows
|
||||||
|
* and does not change normal assistant text/tool output behavior. */
|
||||||
|
persistAgentThinkingLog?: boolean;
|
||||||
/** Research defaults shared across all projects.
|
/** Research defaults shared across all projects.
|
||||||
* Project settings may override these via `researchSettings`. */
|
* Project settings may override these via `researchSettings`. */
|
||||||
researchGlobalDefaults?: ResearchGlobalDefaults;
|
researchGlobalDefaults?: ResearchGlobalDefaults;
|
||||||
|
|||||||
@@ -2027,6 +2027,23 @@ export function SettingsModal({
|
|||||||
Very large tool payloads may still be clipped even when this stays enabled.
|
Very large tool payloads may still be clipped even when this stays enabled.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="persistAgentThinkingLog" className="checkbox-label">
|
||||||
|
<input
|
||||||
|
id="persistAgentThinkingLog"
|
||||||
|
type="checkbox"
|
||||||
|
checked={form.persistAgentThinkingLog === true}
|
||||||
|
onChange={(e) =>
|
||||||
|
setForm((f) => ({ ...f, persistAgentThinkingLog: e.target.checked }))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
Save AI thinking/reasoning in agent logs
|
||||||
|
</label>
|
||||||
|
<div className="settings-field-help">
|
||||||
|
When disabled (default), internal thinking deltas are not persisted as log rows.
|
||||||
|
Assistant text output and tool timeline entries are unchanged.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label htmlFor="fnBinaryCheckEnabled" className="checkbox-label">
|
<label htmlFor="fnBinaryCheckEnabled" className="checkbox-label">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -513,6 +513,29 @@ describe("SettingsModal", () => {
|
|||||||
expect(screen.getByRole("checkbox", { name: "Save tool output in agent logs" })).not.toBeChecked();
|
expect(screen.getByRole("checkbox", { name: "Save tool output in agent logs" })).not.toBeChecked();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("defaults persistAgentThinkingLog checkbox to unchecked", async () => {
|
||||||
|
renderModal({ initialSection: "global-general" });
|
||||||
|
await waitForSettingsModalReady();
|
||||||
|
|
||||||
|
expect(screen.getByRole("checkbox", { name: "Save AI thinking/reasoning in agent logs" })).not.toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reflects persisted checked thinking-log value from global settings", async () => {
|
||||||
|
mockFetchSettings.mockResolvedValue({
|
||||||
|
...defaultSettings,
|
||||||
|
persistAgentThinkingLog: true,
|
||||||
|
});
|
||||||
|
mockFetchSettingsByScope.mockResolvedValue({
|
||||||
|
global: { ...defaultSettings, persistAgentThinkingLog: true },
|
||||||
|
project: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
renderModal({ initialSection: "global-general" });
|
||||||
|
await waitForSettingsModalReady();
|
||||||
|
|
||||||
|
expect(screen.getByRole("checkbox", { name: "Save AI thinking/reasoning in agent logs" })).toBeChecked();
|
||||||
|
});
|
||||||
|
|
||||||
it("saves persistAgentToolOutput only via global settings payload", async () => {
|
it("saves persistAgentToolOutput only via global settings payload", async () => {
|
||||||
renderModal({ initialSection: "global-general" });
|
renderModal({ initialSection: "global-general" });
|
||||||
await waitForSettingsModalReady();
|
await waitForSettingsModalReady();
|
||||||
@@ -532,6 +555,25 @@ describe("SettingsModal", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("saves persistAgentThinkingLog only via global settings payload", async () => {
|
||||||
|
renderModal({ initialSection: "global-general" });
|
||||||
|
await waitForSettingsModalReady();
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByRole("checkbox", { name: "Save AI thinking/reasoning in agent logs" }));
|
||||||
|
await userEvent.click(screen.getByRole("button", { name: "Save" }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockUpdateGlobalSettings).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
const globalPayload = mockUpdateGlobalSettings.mock.calls[0]?.[0] as Record<string, unknown>;
|
||||||
|
expect(globalPayload.persistAgentThinkingLog).toBe(true);
|
||||||
|
if (mockUpdateSettings.mock.calls.length > 0) {
|
||||||
|
const projectPayload = mockUpdateSettings.mock.calls[0]?.[0] as Record<string, unknown>;
|
||||||
|
expect(projectPayload.persistAgentThinkingLog).toBeUndefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("renders global default tracking repo control", async () => {
|
it("renders global default tracking repo control", async () => {
|
||||||
renderModal({ initialSection: "global-general" });
|
renderModal({ initialSection: "global-general" });
|
||||||
await waitForSettingsModalReady();
|
await waitForSettingsModalReady();
|
||||||
|
|||||||
@@ -1136,6 +1136,23 @@ describe("PUT /settings/global", () => {
|
|||||||
expect(res.body.persistAgentToolOutput).toBe(false);
|
expect(res.body.persistAgentToolOutput).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("accepts persistAgentThinkingLog in global updates", async () => {
|
||||||
|
const updatedMerged = { persistAgentThinkingLog: true };
|
||||||
|
(store.updateGlobalSettings as ReturnType<typeof vi.fn>).mockResolvedValue(updatedMerged);
|
||||||
|
|
||||||
|
const res = await REQUEST(
|
||||||
|
buildApp(),
|
||||||
|
"PUT",
|
||||||
|
"/api/settings/global",
|
||||||
|
JSON.stringify({ persistAgentThinkingLog: true }),
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(store.updateGlobalSettings).toHaveBeenCalledWith({ persistAgentThinkingLog: true });
|
||||||
|
expect(res.body.persistAgentThinkingLog).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("returns 500 on update error", async () => {
|
it("returns 500 on update error", async () => {
|
||||||
(store.updateGlobalSettings as ReturnType<typeof vi.fn>).mockRejectedValue(new Error("Write failed"));
|
(store.updateGlobalSettings as ReturnType<typeof vi.fn>).mockRejectedValue(new Error("Write failed"));
|
||||||
|
|
||||||
@@ -1270,7 +1287,12 @@ describe("GET /settings/scopes", () => {
|
|||||||
|
|
||||||
it("returns settings separated by scope", async () => {
|
it("returns settings separated by scope", async () => {
|
||||||
(store.getSettingsByScope as ReturnType<typeof vi.fn>).mockResolvedValue({
|
(store.getSettingsByScope as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||||
global: { themeMode: "dark", defaultProvider: "anthropic", persistAgentToolOutput: true },
|
global: {
|
||||||
|
themeMode: "dark",
|
||||||
|
defaultProvider: "anthropic",
|
||||||
|
persistAgentToolOutput: true,
|
||||||
|
persistAgentThinkingLog: false,
|
||||||
|
},
|
||||||
project: { maxConcurrent: 4, autoMerge: false },
|
project: { maxConcurrent: 4, autoMerge: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1280,9 +1302,11 @@ describe("GET /settings/scopes", () => {
|
|||||||
expect(res.body.global.themeMode).toBe("dark");
|
expect(res.body.global.themeMode).toBe("dark");
|
||||||
expect(res.body.global.defaultProvider).toBe("anthropic");
|
expect(res.body.global.defaultProvider).toBe("anthropic");
|
||||||
expect(res.body.global.persistAgentToolOutput).toBe(true);
|
expect(res.body.global.persistAgentToolOutput).toBe(true);
|
||||||
|
expect(res.body.global.persistAgentThinkingLog).toBe(false);
|
||||||
expect(res.body.project.maxConcurrent).toBe(4);
|
expect(res.body.project.maxConcurrent).toBe(4);
|
||||||
expect(res.body.project.autoMerge).toBe(false);
|
expect(res.body.project.autoMerge).toBe(false);
|
||||||
expect(res.body.project.persistAgentToolOutput).toBeUndefined();
|
expect(res.body.project.persistAgentToolOutput).toBeUndefined();
|
||||||
|
expect(res.body.project.persistAgentThinkingLog).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns exact response envelope shape with only global and project keys", async () => {
|
it("returns exact response envelope shape with only global and project keys", async () => {
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ describe("AgentLogger", () => {
|
|||||||
|
|
||||||
// ── Thinking buffer/flush ────────────────────────────────────────
|
// ── Thinking buffer/flush ────────────────────────────────────────
|
||||||
|
|
||||||
it("buffers thinking deltas and flushes on timer", async () => {
|
it("skips thinking entries by default", async () => {
|
||||||
const store = createMockStore();
|
const store = createMockStore();
|
||||||
const logger = new AgentLogger({
|
const logger = new AgentLogger({
|
||||||
store,
|
store,
|
||||||
@@ -279,12 +279,30 @@ describe("AgentLogger", () => {
|
|||||||
flushIntervalMs: 500,
|
flushIntervalMs: 500,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
logger.onThinking("thought 1 ");
|
||||||
|
logger.onThinking("thought 2");
|
||||||
|
await vi.advanceTimersByTimeAsync(500);
|
||||||
|
|
||||||
|
expect(store.appendAgentLog).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("buffers thinking deltas and flushes on timer when enabled", async () => {
|
||||||
|
const store = createMockStore();
|
||||||
|
const logger = new AgentLogger({
|
||||||
|
store,
|
||||||
|
taskId: "FN-011A",
|
||||||
|
agent: "executor",
|
||||||
|
persistAgentThinkingLog: true,
|
||||||
|
flushSizeBytes: 1024,
|
||||||
|
flushIntervalMs: 500,
|
||||||
|
});
|
||||||
|
|
||||||
logger.onThinking("thought 1 ");
|
logger.onThinking("thought 1 ");
|
||||||
logger.onThinking("thought 2");
|
logger.onThinking("thought 2");
|
||||||
expect(store.appendAgentLog).not.toHaveBeenCalled();
|
expect(store.appendAgentLog).not.toHaveBeenCalled();
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(500);
|
await vi.advanceTimersByTimeAsync(500);
|
||||||
expect(store.appendAgentLog).toHaveBeenCalledWith("FN-011", "thought 1 thought 2", "thinking", undefined, "executor");
|
expect(store.appendAgentLog).toHaveBeenCalledWith("FN-011A", "thought 1 thought 2", "thinking", undefined, "executor");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("flushes thinking on size threshold", async () => {
|
it("flushes thinking on size threshold", async () => {
|
||||||
@@ -293,6 +311,7 @@ describe("AgentLogger", () => {
|
|||||||
store,
|
store,
|
||||||
taskId: "FN-012",
|
taskId: "FN-012",
|
||||||
agent: "triage",
|
agent: "triage",
|
||||||
|
persistAgentThinkingLog: true,
|
||||||
flushSizeBytes: 10,
|
flushSizeBytes: 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -310,6 +329,7 @@ describe("AgentLogger", () => {
|
|||||||
store,
|
store,
|
||||||
taskId: "FN-013",
|
taskId: "FN-013",
|
||||||
agent: "reviewer",
|
agent: "reviewer",
|
||||||
|
persistAgentThinkingLog: true,
|
||||||
flushSizeBytes: 1024,
|
flushSizeBytes: 1024,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -324,6 +344,7 @@ describe("AgentLogger", () => {
|
|||||||
store,
|
store,
|
||||||
taskId: "FN-014",
|
taskId: "FN-014",
|
||||||
agent: "executor",
|
agent: "executor",
|
||||||
|
persistAgentThinkingLog: true,
|
||||||
flushSizeBytes: 1024,
|
flushSizeBytes: 1024,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -463,6 +484,7 @@ describe("AgentLogger", () => {
|
|||||||
const logger = new AgentLogger({
|
const logger = new AgentLogger({
|
||||||
store,
|
store,
|
||||||
taskId: "FN-2090-THINKING",
|
taskId: "FN-2090-THINKING",
|
||||||
|
persistAgentThinkingLog: true,
|
||||||
flushSizeBytes: 1,
|
flushSizeBytes: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1890,6 +1890,7 @@ export class HeartbeatMonitor {
|
|||||||
appendLog: (entry) => this.store.appendRunLog(agentId, run.id, entry),
|
appendLog: (entry) => this.store.appendRunLog(agentId, run.id, entry),
|
||||||
agent: agent.role as AgentRole,
|
agent: agent.role as AgentRole,
|
||||||
persistAgentToolOutput: memorySettings?.persistAgentToolOutput,
|
persistAgentToolOutput: memorySettings?.persistAgentToolOutput,
|
||||||
|
persistAgentThinkingLog: memorySettings?.persistAgentThinkingLog,
|
||||||
});
|
});
|
||||||
} else if (taskId) {
|
} else if (taskId) {
|
||||||
agentLogger = new AgentLogger({
|
agentLogger = new AgentLogger({
|
||||||
@@ -1898,6 +1899,7 @@ export class HeartbeatMonitor {
|
|||||||
agent: agent.role as AgentRole,
|
agent: agent.role as AgentRole,
|
||||||
appendLog: (entry) => this.store.appendRunLog(agentId, run.id, entry),
|
appendLog: (entry) => this.store.appendRunLog(agentId, run.id, entry),
|
||||||
persistAgentToolOutput: memorySettings?.persistAgentToolOutput,
|
persistAgentToolOutput: memorySettings?.persistAgentToolOutput,
|
||||||
|
persistAgentThinkingLog: memorySettings?.persistAgentThinkingLog,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ export function summarizeToolArgs(name: string, args?: Record<string, unknown>):
|
|||||||
export interface AgentLoggerOptions {
|
export interface AgentLoggerOptions {
|
||||||
/** When false, omit `detail` payloads for tool entries while preserving the rows. */
|
/** When false, omit `detail` payloads for tool entries while preserving the rows. */
|
||||||
persistAgentToolOutput?: boolean;
|
persistAgentToolOutput?: boolean;
|
||||||
|
/** When true, persist `thinking` rows. Default: false (skip thinking persistence). */
|
||||||
|
persistAgentThinkingLog?: boolean;
|
||||||
/** The task store used to persist agent log entries (task-store mode). */
|
/** The task store used to persist agent log entries (task-store mode). */
|
||||||
store?: TaskStore;
|
store?: TaskStore;
|
||||||
/** The task ID this logger is associated with (task-store mode). */
|
/** The task ID this logger is associated with (task-store mode). */
|
||||||
@@ -110,6 +112,7 @@ export class AgentLogger {
|
|||||||
private readonly externalToolCb?: (taskId: string, toolName: string) => void;
|
private readonly externalToolCb?: (taskId: string, toolName: string) => void;
|
||||||
private readonly log = createLogger("agent-logger");
|
private readonly log = createLogger("agent-logger");
|
||||||
private readonly persistAgentToolOutput: boolean;
|
private readonly persistAgentToolOutput: boolean;
|
||||||
|
private readonly persistAgentThinkingLog: boolean;
|
||||||
|
|
||||||
constructor(options: AgentLoggerOptions) {
|
constructor(options: AgentLoggerOptions) {
|
||||||
this.store = options.store;
|
this.store = options.store;
|
||||||
@@ -121,6 +124,7 @@ export class AgentLogger {
|
|||||||
this.flushSizeBytes = options.flushSizeBytes ?? FLUSH_SIZE_BYTES;
|
this.flushSizeBytes = options.flushSizeBytes ?? FLUSH_SIZE_BYTES;
|
||||||
this.flushIntervalMs = options.flushIntervalMs ?? FLUSH_INTERVAL_MS;
|
this.flushIntervalMs = options.flushIntervalMs ?? FLUSH_INTERVAL_MS;
|
||||||
this.persistAgentToolOutput = options.persistAgentToolOutput !== false;
|
this.persistAgentToolOutput = options.persistAgentToolOutput !== false;
|
||||||
|
this.persistAgentThinkingLog = options.persistAgentThinkingLog === true;
|
||||||
|
|
||||||
// Bind callbacks so they can be passed directly as function references
|
// Bind callbacks so they can be passed directly as function references
|
||||||
this.onText = this.onText.bind(this);
|
this.onText = this.onText.bind(this);
|
||||||
@@ -149,6 +153,9 @@ export class AgentLogger {
|
|||||||
* as `type: "thinking"` entries, using the same size/timer pattern as `onText`.
|
* as `type: "thinking"` entries, using the same size/timer pattern as `onText`.
|
||||||
*/
|
*/
|
||||||
onThinking(delta: string): void {
|
onThinking(delta: string): void {
|
||||||
|
if (!this.persistAgentThinkingLog) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.thinkingBuffer += delta;
|
this.thinkingBuffer += delta;
|
||||||
if (this.thinkingBuffer.length >= this.flushSizeBytes) {
|
if (this.thinkingBuffer.length >= this.flushSizeBytes) {
|
||||||
if (this.thinkingFlushTimer) { clearTimeout(this.thinkingFlushTimer); this.thinkingFlushTimer = null; }
|
if (this.thinkingFlushTimer) { clearTimeout(this.thinkingFlushTimer); this.thinkingFlushTimer = null; }
|
||||||
@@ -258,6 +265,9 @@ export class AgentLogger {
|
|||||||
if (this.thinkingBuffer.length === 0) return Promise.resolve();
|
if (this.thinkingBuffer.length === 0) return Promise.resolve();
|
||||||
const chunk = this.thinkingBuffer;
|
const chunk = this.thinkingBuffer;
|
||||||
this.thinkingBuffer = "";
|
this.thinkingBuffer = "";
|
||||||
|
if (!this.persistAgentThinkingLog) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
this.writeEntry(chunk, "thinking", undefined, `Failed to flush thinking buffer for ${this.taskId}`, true);
|
this.writeEntry(chunk, "thinking", undefined, `Failed to flush thinking buffer for ${this.taskId}`, true);
|
||||||
return this.flushPendingEntries();
|
return this.flushPendingEntries();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3152,6 +3152,7 @@ export class TaskExecutor {
|
|||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
agent: "executor",
|
agent: "executor",
|
||||||
persistAgentToolOutput: settings.persistAgentToolOutput,
|
persistAgentToolOutput: settings.persistAgentToolOutput,
|
||||||
|
persistAgentThinkingLog: settings.persistAgentThinkingLog,
|
||||||
onAgentText: (taskId, delta) => {
|
onAgentText: (taskId, delta) => {
|
||||||
lastAssistantText += delta;
|
lastAssistantText += delta;
|
||||||
stuckDetector?.recordActivity(taskId);
|
stuckDetector?.recordActivity(taskId);
|
||||||
@@ -4960,6 +4961,7 @@ ${feedback}
|
|||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
agent: "executor",
|
agent: "executor",
|
||||||
persistAgentToolOutput: settings.persistAgentToolOutput,
|
persistAgentToolOutput: settings.persistAgentToolOutput,
|
||||||
|
persistAgentThinkingLog: settings.persistAgentThinkingLog,
|
||||||
onAgentText: this.options.onAgentText,
|
onAgentText: this.options.onAgentText,
|
||||||
onAgentTool: this.options.onAgentTool,
|
onAgentTool: this.options.onAgentTool,
|
||||||
});
|
});
|
||||||
@@ -5769,6 +5771,7 @@ and show an appropriate message to the user.\`
|
|||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
agent: "reviewer",
|
agent: "reviewer",
|
||||||
persistAgentToolOutput: settings.persistAgentToolOutput,
|
persistAgentToolOutput: settings.persistAgentToolOutput,
|
||||||
|
persistAgentThinkingLog: settings.persistAgentThinkingLog,
|
||||||
onAgentText: (taskId, delta) => {
|
onAgentText: (taskId, delta) => {
|
||||||
this.options.onAgentText?.(taskId, delta);
|
this.options.onAgentText?.(taskId, delta);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -917,6 +917,7 @@ async function attemptInMergeVerificationFix(
|
|||||||
taskId,
|
taskId,
|
||||||
agent: "merger",
|
agent: "merger",
|
||||||
persistAgentToolOutput: settings.persistAgentToolOutput,
|
persistAgentToolOutput: settings.persistAgentToolOutput,
|
||||||
|
persistAgentThinkingLog: settings.persistAgentThinkingLog,
|
||||||
onAgentText: options.onAgentText,
|
onAgentText: options.onAgentText,
|
||||||
onAgentTool: options.onAgentTool,
|
onAgentTool: options.onAgentTool,
|
||||||
});
|
});
|
||||||
@@ -2026,6 +2027,7 @@ async function runAiAgentForAutostashConflict(params: {
|
|||||||
taskId,
|
taskId,
|
||||||
agent: "merger",
|
agent: "merger",
|
||||||
persistAgentToolOutput: settings.persistAgentToolOutput,
|
persistAgentToolOutput: settings.persistAgentToolOutput,
|
||||||
|
persistAgentThinkingLog: settings.persistAgentThinkingLog,
|
||||||
onAgentText: options.onAgentText
|
onAgentText: options.onAgentText
|
||||||
? (_id: string, delta: string) => options.onAgentText!(delta)
|
? (_id: string, delta: string) => options.onAgentText!(delta)
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -2395,6 +2397,7 @@ async function runAiAgentForAutostashHardFail(params: {
|
|||||||
taskId,
|
taskId,
|
||||||
agent: "merger",
|
agent: "merger",
|
||||||
persistAgentToolOutput: settings.persistAgentToolOutput,
|
persistAgentToolOutput: settings.persistAgentToolOutput,
|
||||||
|
persistAgentThinkingLog: settings.persistAgentThinkingLog,
|
||||||
onAgentText: options.onAgentText
|
onAgentText: options.onAgentText
|
||||||
? (_id: string, delta: string) => options.onAgentText!(delta)
|
? (_id: string, delta: string) => options.onAgentText!(delta)
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -4442,6 +4445,7 @@ You are assisting with a paused \`git pull --rebase\`.
|
|||||||
taskId,
|
taskId,
|
||||||
agent: "merger",
|
agent: "merger",
|
||||||
persistAgentToolOutput: settings.persistAgentToolOutput,
|
persistAgentToolOutput: settings.persistAgentToolOutput,
|
||||||
|
persistAgentThinkingLog: settings.persistAgentThinkingLog,
|
||||||
onAgentText: options?.onAgentText
|
onAgentText: options?.onAgentText
|
||||||
? (_id, delta) => options.onAgentText?.(delta)
|
? (_id, delta) => options.onAgentText?.(delta)
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -7132,6 +7136,7 @@ async function runAiAgentForCommit(params: AiAgentParams): Promise<{ success: bo
|
|||||||
taskId,
|
taskId,
|
||||||
agent: "merger",
|
agent: "merger",
|
||||||
persistAgentToolOutput: settings.persistAgentToolOutput,
|
persistAgentToolOutput: settings.persistAgentToolOutput,
|
||||||
|
persistAgentThinkingLog: settings.persistAgentThinkingLog,
|
||||||
onAgentText: options.onAgentText
|
onAgentText: options.onAgentText
|
||||||
? (_id, delta) => options.onAgentText!(delta)
|
? (_id, delta) => options.onAgentText!(delta)
|
||||||
: undefined,
|
: undefined,
|
||||||
@@ -7737,6 +7742,7 @@ If issues are found that need attention, describe them clearly and include concr
|
|||||||
taskId,
|
taskId,
|
||||||
agent: "merger",
|
agent: "merger",
|
||||||
persistAgentToolOutput: settings.persistAgentToolOutput,
|
persistAgentToolOutput: settings.persistAgentToolOutput,
|
||||||
|
persistAgentThinkingLog: settings.persistAgentThinkingLog,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -351,6 +351,7 @@ export async function reviewStep(
|
|||||||
? (_id, delta) => options.onText!(delta)
|
? (_id, delta) => options.onText!(delta)
|
||||||
: undefined,
|
: undefined,
|
||||||
persistAgentToolOutput: liveSettings?.persistAgentToolOutput,
|
persistAgentToolOutput: liveSettings?.persistAgentToolOutput,
|
||||||
|
persistAgentThinkingLog: liveSettings?.persistAgentThinkingLog,
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
|||||||
@@ -890,6 +890,7 @@ export class StepSessionExecutor {
|
|||||||
taskId: taskDetail.id,
|
taskId: taskDetail.id,
|
||||||
agent: "executor",
|
agent: "executor",
|
||||||
persistAgentToolOutput: settings.persistAgentToolOutput,
|
persistAgentToolOutput: settings.persistAgentToolOutput,
|
||||||
|
persistAgentThinkingLog: settings.persistAgentThinkingLog,
|
||||||
});
|
});
|
||||||
let session: AgentSession | null = null;
|
let session: AgentSession | null = null;
|
||||||
|
|
||||||
|
|||||||
@@ -913,6 +913,7 @@ export class TriageProcessor {
|
|||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
agent: "triage",
|
agent: "triage",
|
||||||
persistAgentToolOutput: settings.persistAgentToolOutput,
|
persistAgentToolOutput: settings.persistAgentToolOutput,
|
||||||
|
persistAgentThinkingLog: settings.persistAgentThinkingLog,
|
||||||
onAgentText: (id, delta) => {
|
onAgentText: (id, delta) => {
|
||||||
stuckDetector?.recordActivity(task.id);
|
stuckDetector?.recordActivity(task.id);
|
||||||
this.options.onAgentText?.(id, delta);
|
this.options.onAgentText?.(id, delta);
|
||||||
|
|||||||
Reference in New Issue
Block a user