Skip to content

Commit 3945642

Browse files
authored
Merge branch 'main' into meow/webchat-history-pagination
2 parents 43dddb2 + ab559a7 commit 3945642

47 files changed

Lines changed: 3244 additions & 312 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

extensions/fireworks/index.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -144,19 +144,19 @@ describe("fireworks provider plugin", () => {
144144
expect(resolved?.reasoning).toBe(false);
145145
});
146146

147-
it("disables reasoning metadata for Fireworks Kimi k2.6 dynamic models", async () => {
147+
it("defers manifest catalog models to core static-catalog resolution", async () => {
148148
const provider = await registerSingleProviderPlugin(fireworksPlugin);
149-
const resolved = provider.resolveDynamicModel?.(
150-
createProviderDynamicModelContext({
151-
provider: "fireworks",
152-
modelId: "accounts/fireworks/models/kimi-k2p6",
153-
models: [createFireworksDefaultRuntimeModel({ reasoning: false })],
154-
}),
155-
);
156-
157-
expect(resolved?.provider).toBe("fireworks");
158-
expect(resolved?.id).toBe("accounts/fireworks/models/kimi-k2p6");
159-
expect(resolved?.reasoning).toBe(false);
149+
for (const modelId of [FIREWORKS_K2_6_MODEL_ID, FIREWORKS_DEFAULT_MODEL_ID]) {
150+
const resolved = provider.resolveDynamicModel?.(
151+
createProviderDynamicModelContext({
152+
provider: "fireworks",
153+
modelId,
154+
models: [createFireworksDefaultRuntimeModel({ reasoning: false })],
155+
}),
156+
);
157+
158+
expect(resolved).toBeUndefined();
159+
}
160160
});
161161

162162
it("exposes off-only thinking policy for Fireworks Kimi models", async () => {

extensions/fireworks/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ import {
1515
FIREWORKS_DEFAULT_CONTEXT_WINDOW,
1616
FIREWORKS_DEFAULT_MAX_TOKENS,
1717
FIREWORKS_DEFAULT_MODEL_ID,
18+
isFireworksCatalogModelId,
1819
} from "./provider-catalog.js";
1920
import { wrapFireworksProviderStream } from "./stream.js";
2021
import { resolveFireworksThinkingProfile } from "./thinking-policy.js";
2122

2223
const PROVIDER_ID = "fireworks";
24+
2325
function isFireworksGlmModelId(modelId: string): boolean {
2426
const normalized = modelId.trim().toLowerCase();
2527
const lastSegment = normalized.split("/").pop() ?? normalized;
@@ -35,6 +37,11 @@ function resolveFireworksDynamicModel(ctx: ProviderResolveDynamicModelContext) {
3537
if (!modelId) {
3638
return undefined;
3739
}
40+
41+
if (isFireworksCatalogModelId(modelId)) {
42+
return undefined;
43+
}
44+
3845
const isKimiModel = isFireworksKimiModelId(modelId);
3946
const input = resolveFireworksDynamicInput(modelId);
4047

extensions/fireworks/openclaw.plugin.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
{
3838
"id": "accounts/fireworks/models/kimi-k2p6",
3939
"name": "Kimi K2.6",
40+
"reasoning": false,
4041
"input": ["text", "image"],
4142
"contextWindow": 262144,
4243
"maxTokens": 262144,
@@ -50,6 +51,7 @@
5051
{
5152
"id": "accounts/fireworks/routers/kimi-k2p5-turbo",
5253
"name": "Kimi K2.5 Turbo (Fire Pass)",
54+
"reasoning": false,
5355
"input": ["text", "image"],
5456
"contextWindow": 256000,
5557
"maxTokens": 256000,

extensions/fireworks/provider-catalog.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,12 @@ export const FIREWORKS_DEFAULT_MAX_TOKENS = FIREWORKS_DEFAULT_MODEL.maxTokens;
3131
export const FIREWORKS_K2_6_CONTEXT_WINDOW = FIREWORKS_K2_6_MODEL.contextWindow;
3232
export const FIREWORKS_K2_6_MAX_TOKENS = FIREWORKS_K2_6_MODEL.maxTokens;
3333

34-
function cloneFireworksCatalogModel(model: ModelDefinitionConfig): ModelDefinitionConfig {
35-
return {
36-
...model,
37-
input: [...model.input],
38-
cost: { ...model.cost },
39-
};
34+
export function isFireworksCatalogModelId(modelId: string): boolean {
35+
return FIREWORKS_MANIFEST_PROVIDER.models.some((model) => model.id === modelId);
4036
}
4137

4238
export function buildFireworksCatalogModels(): ModelDefinitionConfig[] {
43-
return FIREWORKS_MANIFEST_PROVIDER.models.map(cloneFireworksCatalogModel);
39+
return FIREWORKS_MANIFEST_PROVIDER.models.map((model) => structuredClone(model));
4440
}
4541

4642
export function buildFireworksProvider(): ModelProviderConfig {

src/agents/cli-runner.reliability.test.ts

Lines changed: 181 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import fs from "node:fs";
33
import os from "node:os";
44
import path from "node:path";
55
import { afterEach, describe, expect, it, vi } from "vitest";
6+
import { getReplyPayloadMetadata } from "../auto-reply/reply-payload.js";
67
import {
78
testing as replyRunTesting,
89
createReplyOperation,
@@ -1276,7 +1277,7 @@ describe("runCliAgent reliability", () => {
12761277
releaseAgentEnd();
12771278
});
12781279

1279-
it("persists approved CLI user turns before model execution", async () => {
1280+
it("persists approved CLI user turns and successful assistant output", async () => {
12801281
supervisorSpawnMock.mockResolvedValueOnce(
12811282
createManagedRun({
12821283
reason: "exit",
@@ -1289,7 +1290,7 @@ describe("runCliAgent reliability", () => {
12891290
noOutputTimedOut: false,
12901291
}),
12911292
);
1292-
const { dir, sessionFile } = createSessionFile();
1293+
const { dir, sessionFile, storePath } = createSessionFile();
12931294
const onUserMessagePersisted = vi.fn();
12941295

12951296
try {
@@ -1305,6 +1306,8 @@ describe("runCliAgent reliability", () => {
13051306
sessionFile,
13061307
workspaceDir: dir,
13071308
prompt: "runtime prompt",
1309+
persistAssistantTranscript: true,
1310+
storePath,
13081311
userTurnTranscriptRecorder: createCliUserTurnRecorder({
13091312
text: "display prompt",
13101313
sessionFile,
@@ -1316,6 +1319,9 @@ describe("runCliAgent reliability", () => {
13161319
});
13171320

13181321
expect(result.payloads).toEqual([{ text: "hello from cli" }]);
1322+
expect(getReplyPayloadMetadata(result.payloads?.[0] ?? {})).toMatchObject({
1323+
assistantTranscriptOwned: true,
1324+
});
13191325
expect(onUserMessagePersisted).toHaveBeenCalledOnce();
13201326
expect(onUserMessagePersisted).toHaveBeenCalledWith(
13211327
expect.objectContaining({
@@ -1331,12 +1337,185 @@ describe("runCliAgent reliability", () => {
13311337
content: "display prompt",
13321338
}),
13331339
);
1340+
expect(messages).toContainEqual(
1341+
expect.objectContaining({
1342+
role: "assistant",
1343+
content: [{ type: "text", text: "hello from cli" }],
1344+
api: "cli",
1345+
provider: "codex-cli",
1346+
model: "gpt-5.4",
1347+
idempotencyKey: "cli-assistant:run-persist-cli",
1348+
}),
1349+
);
13341350
expect(JSON.stringify(messages)).not.toContain("runtime prompt");
13351351
} finally {
13361352
fs.rmSync(dir, { recursive: true, force: true });
13371353
}
13381354
});
13391355

1356+
it("lets before_message_write block CLI assistant persistence without delivery fallback", async () => {
1357+
const hookRunner = {
1358+
hasHooks: vi.fn((hookName: string) => hookName === "before_message_write"),
1359+
runBeforeMessageWrite: vi.fn(() => ({ block: true })),
1360+
};
1361+
setHookRunnerForTest(hookRunner);
1362+
supervisorSpawnMock.mockResolvedValueOnce(
1363+
createManagedRun({
1364+
reason: "exit",
1365+
exitCode: 0,
1366+
exitSignal: null,
1367+
durationMs: 50,
1368+
stdout: "secret CLI output",
1369+
stderr: "",
1370+
timedOut: false,
1371+
noOutputTimedOut: false,
1372+
}),
1373+
);
1374+
const { dir, sessionFile, storePath } = createSessionFile();
1375+
1376+
try {
1377+
const context = buildPreparedContext({
1378+
sessionKey: "agent:main:main",
1379+
runId: "run-blocked-cli",
1380+
});
1381+
const result = await runPreparedCliAgent({
1382+
...context,
1383+
params: {
1384+
...context.params,
1385+
agentId: "main",
1386+
sessionFile,
1387+
workspaceDir: dir,
1388+
persistAssistantTranscript: true,
1389+
storePath,
1390+
},
1391+
});
1392+
1393+
expect(result.payloads).toEqual([{ text: "secret CLI output" }]);
1394+
expect(getReplyPayloadMetadata(result.payloads?.[0] ?? {})).toMatchObject({
1395+
assistantTranscriptOwned: true,
1396+
});
1397+
expect(readTranscriptMessages(sessionFile)).toEqual([]);
1398+
expect(hookRunner.runBeforeMessageWrite).toHaveBeenCalledOnce();
1399+
expect(
1400+
callArg(hookRunner.runBeforeMessageWrite, 0, 1, "before_message_write context"),
1401+
).toEqual({
1402+
agentId: "main",
1403+
sessionKey: "agent:main:main",
1404+
});
1405+
} finally {
1406+
fs.rmSync(dir, { recursive: true, force: true });
1407+
}
1408+
});
1409+
1410+
it("does not append late CLI output after the session key is rebound", async () => {
1411+
supervisorSpawnMock.mockResolvedValueOnce(
1412+
createManagedRun({
1413+
reason: "exit",
1414+
exitCode: 0,
1415+
exitSignal: null,
1416+
durationMs: 50,
1417+
stdout: "late CLI output",
1418+
stderr: "",
1419+
timedOut: false,
1420+
noOutputTimedOut: false,
1421+
}),
1422+
);
1423+
const { dir, sessionFile, storePath } = createSessionFile();
1424+
const replacementFile = path.join(path.dirname(sessionFile), "s2.jsonl");
1425+
fs.writeFileSync(
1426+
replacementFile,
1427+
`${JSON.stringify({
1428+
type: "session",
1429+
version: CURRENT_SESSION_VERSION,
1430+
id: "s2",
1431+
timestamp: new Date(0).toISOString(),
1432+
cwd: dir,
1433+
})}\n`,
1434+
"utf-8",
1435+
);
1436+
fs.writeFileSync(
1437+
storePath,
1438+
JSON.stringify({
1439+
"agent:main:main": {
1440+
sessionId: "s2",
1441+
sessionFile: replacementFile,
1442+
updatedAt: Date.now(),
1443+
},
1444+
}),
1445+
"utf-8",
1446+
);
1447+
1448+
try {
1449+
const context = buildPreparedContext({
1450+
sessionKey: "agent:main:main",
1451+
runId: "run-rebound-cli",
1452+
});
1453+
const result = await runPreparedCliAgent({
1454+
...context,
1455+
params: {
1456+
...context.params,
1457+
agentId: "main",
1458+
sessionFile,
1459+
workspaceDir: dir,
1460+
persistAssistantTranscript: true,
1461+
storePath,
1462+
},
1463+
});
1464+
1465+
expect(result.payloads).toEqual([{ text: "late CLI output" }]);
1466+
expect(getReplyPayloadMetadata(result.payloads?.[0] ?? {})).toMatchObject({
1467+
assistantTranscriptOwned: true,
1468+
});
1469+
expect(readTranscriptMessages(sessionFile)).toEqual([]);
1470+
expect(readTranscriptMessages(replacementFile)).toEqual([]);
1471+
} finally {
1472+
fs.rmSync(dir, { recursive: true, force: true });
1473+
}
1474+
});
1475+
1476+
it("does not persist private room-event assistant output", async () => {
1477+
supervisorSpawnMock.mockResolvedValueOnce(
1478+
createManagedRun({
1479+
reason: "exit",
1480+
exitCode: 0,
1481+
exitSignal: null,
1482+
durationMs: 50,
1483+
stdout: "private ambient output",
1484+
stderr: "",
1485+
timedOut: false,
1486+
noOutputTimedOut: false,
1487+
}),
1488+
);
1489+
const { dir, sessionFile, storePath } = createSessionFile();
1490+
1491+
try {
1492+
const context = buildPreparedContext({
1493+
sessionKey: "agent:main:main",
1494+
runId: "run-private-room-event",
1495+
});
1496+
const result = await runPreparedCliAgent({
1497+
...context,
1498+
params: {
1499+
...context.params,
1500+
agentId: "main",
1501+
sessionFile,
1502+
workspaceDir: dir,
1503+
persistAssistantTranscript: true,
1504+
storePath,
1505+
currentInboundEventKind: "room_event",
1506+
},
1507+
});
1508+
1509+
expect(result.payloads).toEqual([{ text: "private ambient output" }]);
1510+
expect(getReplyPayloadMetadata(result.payloads?.[0] ?? {})).toMatchObject({
1511+
assistantTranscriptOwned: true,
1512+
});
1513+
expect(readTranscriptMessages(sessionFile)).toEqual([]);
1514+
} finally {
1515+
fs.rmSync(dir, { recursive: true, force: true });
1516+
}
1517+
});
1518+
13401519
it("passes cwd to approved CLI user-turn persistence", async () => {
13411520
supervisorSpawnMock.mockResolvedValueOnce(
13421521
createManagedRun({

src/agents/cli-runner.spawn.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,9 @@ describe("runCliAgent spawn path", () => {
656656
currentMessageId: "reply-message-1",
657657
senderId: "sender-1",
658658
senderIsOwner: true,
659+
persistAssistantTranscript: true,
660+
storePath: "/tmp/sessions.json",
661+
currentInboundEventKind: "room_event",
659662
});
660663

661664
expect(params.messageChannel).toBe("telegram");
@@ -666,6 +669,9 @@ describe("runCliAgent spawn path", () => {
666669
expect(params.senderId).toBe("sender-1");
667670
expect(params.senderIsOwner).toBe(true);
668671
expect(params.cwd).toBe("/tmp/task-repo");
672+
expect(params.persistAssistantTranscript).toBe(true);
673+
expect(params.storePath).toBe("/tmp/sessions.json");
674+
expect(params.currentInboundEventKind).toBe("room_event");
669675
});
670676

671677
it("forwards static extra system prompt through the compat wrapper", () => {

0 commit comments

Comments
 (0)