fix(tasks): hide internal completion wake rows

This commit is contained in:
Peter Steinberger
2026-04-06 03:03:43 +01:00
parent 85b3203421
commit 124c4c85ab
4 changed files with 101 additions and 2 deletions

View File

@@ -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",

View File

@@ -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({

View File

@@ -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",

View File

@@ -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)) {