Download src/agents/sessions/agent-session-code-mode-source.test.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 29.3 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/sessions/agent-session-code-mode-source.test.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/agents/sessions/agent-session-code-mode-source.test.ts
-
curl -L -o agent-session-code-mode-source.test.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/sessions/agent-session-code-mode-source.test.ts
29.3 kB
| import path from "node:path"; | |
| import type { AssistantMessage, Model } from "openclaw/plugin-sdk/llm"; | |
| import { Type } from "typebox"; | |
| import { afterEach, describe, expect, it } from "vitest"; | |
| import { useAutoCleanupTempDirTracker } from "../../../test/helpers/temp-dir.js"; | |
| import { | |
| appendTranscriptMessages, | |
| appendTranscriptMessageSync, | |
| upsertSessionEntryCore, | |
| } from "../../config/sessions/session-accessor.js"; | |
| import { resolveSqliteTargetFromSessionStorePath } from "../../config/sessions/session-sqlite-target.js"; | |
| import { resetDiagnosticEventsForTest } from "../../infra/diagnostic-events.js"; | |
| import { createDiagnosticTraceContext } from "../../infra/diagnostic-trace-context.js"; | |
| import { resetDiagnosticRunActivityForTest } from "../../logging/diagnostic-run-activity.js"; | |
| import { registerSecretValueForRedaction } from "../../logging/secret-redaction-registry.js"; | |
| import { resetSecretRedactionRegistryForTest } from "../../logging/secret-redaction-registry.test-support.js"; | |
| import { | |
| initializeGlobalHookRunner, | |
| resetGlobalHookRunner, | |
| } from "../../plugins/hook-runner-global.js"; | |
| import { createMockPluginRegistry } from "../../plugins/hooks.test-helpers.js"; | |
| import { createNestedToolActivity } from "../../sessions/nested-tool-activity.js"; | |
| import { closeOpenClawAgentDatabaseByPath } from "../../state/openclaw-agent-db.js"; | |
| import { toToolDefinitions } from "../agent-tool-definition-adapter.js"; | |
| import { isCodeModeExecTool } from "../code-mode-control-tools.js"; | |
| import { createCodeModeHarness, resetCodeModeTestState } from "../code-mode.test-support.js"; | |
| import { wrapStreamFnWithDiagnosticModelCallEvents } from "../embedded-agent-runner/run/attempt.model-diagnostic-events.js"; | |
| import type { AgentMessage } from "../runtime/index.js"; | |
| import { guardSessionManager } from "../session-tool-result-guard-wrapper.js"; | |
| import { registerHeadlessToolSearchCatalog } from "../tool-search.js"; | |
| import { wrapStreamFnCodeModeSource } from "../transcript-code-mode-source.js"; | |
| import { | |
| createAssistant, | |
| createAssistantResultStream, | |
| createTestSession, | |
| registerAgentSessionLoopTestLifecycle, | |
| streamMocks, | |
| } from "./agent-session-loop-correctness.test-support.js"; | |
| import { createResourceLoader } from "./agent-session-loop-resource-loader.test-support.js"; | |
| import type { MessageEndEvent, ToolDefinition } from "./extensions/types.js"; | |
| import { SessionManager } from "./session-manager.js"; | |
| registerAgentSessionLoopTestLifecycle(); | |
| const tempDirs = useAutoCleanupTempDirTracker(afterEach); | |
| afterEach(() => { | |
| resetDiagnosticEventsForTest(); | |
| resetDiagnosticRunActivityForTest(); | |
| resetGlobalHookRunner(); | |
| resetSecretRedactionRegistryForTest(); | |
| }); | |
| describe("AgentSession runtime and transcript projections", () => { | |
| const source = | |
| "function computeToken() { return 42; }\nconst API_TOKEN = computeToken(); return API_TOKEN;"; | |
| const genericLiteral = "fixture-only-not-a-real-secret"; | |
| const registeredLiteral = "fixture-registered-source-value"; | |
| const vendorLiteral = "sk-fixturesyntheticcredential1234567890"; | |
| const customLiteral = "fixture-custom-source-value"; | |
| const maskedSource = `const API_TOKEN = "${genericLiteral}"; const vendor = "${vendorLiteral}"; const registered = "${registeredLiteral}"; const custom = "${customLiteral}";\n${source.replaceAll("API_TOKEN", "OTHER_TOKEN")}`; | |
| const sourceCases = [ | |
| { label: "JavaScript code", args: { code: source }, outcome: "completed" }, | |
| { | |
| label: "explicit JavaScript", | |
| args: { code: source, language: "javascript" }, | |
| outcome: "completed", | |
| }, | |
| { | |
| label: "boolean state", | |
| args: { code: "const HAS_API_TOKEN = false; return HAS_API_TOKEN ? 0 : 42;" }, | |
| outcome: "completed", | |
| }, | |
| { | |
| label: "null state", | |
| args: { code: "let API_TOKEN = null; return API_TOKEN ?? 42;" }, | |
| outcome: "completed", | |
| }, | |
| ...["bash", "", null, 7].map((language) => ({ | |
| label: `invalid language ${JSON.stringify(language)}`, | |
| args: { code: "API_TOKEN=fixtureUnquotedLiteral;", language }, | |
| outcome: "validation", | |
| })), | |
| { | |
| label: "TypeScript annotation", | |
| args: { code: source.replace("API_TOKEN =", "API_TOKEN: number ="), language: "typescript" }, | |
| outcome: "completed", | |
| }, | |
| { | |
| label: "computed expression", | |
| args: { code: "const API_TOKEN = (40 + 2); return API_TOKEN;" }, | |
| outcome: "completed", | |
| }, | |
| { | |
| label: "ordinary total", | |
| args: { code: "const total = 40 + 2; return total;" }, | |
| outcome: "completed", | |
| }, | |
| { label: "command only", args: { command: source }, outcome: "validation" }, | |
| { label: "paired aliases", args: { code: source, command: source }, outcome: "completed" }, | |
| { label: "blank code alternate", args: { code: "", command: source }, outcome: "completed" }, | |
| { | |
| label: "blank command alternate", | |
| args: { code: source, command: " " }, | |
| outcome: "completed", | |
| }, | |
| { | |
| label: "divergent aliases", | |
| args: { code: source, command: "const API_TOKEN = computeOtherToken(); return API_TOKEN;" }, | |
| outcome: "error", | |
| }, | |
| { label: "both blank", args: { code: " ", command: "" }, outcome: "error" }, | |
| { label: "credential masking", args: { code: maskedSource }, outcome: "completed" }, | |
| ]; | |
| it.each(sourceCases)( | |
| "preserves $label through SQLite close, reopen, and the next provider context", | |
| async ({ args, outcome, label }) => { | |
| const dir = tempDirs.make("openclaw-code-source-projection-"); | |
| const scope = { | |
| agentId: "main", | |
| sessionId: "source-projection", | |
| sessionKey: "agent:main:source-projection", | |
| storePath: path.join(dir, "sessions.json"), | |
| }; | |
| const config = { logging: { redactPatterns: ["fixture-custom-source-value"] } }; | |
| registerSecretValueForRedaction(registeredLiteral); | |
| await upsertSessionEntryCore(scope, { sessionId: scope.sessionId, updatedAt: 1 }); | |
| const manager = SessionManager.open(scope, dir); | |
| guardSessionManager(manager, { config, allowedToolNames: ["exec", "wait"] }); | |
| const originalArgs = { | |
| ...args, | |
| note: source, | |
| nested: { code: source, command: source }, | |
| apiKey: "fixture-structured-secret", | |
| }; | |
| const { tools, catalogRef } = createCodeModeHarness(); | |
| registerHeadlessToolSearchCatalog({ catalogRef, tools: [] }); | |
| streamMocks.streamSimple | |
| .mockImplementationOnce((model: Model) => | |
| createAssistantResultStream( | |
| createAssistant( | |
| model, | |
| [ | |
| { type: "text", text: source }, | |
| { type: "toolCall", id: "call_source", name: "exec", arguments: originalArgs }, | |
| ], | |
| "toolUse", | |
| ), | |
| ), | |
| ) | |
| .mockImplementation((model: Model) => | |
| createAssistantResultStream(createAssistant(model, [{ type: "text", text: "Done." }])), | |
| ); | |
| try { | |
| if (label === "command only") { | |
| expect(await tools[0]!.execute("direct_alias", { command: source })).toMatchObject({ | |
| details: { status: "completed", value: 42 }, | |
| }); | |
| } | |
| const { session } = await createTestSession({ | |
| sessionManager: manager, | |
| customTools: toToolDefinitions(tools), | |
| }); | |
| const model = session.agent.state.model!; | |
| let modelCallSeq = 0; | |
| session.agent.streamFn = wrapStreamFnCodeModeSource( | |
| wrapStreamFnWithDiagnosticModelCallEvents(session.agent.streamFn!, { | |
| runId: scope.sessionId, | |
| provider: model.provider, | |
| model: model.id, | |
| trace: createDiagnosticTraceContext(), | |
| nextCallId: () => `${scope.sessionId}:${++modelCallSeq}`, | |
| }), | |
| new Set(tools.filter(isCodeModeExecTool).map((tool) => tool.name)), | |
| ); | |
| await session.prompt("Compute the harmless number."); | |
| expect(streamMocks.streamSimple).toHaveBeenCalledTimes(2); | |
| const liveResult = session.state.messages.find((message) => message.role === "toolResult"); | |
| expect(liveResult).toMatchObject({ toolCallId: "call_source" }); | |
| if (outcome === "validation") { | |
| expect(liveResult).toMatchObject({ | |
| isError: true, | |
| content: [ | |
| { | |
| text: expect.stringContaining( | |
| label.startsWith("invalid language") ? "language" : "code", | |
| ), | |
| }, | |
| ], | |
| }); | |
| } else { | |
| expect(liveResult).toMatchObject({ | |
| details: { status: outcome, ...(outcome === "completed" ? { value: 42 } : {}) }, | |
| }); | |
| } | |
| expect( | |
| session.state.messages.find((message) => message.role === "assistant"), | |
| ).toMatchObject({ content: [{ text: source }, { arguments: originalArgs }] }); | |
| const cached = manager.buildSessionContext(); | |
| session.dispose(); | |
| const databasePath = resolveSqliteTargetFromSessionStorePath(scope.storePath).path!; | |
| expect(closeOpenClawAgentDatabaseByPath(databasePath)).toBe(true); | |
| const reopened = SessionManager.open(scope, dir); | |
| expect(reopened.buildSessionContext()).toEqual(cached); | |
| const { session: nextSession } = await createTestSession({ | |
| sessionManager: reopened, | |
| customTools: toToolDefinitions(tools), | |
| }); | |
| await nextSession.prompt("Recall the earlier calculation."); | |
| const providerContext = streamMocks.streamSimple.mock.calls.at(-1)![1]; | |
| const assistant = providerContext.messages.find( | |
| (message: { role: string }) => message.role === "assistant", | |
| ); | |
| expect(assistant).toMatchObject({ | |
| content: [ | |
| { text: expect.not.stringContaining("API_TOKEN = computeToken()") }, | |
| { | |
| type: "toolCall", | |
| id: "call_source", | |
| name: "exec", | |
| arguments: { | |
| apiKey: expect.not.stringContaining("fixture-structured-secret"), | |
| note: expect.not.stringContaining("API_TOKEN = computeToken()"), | |
| nested: { | |
| code: expect.not.stringContaining("API_TOKEN = computeToken()"), | |
| command: expect.not.stringContaining("API_TOKEN = computeToken()"), | |
| }, | |
| }, | |
| }, | |
| ], | |
| }); | |
| const persistedArgs = assistant.content[1].arguments; | |
| for (const field of ["code", "command"] as const) { | |
| const value = field === "code" ? args.code : "command" in args ? args.command : undefined; | |
| if (typeof value !== "string") { | |
| expect(persistedArgs).not.toHaveProperty(field); | |
| continue; | |
| } | |
| if (label.startsWith("invalid language")) { | |
| expect(persistedArgs[field]).not.toContain("fixtureUnquotedLiteral"); | |
| } else if (label === "credential masking") { | |
| expect(persistedArgs[field]).toContain( | |
| "OTHER_TOKEN = computeToken(); return OTHER_TOKEN;", | |
| ); | |
| for (const literal of [ | |
| genericLiteral, | |
| registeredLiteral, | |
| vendorLiteral, | |
| customLiteral, | |
| ]) { | |
| expect(persistedArgs[field]).not.toContain(literal); | |
| } | |
| } else { | |
| expect(persistedArgs[field]).toBe(value); | |
| } | |
| } | |
| const replayResult = providerContext.messages.find( | |
| (message: { role: string }) => message.role === "toolResult", | |
| ); | |
| expect(replayResult.toolCallId).toBe(assistant.content[1].id); | |
| expect(providerContext.messages.indexOf(replayResult)).toBe( | |
| providerContext.messages.indexOf(assistant) + 1, | |
| ); | |
| } finally { | |
| resetCodeModeTestState(); | |
| } | |
| }, | |
| ); | |
| it.each(["message_end", "before_message_write"] as const)( | |
| "revalidates source ownership after %s replacements", | |
| async (hook) => { | |
| const dir = tempDirs.make("openclaw-source-hooks-"); | |
| const scope = { | |
| agentId: "main", | |
| sessionId: "source-hooks", | |
| sessionKey: "agent:main:source-hooks", | |
| storePath: path.join(dir, "sessions.json"), | |
| }; | |
| const { tools, catalogRef } = createCodeModeHarness(); | |
| registerHeadlessToolSearchCatalog({ catalogRef, tools: [] }); | |
| const other: ToolDefinition = { | |
| name: "other", | |
| label: "Other", | |
| description: "Ordinary JSON tool", | |
| parameters: Type.Object({ code: Type.String() }), | |
| execute: async () => ({ content: [{ type: "text", text: "ordinary" }], details: {} }), | |
| }; | |
| const cases = [ | |
| "unchanged", | |
| "clone", | |
| "mutate-source", | |
| "default-to-javascript", | |
| "javascript-to-default", | |
| "change-dialect", | |
| "unsupported-dialect", | |
| "malformed-dialect", | |
| "rename", | |
| "remove", | |
| "collision", | |
| "replace-with-literal", | |
| ] as const; | |
| let action: (typeof cases)[number] = "unchanged"; | |
| let original: AssistantMessage | undefined; | |
| const replace = (message: AgentMessage): AgentMessage => { | |
| if (message.role !== "assistant" || message.stopReason !== "toolUse") { | |
| return message; | |
| } | |
| const call = message.content.find((block) => block.type === "toolCall")!; | |
| if (call.type !== "toolCall") { | |
| throw new Error("missing call"); | |
| } | |
| if (!call.id.startsWith("hook_")) { | |
| return message; | |
| } | |
| switch (action) { | |
| case "clone": | |
| return { ...message, content: [{ ...call }] }; | |
| case "mutate-source": | |
| call.arguments.code = source.replaceAll("42", "43"); | |
| return message; | |
| case "default-to-javascript": | |
| call.arguments.language = "javascript"; | |
| return message; | |
| case "javascript-to-default": | |
| delete call.arguments.language; | |
| return message; | |
| case "change-dialect": | |
| call.arguments.language = "typescript"; | |
| return message; | |
| case "unsupported-dialect": | |
| call.arguments.language = "bash"; | |
| return message; | |
| case "malformed-dialect": | |
| call.arguments.language = null; | |
| return message; | |
| case "rename": | |
| call.name = "other"; | |
| return message; | |
| case "remove": | |
| return { ...message, content: [{ type: "text", text: "Removed call." }] }; | |
| case "collision": | |
| return { ...message, content: [call, { ...call }] }; | |
| case "replace-with-literal": | |
| return { ...message, content: [{ ...call, arguments: { code: maskedSource } }] }; | |
| default: | |
| return { ...message, content: [{ type: "text", text: "Hook preserved call." }, call] }; | |
| } | |
| }; | |
| const resourceLoader = | |
| hook === "message_end" | |
| ? createResourceLoader( | |
| new Map([ | |
| [ | |
| "message_end", | |
| [ | |
| async (event: unknown) => ({ | |
| message: replace((event as MessageEndEvent).message), | |
| }), | |
| ], | |
| ], | |
| ]), | |
| ) | |
| : createResourceLoader(); | |
| if (hook === "before_message_write") { | |
| initializeGlobalHookRunner( | |
| createMockPluginRegistry([ | |
| { | |
| hookName: hook, | |
| handler: (event: unknown) => ({ | |
| message: replace((event as { message: AgentMessage }).message), | |
| }), | |
| }, | |
| ]), | |
| ); | |
| } | |
| await upsertSessionEntryCore(scope, { sessionId: scope.sessionId, updatedAt: 1 }); | |
| const manager = SessionManager.open(scope, dir); | |
| guardSessionManager(manager, { config: {}, allowedToolNames: ["exec", "wait", "other"] }); | |
| const { session } = await createTestSession({ | |
| sessionManager: manager, | |
| customTools: [...toToolDefinitions(tools), other], | |
| resourceLoader, | |
| }); | |
| session.agent.streamFn = wrapStreamFnCodeModeSource( | |
| session.agent.streamFn!, | |
| new Set(tools.filter(isCodeModeExecTool).map((tool) => tool.name)), | |
| ); | |
| try { | |
| for (const nextAction of cases) { | |
| action = nextAction; | |
| streamMocks.streamSimple | |
| .mockImplementationOnce((model: Model) => { | |
| original = createAssistant( | |
| model, | |
| [ | |
| { | |
| type: "toolCall", | |
| id: `hook_${action}`, | |
| name: "exec", | |
| arguments: { | |
| code: source, | |
| ...(action === "javascript-to-default" ? { language: "javascript" } : {}), | |
| }, | |
| }, | |
| ], | |
| "toolUse", | |
| ); | |
| return createAssistantResultStream(original); | |
| }) | |
| .mockImplementation((model: Model) => | |
| createAssistantResultStream( | |
| createAssistant(model, [{ type: "text", text: "Done." }]), | |
| ), | |
| ); | |
| await session.prompt(`Test ${action}.`); | |
| const stored = manager | |
| .buildSessionContext() | |
| .messages.flatMap((message) => | |
| message.role === "assistant" | |
| ? message.content.filter( | |
| (block) => block.type === "toolCall" && block.id === `hook_${action}`, | |
| ) | |
| : [], | |
| ); | |
| if (action === "remove") { | |
| expect(stored).toHaveLength(0); | |
| continue; | |
| } | |
| expect(stored.length).toBeGreaterThan(0); | |
| for (const block of stored) { | |
| if (block.type !== "toolCall") { | |
| throw new Error("unexpected stored block"); | |
| } | |
| if ( | |
| action === "unchanged" || | |
| action === "default-to-javascript" || | |
| action === "javascript-to-default" | |
| ) { | |
| expect(block.arguments.code).toBe(source); | |
| } else { | |
| expect(block.arguments.code).not.toContain("API_TOKEN = computeToken()"); | |
| } | |
| expect(block.arguments.code).not.toContain(genericLiteral); | |
| } | |
| // Retaining or copying a completed response cannot reuse its append authority. | |
| const late = structuredClone(original!); | |
| late.content = [ | |
| { type: "toolCall", id: `late_${action}`, name: "exec", arguments: { code: source } }, | |
| ]; | |
| manager.appendMessage(late); | |
| guardSessionManager(manager).clearPendingToolResults?.(); | |
| const lateStored = manager.getLeafEntry(); | |
| expect(lateStored).toMatchObject({ | |
| message: { | |
| content: [ | |
| { arguments: { code: expect.not.stringContaining("API_TOKEN = computeToken()") } }, | |
| ], | |
| }, | |
| }); | |
| } | |
| const cached = manager.buildSessionContext(); | |
| session.dispose(); | |
| expect( | |
| closeOpenClawAgentDatabaseByPath( | |
| resolveSqliteTargetFromSessionStorePath(scope.storePath).path!, | |
| ), | |
| ).toBe(true); | |
| expect(SessionManager.open(scope, dir).buildSessionContext()).toEqual(cached); | |
| } finally { | |
| resetCodeModeTestState(); | |
| resetGlobalHookRunner(); | |
| } | |
| }, | |
| ); | |
| it("keeps mixed outer calls separate from a reentrant direct SQLite append", async () => { | |
| const dir = tempDirs.make("openclaw-source-mixed-"); | |
| const scope = { | |
| agentId: "main", | |
| sessionId: "source-mixed", | |
| sessionKey: "agent:main:source-mixed", | |
| storePath: path.join(dir, "sessions.json"), | |
| }; | |
| const { tools, catalogRef } = createCodeModeHarness(); | |
| registerHeadlessToolSearchCatalog({ catalogRef, tools: [] }); | |
| let reentrant: AgentMessage | undefined; | |
| initializeGlobalHookRunner( | |
| createMockPluginRegistry([ | |
| { | |
| hookName: "before_message_write", | |
| handler: (event: unknown) => { | |
| const { message } = event as { message: AgentMessage }; | |
| if (message.role === "assistant" && message.stopReason === "toolUse" && !reentrant) { | |
| // Even the exact live object cannot borrow its outer append's private options. | |
| const outcome = appendTranscriptMessageSync(scope, { | |
| message, | |
| eventId: "reentrant_source", | |
| }); | |
| reentrant = outcome.ok ? outcome.value?.message : undefined; | |
| } | |
| }, | |
| }, | |
| ]), | |
| ); | |
| await upsertSessionEntryCore(scope, { sessionId: scope.sessionId, updatedAt: 1 }); | |
| const manager = SessionManager.open(scope, dir); | |
| guardSessionManager(manager, { config: {}, allowedToolNames: ["exec", "wait", "other"] }); | |
| const otherParameters = Type.Object({ code: Type.String() }); | |
| const other: ToolDefinition<typeof otherParameters> = { | |
| name: "other", | |
| label: "Other", | |
| description: "Ordinary tool", | |
| parameters: otherParameters, | |
| execute: async (_id, args) => ({ | |
| content: [{ type: "text", text: "ordinary" }], | |
| details: { receivedOriginal: args.code === source }, | |
| }), | |
| }; | |
| streamMocks.streamSimple | |
| .mockImplementationOnce((model: Model) => | |
| createAssistantResultStream( | |
| createAssistant( | |
| model, | |
| [ | |
| { type: "toolCall", id: "mixed_rejected", name: "unavailable", arguments: {} }, | |
| { type: "toolCall", id: "mixed_code", name: "exec", arguments: { code: source } }, | |
| { type: "toolCall", id: "mixed_other", name: "other", arguments: { code: source } }, | |
| ], | |
| "toolUse", | |
| ), | |
| ), | |
| ) | |
| .mockImplementation((model: Model) => | |
| createAssistantResultStream(createAssistant(model, [{ type: "text", text: "Done." }])), | |
| ); | |
| try { | |
| const { session } = await createTestSession({ | |
| sessionManager: manager, | |
| customTools: [...toToolDefinitions(tools), other], | |
| }); | |
| session.agent.streamFn = wrapStreamFnCodeModeSource( | |
| session.agent.streamFn!, | |
| new Set(tools.filter(isCodeModeExecTool).map((tool) => tool.name)), | |
| ); | |
| await session.prompt("Run both independent calls."); | |
| expect(streamMocks.streamSimple).toHaveBeenCalledTimes(2); | |
| expect( | |
| session.state.messages.filter((message) => message.role === "toolResult"), | |
| ).toMatchObject([ | |
| { toolCallId: "mixed_rejected", isError: true }, | |
| { toolCallId: "mixed_code", details: { value: 42 } }, | |
| { toolCallId: "mixed_other", details: { receivedOriginal: true } }, | |
| ]); | |
| expect(reentrant).toMatchObject({ | |
| content: [ | |
| { arguments: { code: expect.not.stringContaining("API_TOKEN = computeToken()") } }, | |
| { arguments: { code: expect.not.stringContaining("API_TOKEN = computeToken()") } }, | |
| ], | |
| }); | |
| const stored = manager | |
| .getEntries() | |
| .filter( | |
| (entry) => | |
| entry.type === "message" && | |
| entry.message.role === "assistant" && | |
| entry.message.stopReason === "toolUse", | |
| ); | |
| expect(stored).toHaveLength(2); | |
| expect(stored[1]).toMatchObject({ | |
| message: { | |
| content: [ | |
| { id: "mixed_code", arguments: { code: source } }, | |
| { | |
| id: "mixed_other", | |
| arguments: { code: expect.not.stringContaining("API_TOKEN = computeToken()") }, | |
| }, | |
| ], | |
| }, | |
| }); | |
| const cached = manager.buildSessionContext(); | |
| session.dispose(); | |
| expect( | |
| closeOpenClawAgentDatabaseByPath( | |
| resolveSqliteTargetFromSessionStorePath(scope.storePath).path!, | |
| ), | |
| ).toBe(true); | |
| expect(SessionManager.open(scope, dir).buildSessionContext()).toEqual(cached); | |
| } finally { | |
| resetGlobalHookRunner(); | |
| resetCodeModeTestState(); | |
| } | |
| }); | |
| it("does not lend a previous run's source ownership to a reused manager or ordinary append batches", async () => { | |
| const dir = tempDirs.make("openclaw-source-reuse-"); | |
| const scope = { | |
| agentId: "main", | |
| sessionId: "source-reuse", | |
| sessionKey: "agent:main:source-reuse", | |
| storePath: path.join(dir, "sessions.json"), | |
| }; | |
| await upsertSessionEntryCore(scope, { sessionId: scope.sessionId, updatedAt: 1 }); | |
| const manager = SessionManager.open(scope, dir); | |
| const { tools, catalogRef } = createCodeModeHarness(); | |
| registerHeadlessToolSearchCatalog({ catalogRef, tools: [] }); | |
| const shell: ToolDefinition = { | |
| name: "exec", | |
| label: "Shell", | |
| description: "Ordinary shell owner", | |
| parameters: Type.Object({ command: Type.String() }), | |
| execute: async () => ({ content: [{ type: "text", text: source }], details: {} }), | |
| }; | |
| try { | |
| for (const mode of ["code", "shell", "code"] as const) { | |
| guardSessionManager(manager, { | |
| config: {}, | |
| allowedToolNames: ["exec", "wait"], | |
| runId: mode, | |
| }); | |
| streamMocks.streamSimple | |
| .mockImplementationOnce((model: Model) => | |
| createAssistantResultStream( | |
| createAssistant( | |
| model, | |
| [ | |
| { | |
| type: "toolCall", | |
| id: "reused_id", | |
| name: "exec", | |
| arguments: mode === "code" ? { code: source } : { command: source }, | |
| }, | |
| ], | |
| "toolUse", | |
| ), | |
| ), | |
| ) | |
| .mockImplementation((model: Model) => | |
| createAssistantResultStream(createAssistant(model, [{ type: "text", text: "Done." }])), | |
| ); | |
| const { session } = await createTestSession({ | |
| sessionManager: manager, | |
| customTools: mode === "code" ? toToolDefinitions(tools) : [shell], | |
| }); | |
| session.agent.streamFn = wrapStreamFnCodeModeSource( | |
| session.agent.streamFn!, | |
| new Set(mode === "code" ? tools.filter(isCodeModeExecTool).map((tool) => tool.name) : []), | |
| ); | |
| await session.prompt(`Use ${mode} mode.`); | |
| const latestCall = manager | |
| .buildSessionContext() | |
| .messages.flatMap((message) => | |
| message.role === "assistant" | |
| ? message.content.filter((block) => block.type === "toolCall") | |
| : [], | |
| ) | |
| .at(-1)!; | |
| if (latestCall.type !== "toolCall") { | |
| throw new Error("missing latest call"); | |
| } | |
| if (mode === "code") { | |
| expect(latestCall.arguments.code).toBe(source); | |
| } else { | |
| expect(latestCall.arguments.command).not.toContain("API_TOKEN = computeToken()"); | |
| } | |
| session.dispose(); | |
| } | |
| const nested = [ | |
| createNestedToolActivity({ | |
| runId: "run-test", | |
| scopeId: "scope-test", | |
| afterEntryId: null, | |
| startOrder: 0, | |
| toolCallId: "nested_shell", | |
| toolName: "exec", | |
| parentToolCallId: "reused_id", | |
| input: { | |
| command: source, | |
| code: source, | |
| nested: { code: source }, | |
| toolKind: "code_mode_exec", | |
| }, | |
| result: { content: [{ type: "text", text: source }], details: {} }, | |
| isError: false, | |
| startedAt: 1, | |
| timestamp: 2, | |
| }), | |
| ]; | |
| const messages = nested.map((message, index) => ({ | |
| message: { ...message, idempotencyKey: `batch_${index}` }, | |
| eventId: `batch_${index}`, | |
| })); | |
| const first = await appendTranscriptMessages(scope, { messages }); | |
| const repeated = await appendTranscriptMessages(scope, { messages }); | |
| expect(first.every((result) => result.appended)).toBe(true); | |
| expect(repeated.every((result) => !result.appended)).toBe(true); | |
| expect(repeated.map((result) => result.messageId)).toEqual( | |
| first.map((result) => result.messageId), | |
| ); | |
| expect( | |
| closeOpenClawAgentDatabaseByPath( | |
| resolveSqliteTargetFromSessionStorePath(scope.storePath).path!, | |
| ), | |
| ).toBe(true); | |
| const replay = SessionManager.open(scope, dir).buildSessionContext().messages; | |
| expect(replay.some((message) => message.role === "custom")).toBe(false); | |
| const storedActivity = SessionManager.open(scope, dir).getBranch().at(-1); | |
| expect(storedActivity).toMatchObject({ | |
| message: { | |
| details: { | |
| toolCallId: "nested_shell", | |
| parentToolCallId: "reused_id", | |
| input: { | |
| command: expect.not.stringContaining("API_TOKEN = computeToken()"), | |
| code: expect.not.stringContaining("API_TOKEN = computeToken()"), | |
| nested: { code: expect.not.stringContaining("API_TOKEN = computeToken()") }, | |
| }, | |
| result: { | |
| content: [{ text: expect.not.stringContaining("API_TOKEN = computeToken()") }], | |
| }, | |
| }, | |
| }, | |
| }); | |
| } finally { | |
| resetCodeModeTestState(); | |
| } | |
| }); | |
| }); | |