mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
fix(tasks): hide internal completion wake rows
This commit is contained in:
@@ -102,6 +102,32 @@ describe("buildTasksReply", () => {
|
||||
expect(reply.text).not.toContain("Internal task completion event");
|
||||
});
|
||||
|
||||
it("sanitizes inline internal runtime fences from visible task titles", async () => {
|
||||
createRunningTaskRun({
|
||||
runtime: "cli",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
childSessionKey: "agent:main:main",
|
||||
runId: "run-tasks-inline-fence",
|
||||
task: [
|
||||
"[Mon 2026-04-06 02:42 GMT+1] <<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>",
|
||||
"OpenClaw runtime context (internal):",
|
||||
"This context is runtime-generated, not user-authored. Keep internal details private.",
|
||||
].join("\n"),
|
||||
progressSummary: "done",
|
||||
});
|
||||
completeTaskRunByRunId({
|
||||
runId: "run-tasks-inline-fence",
|
||||
endedAt: Date.now(),
|
||||
terminalSummary: "Finished.",
|
||||
});
|
||||
|
||||
const reply = await buildTasksReplyForTest();
|
||||
|
||||
expect(reply.text).toContain("[Mon 2026-04-06 02:42 GMT+1]");
|
||||
expect(reply.text).not.toContain("BEGIN_OPENCLAW_INTERNAL_CONTEXT");
|
||||
expect(reply.text).not.toContain("OpenClaw runtime context (internal):");
|
||||
});
|
||||
|
||||
it("hides stale completed tasks from the task board", async () => {
|
||||
createQueuedTaskRun({
|
||||
runtime: "cron",
|
||||
|
||||
@@ -797,6 +797,47 @@ describe("gateway agent handler", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("does not create task rows for inter-session completion wakes", async () => {
|
||||
primeMainAgentRun();
|
||||
mocks.agentCommand.mockClear();
|
||||
|
||||
await invokeAgent(
|
||||
{
|
||||
message: [
|
||||
"[Mon 2026-04-06 02:42 GMT+1] <<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>",
|
||||
"OpenClaw runtime context (internal):",
|
||||
"This context is runtime-generated, not user-authored. Keep internal details private.",
|
||||
].join("\n"),
|
||||
sessionKey: "agent:main:main",
|
||||
internalEvents: [
|
||||
{
|
||||
type: "task_completion",
|
||||
source: "music_generation",
|
||||
childSessionKey: "music:task-123",
|
||||
childSessionId: "task-123",
|
||||
announceType: "music generation task",
|
||||
taskLabel: "compose a loop",
|
||||
status: "ok",
|
||||
statusLabel: "completed successfully",
|
||||
result: "MEDIA:/tmp/song.mp3",
|
||||
replyInstruction: "Reply in your normal assistant voice now.",
|
||||
},
|
||||
],
|
||||
inputProvenance: {
|
||||
kind: "inter_session",
|
||||
sourceSessionKey: "music_generate:task-123",
|
||||
sourceChannel: "internal",
|
||||
sourceTool: "music_generate",
|
||||
},
|
||||
idempotencyKey: "music-generation-event-inter-session",
|
||||
},
|
||||
{ reqId: "music-generation-event-inter-session" },
|
||||
);
|
||||
|
||||
await waitForAssertion(() => expect(mocks.agentCommand).toHaveBeenCalled());
|
||||
expect(findTaskByRunId("music-generation-event-inter-session")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("only forwards workspaceDir for spawned sessions with stored workspace inheritance", async () => {
|
||||
primeMainAgentRun();
|
||||
mockMainSessionEntry({
|
||||
|
||||
@@ -192,7 +192,10 @@ function dispatchAgentRunFromGateway(params: {
|
||||
respond: GatewayRequestHandlerOptions["respond"];
|
||||
context: GatewayRequestHandlerOptions["context"];
|
||||
}) {
|
||||
if (params.ingressOpts.sessionKey?.trim()) {
|
||||
const inputProvenance = normalizeInputProvenance(params.ingressOpts.inputProvenance);
|
||||
const shouldTrackTask =
|
||||
params.ingressOpts.sessionKey?.trim() && inputProvenance?.kind !== "inter_session";
|
||||
if (shouldTrackTask) {
|
||||
try {
|
||||
createRunningTaskRun({
|
||||
runtime: "cli",
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
INTERNAL_RUNTIME_CONTEXT_BEGIN,
|
||||
INTERNAL_RUNTIME_CONTEXT_END,
|
||||
} from "../agents/internal-runtime-context.js";
|
||||
import { sanitizeUserFacingText } from "../agents/pi-embedded-helpers/errors.js";
|
||||
import { truncateUtf16Safe } from "../utils.js";
|
||||
import type { TaskRecord } from "./task-registry.types.js";
|
||||
@@ -42,9 +46,34 @@ function truncateTaskStatusText(value: string, maxChars: number): string {
|
||||
return `${truncateUtf16Safe(trimmed, Math.max(0, maxChars - 1)).trimEnd()}…`;
|
||||
}
|
||||
|
||||
function stripInlineLeakedInternalContext(value: string): string {
|
||||
const beginIndex = value.indexOf(INTERNAL_RUNTIME_CONTEXT_BEGIN);
|
||||
if (
|
||||
beginIndex !== -1 &&
|
||||
(value.includes(INTERNAL_RUNTIME_CONTEXT_END) ||
|
||||
value.includes("OpenClaw runtime context (internal):") ||
|
||||
value.includes("[Internal task completion event]"))
|
||||
) {
|
||||
return value.slice(0, beginIndex);
|
||||
}
|
||||
const legacyHeaderIndex = value.indexOf("OpenClaw runtime context (internal):");
|
||||
if (
|
||||
legacyHeaderIndex !== -1 &&
|
||||
(value.includes("Keep internal details private.") ||
|
||||
value.includes("[Internal task completion event]"))
|
||||
) {
|
||||
return value.slice(0, legacyHeaderIndex);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function sanitizeTaskStatusValue(value: unknown, errorContext: boolean): unknown {
|
||||
if (typeof value === "string") {
|
||||
const sanitized = sanitizeUserFacingText(value, { errorContext }).replace(/\s+/g, " ").trim();
|
||||
const sanitized = sanitizeUserFacingText(stripInlineLeakedInternalContext(value), {
|
||||
errorContext,
|
||||
})
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
return sanitized || undefined;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
|
||||
Reference in New Issue
Block a user