mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-23 04:41:14 +00:00
refactor(sdk): collapse plan updates to typed steps (#109660)
* refactor(sdk): collapse plan updates to typed steps Per owner decision, remove the shipped steps: string[] SDK field and its unreleased planSteps replacement early. Collapse onPlanUpdate to one typed steps field. Retain wire-level string normalization for external Codex plugin version skew. * chore(sdk): tighten rebased surface budget
This commit is contained in:
committed by
GitHub
parent
ec42788cef
commit
a1dfd47edd
@@ -167,8 +167,7 @@ type DispatchInboundParams = {
|
||||
onPlanUpdate?: (payload: {
|
||||
phase?: string;
|
||||
explanation?: string;
|
||||
steps?: string[];
|
||||
planSteps?: Array<{ step: string; status: "pending" | "in_progress" | "completed" }>;
|
||||
steps?: Array<{ step: string; status: "pending" | "in_progress" | "completed" }>;
|
||||
}) => Promise<void> | void;
|
||||
onApprovalEvent?: (payload: { phase?: string; command?: string }) => Promise<void> | void;
|
||||
onCommandOutput?: (payload: {
|
||||
@@ -3243,8 +3242,7 @@ describe("processDiscordMessage draft streaming", () => {
|
||||
await params?.replyOptions?.onPlanUpdate?.({
|
||||
phase: "update",
|
||||
explanation: "Implementing the change.",
|
||||
steps: ["Inspect", "Patch", "Test"],
|
||||
planSteps: [
|
||||
steps: [
|
||||
{ step: "Inspect", status: "completed" },
|
||||
{ step: "Patch", status: "in_progress" },
|
||||
{ step: "Test", status: "pending" },
|
||||
|
||||
@@ -1188,7 +1188,7 @@ async function processDiscordMessageInner(
|
||||
if (payload.phase !== "update") {
|
||||
return;
|
||||
}
|
||||
await draftPreview.pushPlanProgress(payload.planSteps, {
|
||||
await draftPreview.pushPlanProgress(payload.steps, {
|
||||
explanation: payload.explanation,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -2952,8 +2952,7 @@ describe("matrix monitor handler draft streaming", () => {
|
||||
onPlanUpdate?: (payload: {
|
||||
phase: string;
|
||||
explanation?: string;
|
||||
steps?: string[];
|
||||
planSteps?: Array<{ step: string; status: "pending" | "in_progress" | "completed" }>;
|
||||
steps?: Array<{ step: string; status: "pending" | "in_progress" | "completed" }>;
|
||||
}) => Promise<void>;
|
||||
onApprovalEvent?: (payload: { phase: string; command?: string }) => Promise<void>;
|
||||
onCommandOutput?: (payload: {
|
||||
@@ -3126,7 +3125,7 @@ describe("matrix monitor handler draft streaming", () => {
|
||||
await opts.onPlanUpdate?.({
|
||||
phase: "update",
|
||||
explanation: "Initial plan",
|
||||
planSteps: [{ step: "Inspect", status: "in_progress" }],
|
||||
steps: [{ step: "Inspect", status: "in_progress" }],
|
||||
});
|
||||
await vi.waitFor(() => {
|
||||
expect(singleTextMessageBody()).toBe("`Initial plan`\n\n`▸ Inspect`");
|
||||
@@ -3135,7 +3134,7 @@ describe("matrix monitor handler draft streaming", () => {
|
||||
await opts.onPlanUpdate?.({
|
||||
phase: "update",
|
||||
explanation: "Revised plan",
|
||||
planSteps: [
|
||||
steps: [
|
||||
{ step: "Inspect", status: "completed" },
|
||||
{ step: "Patch", status: "in_progress" },
|
||||
],
|
||||
|
||||
@@ -1969,7 +1969,7 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
|
||||
if (payload.phase !== "update") {
|
||||
return;
|
||||
}
|
||||
await pushPlanProgress(payload.planSteps, payload.explanation);
|
||||
await pushPlanProgress(payload.steps, payload.explanation);
|
||||
},
|
||||
onApprovalEvent: async (payload) => {
|
||||
if (payload.phase !== "requested") {
|
||||
|
||||
@@ -460,7 +460,7 @@ export function createMSTeamsReplyDispatcher(params: {
|
||||
if (payload?.phase !== "update") {
|
||||
return;
|
||||
}
|
||||
await streamController.pushPlanProgress(normalizeAgentPlanSteps(payload.planSteps), {
|
||||
await streamController.pushPlanProgress(normalizeAgentPlanSteps(payload.steps), {
|
||||
explanation: typeof payload.explanation === "string" ? payload.explanation : undefined,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -105,8 +105,7 @@ let capturedReplyOptions:
|
||||
onPlanUpdate?: (payload: {
|
||||
phase?: string;
|
||||
explanation?: string;
|
||||
steps?: string[];
|
||||
planSteps?: Array<{
|
||||
steps?: Array<{
|
||||
step: string;
|
||||
status: "pending" | "in_progress" | "completed";
|
||||
}>;
|
||||
@@ -1106,8 +1105,7 @@ vi.mock("../reply.runtime.js", () => ({
|
||||
onPlanUpdate?: (payload: {
|
||||
phase?: string;
|
||||
explanation?: string;
|
||||
steps?: string[];
|
||||
planSteps?: Array<{
|
||||
steps?: Array<{
|
||||
step: string;
|
||||
status: "pending" | "in_progress" | "completed";
|
||||
}>;
|
||||
@@ -1173,8 +1171,7 @@ vi.mock("../reply.runtime.js", () => ({
|
||||
await params.replyOptions?.onPlanUpdate?.({
|
||||
phase: entry.phase,
|
||||
explanation: entry.explanation,
|
||||
steps: entry.steps.map((step) => step.step),
|
||||
planSteps: entry.steps,
|
||||
steps: entry.steps,
|
||||
});
|
||||
} else if (entry.kind === "concurrent_items") {
|
||||
await Promise.all(
|
||||
@@ -1281,8 +1278,7 @@ vi.mock("../reply.runtime.js", () => ({
|
||||
onPlanUpdate?: (payload: {
|
||||
phase?: string;
|
||||
explanation?: string;
|
||||
steps?: string[];
|
||||
planSteps?: Array<{
|
||||
steps?: Array<{
|
||||
step: string;
|
||||
status: "pending" | "in_progress" | "completed";
|
||||
}>;
|
||||
@@ -1334,8 +1330,7 @@ vi.mock("../reply.runtime.js", () => ({
|
||||
await params.replyOptions?.onPlanUpdate?.({
|
||||
phase: entry.phase,
|
||||
explanation: entry.explanation,
|
||||
steps: entry.steps.map((step) => step.step),
|
||||
planSteps: entry.steps,
|
||||
steps: entry.steps,
|
||||
});
|
||||
} else if (entry.kind === "concurrent_items") {
|
||||
await Promise.all(
|
||||
|
||||
@@ -2203,7 +2203,7 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
|
||||
if (payload.phase !== "update") {
|
||||
return;
|
||||
}
|
||||
await pushPlanProgress(payload.planSteps, payload.explanation);
|
||||
await pushPlanProgress(payload.steps, payload.explanation);
|
||||
},
|
||||
onApprovalEvent: async (payload) => {
|
||||
if (payload.phase !== "requested") {
|
||||
|
||||
@@ -270,7 +270,7 @@ export function createTelegramProgressController(params: {
|
||||
};
|
||||
const handlePlanUpdate = async (payload: CallbackPayload<"onPlanUpdate">) => {
|
||||
if (payload.phase === "update" && canPushToolProgress()) {
|
||||
await compositor.pushPlanProgress(payload.planSteps, {
|
||||
await compositor.pushPlanProgress(payload.steps, {
|
||||
explanation: payload.explanation,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ describeTelegramDispatch("dispatchTelegramMessage progress-rendering", () => {
|
||||
await replyOptions?.onPlanUpdate?.({
|
||||
phase: "update",
|
||||
explanation: "Implementing the change.",
|
||||
planSteps: [
|
||||
steps: [
|
||||
{ step: "Inspect", status: "completed" },
|
||||
{ step: "Patch", status: "in_progress" },
|
||||
{ step: "Test", status: "pending" },
|
||||
@@ -66,7 +66,7 @@ describeTelegramDispatch("dispatchTelegramMessage progress-rendering", () => {
|
||||
dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => {
|
||||
await replyOptions?.onPlanUpdate?.({
|
||||
phase: "update",
|
||||
planSteps: [
|
||||
steps: [
|
||||
{ step: "Patch", status: "in_progress" },
|
||||
{ step: "Test", status: "pending" },
|
||||
],
|
||||
|
||||
@@ -235,7 +235,7 @@ export function readPluginSdkSurfaceBudgets(env = process.env) {
|
||||
// Harvest: retired qa-live-transport-scenarios subpath -6.
|
||||
// +12: typed plan step/status and checklist formatter across channel barrels.
|
||||
// +8: plan-step ingress union and normalizer across channel barrels.
|
||||
// +4: dual-field plan payload builder for the steps deprecation window.
|
||||
// Harvest: retired dual-field plan payload builder -1.
|
||||
// +12: active plan-step consumers pinned through channel-outbound and mirrors.
|
||||
// +6: app-guided provider setup types retained by plugin-entry and mirrors.
|
||||
// +3: widget HTML validation helpers and tool input error.
|
||||
@@ -258,7 +258,8 @@ export function readPluginSdkSurfaceBudgets(env = process.env) {
|
||||
// tuning constants, and telegram-consumed claim helpers with compat mirrors,
|
||||
// after harvesting exports orphaned by the split-out WhatsApp adapter (#108656).
|
||||
// +10: supplemental sender helpers plus host-owned SQLite lease contracts.
|
||||
8046,
|
||||
// Harvest: retired dual-field plan payload builder -1.
|
||||
8045,
|
||||
env,
|
||||
),
|
||||
publicFunctionExports: readPluginSdkSurfaceBudgetEnv(
|
||||
@@ -271,7 +272,7 @@ export function readPluginSdkSurfaceBudgets(env = process.env) {
|
||||
// Harvest: retired qa-live-transport-scenarios subpath -3.
|
||||
// +4: shared plan checklist formatter across channel barrels.
|
||||
// +4: plan-step normalizer across channel barrels.
|
||||
// +4: dual-field plan payload builder for the steps deprecation window.
|
||||
// Harvest: retired dual-field plan payload builder -1.
|
||||
// +6: active plan-step helpers pinned through channel-outbound and mirrors.
|
||||
// +2: widget HTML document detection and size assertion.
|
||||
// Used-union narrowing of the 31 wildcard barrels.
|
||||
@@ -289,7 +290,8 @@ export function readPluginSdkSurfaceBudgets(env = process.env) {
|
||||
// +9: narrowed drain seam functions and compat mirrors after the
|
||||
// WhatsApp-split harvest (#108656).
|
||||
// +3: supplemental sender helpers plus the PluginStateLeaseRunner callback.
|
||||
4489,
|
||||
// Harvest: retired dual-field plan payload builder -1.
|
||||
4488,
|
||||
env,
|
||||
),
|
||||
publicDeprecatedExports: readPluginSdkSurfaceBudgetEnv(
|
||||
@@ -299,7 +301,6 @@ export function readPluginSdkSurfaceBudgets(env = process.env) {
|
||||
// +77: five zero-consumer subpaths enter their removal window.
|
||||
// +9: typed plan exports and formatter through deprecated channel barrels.
|
||||
// +6: plan-step ingress union and normalizer through deprecated channel barrels.
|
||||
// +3: dual-field plan payload builder through deprecated channel barrels.
|
||||
// +8: channel-outbound plan pins mirrored through deprecated barrels.
|
||||
// Used-union narrowing drops inherited deprecated exports.
|
||||
// +1: Telegram runner alias retained for plugin SDK compatibility.
|
||||
@@ -308,7 +309,8 @@ export function readPluginSdkSurfaceBudgets(env = process.env) {
|
||||
// +1: unified implicit-mention config type through deprecated config-types.
|
||||
// +24: narrowed drain seam compat mirrors in the channel-message
|
||||
// deprecation-window barrels (#108656).
|
||||
3014,
|
||||
// Harvest: retired dual-field plan payload builder -1; lower-only drift -8.
|
||||
3005,
|
||||
env,
|
||||
),
|
||||
publicWildcardReexports: readPluginSdkSurfaceBudgetEnv(
|
||||
|
||||
@@ -242,14 +242,7 @@ export type GetReplyOptions = {
|
||||
phase?: string;
|
||||
title?: string;
|
||||
explanation?: string;
|
||||
/**
|
||||
* @deprecated Shipped pre-2026.8 shape: plain step text without statuses.
|
||||
* Still populated for existing consumers; migrate to `planSteps` and
|
||||
* remove with the deprecation window.
|
||||
*/
|
||||
steps?: string[];
|
||||
/** Canonical typed checklist snapshot; replaces `steps`. */
|
||||
planSteps?: AgentPlanStep[];
|
||||
steps?: AgentPlanStep[];
|
||||
source?: string;
|
||||
}) => Promise<void> | void;
|
||||
/** Called when an approval becomes pending or resolves. */
|
||||
|
||||
@@ -79,8 +79,7 @@ describe("runCliAgentWithLifecycle", () => {
|
||||
title: "Plan updated",
|
||||
explanation: undefined,
|
||||
source: "codex-exec",
|
||||
steps: ["Inspect", "Patch"],
|
||||
planSteps: [
|
||||
steps: [
|
||||
{ step: "Inspect", status: "completed" },
|
||||
{ step: "Patch", status: "pending" },
|
||||
],
|
||||
@@ -122,8 +121,7 @@ describe("runCliAgentWithLifecycle", () => {
|
||||
|
||||
expect(onPlanUpdate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
steps: ["Inspect", "Patch"],
|
||||
planSteps: [
|
||||
steps: [
|
||||
{ step: "Inspect", status: "pending" },
|
||||
{ step: "Patch", status: "pending" },
|
||||
],
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
resolveAgentRunAbortLifecycleFields,
|
||||
resolveAgentRunErrorLifecycleFields,
|
||||
} from "../../agents/run-termination.js";
|
||||
import { buildPlanUpdateStepFields } from "../../channels/streaming.js";
|
||||
import { normalizeAgentPlanSteps } from "../../channels/streaming.js";
|
||||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
import { updateSessionEntry } from "../../config/sessions/session-accessor.js";
|
||||
import type { AgentEventPayload } from "../../infra/agent-events.js";
|
||||
@@ -378,7 +378,7 @@ function createPlanUpdateBridge(params: {
|
||||
phase: normalizeOptionalString(evt.data.phase),
|
||||
title: normalizeOptionalString(evt.data.title),
|
||||
explanation: normalizeOptionalString(evt.data.explanation),
|
||||
...buildPlanUpdateStepFields(evt.data.steps),
|
||||
steps: normalizeAgentPlanSteps(evt.data.steps),
|
||||
source: normalizeOptionalString(evt.data.source),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { readStringValue } from "@openclaw/normalization-core/string-coerce";
|
||||
import { isMessagingToolSendAction } from "../../agents/embedded-agent-messaging.js";
|
||||
import type { RunEmbeddedAgentParams } from "../../agents/embedded-agent-runner/run/params.js";
|
||||
import { buildPlanUpdateStepFields } from "../../channels/streaming.js";
|
||||
import { normalizeAgentPlanSteps } from "../../channels/streaming.js";
|
||||
import { logVerbose } from "../../globals.js";
|
||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import type { ReplyPayload } from "../types.js";
|
||||
@@ -203,7 +203,7 @@ export function createAgentRunEventHandler(params: {
|
||||
phase: readStringValue(evt.data.phase),
|
||||
title: readStringValue(evt.data.title),
|
||||
explanation: readStringValue(evt.data.explanation),
|
||||
...buildPlanUpdateStepFields(evt.data.steps),
|
||||
steps: normalizeAgentPlanSteps(evt.data.steps),
|
||||
source: readStringValue(evt.data.source),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -103,8 +103,7 @@ describe("runAgentTurnWithFallback: command events", () => {
|
||||
phase: "update",
|
||||
title: "Assistant proposed a plan",
|
||||
explanation: "Inspect code, patch it, run tests.",
|
||||
steps: ["Inspect code", "Patch code", "Run tests", "legacy string"],
|
||||
planSteps: [
|
||||
steps: [
|
||||
{ step: "Inspect code", status: "completed" },
|
||||
{ step: "Patch code", status: "in_progress" },
|
||||
{ step: "Run tests", status: "pending" },
|
||||
|
||||
@@ -63,7 +63,7 @@ describe("dispatchReplyFromConfig", () => {
|
||||
await opts?.onPlanUpdate?.({
|
||||
phase: "update",
|
||||
explanation: "Inspect code.",
|
||||
planSteps: [{ step: "Patch code", status: "in_progress" }],
|
||||
steps: [{ step: "Patch code", status: "in_progress" }],
|
||||
});
|
||||
await opts?.onApprovalEvent?.({
|
||||
phase: "requested",
|
||||
|
||||
@@ -668,7 +668,7 @@ describe("dispatchReplyFromConfig", () => {
|
||||
await opts?.onPlanUpdate?.({
|
||||
phase: "update",
|
||||
explanation: "Inspect code, patch it, run tests.",
|
||||
planSteps: [
|
||||
steps: [
|
||||
{ step: "Inspect code", status: "completed" },
|
||||
{ step: "Patch code", status: "in_progress" },
|
||||
{ step: "Run tests", status: "pending" },
|
||||
@@ -711,11 +711,11 @@ describe("dispatchReplyFromConfig", () => {
|
||||
) => {
|
||||
await opts?.onPlanUpdate?.({
|
||||
phase: "update",
|
||||
planSteps: [{ step: "Inspect code", status: "in_progress" }],
|
||||
steps: [{ step: "Inspect code", status: "in_progress" }],
|
||||
});
|
||||
await opts?.onPlanUpdate?.({
|
||||
phase: "update",
|
||||
planSteps: [
|
||||
steps: [
|
||||
{ step: "Inspect code", status: "completed" },
|
||||
{ step: "Patch code", status: "in_progress" },
|
||||
],
|
||||
@@ -794,7 +794,7 @@ describe("dispatchReplyFromConfig", () => {
|
||||
await opts?.onPlanUpdate?.({
|
||||
phase: "update",
|
||||
explanation: "Inspect code, patch it, run tests.",
|
||||
planSteps: [
|
||||
steps: [
|
||||
{ step: "Inspect code", status: "completed" },
|
||||
{ step: "Patch code", status: "in_progress" },
|
||||
{ step: "Run tests", status: "pending" },
|
||||
@@ -848,7 +848,7 @@ describe("dispatchReplyFromConfig", () => {
|
||||
await opts?.onPlanUpdate?.({
|
||||
phase: "update",
|
||||
explanation: "Inspect code, patch it, run tests.",
|
||||
planSteps: [
|
||||
steps: [
|
||||
{ step: "Inspect code", status: "completed" },
|
||||
{ step: "Patch code", status: "in_progress" },
|
||||
{ step: "Run tests", status: "pending" },
|
||||
@@ -905,7 +905,7 @@ describe("dispatchReplyFromConfig", () => {
|
||||
await opts?.onPlanUpdate?.({
|
||||
phase: "update",
|
||||
explanation: "Inspect code, patch it, run tests.",
|
||||
planSteps: [
|
||||
steps: [
|
||||
{ step: "Inspect code", status: "completed" },
|
||||
{ step: "Patch code", status: "in_progress" },
|
||||
{ step: "Run tests", status: "pending" },
|
||||
|
||||
@@ -752,10 +752,9 @@ describe("dispatchReplyFromConfig", () => {
|
||||
) => {
|
||||
await opts?.onToolStart?.({ name: "exec", phase: "start" });
|
||||
await opts?.onItemEvent?.({ itemId: "1", kind: "tool", progressText: "running exec" });
|
||||
// Shipped pre-2026.8 producer shape: plain string steps.
|
||||
await opts?.onPlanUpdate?.({
|
||||
phase: "update",
|
||||
steps: ["Run command"],
|
||||
steps: ["Run command"] as never,
|
||||
});
|
||||
await opts?.onApprovalEvent?.({ phase: "requested", command: "pnpm test" });
|
||||
await opts?.onCommandOutput?.({ phase: "end", name: "exec", status: "ok", exitCode: 0 });
|
||||
@@ -793,8 +792,7 @@ describe("dispatchReplyFromConfig", () => {
|
||||
});
|
||||
expect(onPlanUpdate).toHaveBeenCalledWith({
|
||||
phase: "update",
|
||||
steps: ["Run command"],
|
||||
planSteps: [{ step: "Run command", status: "pending" }],
|
||||
steps: [{ step: "Run command", status: "pending" }],
|
||||
});
|
||||
expect(onApprovalEvent).toHaveBeenCalledWith({
|
||||
phase: "requested",
|
||||
|
||||
@@ -2314,16 +2314,13 @@ async function dispatchReplyFromConfigInner(
|
||||
if (isDispatchOperationAborted()) {
|
||||
return;
|
||||
}
|
||||
// External resolvers call this SDK callback directly and may
|
||||
// send only the shipped string form; normalize once so
|
||||
// channel forwards and fallback notices see both fields.
|
||||
const planSteps =
|
||||
normalizeAgentPlanSteps(payload.planSteps) ??
|
||||
normalizeAgentPlanSteps(payload.steps);
|
||||
const steps = normalizeAgentPlanSteps(payload.steps);
|
||||
const normalized = {
|
||||
...payload,
|
||||
steps: planSteps?.map((entry) => entry.step) ?? payload.steps,
|
||||
planSteps,
|
||||
phase: payload.phase,
|
||||
title: payload.title,
|
||||
explanation: payload.explanation,
|
||||
steps,
|
||||
source: payload.source,
|
||||
};
|
||||
markProgress();
|
||||
await waitForPendingDirectBlockReplyDelivery(
|
||||
@@ -2349,7 +2346,7 @@ async function dispatchReplyFromConfigInner(
|
||||
}
|
||||
await sendPlanUpdate({
|
||||
explanation: normalized.explanation,
|
||||
steps: planSteps,
|
||||
steps,
|
||||
});
|
||||
},
|
||||
onApprovalEvent: async (payload) => {
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
import { withLocalSessionPlacementTurnAdmission } from "../../agents/session-placement-admission.js";
|
||||
import { resolveSessionRuntimeOverrideForProvider } from "../../agents/session-runtime-compat.js";
|
||||
import { resolveCandidateThinkingLevel } from "../../agents/thinking-runtime.js";
|
||||
import { buildPlanUpdateStepFields } from "../../channels/streaming.js";
|
||||
import { normalizeAgentPlanSteps } from "../../channels/streaming.js";
|
||||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
import { loadSessionEntry, updateSessionEntry } from "../../config/sessions/session-accessor.js";
|
||||
import type { TypingMode } from "../../config/types.js";
|
||||
@@ -296,7 +296,7 @@ async function forwardFollowupProgressEvent(params: {
|
||||
phase: readStringValue(evt.data.phase),
|
||||
title: readStringValue(evt.data.title),
|
||||
explanation: readStringValue(evt.data.explanation),
|
||||
...buildPlanUpdateStepFields(evt.data.steps),
|
||||
steps: normalizeAgentPlanSteps(evt.data.steps),
|
||||
source: readStringValue(evt.data.source),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildChannelProgressDraftLine,
|
||||
buildPlanUpdateStepFields,
|
||||
formatChannelProgressDraftText,
|
||||
formatPlanChecklistLines,
|
||||
normalizeAgentPlanSteps,
|
||||
@@ -115,7 +114,7 @@ describe("mergeChannelProgressDraftLine", () => {
|
||||
});
|
||||
|
||||
describe("normalizeAgentPlanSteps", () => {
|
||||
it("normalizes legacy strings and typed entries, dropping blanks", () => {
|
||||
it("normalizes external-plugin string steps and typed entries, dropping blanks", () => {
|
||||
expect(
|
||||
normalizeAgentPlanSteps([
|
||||
"Inspect",
|
||||
@@ -130,14 +129,6 @@ describe("normalizeAgentPlanSteps", () => {
|
||||
]);
|
||||
expect(normalizeAgentPlanSteps(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("builds both deprecation-window payload fields", () => {
|
||||
expect(buildPlanUpdateStepFields([{ step: "Inspect", status: "completed" }])).toEqual({
|
||||
steps: ["Inspect"],
|
||||
planSteps: [{ step: "Inspect", status: "completed" }],
|
||||
});
|
||||
expect(buildPlanUpdateStepFields(undefined)).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("streaming config resolution", () => {
|
||||
|
||||
@@ -186,10 +186,6 @@ export type AgentPlanStep = {
|
||||
status: AgentPlanStepStatus;
|
||||
};
|
||||
|
||||
/**
|
||||
* Plan-event ingress shape for the SDK deprecation window: shipped producers
|
||||
* through 2026.7.x sent `steps: string[]`. Remove with the string form.
|
||||
*/
|
||||
export type AgentPlanStepInput = AgentPlanStep | string;
|
||||
|
||||
function isAgentPlanStepStatus(value: unknown): value is AgentPlanStepStatus {
|
||||
@@ -197,25 +193,9 @@ function isAgentPlanStepStatus(value: unknown): value is AgentPlanStepStatus {
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes plan-event steps at public ingress boundaries. Legacy string
|
||||
* steps become pending typed steps; malformed entries are dropped.
|
||||
* Older installed @openclaw/codex releases emit plan steps as strings.
|
||||
* Normalize that external-plugin version skew to pending typed steps.
|
||||
*/
|
||||
/**
|
||||
* Builds both plan-step payload fields for `onPlanUpdate` during the SDK
|
||||
* deprecation window: canonical `planSteps` plus the shipped pre-2026.8
|
||||
* `steps: string[]` form. Collapse to `planSteps` when the window closes.
|
||||
*/
|
||||
export function buildPlanUpdateStepFields(value: unknown): {
|
||||
steps?: string[];
|
||||
planSteps?: AgentPlanStep[];
|
||||
} {
|
||||
const planSteps = normalizeAgentPlanSteps(value);
|
||||
if (!planSteps) {
|
||||
return {};
|
||||
}
|
||||
return { steps: planSteps.map((entry) => entry.step), planSteps };
|
||||
}
|
||||
|
||||
export function normalizeAgentPlanSteps(value: unknown): AgentPlanStep[] | undefined {
|
||||
if (!Array.isArray(value)) {
|
||||
return undefined;
|
||||
|
||||
Reference in New Issue
Block a user