openclaw/scripts/fixtures/packed-plugin-sdk-progress-consumer.ts
Ayaan Zaidi 5c906f9d73
fix: keep routine polling out of progress and history (#151456)
## What Problem This Solves
Repeated process polls crowded out useful progress. This larger change replaces separate channel and client decisions with shared activity preparation.

## Why This Change Was Made
This presentation state has one writer. Channels, web, terminal and native clients consume prepared facts; raw tool output remains available under details.

## User Impact
Routine polls stay quiet. Failures and approvals remain visible. Unmatched historical calls show an unknown outcome, not a permanent spinner.

## Evidence
- Real Telegram: repeated rows before; nine quiet polls, commentary, correct final result and draft cleanup after.
- Deliberate exit 2 remains visible without changing raw execution fields.
- Package build, focused channel/history/web/terminal tests and core/UI types pass.
- The same unchanged plugin compiles and runs against the released and candidate packages.
- Browser red/green: expanded item-only failures, blocked calls and unknown outcomes keep their prepared status live and after reload; 420 focused UI tests pass. Raw results remain unchanged.
- Reconnect testing includes main's fix in #151567 for the observed route loop.
- The maintainer explicitly waived native before/after screenshots and manual native UI verification for this PR. Hosted native tests remain required; device UI behavior is not claimed.

| Before | After |
| --- | --- |
| ![Repeated poll rows](https://github.com/user-attachments/assets/36ec98b4-30a0-4b62-af48-e11dbfc58982) | ![Useful progress without poll rows](https://github.com/user-attachments/assets/2cbaaf9b-72d6-41f5-a0e5-e7b31050be76) |

![Final result after progress cleanup](https://github.com/user-attachments/assets/6c8b9da1-441c-4f19-a13b-e81088ba283d)

Web expanded tool details (actual browser, cropped):

| Before: false completion | After: preserved outcome |
| --- | --- |
| ![Web details incorrectly show Completed](https://github.com/user-attachments/assets/f5cb1642-0eaf-4ec0-b689-b499ca9ce972) | ![Web details preserve failed and blocked outcomes](https://github.com/user-attachments/assets/a47b18f1-eca6-4be2-87d7-a135a12599bd) |

## Compatibility
Optional chat/task history activity; no protocol-version, user-config, store or migration change. Released plugin callbacks remain functional. Updated bundled adapters opt into prepared items. The shared SDK exports and two updated Slack progress snapshots are approved.

## Consumers
Channels, web, terminal and native adapters share the prepared facts.

## Invalidation
Existing run ownership, final-delivery fences and history refresh rules remain.

## Tests
Failure, approval, privacy, history, plugin-upgrade and authority regressions remain. File-size repairs move existing tests without removing their assertions.

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-09-18 15:38:01 +05:30

109 lines
3.2 KiB
TypeScript

import assert from "node:assert/strict";
import {
buildChannelProgressDraftLine,
createChannelProgressDraftCompositor,
} from "openclaw/plugin-sdk/channel-outbound";
// Run these unchanged consumer bytes against both the released and candidate packages.
const published: string[] = [];
const entry = {
streaming: {
mode: "partial" as const,
preview: { toolProgress: true, commandText: "raw" as const },
},
};
const progress = createChannelProgressDraftCompositor({
entry,
mode: "partial",
active: true,
seed: "legacy-sdk-consumer",
update: (text) => {
published.push(text);
return true;
},
});
await progress.pushToolEvent({
toolCallId: "exec-1",
name: "exec",
phase: "start",
args: { command: "ls -alh" },
detailMode: "raw",
});
assert.match(published.at(-1) ?? "", /ls -alh/u);
const beforeTerminal = published.length;
await progress.pushCommandOutputEvent({ toolCallId: "exec-1", phase: "delta" });
await progress.pushCommandOutputEvent({ toolCallId: "exec-1" });
await progress.pushPatchEvent({ phase: "update", modified: ["pending.ts"] });
await progress.pushPatchEvent({ modified: ["missing-phase.ts"] });
assert.equal(published.length, beforeTerminal);
await progress.pushCommandOutputEvent({
toolCallId: "exec-1",
name: "exec",
phase: "end",
title: "ls -alh",
exitCode: 7,
});
assert.match(published.at(-1) ?? "", /exit 7/u);
assert.match(published.at(-1) ?? "", /ls -alh/u);
await progress.pushPatchEvent({
itemId: "patch-1",
phase: "end",
name: "apply_patch",
summary: "1 modified",
modified: ["src/example.ts"],
});
assert.match(published.at(-1) ?? "", /1 modified/u);
assert.match(published.at(-1) ?? "", /src\/example\.ts/u);
progress.cancel();
const built: string[] = [];
const custom = createChannelProgressDraftCompositor({
entry,
mode: "partial",
active: true,
seed: "legacy-sdk-custom-builder",
buildProgressEventLine: (input, options) => {
built.push(input.event);
if (input.event === "tool") {
assert.deepEqual(input.args, { command: "ls -alh" });
assert.equal(options?.detailMode, "raw");
}
const line = buildChannelProgressDraftLine(input, { ...options, commandText: "raw" });
return line ? `Custom ${line.text}` : undefined;
},
update: (text) => {
published.push(text);
return true;
},
});
await custom.pushToolEvent({
toolCallId: "custom-exec",
name: "exec",
phase: "start",
args: { command: "ls -alh" },
detailMode: "raw",
});
assert.match(published.at(-1) ?? "", /Custom .*ls -alh/u);
await custom.pushCommandOutputEvent({ phase: "delta" });
await custom.pushPatchEvent({ phase: "start", summary: "hidden" });
assert.deepEqual(built, ["tool"]);
await custom.pushCommandOutputEvent({
phase: "end",
name: "exec",
title: "custom command",
exitCode: 3,
});
await custom.pushPatchEvent({
phase: "end",
name: "apply_patch",
summary: "1 added",
added: ["new.ts"],
});
assert.deepEqual(built, ["tool", "command-output", "patch"]);
assert.match(published.at(-1) ?? "", /Custom .*exit 3/u);
assert.match(published.at(-1) ?? "", /Custom .*1 added/u);
custom.cancel();
console.log("legacy progress consumer: raw tool, command, patch, and custom builder passed");