fix: add compaction headroom for memory writes
This commit is contained in:
@@ -34,6 +34,8 @@
|
|||||||
- Docker: allow optional home volume + extra bind mounts in `docker-setup.sh`. (#679) — thanks @gabriel-trigo.
|
- Docker: allow optional home volume + extra bind mounts in `docker-setup.sh`. (#679) — thanks @gabriel-trigo.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
- Agents/Pi: raise compaction `reserveTokens` floor to 20k to leave enough headroom for pre-compaction “memory write” turns.
|
||||||
|
- Auto-reply: suppress draft/typing streaming for `NO_REPLY` (silent system ops) so it doesn’t leak partial output.
|
||||||
- CLI/Status: expand tables to full terminal width; clarify provider setup vs runtime warnings; richer per-provider detail; token previews in `status` while keeping `status --all` redacted; add troubleshooting link footer; keep log tails pasteable; show gateway auth used when reachable; surface provider runtime errors (Signal/iMessage/Slack); harden `tailscale status --json` parsing; make `status --all` scan progress determinate; and replace the footer with a 3-line “Next steps” recommendation (share/debug/probe).
|
- CLI/Status: expand tables to full terminal width; clarify provider setup vs runtime warnings; richer per-provider detail; token previews in `status` while keeping `status --all` redacted; add troubleshooting link footer; keep log tails pasteable; show gateway auth used when reachable; surface provider runtime errors (Signal/iMessage/Slack); harden `tailscale status --json` parsing; make `status --all` scan progress determinate; and replace the footer with a 3-line “Next steps” recommendation (share/debug/probe).
|
||||||
- CLI/Gateway: clarify that `clawdbot gateway status` reports RPC health (connect + RPC) and shows RPC failures separately from connect failures.
|
- CLI/Gateway: clarify that `clawdbot gateway status` reports RPC health (connect + RPC) and shows RPC failures separately from connect failures.
|
||||||
- CLI/Update: gate progress spinner on stdout TTY and align clean-check step label. (#701) — thanks @bjesuiter.
|
- CLI/Update: gate progress spinner on stdout TTY and align clean-check step label. (#701) — thanks @bjesuiter.
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ import {
|
|||||||
import { normalizeModelCompat } from "./model-compat.js";
|
import { normalizeModelCompat } from "./model-compat.js";
|
||||||
import { ensureClawdbotModelsJson } from "./models-config.js";
|
import { ensureClawdbotModelsJson } from "./models-config.js";
|
||||||
import type { MessagingToolSend } from "./pi-embedded-messaging.js";
|
import type { MessagingToolSend } from "./pi-embedded-messaging.js";
|
||||||
|
import { ensurePiCompactionReserveTokens } from "./pi-settings.js";
|
||||||
import { acquireSessionWriteLock } from "./session-write-lock.js";
|
import { acquireSessionWriteLock } from "./session-write-lock.js";
|
||||||
|
|
||||||
export type { MessagingToolSend } from "./pi-embedded-messaging.js";
|
export type { MessagingToolSend } from "./pi-embedded-messaging.js";
|
||||||
@@ -981,6 +982,7 @@ export async function compactEmbeddedPiSession(params: {
|
|||||||
effectiveWorkspace,
|
effectiveWorkspace,
|
||||||
agentDir,
|
agentDir,
|
||||||
);
|
);
|
||||||
|
ensurePiCompactionReserveTokens({ settingsManager });
|
||||||
const additionalExtensionPaths = buildEmbeddedExtensionPaths({
|
const additionalExtensionPaths = buildEmbeddedExtensionPaths({
|
||||||
cfg: params.config,
|
cfg: params.config,
|
||||||
sessionManager,
|
sessionManager,
|
||||||
@@ -1369,6 +1371,7 @@ export async function runEmbeddedPiAgent(params: {
|
|||||||
effectiveWorkspace,
|
effectiveWorkspace,
|
||||||
agentDir,
|
agentDir,
|
||||||
);
|
);
|
||||||
|
ensurePiCompactionReserveTokens({ settingsManager });
|
||||||
const additionalExtensionPaths = buildEmbeddedExtensionPaths({
|
const additionalExtensionPaths = buildEmbeddedExtensionPaths({
|
||||||
cfg: params.config,
|
cfg: params.config,
|
||||||
sessionManager,
|
sessionManager,
|
||||||
|
|||||||
37
src/agents/pi-settings.test.ts
Normal file
37
src/agents/pi-settings.test.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import {
|
||||||
|
DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR,
|
||||||
|
ensurePiCompactionReserveTokens,
|
||||||
|
} from "./pi-settings.js";
|
||||||
|
|
||||||
|
describe("ensurePiCompactionReserveTokens", () => {
|
||||||
|
it("bumps reserveTokens when below floor", () => {
|
||||||
|
const settingsManager = {
|
||||||
|
getCompactionReserveTokens: () => 16_384,
|
||||||
|
applyOverrides: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = ensurePiCompactionReserveTokens({ settingsManager });
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
didOverride: true,
|
||||||
|
reserveTokens: DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR,
|
||||||
|
});
|
||||||
|
expect(settingsManager.applyOverrides).toHaveBeenCalledWith({
|
||||||
|
compaction: { reserveTokens: DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not override when already above floor", () => {
|
||||||
|
const settingsManager = {
|
||||||
|
getCompactionReserveTokens: () => 32_000,
|
||||||
|
applyOverrides: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = ensurePiCompactionReserveTokens({ settingsManager });
|
||||||
|
|
||||||
|
expect(result).toEqual({ didOverride: false, reserveTokens: 32_000 });
|
||||||
|
expect(settingsManager.applyOverrides).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
27
src/agents/pi-settings.ts
Normal file
27
src/agents/pi-settings.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
export const DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR = 20_000;
|
||||||
|
|
||||||
|
type PiSettingsManagerLike = {
|
||||||
|
getCompactionReserveTokens: () => number;
|
||||||
|
applyOverrides: (overrides: {
|
||||||
|
compaction: { reserveTokens: number };
|
||||||
|
}) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ensurePiCompactionReserveTokens(params: {
|
||||||
|
settingsManager: PiSettingsManagerLike;
|
||||||
|
minReserveTokens?: number;
|
||||||
|
}): { didOverride: boolean; reserveTokens: number } {
|
||||||
|
const minReserveTokens =
|
||||||
|
params.minReserveTokens ?? DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR;
|
||||||
|
const current = params.settingsManager.getCompactionReserveTokens();
|
||||||
|
|
||||||
|
if (current >= minReserveTokens) {
|
||||||
|
return { didOverride: false, reserveTokens: current };
|
||||||
|
}
|
||||||
|
|
||||||
|
params.settingsManager.applyOverrides({
|
||||||
|
compaction: { reserveTokens: minReserveTokens },
|
||||||
|
});
|
||||||
|
|
||||||
|
return { didOverride: true, reserveTokens: minReserveTokens };
|
||||||
|
}
|
||||||
@@ -164,6 +164,25 @@ describe("runReplyAgent typing (heartbeat)", () => {
|
|||||||
expect(typing.startTypingLoop).not.toHaveBeenCalled();
|
expect(typing.startTypingLoop).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("suppresses partial streaming for NO_REPLY", async () => {
|
||||||
|
const onPartialReply = vi.fn();
|
||||||
|
runEmbeddedPiAgentMock.mockImplementationOnce(
|
||||||
|
async (params: EmbeddedPiAgentParams) => {
|
||||||
|
await params.onPartialReply?.({ text: "NO_REPLY" });
|
||||||
|
return { payloads: [{ text: "NO_REPLY" }], meta: {} };
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { run, typing } = createMinimalRun({
|
||||||
|
opts: { isHeartbeat: false, onPartialReply },
|
||||||
|
});
|
||||||
|
await run();
|
||||||
|
|
||||||
|
expect(onPartialReply).not.toHaveBeenCalled();
|
||||||
|
expect(typing.startTypingOnText).not.toHaveBeenCalled();
|
||||||
|
expect(typing.startTypingLoop).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("starts typing only on deltas in message mode", async () => {
|
it("starts typing only on deltas in message mode", async () => {
|
||||||
runEmbeddedPiAgentMock.mockImplementationOnce(async () => ({
|
runEmbeddedPiAgentMock.mockImplementationOnce(async () => ({
|
||||||
payloads: [{ text: "final" }],
|
payloads: [{ text: "final" }],
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import {
|
|||||||
import { stripHeartbeatToken } from "../heartbeat.js";
|
import { stripHeartbeatToken } from "../heartbeat.js";
|
||||||
import type { OriginatingChannelType, TemplateContext } from "../templating.js";
|
import type { OriginatingChannelType, TemplateContext } from "../templating.js";
|
||||||
import { normalizeVerboseLevel, type VerboseLevel } from "../thinking.js";
|
import { normalizeVerboseLevel, type VerboseLevel } from "../thinking.js";
|
||||||
import { SILENT_REPLY_TOKEN } from "../tokens.js";
|
import { isSilentReplyText, SILENT_REPLY_TOKEN } from "../tokens.js";
|
||||||
import type { GetReplyOptions, ReplyPayload } from "../types.js";
|
import type { GetReplyOptions, ReplyPayload } from "../types.js";
|
||||||
import {
|
import {
|
||||||
createAudioAsVoiceBuffer,
|
createAudioAsVoiceBuffer,
|
||||||
@@ -485,6 +485,7 @@ export async function runReplyAgent(params: {
|
|||||||
}
|
}
|
||||||
text = stripped.text;
|
text = stripped.text;
|
||||||
}
|
}
|
||||||
|
if (isSilentReplyText(text, SILENT_REPLY_TOKEN)) return;
|
||||||
await typingSignals.signalTextDelta(text);
|
await typingSignals.signalTextDelta(text);
|
||||||
await opts.onPartialReply?.({
|
await opts.onPartialReply?.({
|
||||||
text,
|
text,
|
||||||
|
|||||||
Reference in New Issue
Block a user