fix(sqlite): align rebased runtime surfaces

This commit is contained in:
Peter Steinberger
2026-05-15 18:49:55 +01:00
parent 3421401f59
commit 1fd94dfde7
11 changed files with 55 additions and 71 deletions

View File

@@ -8255,7 +8255,7 @@ describe("runCodexAppServerAttempt", () => {
expect(llmInput).toHaveBeenCalledTimes(1);
expect(llmOutput).toHaveBeenCalledTimes(1);
expect(agentEnd).toHaveBeenCalledTimes(1);
const [llmOutputPayload] = mockCall(llmOutput, "llm_output") as [
const [llmOutputPayload] = llmOutput.mock.calls[0] as [
{
assistantTexts?: string[];
harnessId?: string;
@@ -8273,8 +8273,8 @@ describe("runCodexAppServerAttempt", () => {
expect(llmOutputPayload.resolvedRef).toBe("codex/gpt-5.4-codex");
expect(llmOutputPayload.harnessId).toBe("codex");
expect(llmOutputPayload.runId).toBe("run-1");
expect(llmOutputPayload.sessionId).toBe("session-1");
const [agentEndPayload] = mockCall(agentEnd, "agent_end") as [
expect(llmOutputPayload.sessionId).toBe(sessionId);
const [agentEndPayload] = agentEnd.mock.calls[0] as [
{ error?: string; messages?: Array<{ role?: string }>; success?: boolean },
unknown,
];

View File

@@ -74,7 +74,7 @@ import {
import { resolveSlackThreadTargets } from "../../threading.js";
import type { SlackMessageEvent } from "../../types.js";
import { normalizeSlackAllowOwnerEntry } from "../allow-list.js";
import { resolveStorePath, updateLastRoute } from "../config.runtime.js";
import { updateLastRoute } from "../config.runtime.js";
import { recordInboundSession } from "../conversation.runtime.js";
import { escapeSlackMrkdwn } from "../mrkdwn.js";
import {
@@ -359,10 +359,6 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
: undefined;
if (prepared.isDirectMessage) {
const sessionCfg = cfg.session;
const storePath = resolveStorePath(sessionCfg?.store, {
agentId: route.agentId,
});
const pinnedMainDmOwner = resolvePinnedMainDmOwnerFromAllowlist({
dmScope: cfg.session?.dmScope,
allowFrom: ctx.allowFrom,
@@ -384,7 +380,7 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
);
} else {
await updateLastRoute({
storePath,
agentId: route.agentId,
sessionKey: inboundLastRouteSessionKey,
deliveryContext: {
channel: "slack",

View File

@@ -1469,7 +1469,6 @@ export async function runEmbeddedAgent(
durationMs: Date.now() - started,
agentMeta: buildErrorAgentMeta({
sessionId: activeSessionId,
sessionFile: activeSessionFile,
provider,
model: model.id,
contextTokens: ctxInfo.tokens,
@@ -1795,7 +1794,6 @@ export async function runEmbeddedAgent(
durationMs: Date.now() - started,
agentMeta: buildErrorAgentMeta({
sessionId: activeSessionId,
sessionFile: activeSessionFile,
provider,
model: model.id,
contextTokens: ctxInfo.tokens,
@@ -2324,7 +2322,6 @@ export async function runEmbeddedAgent(
durationMs: Date.now() - started,
agentMeta: buildErrorAgentMeta({
sessionId: sessionIdUsed,
sessionFile: activeSessionFile,
provider,
model: model.id,
contextTokens: ctxInfo.tokens,
@@ -2357,7 +2354,6 @@ export async function runEmbeddedAgent(
durationMs: Date.now() - started,
agentMeta: buildErrorAgentMeta({
sessionId: sessionIdUsed,
sessionFile: activeSessionFile,
provider,
model: model.id,
contextTokens: ctxInfo.tokens,
@@ -2448,7 +2444,6 @@ export async function runEmbeddedAgent(
durationMs: Date.now() - started,
agentMeta: buildErrorAgentMeta({
sessionId: sessionIdUsed,
sessionFile: activeSessionFile,
provider,
model: model.id,
contextTokens: ctxInfo.tokens,
@@ -2489,7 +2484,6 @@ export async function runEmbeddedAgent(
durationMs: Date.now() - started,
agentMeta: buildErrorAgentMeta({
sessionId: sessionIdUsed,
sessionFile: activeSessionFile,
provider,
model: model.id,
contextTokens: ctxInfo.tokens,

View File

@@ -114,6 +114,21 @@ async function resolveQueueEmbeddedAgentMessageOutcome(
);
}
async function runAnnounceAgentCall(params: {
agentParams: Record<string, unknown>;
expectFinal?: boolean;
timeoutMs?: number;
}): Promise<unknown> {
return await subagentAnnounceDeliveryDeps.dispatchGatewayMethodInProcess(
"agent",
params.agentParams,
{
expectFinal: params.expectFinal,
timeoutMs: params.timeoutMs,
},
);
}
function formatQueueWakeFailureError(
fallback: string,
outcome: EmbeddedAgentQueueMessageOutcome,

View File

@@ -74,6 +74,9 @@ function inferCompletionChatTypeFromTarget(to: string | undefined): CompletionCh
if (normalized.startsWith("group:")) {
return "group";
}
if (normalized.endsWith("@g.us")) {
return "group";
}
if (normalized.startsWith("channel:") || normalized.startsWith("thread:")) {
return "channel";
}

View File

@@ -2794,10 +2794,6 @@ async function updateCommandInternal(opts: UpdateCommandOptions): Promise<void>
if (timeoutMs === null) {
return;
}
if (opts.dryRun !== true) {
await disableCurrentOpenClawUpdateLaunchdJob().catch(() => undefined);
assertConfigWriteAllowedInCurrentMode();
}
const updateStepTimeoutMs = timeoutMs ?? DEFAULT_UPDATE_STEP_TIMEOUT_MS;
let root = await resolveUpdateRoot();
@@ -2910,6 +2906,10 @@ async function updateCommandInternal(opts: UpdateCommandOptions): Promise<void>
defaultRuntime.exit(1);
return;
}
if (opts.dryRun !== true) {
await disableCurrentOpenClawUpdateLaunchdJob().catch(() => undefined);
assertConfigWriteAllowedInCurrentMode();
}
const installKind = updateStatus.installKind;
const switchToGit = requestedChannel === "dev" && installKind !== "git";

View File

@@ -105,6 +105,13 @@ function requirePersistedJob(jobs: Array<Record<string, unknown>>, index: number
return job;
}
function requireRecord(value: unknown, label: string): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error(`expected ${label}`);
}
return value as Record<string, unknown>;
}
function expectNoteContaining(message: string, title: string): void {
expect(
noteMock.mock.calls.some(
@@ -174,7 +181,7 @@ describe("maybeRepairLegacyCronStore", () => {
state: {},
},
]);
const prompter = makePrompter(true);
const prompter = makePrompter(false);
await maybeRepairLegacyCronStore({
cfg: {
@@ -189,7 +196,7 @@ describe("maybeRepairLegacyCronStore", () => {
prompter,
});
expect(prompter.confirm).not.toHaveBeenCalled();
expect(prompter.confirm).toHaveBeenCalledOnce();
expectNoteContaining("Cron model overrides detected", "Cron");
expectNoteContaining("2 jobs set `payload.model`", "Cron");
expectNoteContaining("Provider namespaces: anthropic=1, openai=1", "Cron");

View File

@@ -357,7 +357,7 @@ export async function maybeRepairLegacyCronStore(params: {
if (rawJobs.length === 0 && !hasLegacyStoreFile && !hasLegacyStateSidecar && !hasLegacyRunLogs) {
return;
}
noteCronModelOverrides({ cfg: params.cfg, jobs: rawJobs, storePath });
noteCronModelOverrides({ cfg: params.cfg, jobs: rawJobs, storePath: legacyStorePath });
const normalized = normalizeStoredCronJobs(rawJobs);
const legacyWebhook = normalizeOptionalString(params.cfg.cron?.webhook);

View File

@@ -9,8 +9,6 @@ import {
makeJob,
seedCronSessionRows,
seedMainRouteSession,
writeSessionStore,
writeSessionStoreEntries,
} from "./isolated-agent.test-harness.js";
import {
DEFAULT_AGENT_TURN_PAYLOAD,
@@ -25,8 +23,7 @@ import { setupRunCronIsolatedAgentTurnSuite } from "./isolated-agent/run.suite-h
import {
dispatchCronDeliveryMock,
mockRunCronFallbackPassthrough,
runEmbeddedAgentMock,
updateSessionStoreMock,
runEmbeddedPiAgentMock,
} from "./isolated-agent/run.test-harness.js";
import { normalizeCronJobCreate } from "./normalize.js";
import type { CronJob } from "./types.js";
@@ -158,12 +155,9 @@ describe("runCronIsolatedAgentTurn session identity", () => {
await withTempHome(async (home) => {
const deps = makeDeps();
const boundSessionKey = "agent:main:telegram:direct:42";
const originalSessionFile = path.join(home, "bound-session.jsonl");
const rotatedSessionFile = path.join(home, "bound-session-rotated.jsonl");
const storePath = await writeSessionStoreEntries(home, {
await seedCronSessionRows(home, {
[boundSessionKey]: {
sessionId: "bound-session",
sessionFile: originalSessionFile,
updatedAt: Date.now(),
lastInteractionAt: Date.now() - 1_000,
systemSent: true,
@@ -175,7 +169,6 @@ describe("runCronIsolatedAgentTurn session identity", () => {
durationMs: 5,
agentMeta: {
sessionId: "bound-session-rotated",
sessionFile: rotatedSessionFile,
provider: "anthropic",
model: "claude-opus-4-6",
compactionCount: 1,
@@ -183,12 +176,6 @@ describe("runCronIsolatedAgentTurn session identity", () => {
},
},
});
updateSessionStoreMock.mockImplementation(async (targetStorePath, update) => {
const raw = await fs.readFile(targetStorePath, "utf-8");
const store = JSON.parse(raw) as Record<string, SessionEntry>;
update(store);
await fs.writeFile(targetStorePath, JSON.stringify(store, null, 2), "utf-8");
});
const currentBoundJob = normalizeCronJobCreate(
{
...makeJob(DEFAULT_AGENT_TURN_PAYLOAD),
@@ -199,7 +186,7 @@ describe("runCronIsolatedAgentTurn session identity", () => {
) as CronJob;
const res = await runCronIsolatedAgentTurn({
cfg: makeCfg(home, storePath),
cfg: makeCfg(home),
deps,
job: currentBoundJob,
message: DEFAULT_MESSAGE,
@@ -213,14 +200,10 @@ describe("runCronIsolatedAgentTurn session identity", () => {
expect.objectContaining({ sessionId: "bound-session-rotated" }),
);
const finalPersist = updateSessionStoreMock.mock.calls.at(-1);
expect(finalPersist?.[0]).toBe(storePath);
const persistedStore: Record<string, { [key: string]: unknown }> = {};
(finalPersist?.[1] as (store: typeof persistedStore) => void)(persistedStore);
expect(persistedStore[boundSessionKey]).toEqual(
const persisted = await readSessionEntry("main", boundSessionKey);
expect(persisted).toEqual(
expect.objectContaining({
sessionId: "bound-session-rotated",
sessionFile: rotatedSessionFile,
usageFamilyKey: boundSessionKey,
usageFamilySessionIds: ["bound-session", "bound-session-rotated"],
}),

View File

@@ -171,7 +171,6 @@ describe("createPersistCronSessionEntry", () => {
const cronSession = makeCronSession(
makeSessionEntry({
sessionId: "bound-session",
sessionFile: "/tmp/bound-session.jsonl",
}),
);
const changed = adoptCronRunSessionMetadata({
@@ -179,37 +178,31 @@ describe("createPersistCronSessionEntry", () => {
sessionKey: "agent:main:telegram:direct:42",
runMeta: {
sessionId: "bound-session-rotated",
sessionFile: "/tmp/bound-session-rotated.jsonl",
},
});
const updateSessionStore = vi.fn(
async (_storePath, update: (store: Record<string, SessionEntry>) => void) => {
const store: Record<string, SessionEntry> = {};
update(store);
expect(store["agent:main:telegram:direct:42"]).toEqual({
sessionId: "bound-session-rotated",
sessionFile: "/tmp/bound-session-rotated.jsonl",
usageFamilyKey: "agent:main:telegram:direct:42",
usageFamilySessionIds: ["bound-session", "bound-session-rotated"],
updatedAt: 1000,
systemSent: true,
});
},
);
const persistSessionRow = vi.fn(async (sessionKey: string, entry: SessionEntry) => {
expect(sessionKey).toBe("agent:main:telegram:direct:42");
expect(entry).toEqual({
sessionId: "bound-session-rotated",
usageFamilyKey: "agent:main:telegram:direct:42",
usageFamilySessionIds: ["bound-session", "bound-session-rotated"],
updatedAt: 1000,
systemSent: true,
});
});
expect(changed).toBe(true);
const persist = createPersistCronSessionEntry({
isFastTestEnv: false,
cronSession,
agentSessionKey: "agent:main:telegram:direct:42",
updateSessionStore,
persistSessionRow,
});
await persist();
expect(cronSession.store["agent:main:telegram:direct:42"]).toEqual({
sessionId: "bound-session-rotated",
sessionFile: "/tmp/bound-session-rotated.jsonl",
usageFamilyKey: "agent:main:telegram:direct:42",
usageFamilySessionIds: ["bound-session", "bound-session-rotated"],
updatedAt: 1000,

View File

@@ -73,18 +73,16 @@ export function adoptCronRunSessionMetadata(params: {
sessionKey: string;
runMeta?: {
sessionId?: string;
sessionFile?: string;
};
}): boolean {
const nextSessionId = normalizeSessionField(params.runMeta?.sessionId);
const nextSessionFile = normalizeSessionField(params.runMeta?.sessionFile);
if (!nextSessionFile) {
if (!nextSessionId) {
return false;
}
let changed = false;
const previousSessionId = params.entry.sessionId;
if (nextSessionId && nextSessionId !== previousSessionId) {
if (nextSessionId !== previousSessionId) {
params.entry.sessionId = nextSessionId;
params.entry.usageFamilyKey = params.entry.usageFamilyKey ?? params.sessionKey;
params.entry.usageFamilySessionIds = Array.from(
@@ -97,11 +95,6 @@ export function adoptCronRunSessionMetadata(params: {
changed = true;
}
if (nextSessionFile !== params.entry.sessionFile) {
params.entry.sessionFile = nextSessionFile;
changed = true;
}
return changed;
}