fix: preserve restart continuation authority

This commit is contained in:
VACInc
2026-05-08 06:28:51 -04:00
committed by Ayaan Zaidi
parent cc04874b09
commit 09be7de2b2
3 changed files with 86 additions and 6 deletions

View File

@@ -212,6 +212,7 @@ Docs: https://docs.openclaw.ai
- CLI/router: when `openclaw <name>` does not match a CLI subcommand, check plugin tool manifests first so names like `lcm_recent` get an agent-tool diagnostic instead of the misleading suggestion to add the tool name to `plugins.allow`. Fixes #77214. Thanks @100yenadmin.
- QA-lab/parity: bump the live mock-openai parity baseline from `claude-opus-4-6`/`claude-sonnet-4-6` to `claude-opus-4-7`/`claude-sonnet-4-7` and the candidate alt from `gpt-5.4-alt` to `gpt-5.5-alt` in `openclaw-release-checks.yml` and `qa-live-transports-convex.yml`, matching the active Opus 4.7 / GPT-5.5 defaults already used elsewhere on main. Carries forward the surface-bump portion of #74290. Thanks @100yenadmin.
- QA-lab/scenarios: raise the `approval-turn-tool-followthrough` per-turn fallback timeouts from 20s/30s to 60s so cold mock-gateway parity runs do not flake on the approval-turn chain. Carries forward the timeout-bump portion of #74290. Thanks @100yenadmin.
- Gateway/restart continuation: treat routed post-reboot agent turns as trusted internal continuations while preserving the original Telegram topic route, so owner-only tools remain available for chained restart workflows after reboot.
- Agents/compaction: keep the recent tail after manual `/compact` when Pi returns an empty or no-op compaction summary, preventing blank checkpoints from replacing the live context.
- Native commands: handle slash commands before workspace and agent-reply bootstrap so Telegram `/status` and other command-only native replies do not wait behind full agent turn setup.
- Telegram/groups: include the recent local chat window and nearby reply-target window as generic inbound context so stale reply ancestry does not overshadow the live group conversation.

View File

@@ -543,10 +543,16 @@ describe("scheduleRestartSentinelWake", () => {
BodyForAgent: "stamped:Reply with exactly: Yay! I did it!",
BodyForCommands: "",
CommandBody: "",
CommandAuthorized: false,
CommandAuthorized: true,
GatewayClientScopes: ["operator.admin"],
InputProvenance: {
kind: "internal_system",
sourceChannel: "whatsapp",
sourceTool: "restart-sentinel",
},
SessionKey: "agent:main:main",
Provider: "whatsapp",
Surface: "whatsapp",
Provider: "webchat",
Surface: "webchat",
OriginatingChannel: "whatsapp",
OriginatingTo: "+15550002",
MessageThreadId: "thread-42",
@@ -600,6 +606,72 @@ describe("scheduleRestartSentinelWake", () => {
);
});
it("authorizes routed agentTurn continuations while preserving Telegram topic routing", async () => {
mocks.readRestartSentinel.mockResolvedValue({
payload: {
sessionKey: "agent:main:telegram:group:-1003826723328:topic:13757",
ts: 123,
continuation: {
kind: "agentTurn",
message: "continue in topic",
},
},
} as unknown as Awaited<ReturnType<typeof mocks.readRestartSentinel>>);
mocks.parseSessionThreadInfo.mockReturnValue({
baseSessionKey: "agent:main:telegram:group:-1003826723328",
threadId: "13757",
});
mocks.loadSessionEntry.mockReturnValue({
cfg: {},
entry: {
sessionId: "agent:main:telegram:group:-1003826723328:topic:13757",
updatedAt: 0,
origin: { provider: "telegram", chatType: "group" },
},
store: {},
storePath: "/tmp/sessions.json",
canonicalKey: "agent:main:telegram:group:-1003826723328:topic:13757",
legacyKey: undefined,
});
mocks.deliveryContextFromSession.mockReturnValue({
channel: "telegram",
to: "telegram:-1003826723328:topic:13757",
accountId: "default",
threadId: 13757,
});
mocks.resolveOutboundTarget.mockReturnValue({
ok: true as const,
to: "telegram:-1003826723328:topic:13757",
});
await scheduleRestartSentinelWake({ deps: {} as never });
expect(mocks.recordInboundSessionAndDispatchReply).toHaveBeenCalledWith(
expect.objectContaining({
channel: "telegram",
accountId: "default",
routeSessionKey: "agent:main:telegram:group:-1003826723328:topic:13757",
ctxPayload: expect.objectContaining({
Body: "continue in topic",
CommandAuthorized: true,
GatewayClientScopes: ["operator.admin"],
InputProvenance: {
kind: "internal_system",
sourceChannel: "telegram",
sourceTool: "restart-sentinel",
},
Provider: "webchat",
Surface: "webchat",
ChatType: "group",
OriginatingChannel: "telegram",
OriginatingTo: "telegram:-1003826723328:topic:13757",
ExplicitDeliverRoute: true,
MessageThreadId: "13757",
}),
}),
);
});
it("preserves derived reply transport ids in continuation context", async () => {
mocks.getChannelPlugin.mockReturnValue({
id: "whatsapp",

View File

@@ -41,6 +41,7 @@ import {
deliveryContextFromSession,
mergeDeliveryContext,
} from "../utils/delivery-context.shared.js";
import { INTERNAL_MESSAGE_CHANNEL } from "../utils/message-channel.js";
import { injectTimestamp, timestampOptsFromConfig } from "./server-methods/agent-timestamp.js";
import { loadSessionEntry } from "./session-utils.js";
import { runStartupTasks, type StartupTask } from "./startup-tasks.js";
@@ -287,10 +288,16 @@ async function deliverQueuedSessionDelivery(params: {
AccountId: route.accountId,
MessageSid: messageId,
Timestamp: Date.now(),
Provider: route.channel,
Surface: route.channel,
InputProvenance: {
kind: "internal_system",
sourceChannel: route.channel,
sourceTool: "restart-sentinel",
},
Provider: INTERNAL_MESSAGE_CHANNEL,
Surface: INTERNAL_MESSAGE_CHANNEL,
ChatType: route.chatType,
CommandAuthorized: false,
CommandAuthorized: true,
GatewayClientScopes: ["operator.admin"],
ReplyToId: route.replyToId,
OriginatingChannel: route.channel,
OriginatingTo: route.to,