mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
Merged via squash.
Prepared head SHA: 4978f7b8b5
Co-authored-by: ly85206559 <12526624+ly85206559@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
This commit is contained in:
@@ -25,7 +25,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/device pairing: restrict non-admin paired-device sessions (device-token auth) to their own pairing list, approve, and reject actions so a paired device cannot enumerate other devices or approve/reject pairing requests authored by another device. Admin and shared-secret operator sessions retain full visibility. (#69375) Thanks @eleqtrizit.
|
||||
- Agents/gateway tool: extend the agent-facing `gateway` tool's config mutation guard so model-driven `config.patch` and `config.apply` cannot rewrite operator-trusted paths (sandbox, plugin trust, gateway auth/TLS, hook routing and tokens, SSRF policy, MCP servers, workspace filesystem hardening) and cannot bypass the guard by editing per-agent sandbox, tools, or embedded-Pi overrides in place under `agents.list[]`. (#69377) Thanks @eleqtrizit.
|
||||
- Gateway/websocket broadcasts: require `operator.read` (or higher) for chat, agent, and tool-result event frames so pairing-scoped and node-role sessions no longer passively receive session chat content, and scope-gate unknown broadcast events by default. Plugin-defined `plugin.*` broadcasts are scoped to operator.write/admin, and status/transport events (`heartbeat`, `presence`, `tick`, etc.) remain unrestricted. Per-client sequence numbers preserve per-connection monotonicity. (#69373) Thanks @eleqtrizit.
|
||||
|
||||
- Agents/compaction: always reload embedded Pi resources through an explicit loader and reapply reserve-token overrides so runs without extension factories no longer silently lose compaction settings before session start. (#67146) Thanks @ly85206559.
|
||||
## 2026.4.20
|
||||
|
||||
### Changes
|
||||
|
||||
@@ -70,6 +70,7 @@ import {
|
||||
consumeCompactionSafeguardCancelReason,
|
||||
setCompactionSafeguardCancelReason,
|
||||
} from "../pi-hooks/compaction-safeguard-runtime.js";
|
||||
import { applyPiCompactionSettingsFromConfig } from "../pi-settings.js";
|
||||
import { createPreparedEmbeddedPiSettingsManager } from "../pi-project-settings.js";
|
||||
import { createOpenClawCodingTools } from "../pi-tools.js";
|
||||
import { wrapStreamFnTextTransforms } from "../plugin-text-transforms.js";
|
||||
@@ -823,18 +824,20 @@ export async function compactEmbeddedPiSessionDirect(
|
||||
modelId,
|
||||
model,
|
||||
});
|
||||
// Only create an explicit resource loader when there are extension factories
|
||||
// to register; otherwise let createAgentSession use its built-in default.
|
||||
let resourceLoader: DefaultResourceLoader | undefined;
|
||||
if (extensionFactories.length > 0) {
|
||||
resourceLoader = new DefaultResourceLoader({
|
||||
cwd: resolvedWorkspace,
|
||||
agentDir,
|
||||
settingsManager,
|
||||
extensionFactories,
|
||||
});
|
||||
await resourceLoader.reload();
|
||||
}
|
||||
const resourceLoader = new DefaultResourceLoader({
|
||||
cwd: resolvedWorkspace,
|
||||
agentDir,
|
||||
settingsManager,
|
||||
extensionFactories,
|
||||
});
|
||||
await resourceLoader.reload();
|
||||
// DefaultResourceLoader.reload() rehydrates settings from disk and can drop OpenClaw
|
||||
// compaction overrides applied in createPreparedEmbeddedPiSettingsManager.
|
||||
applyPiCompactionSettingsFromConfig({
|
||||
settingsManager,
|
||||
cfg: params.config,
|
||||
contextTokenBudget: ctxInfo.tokens,
|
||||
});
|
||||
|
||||
const { builtInTools, customTools } = splitSdkTools({
|
||||
tools: effectiveTools,
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
cleanupTempPaths,
|
||||
createContextEngineAttemptRunner,
|
||||
getHoisted,
|
||||
resetEmbeddedAttemptHarness,
|
||||
} from "./attempt.spawn-workspace.test-support.js";
|
||||
|
||||
const hoisted = getHoisted();
|
||||
|
||||
describe("runEmbeddedAttempt resource loader wiring", () => {
|
||||
const tempPaths: string[] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
resetEmbeddedAttemptHarness();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanupTempPaths(tempPaths);
|
||||
});
|
||||
|
||||
it("passes an explicit resourceLoader to createAgentSession even without extension factories", async () => {
|
||||
await createContextEngineAttemptRunner({
|
||||
sessionKey: "agent:main:discord:dm:test-resource-loader",
|
||||
tempPaths,
|
||||
contextEngine: {
|
||||
assemble: async ({ messages }) => ({
|
||||
messages,
|
||||
estimatedTokens: 1,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(hoisted.createAgentSessionMock).toHaveBeenCalled();
|
||||
expect(hoisted.createAgentSessionMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
resourceLoader: expect.objectContaining({
|
||||
reload: expect.any(Function),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -284,6 +284,13 @@ vi.mock("../../pi-project-settings.js", () => ({
|
||||
|
||||
vi.mock("../../pi-settings.js", () => ({
|
||||
applyPiAutoCompactionGuard: () => {},
|
||||
applyPiCompactionSettingsFromConfig: () => ({
|
||||
didOverride: false,
|
||||
compaction: {
|
||||
reserveTokens: 0,
|
||||
keepRecentTokens: 40_000,
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("../extensions.js", () => ({
|
||||
|
||||
@@ -79,8 +79,11 @@ import {
|
||||
resolveBootstrapTotalMaxChars,
|
||||
} from "../../pi-embedded-helpers.js";
|
||||
import { subscribeEmbeddedPiSession } from "../../pi-embedded-subscribe.js";
|
||||
import {
|
||||
applyPiAutoCompactionGuard,
|
||||
applyPiCompactionSettingsFromConfig,
|
||||
} from "../../pi-settings.js";
|
||||
import { createPreparedEmbeddedPiSettingsManager } from "../../pi-project-settings.js";
|
||||
import { applyPiAutoCompactionGuard } from "../../pi-settings.js";
|
||||
import {
|
||||
createClientToolNameConflictError,
|
||||
findClientToolNameConflicts,
|
||||
@@ -1019,18 +1022,20 @@ export async function runEmbeddedAttempt(
|
||||
modelId: params.modelId,
|
||||
model: params.model,
|
||||
});
|
||||
// Only create an explicit resource loader when there are extension factories
|
||||
// to register; otherwise let createAgentSession use its built-in default.
|
||||
let resourceLoader: DefaultResourceLoader | undefined;
|
||||
if (extensionFactories.length > 0) {
|
||||
resourceLoader = new DefaultResourceLoader({
|
||||
cwd: resolvedWorkspace,
|
||||
agentDir,
|
||||
settingsManager,
|
||||
extensionFactories,
|
||||
});
|
||||
await resourceLoader.reload();
|
||||
}
|
||||
const resourceLoader = new DefaultResourceLoader({
|
||||
cwd: resolvedWorkspace,
|
||||
agentDir,
|
||||
settingsManager,
|
||||
extensionFactories,
|
||||
});
|
||||
await resourceLoader.reload();
|
||||
// DefaultResourceLoader.reload() rehydrates settings from disk and can drop OpenClaw
|
||||
// compaction overrides applied in createPreparedEmbeddedPiSettingsManager.
|
||||
applyPiCompactionSettingsFromConfig({
|
||||
settingsManager,
|
||||
cfg: params.config,
|
||||
contextTokenBudget: params.contextTokenBudget,
|
||||
});
|
||||
|
||||
// Get hook runner early so it's available when creating tools
|
||||
const hookRunner = getGlobalHookRunner();
|
||||
|
||||
@@ -23,6 +23,39 @@ describe("applyPiCompactionSettingsFromConfig", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("can restore reserveTokens after a simulated resource loader reload drops them below floor", () => {
|
||||
const cfg = {
|
||||
agents: { defaults: { compaction: { reserveTokensFloor: DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR } } },
|
||||
} as const;
|
||||
let reserve = 16_384;
|
||||
const keep = 20_000;
|
||||
const settingsManager = {
|
||||
getCompactionReserveTokens: () => reserve,
|
||||
getCompactionKeepRecentTokens: () => keep,
|
||||
applyOverrides: vi.fn((overrides: { compaction: { reserveTokens?: number } }) => {
|
||||
if (overrides.compaction.reserveTokens !== undefined) {
|
||||
reserve = overrides.compaction.reserveTokens;
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
const first = applyPiCompactionSettingsFromConfig({
|
||||
settingsManager,
|
||||
cfg,
|
||||
contextTokenBudget: 100_000,
|
||||
});
|
||||
expect(first.compaction.reserveTokens).toBe(DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR);
|
||||
|
||||
reserve = 16_384;
|
||||
const second = applyPiCompactionSettingsFromConfig({
|
||||
settingsManager,
|
||||
cfg,
|
||||
contextTokenBudget: 100_000,
|
||||
});
|
||||
expect(second.compaction.reserveTokens).toBe(DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR);
|
||||
expect(reserve).toBe(DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR);
|
||||
});
|
||||
|
||||
it("does not override when already above floor and not in safeguard mode", () => {
|
||||
const settingsManager = {
|
||||
getCompactionReserveTokens: () => 32_000,
|
||||
|
||||
Reference in New Issue
Block a user