mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor: consolidate coercion ownership (#122692)
* refactor: consolidate coercion ownership * test: align shard check with weighted planning * chore: refresh plugin SDK API baseline
This commit is contained in:
committed by
GitHub
parent
7c58151445
commit
c23d66e3b5
@@ -571,6 +571,7 @@ const config = {
|
||||
"src/boolean-coercion.ts!",
|
||||
"src/error-coercion.ts!",
|
||||
"src/expect.ts!",
|
||||
"src/json-coercion.ts!",
|
||||
"src/number-coercion.ts!",
|
||||
"src/phone-presentation.ts!",
|
||||
"src/record-coerce.ts!",
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"4aa3e760b353d1df519118c3695c9b2421b6f8d003ac2ffbb093630abe238360","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"}
|
||||
{"contentHash":"a5aa9055bb7a6505df117fd57bc2f37d7aa9b20b1cff3c2531dae7fe0fc41f0b","entrypoint":"agent-harness-runtime","importSpecifier":"openclaw/plugin-sdk/agent-harness-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"dbaf8a557d0aba2a387280f134f76b5dfc156aaaa27abef5ff2dbfd21d96830b","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
|
||||
{"contentHash":"9d3b87f5ecf49e5d95ac0359ae35bd029238b11e34a88d452c05d50ffc7a60a4","entrypoint":"agent-harness","importSpecifier":"openclaw/plugin-sdk/agent-harness"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"aa63172e6cc1bb34ba6fcc47c0c6cb20ab9c6efb061939b23351a54ec563c933","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
|
||||
{"contentHash":"fa576f6d45ac6077714529a5d8d496800191ed5e28015e4744dada75c8a22e2c","entrypoint":"channel-core","importSpecifier":"openclaw/plugin-sdk/channel-core"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"9dbf56ae53ce48e962a0a2acaa28786968111ade8d3b6ec9112d8797dc18b0ef","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"}
|
||||
{"contentHash":"b29f2c2f0e8a9b13870fac3919deb611a45f307ef317baf1916e8c7621547ca1","entrypoint":"channel-entry-contract","importSpecifier":"openclaw/plugin-sdk/channel-entry-contract"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"345577990d31114e6e94bb9ee4b4c1d382446c713b7d5743b80688fd906ae36d","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
|
||||
{"contentHash":"08b9e89e4603dde1b13ce98bc534f5578fc66f8e9d7236f799954a7baaca39cb","entrypoint":"channel-message","importSpecifier":"openclaw/plugin-sdk/channel-message"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"84c644ecaadfd74ec78bd5dfde78f92e6de1f12a05cfd156539f71797fb02ea8","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
|
||||
{"contentHash":"0d20ad92fe85134d8dd0d4fbd144cba687a65d91930056f08eb8a03b791bf963","entrypoint":"channel-outbound","importSpecifier":"openclaw/plugin-sdk/channel-outbound"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"269ff82ec3c64e671804ab242cae73d5a5031493cdd76da458c4fd7198b9351b","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"}
|
||||
{"contentHash":"a3df582853be918f4893acf8501a6b7df14f7d04e27ff249823e243926278598","entrypoint":"channel-plugin-common","importSpecifier":"openclaw/plugin-sdk/channel-plugin-common"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"e141ebb54f0102829e7edcd2bd0d1e9b21cc71ae972284dcb916e3a62031324a","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
|
||||
{"contentHash":"28f52a46ac1d7bb3c522d9f5ff62d9b3b6e10cdec2be9f23b7ca2b8d8e4ca12c","entrypoint":"core","importSpecifier":"openclaw/plugin-sdk/core"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"0e33b7a69d8c8a038922448b5ff5c049825d3872bb708b2c35bcd5472be98fb4","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
|
||||
{"contentHash":"fb5d2a6e3e29e2a227b891b39e42462dab3dc62b804a2b7268d14346278fa1ea","entrypoint":"discord","importSpecifier":"openclaw/plugin-sdk/discord"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"dd10bdfce14b5dc2ddd976448ff67ae9ad3ef396fe33efd9cb64a737ff279e45","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"}
|
||||
{"contentHash":"bef4716ee06950504ba2dd90333092cb71e56ce10d97d8ba79c3372871f2dddc","entrypoint":"inbound-reply-dispatch","importSpecifier":"openclaw/plugin-sdk/inbound-reply-dispatch"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"3d54eabf64f44c0597a4273fa3a993d91ce335bca196fc12c49b9a472a752f22","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
|
||||
{"contentHash":"8ead34b0bfed6204ad25b2753b27dc6b239b19afc8afbd0f498379124227eb4a","entrypoint":"meeting-runtime","importSpecifier":"openclaw/plugin-sdk/meeting-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"fbaffdb6c6ea7f5713950c64f984dd1c297f9e556d6889acd0f60dc75e65fd4c","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
|
||||
{"contentHash":"03c4a48091e9d1db55ef24461f1e4a5fdb333a4be2d90574fea86fba01c5cc4f","entrypoint":"plugin-entry","importSpecifier":"openclaw/plugin-sdk/plugin-entry"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"cf61011d1a62cfa55ada8245928bd6877b6bbd5fa9f9fea63837697853c99067","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
|
||||
{"contentHash":"ca0acf4e21e1978c6f51dce666d0f92b025f3184423be50cd659c8ae3d3e3cc5","entrypoint":"plugin-runtime","importSpecifier":"openclaw/plugin-sdk/plugin-runtime"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"7b87709014d10313b7ade61594ad5e8ac032695ab97439abb2781f5dc16e09f8","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"}
|
||||
{"contentHash":"fc10e0e479166eae7d9b7316c240d14b6c78ac9d78b2715f09f5f0d61fe99372","entrypoint":"provider-catalog-runtime","importSpecifier":"openclaw/plugin-sdk/provider-catalog-runtime"}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"contentHash":"98ad69c4e05380e77b259132c66463b536675331ae77fea7465230b93c9daca6","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"}
|
||||
{"contentHash":"d38378e903e47a17e5d3b86ae87c9e93e7513c883b2e6a49e1a6d2ea2f3b1514","entrypoint":"tool-plugin","importSpecifier":"openclaw/plugin-sdk/tool-plugin"}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"contentHash":"0a5b8c7968e6761a2b73be6b88131758da62eb9cd3d00d20d58a18ba01b8ad87","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"}
|
||||
{"contentHash":"e150792598fe5067b2eeaa80b4f3cf417c48ffe96f3c15cecced6fd3c0683992","entrypoint":"webhook-ingress","importSpecifier":"openclaw/plugin-sdk/webhook-ingress"}
|
||||
|
||||
@@ -202,23 +202,19 @@ Audit the current migration queue with `pnpm plugins:boundary-report`:
|
||||
| ------------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||
| `--summary` (or `pnpm plugins:boundary-report:summary`) | Compact counts instead of full detail. |
|
||||
| `--json` | Machine-readable report. |
|
||||
| `--owner <id>` | Filter to one plugin or compatibility owner. |
|
||||
| `--fail-on-cross-owner` | Exit non-zero on cross-owner reserved SDK imports. |
|
||||
| `--owner <id>` | Filter to one compatibility owner. |
|
||||
| `--fail-on-eligible-compat` | Exit non-zero on or after a deprecated compat record's `removeAfter` date. |
|
||||
| `--fail-on-unclassified-unused-reserved` | Exit non-zero on unused reserved SDK shims. |
|
||||
|
||||
`pnpm plugins:boundary-report:ci` runs with all three fail flags. Deprecated
|
||||
records normally have an explicit `removeAfter` date. A contract tied to a
|
||||
version boundary instead declares a `removalGate`; `next-plugin-sdk-major` is an
|
||||
approved major-version gate, not a pending owner decision, and is never
|
||||
date-eligible. A record with neither field appears as `no-date` and remains
|
||||
ineligible until its owner publishes a gate. The report displays either the date
|
||||
or named gate, counts local code/doc references, lists `removal-pending` records
|
||||
with their blockers and surface-token reader references, surfaces cross-owner
|
||||
reserved SDK imports, and summarizes the private memory-host SDK bridge. Those
|
||||
reader references are triage signals, not published-artifact proof. Reserved SDK
|
||||
subpaths must have tracked owner usage; unused reserved exports should be removed
|
||||
from the public SDK.
|
||||
`pnpm plugins:boundary-report:ci` runs with the compatibility fail flag.
|
||||
Deprecated records normally have an explicit `removeAfter` date. A contract
|
||||
tied to a version boundary instead declares a `removalGate`;
|
||||
`next-plugin-sdk-major` is an approved major-version gate, not a pending owner
|
||||
decision, and is never date-eligible. A record with neither field appears as
|
||||
`no-date` and remains ineligible until its owner publishes a gate. The report
|
||||
displays either the date or named gate, counts local code/doc references, lists
|
||||
`removal-pending` records with their blockers and surface-token reader
|
||||
references, and summarizes the private memory-host SDK bridge. Those reader
|
||||
references are triage signals, not published-artifact proof.
|
||||
|
||||
### Media legacy projection
|
||||
|
||||
|
||||
@@ -16,14 +16,11 @@ private-local entries explicitly. Three files define the boundary:
|
||||
excluded from the typed, documented SDK. Production entries remain available
|
||||
as JavaScript-only host runtime exports for separately published official
|
||||
plugins; test-only entries stay unexported.
|
||||
- `src/plugin-sdk/entrypoints.ts`: classification metadata for deprecated
|
||||
subpaths, reserved bundled helpers, supported bundled facades, and
|
||||
plugin-owned public surfaces.
|
||||
- `scripts/lib/plugin-sdk-entries.mts`: derived public/private export metadata,
|
||||
supported bundled facades, and plugin-owned public surfaces.
|
||||
|
||||
Maintainers audit the public export count with `pnpm plugin-sdk:surface` and
|
||||
active reserved helper subpaths with `pnpm plugins:boundary-report:summary`;
|
||||
unused reserved helper exports fail the CI report instead of staying in the
|
||||
public SDK as dormant compatibility debt.
|
||||
the compatibility queue with `pnpm plugins:boundary-report:summary`.
|
||||
|
||||
For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview).
|
||||
|
||||
@@ -64,10 +61,10 @@ longer package exports: `agent-runtime-test-contracts`,
|
||||
|
||||
### Bundled plugin helper subpaths
|
||||
|
||||
Bundled-only helper modules are private-local after the July 2026 sweep. Cross-owner imports are blocked by package contract guardrails. `src/plugin-sdk/entrypoints.ts` separately tracks the supported bundled facades that remain public, SDK
|
||||
entrypoints backed by their bundled plugin until generic contracts replace
|
||||
`plugin-sdk/qa-runner-runtime`, `plugin-sdk/telegram-account`,
|
||||
deprecated for new code; see the per-row notes below.
|
||||
Bundled-only helper modules are private-local after the July 2026 sweep.
|
||||
Package contract guardrails classify the supported bundled facades that remain
|
||||
public until generic contracts replace them. Those facades are deprecated for
|
||||
new code; see the per-row notes below.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Channel subpaths">
|
||||
@@ -223,7 +220,7 @@ Use `isLoopbackHost(host)` when a plugin must accept only the local machine. It
|
||||
| `plugin-sdk/browser-config` | Private-local after July 2026; Supported browser config facade for normalized profile/defaults, CDP URL parsing, and browser-control auth helpers |
|
||||
| `plugin-sdk/agent-harness-task-runtime` | Private-local after July 2026; Generic task lifecycle and completion delivery helpers for harness-backed agents using a host-issued task scope |
|
||||
| `plugin-sdk/agent-harness-runtime` | Agent-harness runtime helpers. `acquireSessionWriteLock`, `resolveSessionWriteLockAcquireTimeoutMs`, `resolveSessionWriteLockOptions`, and `SessionWriteLockAcquireTimeoutConfig` are deprecated no-op compatibility exports scheduled for removal in the 2026.10 release train. They no longer block or create lock sidecars; harnesses should rely on OpenClaw's per-session lane plus the durable writer claim and in-transaction fence. |
|
||||
| `plugin-sdk/codex-mcp-projection` | Private-local after July 2026; Reserved bundled Codex helper for projecting user MCP server config into Codex thread config; not for third-party plugins |
|
||||
| `plugin-sdk/codex-mcp-projection` | Private-local after July 2026; Bundled Codex helper for projecting user MCP server config into Codex thread config; not for third-party plugins |
|
||||
| `plugin-sdk/codex-session-transcript-runtime` | Private-local bundled Codex helper for serializing transcript-mirror writes; not for third-party plugins |
|
||||
| `plugin-sdk/channel-runtime-context` | Generic channel runtime-context registration and lookup helpers |
|
||||
| `plugin-sdk/matrix` | Deprecated Matrix compatibility facade for older third-party channel packages; new plugins should import `plugin-sdk/run-command` directly |
|
||||
@@ -403,8 +400,8 @@ Use `isLoopbackHost(host)` when a plugin must accept only the local machine. It
|
||||
|
||||
| Subpath | Owner and purpose |
|
||||
| --- | --- |
|
||||
| `plugin-sdk/codex-mcp-projection` | Private-local after July 2026; Bundled Codex plugin helper for projecting user MCP server config into Codex app-server thread config (reserved package export) |
|
||||
| `plugin-sdk/codex-session-transcript-runtime` | Private-local bundled Codex plugin helper for serializing transcript-mirror writes (reserved package export) |
|
||||
| `plugin-sdk/codex-mcp-projection` | Private-local after July 2026; Bundled Codex plugin helper for projecting user MCP server config into Codex app-server thread config (default-only package export) |
|
||||
| `plugin-sdk/codex-session-transcript-runtime` | Private-local bundled Codex plugin helper for serializing transcript-mirror writes (default-only package export) |
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
resetActiveRecallStateForTests,
|
||||
setCachedResult,
|
||||
shouldCacheResult,
|
||||
toSingleLineLogValue,
|
||||
toSingleLineErrorMessage,
|
||||
} from "./recall-state.js";
|
||||
import { maybeResolveActiveRecall } from "./recall.js";
|
||||
import {
|
||||
@@ -376,9 +376,7 @@ export default definePluginEntry({
|
||||
runId: ctx.runId,
|
||||
}).catch((error: unknown) => {
|
||||
api.logger.debug?.(
|
||||
`active-memory: lane-1 trigger recall failed: ${toSingleLineLogValue(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
)}`,
|
||||
`active-memory: lane-1 trigger recall failed: ${toSingleLineErrorMessage(error)}`,
|
||||
);
|
||||
return { hasStrongHit: false, injectedCount: 0 };
|
||||
});
|
||||
@@ -483,9 +481,7 @@ export default definePluginEntry({
|
||||
if (deadlineController.signal.aborted) {
|
||||
return undefined;
|
||||
}
|
||||
const message = toSingleLineLogValue(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
const message = toSingleLineErrorMessage(error);
|
||||
api.logger.warn?.(
|
||||
`active-memory: before_prompt_build failed, skipping memory lookup: ${message}`,
|
||||
);
|
||||
@@ -547,9 +543,7 @@ export default definePluginEntry({
|
||||
runId: ctx.runId,
|
||||
}).catch((error: unknown) => {
|
||||
api.logger.debug?.(
|
||||
`active-memory: lane-1 prewarm failed: ${toSingleLineLogValue(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
)}`,
|
||||
`active-memory: lane-1 prewarm failed: ${toSingleLineErrorMessage(error)}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from "./config.js";
|
||||
import { buildRecallPrompt } from "./prompt.js";
|
||||
import { getModelRef } from "./query.js";
|
||||
import { toSingleLineLogValue } from "./recall-state.js";
|
||||
import { toSingleLineErrorMessage } from "./recall-state.js";
|
||||
import { resolveRecallRunChannelContext } from "./session.js";
|
||||
import {
|
||||
attachPartialTimeoutData,
|
||||
@@ -383,7 +383,7 @@ async function runRecallSubagent(params: {
|
||||
return { rawReply: "NONE", resultStatus: "unavailable" };
|
||||
}
|
||||
if (!params.abortSignal?.aborted) {
|
||||
const message = toSingleLineLogValue(error instanceof Error ? error.message : String(error));
|
||||
const message = toSingleLineErrorMessage(error);
|
||||
params.api.logger.warn?.(
|
||||
`active-memory: memory sub-agent failed, skipping recall: ${message}`,
|
||||
);
|
||||
@@ -398,9 +398,7 @@ async function runRecallSubagent(params: {
|
||||
sources: transcriptSources,
|
||||
sessionFile: artifactSessionFile,
|
||||
}).catch((error: unknown) => {
|
||||
const message = toSingleLineLogValue(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
const message = toSingleLineErrorMessage(error);
|
||||
params.api.logger.debug?.(
|
||||
`active-memory: failed to persist recall transcript ${artifactSessionFile}: ${message}`,
|
||||
);
|
||||
@@ -412,9 +410,7 @@ async function runRecallSubagent(params: {
|
||||
sessionKey: subagentSessionKey,
|
||||
storePath,
|
||||
}).catch((error: unknown) => {
|
||||
const message = toSingleLineLogValue(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
const message = toSingleLineErrorMessage(error);
|
||||
params.api.logger.warn?.(
|
||||
`active-memory: failed to clean up recall session ${subagentSessionKey}: ${message}`,
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import crypto from "node:crypto";
|
||||
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { closeActiveMemorySearchManager } from "openclaw/plugin-sdk/memory-host-search";
|
||||
import {
|
||||
asDateTimestampMs,
|
||||
@@ -69,9 +70,7 @@ function scheduleMemorySearchCleanupAfterTimeout(
|
||||
api.logger.debug?.(`${logPrefix} released memory search managers after timeout`);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
const message = toSingleLineLogValue(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
const message = toSingleLineErrorMessage(error);
|
||||
api.logger.warn?.(
|
||||
`${logPrefix} failed to release memory search managers after timeout: ${message}`,
|
||||
);
|
||||
@@ -223,6 +222,10 @@ function toSingleLineLogValue(value: unknown): string {
|
||||
: singleLine;
|
||||
}
|
||||
|
||||
function toSingleLineErrorMessage(error: unknown): string {
|
||||
return toSingleLineLogValue(coerceErrorMessage(error));
|
||||
}
|
||||
|
||||
function shouldCacheResult(result: ActiveRecallResult): boolean {
|
||||
return result.status === "ok" && result.summary.length > 0;
|
||||
}
|
||||
@@ -252,5 +255,6 @@ export {
|
||||
scheduleMemorySearchCleanupAfterTimeout,
|
||||
setCachedResult,
|
||||
shouldCacheResult,
|
||||
toSingleLineErrorMessage,
|
||||
toSingleLineLogValue,
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
scheduleMemorySearchCleanupAfterTimeout,
|
||||
setCachedResult,
|
||||
shouldCacheResult,
|
||||
toSingleLineErrorMessage,
|
||||
toSingleLineLogValue,
|
||||
} from "./recall-state.js";
|
||||
import {
|
||||
@@ -441,7 +442,7 @@ async function resolveActiveRecall(
|
||||
params.abortSignal?.throwIfAborted();
|
||||
return result;
|
||||
}
|
||||
const message = toSingleLineLogValue(error instanceof Error ? error.message : String(error));
|
||||
const message = toSingleLineErrorMessage(error);
|
||||
if (params.config.logging) {
|
||||
params.api.logger.warn?.(`${logPrefix} failed error=${message}; skipping recall`);
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ function createPinnedAgentForCdpUrl(
|
||||
assertPinnedAgentAuthority(url, connectionOptions);
|
||||
} catch (err) {
|
||||
const socket = new net.Socket();
|
||||
const error = err instanceof Error ? err : new Error(String(err));
|
||||
const error = toStringifiedError(err);
|
||||
process.nextTick(() => {
|
||||
callback?.(error, socket);
|
||||
socket.destroy(error);
|
||||
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
deriveDefaultBrowserControlPort,
|
||||
} from "../config/port-defaults.js";
|
||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||
import { parseBooleanValue } from "../sdk-config.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { parseBooleanValue } from "../utils/boolean.js";
|
||||
import { parseBrowserHttpUrl, redactCdpUrl, isLoopbackHost } from "./cdp.helpers.js";
|
||||
import {
|
||||
DEFAULT_AI_SNAPSHOT_MAX_CHARS,
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Boolean parsing helper re-exported for Browser CLI/config code.
|
||||
*/
|
||||
export { parseBooleanValue } from "../sdk-config.js";
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
type NativeHookRelayProcessResponse,
|
||||
type NativeHookRelayRegistrationHandle,
|
||||
} from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { coerceErrorMessage as formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import {
|
||||
normalizeTrimmedStringList,
|
||||
readStringField as readString,
|
||||
@@ -232,7 +232,7 @@ export async function handleCodexAppServerApprovalRequest(params: {
|
||||
message: cancelled
|
||||
? "Codex app-server approval cancelled because the run stopped."
|
||||
: `Codex app-server approval route failed: ${formatCodexDisplayText(
|
||||
formatErrorMessage(error),
|
||||
coerceErrorMessage(error),
|
||||
)}`,
|
||||
});
|
||||
return buildApprovalResponse(
|
||||
@@ -585,7 +585,7 @@ async function runNativeRelayToolPolicyForApprovalRequest(params: {
|
||||
handled: true,
|
||||
blocked: true,
|
||||
reason: `OpenClaw native hook relay unavailable for Codex app-server approval: ${formatCodexDisplayText(
|
||||
formatErrorMessage(error),
|
||||
coerceErrorMessage(error),
|
||||
)}`,
|
||||
failureDisposition: "failed",
|
||||
};
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { createInterface, type Interface as ReadlineInterface } from "node:readline";
|
||||
import { embeddedAgentLog, OPENCLAW_VERSION } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { coerceErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import { resolveCodexAppServerRuntimeOptions, type CodexAppServerStartOptions } from "./config.js";
|
||||
import {
|
||||
@@ -225,12 +227,8 @@ export class CodexAppServerClient {
|
||||
this.child = child;
|
||||
this.lines = createInterface({ input: child.stdout });
|
||||
this.lines.on("line", (line) => this.handleLine(line));
|
||||
this.lines.on("error", (error) =>
|
||||
this.closeWithError(error instanceof Error ? error : new Error(String(error))),
|
||||
);
|
||||
child.stdout.on("error", (error) =>
|
||||
this.closeWithError(error instanceof Error ? error : new Error(String(error))),
|
||||
);
|
||||
this.lines.on("error", (error) => this.closeWithError(toStringifiedError(error)));
|
||||
child.stdout.on("error", (error) => this.closeWithError(toStringifiedError(error)));
|
||||
child.stderr.setEncoding("utf8");
|
||||
child.stderr.on("data", (text: string) => {
|
||||
this.stderrTail = appendBoundedTail(this.stderrTail, text, CODEX_APP_SERVER_STDERR_TAIL_MAX);
|
||||
@@ -244,9 +242,7 @@ export class CodexAppServerClient {
|
||||
child.stderr.on("error", (error) => {
|
||||
embeddedAgentLog.warn("codex app-server stderr stream failed", { error });
|
||||
});
|
||||
child.once("error", (error) =>
|
||||
this.closeWithError(error instanceof Error ? error : new Error(String(error))),
|
||||
);
|
||||
child.once("error", (error) => this.closeWithError(toStringifiedError(error)));
|
||||
child.once("exit", (code, signal) => {
|
||||
this.transportExited = true;
|
||||
this.closeWithError(buildCodexAppServerExitError(code, signal, this.stderrTail));
|
||||
@@ -255,9 +251,7 @@ export class CodexAppServerClient {
|
||||
// stream. When the child process terminates abruptly the pipe can break
|
||||
// before the "exit" event fires, so a pending writeMessage() produces an
|
||||
// asynchronous error on stdin that would otherwise crash the gateway.
|
||||
child.stdin.on?.("error", (error) =>
|
||||
this.closeWithError(error instanceof Error ? error : new Error(String(error))),
|
||||
);
|
||||
child.stdin.on?.("error", (error) => this.closeWithError(toStringifiedError(error)));
|
||||
}
|
||||
|
||||
/** Starts a new app-server client using resolved runtime start options. */
|
||||
@@ -638,7 +632,7 @@ export class CodexAppServerClient {
|
||||
onWriteAttempt?.();
|
||||
this.writeMessage(message, (error) => rejectPending(error));
|
||||
} catch (error) {
|
||||
rejectPending(error instanceof Error ? error : new Error(String(error)));
|
||||
rejectPending(toStringifiedError(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -829,7 +823,7 @@ export class CodexAppServerClient {
|
||||
}
|
||||
this.writeMessage({ id: request.id, result: defaultServerRequestResponse(request) });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const message = coerceErrorMessage(error);
|
||||
embeddedAgentLog.warn("codex app-server server request handler failed", {
|
||||
id: request.id,
|
||||
method: request.method,
|
||||
@@ -1028,10 +1022,10 @@ function buildCodexAppServerRuntimeIdentity(
|
||||
response: CodexInitializeResponse,
|
||||
serverVersion: string,
|
||||
): CodexAppServerRuntimeIdentity {
|
||||
const userAgent = readNonEmptyInitializeString(response.userAgent);
|
||||
const codexHome = readNonEmptyInitializeString(response.codexHome);
|
||||
const platformFamily = readNonEmptyInitializeString(response.platformFamily);
|
||||
const platformOs = readNonEmptyInitializeString(response.platformOs);
|
||||
const userAgent = normalizeOptionalString(response.userAgent);
|
||||
const codexHome = normalizeOptionalString(response.codexHome);
|
||||
const platformFamily = normalizeOptionalString(response.platformFamily);
|
||||
const platformOs = normalizeOptionalString(response.platformOs);
|
||||
return {
|
||||
serverVersion,
|
||||
...(userAgent ? { userAgent } : {}),
|
||||
@@ -1041,11 +1035,6 @@ function buildCodexAppServerRuntimeIdentity(
|
||||
};
|
||||
}
|
||||
|
||||
function readNonEmptyInitializeString(value: string | undefined): string | undefined {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
/** Extracts the Codex version from the app-server initialize user-agent field. */
|
||||
function readCodexVersionFromUserAgent(userAgent: string | undefined): string | undefined {
|
||||
// Codex returns `<originator>/<codex-version> ...`; the originator can be
|
||||
@@ -1096,7 +1085,7 @@ function shouldBufferCodexAppServerParseFailure(value: string, error: unknown):
|
||||
if (!value.startsWith("{") && !value.startsWith("[")) {
|
||||
return false;
|
||||
}
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const message = coerceErrorMessage(error);
|
||||
return (
|
||||
message.includes("Unterminated string") || message.includes("Unexpected end of JSON input")
|
||||
);
|
||||
@@ -1107,7 +1096,7 @@ function logCodexAppServerParseFailure(value: string, error: unknown, fragmentCo
|
||||
const suffix = fragmentCount > 1 ? ` fragments=${fragmentCount}` : "";
|
||||
embeddedAgentLog.warn("failed to parse codex app-server message", {
|
||||
error,
|
||||
errorMessage: error instanceof Error ? error.message : String(error),
|
||||
errorMessage: coerceErrorMessage(error),
|
||||
fragmentCount,
|
||||
linePreview,
|
||||
consoleMessage: `failed to parse codex app-server message${suffix}: preview=${JSON.stringify(
|
||||
|
||||
@@ -123,7 +123,7 @@ function watchCodexNativeCompactionCompletion(params: {
|
||||
embeddedAgentLog.error("failed to retire unconfirmed codex app-server compaction", {
|
||||
threadId: params.threadId,
|
||||
turnId: compactionTurnId,
|
||||
reason: formatCompactionError(error),
|
||||
reason: coerceErrorMessage(error),
|
||||
});
|
||||
// Keep the lifecycle fence held when neither terminal state nor thread
|
||||
// retirement can be proven. Releasing would permit same-thread overlap.
|
||||
@@ -160,7 +160,7 @@ function watchCodexNativeCompactionCompletion(params: {
|
||||
embeddedAgentLog.warn("codex app-server compaction interrupt request failed", {
|
||||
threadId: params.threadId,
|
||||
turnId: compactionTurnId,
|
||||
reason: formatCompactionError(error),
|
||||
reason: coerceErrorMessage(error),
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -510,7 +510,7 @@ async function compactCodexNativeThread(
|
||||
return {
|
||||
ok: false,
|
||||
compacted: false,
|
||||
reason: formatCompactionError(error),
|
||||
reason: coerceErrorMessage(error),
|
||||
};
|
||||
}
|
||||
const { appServer, usesSupervisionConnection } = connection;
|
||||
@@ -652,7 +652,7 @@ async function compactCodexNativeThread(
|
||||
// Transport errors after the write leave the server-side start
|
||||
// ambiguous. Retire or detach the thread before releasing its fence.
|
||||
await completionWatch.retireUnconfirmedRequest(
|
||||
`codex app-server compaction start was unconfirmed: ${formatCompactionError(error)}`,
|
||||
`codex app-server compaction start was unconfirmed: ${coerceErrorMessage(error)}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -754,7 +754,7 @@ async function compactCodexNativeThread(
|
||||
if (isCodexThreadNotFoundError(error)) {
|
||||
return failedCodexThreadBindingCompactionResult(params, {
|
||||
threadId: binding.threadId,
|
||||
reason: formatCompactionError(error),
|
||||
reason: coerceErrorMessage(error),
|
||||
recovery: "stale_thread_binding",
|
||||
});
|
||||
}
|
||||
@@ -762,12 +762,12 @@ async function compactCodexNativeThread(
|
||||
sessionId: params.sessionId,
|
||||
sessionKey: params.sessionKey,
|
||||
threadId: binding.threadId,
|
||||
reason: formatCompactionError(error),
|
||||
reason: coerceErrorMessage(error),
|
||||
});
|
||||
return {
|
||||
ok: false,
|
||||
compacted: false,
|
||||
reason: formatCompactionError(error),
|
||||
reason: coerceErrorMessage(error),
|
||||
};
|
||||
} finally {
|
||||
completionWatch.cancel();
|
||||
@@ -967,7 +967,4 @@ function isCodexThreadNotFoundError(error: unknown): boolean {
|
||||
return coerceErrorMessage(error).toLowerCase().includes("thread not found");
|
||||
}
|
||||
|
||||
function formatCompactionError(error: unknown): string {
|
||||
return coerceErrorMessage(error);
|
||||
}
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* backed by OpenAI auth but should still report Codex Responses semantics.
|
||||
*/
|
||||
import type { AgentHarnessAttemptParamsV2 } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty as normalizeRuntimeId } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export type CodexLocalRuntimeAttributionParams = Pick<
|
||||
AgentHarnessAttemptParamsV2,
|
||||
@@ -19,10 +20,6 @@ type CodexLocalRuntimeAttribution = {
|
||||
api?: string;
|
||||
};
|
||||
|
||||
function normalizeRuntimeId(value: string | undefined): string {
|
||||
return value?.trim().toLowerCase() ?? "";
|
||||
}
|
||||
|
||||
/** Maps local Codex runtime plans onto the provider/api pair exposed to event projection. */
|
||||
export function resolveCodexLocalRuntimeAttribution(
|
||||
params: CodexLocalRuntimeAttributionParams,
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
* runtime rows so parent sessions can observe child progress.
|
||||
*/
|
||||
import type { AgentHarnessTaskRuntime } from "openclaw/plugin-sdk/agent-harness-task-runtime";
|
||||
import { readStringField as readString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
normalizeOptionalString,
|
||||
readStringField as readString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { CODEX_NATIVE_SUBAGENT_RUN_ID_PREFIX } from "./native-subagent-task-ids.js";
|
||||
import type {
|
||||
CodexServerNotification,
|
||||
@@ -102,13 +105,13 @@ export class CodexNativeSubagentTaskMirror {
|
||||
}
|
||||
const threadId = thread.id.trim();
|
||||
const label =
|
||||
trimOptional(spawn.agent_nickname) ??
|
||||
trimOptional(thread.agentNickname) ??
|
||||
trimOptional(spawn.agent_role) ??
|
||||
trimOptional(thread.agentRole) ??
|
||||
normalizeOptionalString(spawn.agent_nickname) ??
|
||||
normalizeOptionalString(thread.agentNickname) ??
|
||||
normalizeOptionalString(spawn.agent_role) ??
|
||||
normalizeOptionalString(thread.agentRole) ??
|
||||
"Codex subagent";
|
||||
const task =
|
||||
trimOptional(thread.preview) ??
|
||||
normalizeOptionalString(thread.preview) ??
|
||||
`Codex native subagent${label === "Codex subagent" ? "" : ` ${label}`}`;
|
||||
const createdAt = secondsToMillis(thread.createdAt) ?? this.now();
|
||||
if (
|
||||
@@ -258,13 +261,16 @@ export class CodexNativeSubagentTaskMirror {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const threadId = trimOptional(readString(item, "agentThreadId"));
|
||||
const threadId = normalizeOptionalString(readString(item, "agentThreadId"));
|
||||
const kind = normalizeSubagentActivityKind(readString(item, "kind"));
|
||||
if (!threadId || !kind) {
|
||||
return;
|
||||
}
|
||||
if (kind === "started") {
|
||||
this.createTaskFromSubagentActivity(threadId, trimOptional(readString(item, "agentPath")));
|
||||
this.createTaskFromSubagentActivity(
|
||||
threadId,
|
||||
normalizeOptionalString(readString(item, "agentPath")),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (this.mirrorStateByThreadId.get(threadId) !== "mirrored") {
|
||||
@@ -293,7 +299,7 @@ export class CodexNativeSubagentTaskMirror {
|
||||
}
|
||||
|
||||
private createTaskFromCollabSpawnItem(threadId: string, item: JsonObject): void {
|
||||
const prompt = trimOptional(readString(item, "prompt"));
|
||||
const prompt = normalizeOptionalString(readString(item, "prompt"));
|
||||
const createdAt = this.now();
|
||||
this.createRunningTask({
|
||||
threadId,
|
||||
@@ -366,7 +372,7 @@ export class CodexNativeSubagentTaskMirror {
|
||||
runId,
|
||||
lastEventAt: eventAt,
|
||||
progressSummary:
|
||||
trimOptional(message) ??
|
||||
normalizeOptionalString(message) ??
|
||||
(normalizedStatus === "pendingInit"
|
||||
? "Codex native subagent is initializing."
|
||||
: normalizedStatus === "interrupted"
|
||||
@@ -377,7 +383,7 @@ export class CodexNativeSubagentTaskMirror {
|
||||
}
|
||||
if (normalizedStatus === "completed") {
|
||||
this.terminalRunIds.add(runId);
|
||||
const summary = trimOptional(message) ?? "Codex native subagent completed.";
|
||||
const summary = normalizeOptionalString(message) ?? "Codex native subagent completed.";
|
||||
if (this.expectedAuthoritativeRunIds.has(runId)) {
|
||||
this.runtime.recordTaskRunProgressByRunId({
|
||||
runId,
|
||||
@@ -405,8 +411,8 @@ export class CodexNativeSubagentTaskMirror {
|
||||
status: "succeeded",
|
||||
endedAt: eventAt,
|
||||
lastEventAt: eventAt,
|
||||
progressSummary: trimOptional(message) ?? "Codex native subagent blocked.",
|
||||
terminalSummary: trimOptional(message) ?? "Codex native subagent blocked.",
|
||||
progressSummary: normalizeOptionalString(message) ?? "Codex native subagent blocked.",
|
||||
terminalSummary: normalizeOptionalString(message) ?? "Codex native subagent blocked.",
|
||||
terminalOutcome: "blocked",
|
||||
});
|
||||
return;
|
||||
@@ -417,9 +423,12 @@ export class CodexNativeSubagentTaskMirror {
|
||||
status: normalizedStatus === "shutdown" ? "cancelled" : "failed",
|
||||
endedAt: eventAt,
|
||||
lastEventAt: eventAt,
|
||||
error: trimOptional(message) ?? `Codex native subagent status: ${normalizedStatus}`,
|
||||
progressSummary: trimOptional(message) ?? `Codex native subagent ${normalizedStatus}.`,
|
||||
terminalSummary: trimOptional(message) ?? "Codex native subagent did not complete.",
|
||||
error:
|
||||
normalizeOptionalString(message) ?? `Codex native subagent status: ${normalizedStatus}`,
|
||||
progressSummary:
|
||||
normalizeOptionalString(message) ?? `Codex native subagent ${normalizedStatus}.`,
|
||||
terminalSummary:
|
||||
normalizeOptionalString(message) ?? "Codex native subagent did not complete.",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -580,8 +589,3 @@ function secondsToMillis(value: number | null | undefined): number | undefined {
|
||||
}
|
||||
return value * 1000;
|
||||
}
|
||||
|
||||
function trimOptional(value: string | null | undefined): string | undefined {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Activates curated Codex marketplace plugins and keeps require-active
|
||||
* marketplaces outside OpenClaw's install authority.
|
||||
*/
|
||||
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import type { CodexAppInventoryCache, CodexAppInventoryRequest } from "./app-inventory-cache.js";
|
||||
import {
|
||||
CODEX_PLUGINS_MARKETPLACE_NAME,
|
||||
@@ -138,9 +139,7 @@ export async function ensureCodexPluginActivation(
|
||||
marketplace: resolved.marketplace,
|
||||
diagnostics: [
|
||||
{
|
||||
message: `Codex plugin install failed: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
message: `Codex plugin install failed: ${coerceErrorMessage(error)}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -163,9 +162,7 @@ export async function ensureCodexPluginActivation(
|
||||
} catch (error) {
|
||||
refreshFailed = true;
|
||||
refreshDiagnostics.push({
|
||||
message: `Codex plugin runtime refresh failed after install: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
message: `Codex plugin runtime refresh failed after install: ${coerceErrorMessage(error)}`,
|
||||
});
|
||||
}
|
||||
const authRequired = installResponse.appsNeedingAuth.length > 0;
|
||||
@@ -212,7 +209,7 @@ async function refreshCodexPluginRuntimeState(params: {
|
||||
} satisfies v2.HooksListParams) as Promise<v2.HooksListResponse>);
|
||||
} catch (error) {
|
||||
diagnostics.push({
|
||||
message: `Codex hooks refresh skipped: ${error instanceof Error ? error.message : String(error)}`,
|
||||
message: `Codex hooks refresh skipped: ${coerceErrorMessage(error)}`,
|
||||
});
|
||||
}
|
||||
await params.request("config/mcpServer/reload", undefined);
|
||||
@@ -240,9 +237,7 @@ async function refreshCodexPluginRuntimeState(params: {
|
||||
});
|
||||
} catch (error) {
|
||||
diagnostics.push({
|
||||
message: `Codex app inventory refresh skipped: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
message: `Codex app inventory refresh skipped: ${coerceErrorMessage(error)}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import { embeddedAgentLog } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import type { WebSocket } from "ws";
|
||||
import type { JsonObject, JsonValue } from "../protocol.js";
|
||||
import { requireObject, requireString, requireStringArray } from "./json-rpc.js";
|
||||
@@ -69,7 +70,7 @@ export async function startProcess(
|
||||
await runProcess(execServer, managed, { argv, cwd, env });
|
||||
} catch (error) {
|
||||
processes.delete(processId);
|
||||
managed.failure = error instanceof Error ? error.message : String(error);
|
||||
managed.failure = coerceErrorMessage(error);
|
||||
managed.exitCode = null;
|
||||
managed.exited = true;
|
||||
managed.closed = true;
|
||||
@@ -114,11 +115,11 @@ async function runProcess(
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
} catch (error) {
|
||||
managed.failure = error instanceof Error ? error.message : String(error);
|
||||
managed.failure = coerceErrorMessage(error);
|
||||
await finalizeProcess(managed).catch((finalizeError: unknown) => {
|
||||
embeddedAgentLog.warn("codex sandbox exec-server finalize after start failure failed", {
|
||||
processId: managed.processId,
|
||||
error: finalizeError instanceof Error ? finalizeError.message : String(finalizeError),
|
||||
error: coerceErrorMessage(finalizeError),
|
||||
});
|
||||
});
|
||||
throw error;
|
||||
@@ -209,7 +210,7 @@ function emitProcessClosed(managed: ManagedProcess, exitCode: number | null): vo
|
||||
});
|
||||
}
|
||||
void finalizeProcess(managed).catch((error: unknown) => {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const message = coerceErrorMessage(error);
|
||||
managed.failure ??= message;
|
||||
embeddedAgentLog.warn("codex sandbox exec-server finalize failed", {
|
||||
processId: managed.processId,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Codex plugin module implements apply behavior.
|
||||
import path from "node:path";
|
||||
import { coerceErrorMessage as formatCodexMigrationError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import {
|
||||
applyMigrationManualItem,
|
||||
markMigrationItemConflict,
|
||||
@@ -273,14 +273,14 @@ async function applyCodexPluginInstallItem(
|
||||
...item.details,
|
||||
code: "plugin_inventory_unavailable",
|
||||
warningReason: CODEX_PLUGIN_LOAD_WARNING,
|
||||
diagnostic: formatCodexMigrationError(error),
|
||||
diagnostic: coerceErrorMessage(error),
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
status: "error",
|
||||
reason: formatCodexMigrationError(error),
|
||||
reason: coerceErrorMessage(error),
|
||||
details: {
|
||||
...item.details,
|
||||
code: "plugin_install_failed",
|
||||
@@ -290,7 +290,7 @@ async function applyCodexPluginInstallItem(
|
||||
}
|
||||
|
||||
function isCodexPluginInventoryLoadError(error: unknown): boolean {
|
||||
const message = formatCodexMigrationError(error);
|
||||
const message = coerceErrorMessage(error);
|
||||
return message.includes("codex app-server plugin/list timed out");
|
||||
}
|
||||
|
||||
@@ -473,7 +473,7 @@ async function applyCodexPluginConfigItem(
|
||||
if (error instanceof CodexPluginConfigConflictError) {
|
||||
return markMigrationItemConflict(item, error.reason);
|
||||
}
|
||||
return markMigrationItemError(item, error instanceof Error ? error.message : String(error));
|
||||
return markMigrationItemError(item, coerceErrorMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Codex plugin module implements source behavior.
|
||||
import path from "node:path";
|
||||
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { isPathInside } from "openclaw/plugin-sdk/security-runtime";
|
||||
import {
|
||||
defaultCodexAppInventoryCache,
|
||||
@@ -155,7 +156,7 @@ async function discoverInstalledCuratedPlugins(
|
||||
} catch (error) {
|
||||
return {
|
||||
plugins: [],
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: coerceErrorMessage(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -297,7 +298,7 @@ async function withPluginMigrationEligibility(params: {
|
||||
sourceAccountError = "Codex app-server did not report an authenticated source account.";
|
||||
}
|
||||
} catch (error) {
|
||||
sourceAccountError = error instanceof Error ? error.message : String(error);
|
||||
sourceAccountError = coerceErrorMessage(error);
|
||||
}
|
||||
if (sourceAccountError && !params.verifyPluginApps) {
|
||||
for (const { plugin, apps } of pending) {
|
||||
@@ -335,7 +336,7 @@ async function withPluginMigrationEligibility(params: {
|
||||
|
||||
const snapshot = await refreshSourceAppInventory(params.requestOptions).catch(
|
||||
(error: unknown) => {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const message = coerceErrorMessage(error);
|
||||
for (const { plugin, apps } of pending) {
|
||||
evaluated.push({
|
||||
...plugin,
|
||||
@@ -427,7 +428,7 @@ async function readPluginDetail(
|
||||
});
|
||||
return { ok: true, detail: response.plugin };
|
||||
} catch (error) {
|
||||
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
||||
return { ok: false, error: coerceErrorMessage(error) };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "discord-api-types/v10";
|
||||
import type { BaseMessageInteractiveComponent } from "./components.js";
|
||||
import type { AutocompleteInteraction, CommandInteraction } from "./interactions.js";
|
||||
import { stripUndefinedFields as clean } from "./undefined-fields.js";
|
||||
|
||||
type ConditionalCommandOption = (interaction: unknown) => boolean;
|
||||
type CommandOption = Record<string, unknown> & {
|
||||
@@ -25,10 +26,6 @@ type RawSubcommandOption = {
|
||||
options?: RawSubcommandOption[];
|
||||
};
|
||||
|
||||
function clean<T extends Record<string, unknown>>(value: T): T {
|
||||
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)) as T;
|
||||
}
|
||||
|
||||
function resolveConditionalCommandOption(
|
||||
value: boolean | ConditionalCommandOption,
|
||||
interaction: unknown,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Discord plugin module implements components.base behavior.
|
||||
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
||||
import type { BaseComponentInteraction } from "./interactions.js";
|
||||
export { stripUndefinedFields as clean } from "./undefined-fields.js";
|
||||
|
||||
export type ComponentParserResult = {
|
||||
key: string;
|
||||
@@ -35,10 +36,6 @@ export function parseCustomId(id: string): ComponentParserResult {
|
||||
return { key, data };
|
||||
}
|
||||
|
||||
export function clean<T extends Record<string, unknown>>(value: T): T {
|
||||
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)) as T;
|
||||
}
|
||||
|
||||
export function colorToNumber(value: string | number | undefined): number | undefined {
|
||||
if (typeof value === "number") {
|
||||
return Number.isInteger(value) && value >= 0 && value <= 0xffffff ? value : undefined;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// Discord plugin module implements embeds behavior.
|
||||
import type { APIEmbed } from "discord-api-types/v10";
|
||||
|
||||
function clean<T extends Record<string, unknown>>(value: T): T {
|
||||
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)) as T;
|
||||
}
|
||||
import { stripUndefinedFields as clean } from "./undefined-fields.js";
|
||||
|
||||
export class Embed {
|
||||
title?: string;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Discord plugin module implements payload behavior.
|
||||
import { MessageFlags, type APIEmbed } from "discord-api-types/v10";
|
||||
import { Embed } from "./embeds.js";
|
||||
import { stripUndefinedFields as clean } from "./undefined-fields.js";
|
||||
|
||||
export type MessagePayloadFile = {
|
||||
name: string;
|
||||
@@ -29,10 +30,6 @@ export type TopLevelComponents = {
|
||||
serialize: () => unknown;
|
||||
};
|
||||
|
||||
function clean<T extends Record<string, unknown>>(value: T): T {
|
||||
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)) as T;
|
||||
}
|
||||
|
||||
function serializeAnyComponent(component: { serialize: () => unknown }): unknown {
|
||||
return component.serialize();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// Discord plugin module implements undefined field filtering.
|
||||
export function stripUndefinedFields<T extends object>(value: T): T {
|
||||
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)) as T;
|
||||
}
|
||||
@@ -8,6 +8,9 @@ import {
|
||||
type MessagePayloadObject,
|
||||
type TopLevelComponents,
|
||||
} from "./internal/discord.js";
|
||||
import { stripUndefinedFields } from "./internal/undefined-fields.js";
|
||||
|
||||
export { stripUndefinedFields };
|
||||
|
||||
const SUPPRESS_EMBEDS_FLAG = MessageFlags.SuppressEmbeds;
|
||||
export const SUPPRESS_NOTIFICATIONS_FLAG = MessageFlags.SuppressNotifications;
|
||||
@@ -130,10 +133,6 @@ export function buildDiscordMessageRequest(params: DiscordMessageRequestParams)
|
||||
});
|
||||
}
|
||||
|
||||
export function stripUndefinedFields<T extends object>(value: T): T {
|
||||
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)) as T;
|
||||
}
|
||||
|
||||
function hasV2Components(components?: TopLevelComponents[]): boolean {
|
||||
return Boolean(components?.some((component) => "isV2" in component && component.isV2));
|
||||
}
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
// Feishu helper module supports agent config behavior.
|
||||
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
||||
import type { ClawdbotConfig } from "./bot-runtime-api.js";
|
||||
|
||||
type ReasoningDefault = "on" | "stream" | "off";
|
||||
|
||||
const DEFAULT_AGENT_ID = "main";
|
||||
|
||||
function normalizeAgentId(value: string | undefined | null): string {
|
||||
const normalized = (value ?? "").trim().toLowerCase();
|
||||
return normalized || DEFAULT_AGENT_ID;
|
||||
}
|
||||
|
||||
export function resolveFeishuConfigReasoningDefault(
|
||||
cfg: ClawdbotConfig,
|
||||
agentId: string,
|
||||
|
||||
@@ -17,7 +17,10 @@ import {
|
||||
resolveStorePath,
|
||||
} from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { legacyConfigRules, normalizeCompatibilityConfig } from "./doctor-contract.js";
|
||||
|
||||
const FEISHU_STATE_DIR = "feishu";
|
||||
@@ -175,10 +178,6 @@ function isFeishuAcpBindingSessionKey(key: string): boolean {
|
||||
return /^agent:[^:]+:acp:binding:feishu(?::|$)/.test(key.trim().toLowerCase());
|
||||
}
|
||||
|
||||
function normalizeMetadataString(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
||||
}
|
||||
|
||||
function isFeishuSessionEntry(key: string, value: unknown): boolean {
|
||||
if (isFeishuAcpBindingSessionKey(key)) {
|
||||
return false;
|
||||
@@ -190,29 +189,29 @@ function isFeishuSessionEntry(key: string, value: unknown): boolean {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
normalizeMetadataString(value.channel) === "feishu" ||
|
||||
normalizeMetadataString(value.lastChannel) === "feishu"
|
||||
normalizeLowercaseStringOrEmpty(value.channel) === "feishu" ||
|
||||
normalizeLowercaseStringOrEmpty(value.lastChannel) === "feishu"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
const route = isRecord(value.route) ? value.route : null;
|
||||
if (normalizeMetadataString(route?.channel) === "feishu") {
|
||||
if (normalizeLowercaseStringOrEmpty(route?.channel) === "feishu") {
|
||||
return true;
|
||||
}
|
||||
const deliveryContext = isRecord(value.deliveryContext) ? value.deliveryContext : null;
|
||||
if (normalizeMetadataString(deliveryContext?.channel) === "feishu") {
|
||||
if (normalizeLowercaseStringOrEmpty(deliveryContext?.channel) === "feishu") {
|
||||
return true;
|
||||
}
|
||||
const pendingDeliveryContext = isRecord(value.pendingFinalDeliveryContext)
|
||||
? value.pendingFinalDeliveryContext
|
||||
: null;
|
||||
if (normalizeMetadataString(pendingDeliveryContext?.channel) === "feishu") {
|
||||
if (normalizeLowercaseStringOrEmpty(pendingDeliveryContext?.channel) === "feishu") {
|
||||
return true;
|
||||
}
|
||||
const origin = isRecord(value.origin) ? value.origin : null;
|
||||
const originProvider = normalizeMetadataString(origin?.provider);
|
||||
const originSurface = normalizeMetadataString(origin?.surface);
|
||||
const originFrom = normalizeMetadataString(origin?.from);
|
||||
const originProvider = normalizeLowercaseStringOrEmpty(origin?.provider);
|
||||
const originSurface = normalizeLowercaseStringOrEmpty(origin?.surface);
|
||||
const originFrom = normalizeLowercaseStringOrEmpty(origin?.from);
|
||||
return (
|
||||
originProvider === "feishu" ||
|
||||
originSurface.startsWith("feishu") ||
|
||||
|
||||
@@ -108,7 +108,7 @@ describe("google-meet lazy imports", () => {
|
||||
vi.doMock("openclaw/plugin-sdk/routing", () => {
|
||||
routingImports += 1;
|
||||
return {
|
||||
normalizeAgentId: (value: string) => value,
|
||||
normalizeAgentId: vi.fn((value: string) => value),
|
||||
parseAgentSessionKey: () => ({ agentId: "main" }),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
resolveStorePath,
|
||||
} from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import { openNodeSqliteDatabase } from "openclaw/plugin-sdk/sqlite-runtime";
|
||||
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import { waitForTransportReady } from "openclaw/plugin-sdk/transport-ready-runtime";
|
||||
import { resolveIMessageAccount } from "../accounts.js";
|
||||
@@ -112,7 +113,7 @@ import {
|
||||
loadIMessageRecoveryCursor,
|
||||
resolveIMessageRecoveryCursorDbIdentity,
|
||||
} from "./recovery-cursor.js";
|
||||
import { normalizeAllowList, resolveRuntime } from "./runtime.js";
|
||||
import { resolveRuntime } from "./runtime.js";
|
||||
import { createSelfChatCache } from "./self-chat-cache.js";
|
||||
import type { IMessageAttachment, IMessagePayload, MonitorIMessageOpts } from "./types.js";
|
||||
import { sanitizeIMessageWatchErrorPayload } from "./watch-error-log.js";
|
||||
@@ -371,9 +372,9 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P
|
||||
const selfChatCache = createSelfChatCache();
|
||||
const loopRateLimiter = createLoopRateLimiter();
|
||||
const textLimit = resolveTextChunkLimit(cfg, "imessage", accountInfo.accountId);
|
||||
const allowFrom = normalizeAllowList(opts.allowFrom ?? imessageCfg.allowFrom);
|
||||
const allowFrom = normalizeStringEntries(opts.allowFrom ?? imessageCfg.allowFrom);
|
||||
const configuredGroupAllowFrom = opts.groupAllowFrom ?? imessageCfg.groupAllowFrom;
|
||||
const groupAllowFrom = normalizeAllowList(
|
||||
const groupAllowFrom = normalizeStringEntries(
|
||||
configuredGroupAllowFrom ??
|
||||
(imessageCfg.allowFrom && imessageCfg.allowFrom.length > 0 ? imessageCfg.allowFrom : []),
|
||||
);
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
// Imessage plugin module implements runtime behavior.
|
||||
import { createNonExitingRuntime, type RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { MonitorIMessageOpts } from "./types.js";
|
||||
|
||||
export function resolveRuntime(opts: MonitorIMessageOpts): RuntimeEnv {
|
||||
return opts.runtime ?? createNonExitingRuntime();
|
||||
}
|
||||
|
||||
export function normalizeAllowList(list?: Array<string | number>) {
|
||||
return normalizeStringEntries(list);
|
||||
}
|
||||
|
||||
@@ -12,13 +12,12 @@ import {
|
||||
} from "openclaw/plugin-sdk/account-id";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { hasConfiguredSecretInput } from "openclaw/plugin-sdk/secret-input";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
resolveMatrixAccountStringValues,
|
||||
type MatrixResolvedStringField,
|
||||
} from "./auth-precedence.js";
|
||||
import { getMatrixScopedEnvVarNames, listMatrixEnvAccountIds } from "./env-vars.js";
|
||||
import { isRecord } from "./record-shared.js";
|
||||
|
||||
type MatrixTopologyStringSources = Partial<Record<MatrixResolvedStringField, string>>;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
migrateLegacyFlatAllowPrivateNetworkAlias,
|
||||
stripRetiredChannelKeys,
|
||||
} from "openclaw/plugin-sdk/runtime-doctor-migrations";
|
||||
import { isRecord } from "./record-shared.js";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { MatrixStreamingMode } from "./types.js";
|
||||
|
||||
function parseMatrixStreamingMode(value: unknown): MatrixStreamingMode | null {
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
PluginStateKeyedStore,
|
||||
PluginStateSyncKeyedStore,
|
||||
} from "openclaw/plugin-sdk/plugin-state-runtime";
|
||||
import { isRecord } from "../../record-shared.js";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { getMatrixRuntime } from "../../runtime.js";
|
||||
import { createAsyncLock } from "../async-lock.js";
|
||||
import { LogService } from "../sdk/logger.js";
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
PluginStateKeyedStore,
|
||||
PluginStateSyncKeyedStore,
|
||||
} from "openclaw/plugin-sdk/plugin-state-runtime";
|
||||
import { isRecord } from "../../record-shared.js";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { getMatrixRuntime } from "../../runtime.js";
|
||||
import { resolveMatrixAccountStorageRoot } from "../../storage-paths.js";
|
||||
import {
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
PluginStateKeyedStore,
|
||||
PluginStateSyncKeyedStore,
|
||||
} from "openclaw/plugin-sdk/plugin-state-runtime";
|
||||
import { isRecord } from "../record-shared.js";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { getMatrixRuntime } from "../runtime.js";
|
||||
import type { MatrixStoredRecoveryKey } from "./sdk/types.js";
|
||||
import { resolveMatrixSqliteStateEnv } from "./sqlite-state.js";
|
||||
|
||||
@@ -19,7 +19,7 @@ import type { DatabaseSync } from "node:sqlite";
|
||||
// plugin-state-store/kysely graph, so the value import stays lazy below.
|
||||
import type { PersistentDedupeEntry } from "openclaw/plugin-sdk/persistent-dedupe";
|
||||
import type { PluginDoctorStateMigrationContext } from "openclaw/plugin-sdk/runtime-doctor-migrations";
|
||||
import { isRecord } from "../../record-shared.js";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { normalizeMatrixStorageMetadata } from "../client/storage.js";
|
||||
|
||||
const LEGACY_SQLITE_NAMESPACE = "inbound-dedupe";
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
// Matrix plugin module implements record shared behavior.
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export { isRecord };
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
legacyStateFileExists,
|
||||
type PluginDoctorStateMigration,
|
||||
} from "openclaw/plugin-sdk/runtime-doctor-migrations";
|
||||
// This doctor closure must stay dependency-light while accepting legacy array-backed objects.
|
||||
import { asOptionalObjectRecord as readLegacyObjectRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
// sqlite-runtime re-exports the agent-db/kysely graph; keep it lazy so doctor
|
||||
// enumeration does not cold-load it with this closure.
|
||||
import {
|
||||
@@ -26,13 +28,6 @@ function formatLegacyVectorRows(count: number | undefined): string {
|
||||
|
||||
type MemoryFtsTokenizer = "unicode61" | "trigram";
|
||||
|
||||
// This doctor closure must stay dependency-light while accepting legacy array-backed objects.
|
||||
function readLegacyObjectRecord(value: unknown): Record<string, unknown> | undefined {
|
||||
return value !== null && typeof value === "object"
|
||||
? (value as Record<string, unknown>)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function resolveConfiguredAgentIds(config: unknown): string[] {
|
||||
const cfg = config as { agents?: { entries?: unknown; list?: unknown } };
|
||||
const entries = readLegacyObjectRecord(cfg.agents?.entries);
|
||||
|
||||
@@ -285,10 +285,10 @@ export async function downloadMSTeamsGraphMedia(params: {
|
||||
} catch (err) {
|
||||
params.logger?.debug?.("graph media token acquisition failed", {
|
||||
messageUrl,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
error: coerceErrorMessage(err),
|
||||
});
|
||||
params.logger?.warn?.("msteams graph token acquisition failed", {
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
error: coerceErrorMessage(err),
|
||||
});
|
||||
return { media: [], messageUrl, tokenError: true };
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
resolveChannelProgressDraftMaxLines,
|
||||
resolveChannelStreamingPreviewToolProgress,
|
||||
} from "openclaw/plugin-sdk/channel-outbound";
|
||||
import { coerceErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { MSTeamsConfig, ReplyPayload } from "../runtime-api.js";
|
||||
import { extractMessageId } from "./media-helpers.js";
|
||||
@@ -280,9 +281,7 @@ export function createTeamsReplyStreamController(params: {
|
||||
canceledLocally = true;
|
||||
return;
|
||||
}
|
||||
params.log?.debug?.(
|
||||
`stream informative update failed: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
params.log?.debug?.(`stream informative update failed: ${coerceErrorMessage(err)}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -371,7 +370,7 @@ export function createTeamsReplyStreamController(params: {
|
||||
// cumulative prefix Teams accepted; failed emits prove no delivery.
|
||||
streamFailed = true;
|
||||
params.log?.warn?.(
|
||||
`msteams stream emit failed, falling back to block delivery: ${err instanceof Error ? err.message : String(err)}`,
|
||||
`msteams stream emit failed, falling back to block delivery: ${coerceErrorMessage(err)}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -500,7 +499,7 @@ export function createTeamsReplyStreamController(params: {
|
||||
streamFailed = true;
|
||||
replacementEmitFailed = true;
|
||||
params.log?.warn?.(
|
||||
`msteams stream replacement failed, falling back to block delivery: ${err instanceof Error ? err.message : String(err)}`,
|
||||
`msteams stream replacement failed, falling back to block delivery: ${coerceErrorMessage(err)}`,
|
||||
);
|
||||
// Retain ownership until finalize so payloads held before this
|
||||
// failed replacement cannot be overtaken by this or later blocks.
|
||||
@@ -549,9 +548,7 @@ export function createTeamsReplyStreamController(params: {
|
||||
}
|
||||
// Non-cancel emit failure: fall through to block delivery as a
|
||||
// safety net so the user still sees the final reply.
|
||||
params.log?.debug?.(
|
||||
`progress-mode finalize failed: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
params.log?.debug?.(`progress-mode finalize failed: ${coerceErrorMessage(err)}`);
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
@@ -669,9 +666,7 @@ export function createTeamsReplyStreamController(params: {
|
||||
// the reply pipeline after the user already saw the response.
|
||||
streamFailed = true;
|
||||
streamFinalizationPending = false;
|
||||
params.log?.warn?.(
|
||||
`msteams stream finalize failed: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
params.log?.warn?.(`msteams stream finalize failed: ${coerceErrorMessage(err)}`);
|
||||
const fallback = pendingFinalPayload;
|
||||
pendingFinalPayload = undefined;
|
||||
const replacementFallback =
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
// Ollama plugin module implements ollama json behavior.
|
||||
export {
|
||||
parseJsonObjectPreservingUnsafeIntegers,
|
||||
parseJsonPreservingUnsafeIntegers,
|
||||
} from "openclaw/plugin-sdk/json-unsafe-integers";
|
||||
@@ -2,6 +2,10 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { StreamFn } from "openclaw/plugin-sdk/agent-core";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import {
|
||||
parseJsonObjectPreservingUnsafeIntegers,
|
||||
parseJsonPreservingUnsafeIntegers,
|
||||
} from "openclaw/plugin-sdk/json-unsafe-integers";
|
||||
import type {
|
||||
AssistantMessage,
|
||||
StopReason,
|
||||
@@ -26,10 +30,6 @@ import {
|
||||
import { estimateStringChars, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import { OLLAMA_CLOUD_BASE_URL, OLLAMA_DEFAULT_BASE_URL } from "./defaults.js";
|
||||
import { normalizeOllamaWireModelId } from "./model-id.js";
|
||||
import {
|
||||
parseJsonObjectPreservingUnsafeIntegers,
|
||||
parseJsonPreservingUnsafeIntegers,
|
||||
} from "./ollama-json.js";
|
||||
import { buildOllamaBaseUrlSsrFPolicy, isOllamaCloudModel } from "./provider-models.js";
|
||||
import {
|
||||
createOllamaVisibleContentSanitizer,
|
||||
|
||||
@@ -7,6 +7,9 @@ import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-aut
|
||||
import {
|
||||
getCachedLiveProviderModelRows,
|
||||
LiveModelCatalogHttpError,
|
||||
readLiveModelCatalogBooleanField,
|
||||
readLiveModelCatalogPositiveSafeIntegerField,
|
||||
readLiveModelCatalogStringField,
|
||||
type LiveModelCatalogFetchGuard,
|
||||
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
||||
import {
|
||||
@@ -342,28 +345,6 @@ async function buildOpenAILiveProviderConfig(
|
||||
}
|
||||
}
|
||||
|
||||
function readCodexModelString(row: unknown, key: string): string | undefined {
|
||||
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
||||
return undefined;
|
||||
}
|
||||
const value = (row as Record<string, unknown>)[key];
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function readCodexModelPositiveInteger(row: unknown, keys: readonly string[]): number | undefined {
|
||||
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
||||
return undefined;
|
||||
}
|
||||
const record = row as Record<string, unknown>;
|
||||
for (const key of keys) {
|
||||
const value = record[key];
|
||||
if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function readCodexModelStringArray(row: unknown, keys: readonly string[]): readonly string[] {
|
||||
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
||||
return [];
|
||||
@@ -399,14 +380,6 @@ function readCodexReasoningLevels(row: unknown): readonly string[] | undefined {
|
||||
});
|
||||
}
|
||||
|
||||
function readCodexModelBoolean(row: unknown, key: string): boolean | undefined {
|
||||
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
||||
return undefined;
|
||||
}
|
||||
const value = (row as Record<string, unknown>)[key];
|
||||
return typeof value === "boolean" ? value : undefined;
|
||||
}
|
||||
|
||||
function readCodexModelRows(body: unknown): readonly unknown[] {
|
||||
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
||||
throw new Error("OpenAI Codex model discovery response must be { models: [] }");
|
||||
@@ -419,12 +392,15 @@ function readCodexModelRows(body: unknown): readonly unknown[] {
|
||||
}
|
||||
|
||||
function shouldIncludeCodexModelRow(row: unknown): boolean {
|
||||
const visibility = normalizeLowercaseStringOrEmpty(readCodexModelString(row, "visibility") ?? "");
|
||||
const visibility = normalizeLowercaseStringOrEmpty(
|
||||
readLiveModelCatalogStringField(row, "visibility") ?? "",
|
||||
);
|
||||
if (visibility && visibility !== "list") {
|
||||
return false;
|
||||
}
|
||||
const showInPicker =
|
||||
readCodexModelBoolean(row, "show_in_picker") ?? readCodexModelBoolean(row, "showInPicker");
|
||||
readLiveModelCatalogBooleanField(row, "show_in_picker") ??
|
||||
readLiveModelCatalogBooleanField(row, "showInPicker");
|
||||
return showInPicker !== false;
|
||||
}
|
||||
|
||||
@@ -496,7 +472,8 @@ function buildOpenAICodexModelFromLiveRow(row: unknown): ModelDefinitionConfig |
|
||||
if (!shouldIncludeCodexModelRow(row)) {
|
||||
return undefined;
|
||||
}
|
||||
const modelId = readCodexModelString(row, "slug") ?? readCodexModelString(row, "id");
|
||||
const modelId =
|
||||
readLiveModelCatalogStringField(row, "slug") ?? readLiveModelCatalogStringField(row, "id");
|
||||
if (!modelId) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -506,7 +483,7 @@ function buildOpenAICodexModelFromLiveRow(row: unknown): ModelDefinitionConfig |
|
||||
const normalizedModelId = normalizeLowercaseStringOrEmpty(modelId);
|
||||
const fallback = resolveCodexModelFallback(modelId);
|
||||
const reasoningLevels = readCodexReasoningLevels(row);
|
||||
const observedContextTokens = readCodexModelPositiveInteger(row, [
|
||||
const observedContextTokens = readLiveModelCatalogPositiveSafeIntegerField(row, [
|
||||
"context_window",
|
||||
"contextWindow",
|
||||
]);
|
||||
@@ -518,12 +495,12 @@ function buildOpenAICodexModelFromLiveRow(row: unknown): ModelDefinitionConfig |
|
||||
)
|
||||
: observedContextTokens;
|
||||
const contextWindow =
|
||||
readCodexModelPositiveInteger(row, ["max_context_window", "maxContextWindow"]) ??
|
||||
readLiveModelCatalogPositiveSafeIntegerField(row, ["max_context_window", "maxContextWindow"]) ??
|
||||
fallback?.contextWindow ??
|
||||
observedContextTokens ??
|
||||
DEFAULT_CONTEXT_TOKENS;
|
||||
const maxTokens =
|
||||
readCodexModelPositiveInteger(row, [
|
||||
readLiveModelCatalogPositiveSafeIntegerField(row, [
|
||||
"max_output_tokens",
|
||||
"maxOutputTokens",
|
||||
"max_completion_tokens",
|
||||
@@ -548,7 +525,7 @@ function buildOpenAICodexModelFromLiveRow(row: unknown): ModelDefinitionConfig |
|
||||
|
||||
return normalizeOpenAICodexCatalogModel({
|
||||
id: modelId,
|
||||
name: readCodexModelString(row, "display_name") ?? fallback?.name ?? modelId,
|
||||
name: readLiveModelCatalogStringField(row, "display_name") ?? fallback?.name ?? modelId,
|
||||
api: "openai-chatgpt-responses",
|
||||
baseUrl: OPENAI_CODEX_RESPONSES_BASE_URL,
|
||||
reasoning: (reasoningLevels?.length ?? 0) > 0 || fallback?.reasoning || false,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { coerceErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { resolveProviderRequestHeaders } from "openclaw/plugin-sdk/provider-http";
|
||||
import {
|
||||
captureWsEvent,
|
||||
@@ -324,7 +324,7 @@ export class OpenAIRealtimeBridge extends OpenAIRealtimeEvents implements Realti
|
||||
direction: "local",
|
||||
kind: "error",
|
||||
flowId: this.flowId,
|
||||
errorText: error instanceof Error ? error.message : String(error),
|
||||
errorText: coerceErrorMessage(error),
|
||||
meta: {
|
||||
provider: "openai",
|
||||
capability: "realtime-voice",
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
ProviderDefaultThinkingPolicyContext,
|
||||
ProviderThinkingProfile,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { normalizeLowercaseStringOrEmpty as normalizeModelId } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
OPENAI_GPT_53_CODEX_SPARK_MODEL_ID,
|
||||
OPENAI_GPT_54_MINI_MODEL_ID,
|
||||
@@ -53,10 +54,6 @@ const OPENAI_UNIFIED_XHIGH_MODEL_IDS = [
|
||||
OPENAI_GPT_54_NANO_MODEL_ID,
|
||||
] as const;
|
||||
|
||||
function normalizeModelId(value: string): string {
|
||||
return value.trim().toLowerCase();
|
||||
}
|
||||
|
||||
function matchesExactOrPrefix(id: string, values: readonly string[]): boolean {
|
||||
const normalizedId = normalizeModelId(id);
|
||||
return values.some((value) => {
|
||||
|
||||
@@ -12,7 +12,10 @@ import {
|
||||
resolveSiteName,
|
||||
wrapWebContent,
|
||||
} from "openclaw/plugin-sdk/provider-web-search";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
normalizeBoundedOptionalString,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
|
||||
// Internal-only bounds (the model-facing tool schema declares its own copies).
|
||||
@@ -30,6 +33,11 @@ export const PARALLEL_SESSION_ID_MAX_LENGTH = 1000;
|
||||
export const PARALLEL_FREE_SESSION_ID_MAX_LENGTH = 100;
|
||||
const PARALLEL_CLIENT_MODEL_MAX_LENGTH = 100;
|
||||
|
||||
export const normalizeParallelSessionId: (
|
||||
value: string | undefined,
|
||||
maxLength: number,
|
||||
) => string | undefined = normalizeBoundedOptionalString;
|
||||
|
||||
type ParallelSearchResult = {
|
||||
title?: unknown;
|
||||
url?: unknown;
|
||||
@@ -93,14 +101,6 @@ export function resolveParallelSearchCount(
|
||||
});
|
||||
}
|
||||
|
||||
export function normalizeParallelSessionId(
|
||||
value: string | undefined,
|
||||
maxLength: number,
|
||||
): string | undefined {
|
||||
const trimmed = normalizeOptionalString(value);
|
||||
return trimmed && trimmed.length <= maxLength ? trimmed : undefined;
|
||||
}
|
||||
|
||||
export function normalizeParallelObjective(value: string | undefined): string | undefined {
|
||||
const trimmed = normalizeOptionalString(value);
|
||||
if (!trimmed) {
|
||||
|
||||
@@ -6,7 +6,10 @@ import {
|
||||
} from "openclaw/plugin-sdk/exec-approvals-runtime";
|
||||
import type { HealthCheckContext, HealthFinding } from "openclaw/plugin-sdk/health";
|
||||
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
isRecord,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { EXEC_APPROVALS_POLICY_DOCUMENT_NAME } from "../exec-approvals-uri.js";
|
||||
import type { PolicyAuthProfileEvidence } from "../policy-state.js";
|
||||
import { CHECK_IDS } from "./check-ids.js";
|
||||
@@ -16,6 +19,8 @@ import {
|
||||
} from "./policy-constants.js";
|
||||
import { readPolicyStringArray } from "./utils.js";
|
||||
|
||||
export const normalizePolicyChannelId: (value: string) => string = normalizeLowercaseStringOrEmpty;
|
||||
|
||||
const loadFsPromisesModule = createLazyRuntimeModule(() => import("node:fs/promises"));
|
||||
|
||||
export async function readPolicyFile(
|
||||
@@ -287,10 +292,6 @@ export function authProfileHasMetadata(
|
||||
);
|
||||
}
|
||||
|
||||
export function normalizePolicyChannelId(value: string): string {
|
||||
return value.trim().toLowerCase();
|
||||
}
|
||||
|
||||
export function execApprovalsDisplayName(): string {
|
||||
return resolveExecApprovalsDisplayPath();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Shared policy doctor value readers.
|
||||
import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
export { readBooleanPath as readPolicyBoolean } from "../policy-state-helpers.js";
|
||||
|
||||
export function readPolicyStringArray(
|
||||
policy: unknown,
|
||||
@@ -50,14 +51,3 @@ export function ocPathSegment(value: string): string {
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
export function readPolicyBoolean(policy: unknown, path: readonly string[]): boolean | undefined {
|
||||
let current: unknown = policy;
|
||||
for (const part of path) {
|
||||
if (!isRecord(current)) {
|
||||
return undefined;
|
||||
}
|
||||
current = current[part];
|
||||
}
|
||||
return typeof current === "boolean" ? current : undefined;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
// Qa Lab plugin module defines shared suite errors.
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
|
||||
export function toQaError(value: unknown): Error {
|
||||
return value instanceof Error ? value : new Error(formatErrorMessage(value));
|
||||
}
|
||||
|
||||
type QaSuiteArtifactErrorCode =
|
||||
| "evidence_missing"
|
||||
| "report_missing"
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
writeQaRequestBodyLimitError,
|
||||
} from "./bus-server.js";
|
||||
import { createQaBusState, type QaBusState } from "./bus-state.js";
|
||||
import { toQaError } from "./errors.js";
|
||||
import {
|
||||
QaEvidenceGalleryError,
|
||||
buildQaEvidenceGalleryModel,
|
||||
@@ -220,10 +221,6 @@ function createQaLabConfig(baseUrl: string): OpenClawConfig {
|
||||
return createQaChannelGatewayConfig({ baseUrl });
|
||||
}
|
||||
|
||||
function normalizeQaLabCleanupError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(formatErrorMessage(error));
|
||||
}
|
||||
|
||||
function detectQaEvidenceArtifactContentType(filePath: string): string {
|
||||
const lower = filePath.toLowerCase();
|
||||
if (lower.endsWith(".png")) {
|
||||
@@ -589,7 +586,7 @@ export async function startQaLabServer(
|
||||
return;
|
||||
}
|
||||
fs.createReadStream(artifactFile)
|
||||
.on("error", (error) => res.destroy(normalizeQaLabCleanupError(error)))
|
||||
.on("error", (error) => res.destroy(toQaError(error)))
|
||||
.pipe(res);
|
||||
return;
|
||||
}
|
||||
@@ -957,14 +954,14 @@ export async function startQaLabServer(
|
||||
try {
|
||||
await gateway?.stop();
|
||||
} catch (error) {
|
||||
cleanupError = normalizeQaLabCleanupError(error);
|
||||
cleanupError = toQaError(error);
|
||||
}
|
||||
const results = await Promise.allSettled([
|
||||
Promise.resolve().then(() => (serverListening ? closeQaHttpServer(server) : undefined)),
|
||||
Promise.resolve().then(releaseCaptureStore),
|
||||
]);
|
||||
const failed = results.find((result) => result.status === "rejected");
|
||||
return cleanupError ?? (failed ? normalizeQaLabCleanupError(failed.reason) : undefined);
|
||||
return cleanupError ?? (failed ? toQaError(failed.reason) : undefined);
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -3,8 +3,8 @@ import type {
|
||||
WhatsAppQaDriverObservedMessage,
|
||||
WhatsAppQaDriverSession,
|
||||
} from "@openclaw/whatsapp/api.js";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import type { startQaGatewayChild } from "../../gateway-child.js";
|
||||
export { toQaError as toWhatsAppQaError } from "../../errors.js";
|
||||
|
||||
export type WhatsAppQaRuntimeEnv = {
|
||||
driverAuthArchiveBase64: string;
|
||||
@@ -19,10 +19,6 @@ export type WhatsAppQaApprovalDecision = "allow-once" | "deny";
|
||||
type WhatsAppQaApprovalDecisionMode = "reaction" | "rpc";
|
||||
type WhatsAppQaScenarioPosture = "direct-gateway" | "native-approval" | "user-path";
|
||||
|
||||
export function toWhatsAppQaError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(formatErrorMessage(error));
|
||||
}
|
||||
|
||||
type WhatsAppQaMessageSendMode =
|
||||
| {
|
||||
kind?: "text";
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from "node:path";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { pathExists } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { ensureRepoBoundDirectory, resolveRepoRelativeOutputDir } from "../cli-paths.js";
|
||||
import { toQaError } from "../errors.js";
|
||||
import {
|
||||
acquireQaCredentialLease,
|
||||
startQaCredentialLeaseHeartbeat,
|
||||
@@ -1464,7 +1465,7 @@ export async function runMantisSlackDesktopSmoke(
|
||||
timer.updatePhaseStatus("crabbox.remote_run", "accepted");
|
||||
}
|
||||
if (remoteRunError && !gatewaySetupCompleted && !slackQaCompleted) {
|
||||
throw toMantisError(remoteRunError);
|
||||
throw toQaError(remoteRunError);
|
||||
}
|
||||
if (gatewaySetup && !gatewaySetupCompleted) {
|
||||
throw new Error("Slack desktop gateway setup did not report a live OpenClaw gateway.");
|
||||
@@ -1580,7 +1581,4 @@ export async function runMantisSlackDesktopSmoke(
|
||||
}
|
||||
}
|
||||
|
||||
function toMantisError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(formatErrorMessage(error));
|
||||
}
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from "node:path";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { pathExists } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { ensureRepoBoundDirectory, resolveRepoRelativeOutputDir } from "../cli-paths.js";
|
||||
import { toQaError } from "../errors.js";
|
||||
import {
|
||||
acquireQaCredentialLease,
|
||||
startQaCredentialLeaseHeartbeat,
|
||||
@@ -727,7 +728,7 @@ export async function runMantisTelegramDesktopBuilder(
|
||||
timer.updatePhaseStatus("crabbox.remote_run", "accepted");
|
||||
}
|
||||
if (remoteRunError && !gatewaySetupCompleted) {
|
||||
throw toMantisError(remoteRunError);
|
||||
throw toQaError(remoteRunError);
|
||||
}
|
||||
if (gatewaySetup && !gatewaySetupCompleted) {
|
||||
throw new Error("Telegram desktop builder did not report a live OpenClaw gateway.");
|
||||
@@ -832,7 +833,4 @@ export async function runMantisTelegramDesktopBuilder(
|
||||
}
|
||||
}
|
||||
|
||||
function toMantisError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(formatErrorMessage(error));
|
||||
}
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
||||
@@ -4,6 +4,7 @@ import path from "node:path";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { pathExists, writeExternalFileWithinRoot } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { ensureRepoBoundDirectory, resolveRepoRelativeOutputDir } from "../cli-paths.js";
|
||||
import { toQaError } from "../errors.js";
|
||||
import { isTruthyOptIn, trimToValue } from "../mantis-options.runtime.js";
|
||||
import {
|
||||
type CommandRunner,
|
||||
@@ -229,14 +230,10 @@ async function runCommandWithExternalOutput(params: {
|
||||
},
|
||||
});
|
||||
if (deferredError) {
|
||||
throw toMantisError(deferredError);
|
||||
throw toQaError(deferredError);
|
||||
}
|
||||
}
|
||||
|
||||
function toMantisError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(formatErrorMessage(error));
|
||||
}
|
||||
|
||||
function buildVisualDriverArgs(params: {
|
||||
browserUrl: string;
|
||||
crabboxBin: string;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { setTimeout as sleep } from "node:timers/promises";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { toQaError } from "./errors.js";
|
||||
import { startQaGatewayChild } from "./gateway-child.js";
|
||||
import { startQaLabServer } from "./lab-server.js";
|
||||
import { resolveQaLiveTurnTimeoutMs } from "./live-timeout.js";
|
||||
@@ -31,10 +32,6 @@ type ManualLaneResult = {
|
||||
watchUrl: string;
|
||||
};
|
||||
|
||||
function normalizeManualLaneCleanupError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(formatErrorMessage(error));
|
||||
}
|
||||
|
||||
async function stopManualLaneResource(
|
||||
resource: { stop: () => Promise<void> | void } | null | undefined,
|
||||
): Promise<Error | undefined> {
|
||||
@@ -45,7 +42,7 @@ async function stopManualLaneResource(
|
||||
await resource.stop();
|
||||
return undefined;
|
||||
} catch (error) {
|
||||
return normalizeManualLaneCleanupError(error);
|
||||
return toQaError(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +55,7 @@ async function stopManualLaneAuxiliaryResources(resources: {
|
||||
.map((resource) => Promise.resolve().then(() => resource.stop()));
|
||||
const results = await Promise.allSettled(stopTasks);
|
||||
const failed = results.find((result) => result.status === "rejected");
|
||||
return failed ? normalizeManualLaneCleanupError(failed.reason) : undefined;
|
||||
return failed ? toQaError(failed.reason) : undefined;
|
||||
}
|
||||
|
||||
function resolveManualLaneTimeoutMs(params: {
|
||||
@@ -188,13 +185,13 @@ export async function runQaManualLane(params: QaManualLaneParams) {
|
||||
} finally {
|
||||
let transportCleanupBeforeError: Error | undefined;
|
||||
await transportCleanupBeforeGatewayStop?.().catch((error: unknown) => {
|
||||
transportCleanupBeforeError = normalizeManualLaneCleanupError(error);
|
||||
transportCleanupBeforeError = toQaError(error);
|
||||
});
|
||||
const gatewayCleanupError = await stopManualLaneResource(gateway);
|
||||
let transportCleanupAfterError: Error | undefined;
|
||||
if (!gatewayCleanupError) {
|
||||
await transportCleanupAfterGatewayStop?.().catch((error: unknown) => {
|
||||
transportCleanupAfterError = normalizeManualLaneCleanupError(error);
|
||||
transportCleanupAfterError = toQaError(error);
|
||||
});
|
||||
}
|
||||
const auxiliaryCleanupError = await stopManualLaneAuxiliaryResources({ lab, mock });
|
||||
|
||||
@@ -4,6 +4,7 @@ import fs from "node:fs";
|
||||
import { access, mkdir, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { OpenClawCrablineChannelDriverSelection } from "@openclaw/crabline";
|
||||
import { coerceErrorMessage, toStringifiedError } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { runExec } from "openclaw/plugin-sdk/process-runtime";
|
||||
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { appendRegularFile } from "openclaw/plugin-sdk/security-runtime";
|
||||
@@ -481,14 +482,14 @@ async function waitForGuestReady(logPath: string, vmName: string) {
|
||||
lastError = error;
|
||||
await appendMultipassLog(
|
||||
logPath,
|
||||
`guest-ready retry ${attempt}/12: ${error instanceof Error ? error.message : String(error)}\n\n`,
|
||||
`guest-ready retry ${attempt}/12: ${coerceErrorMessage(error)}\n\n`,
|
||||
);
|
||||
if (attempt < 12) {
|
||||
await sleep(2_000);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
||||
throw toStringifiedError(lastError);
|
||||
}
|
||||
|
||||
async function mountRepo(logPath: string, repoRoot: string, vmName: string) {
|
||||
@@ -505,14 +506,14 @@ async function mountRepo(logPath: string, repoRoot: string, vmName: string) {
|
||||
lastError = error;
|
||||
await appendMultipassLog(
|
||||
logPath,
|
||||
`mount retry ${attempt}/5: ${error instanceof Error ? error.message : String(error)}\n\n`,
|
||||
`mount retry ${attempt}/5: ${coerceErrorMessage(error)}\n\n`,
|
||||
);
|
||||
if (attempt < 5) {
|
||||
await sleep(2_000);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
||||
throw toStringifiedError(lastError);
|
||||
}
|
||||
|
||||
async function mountCodexHome(logPath: string, hostCodexHomePath: string, vmName: string) {
|
||||
@@ -529,14 +530,14 @@ async function mountCodexHome(logPath: string, hostCodexHomePath: string, vmName
|
||||
lastError = error;
|
||||
await appendMultipassLog(
|
||||
logPath,
|
||||
`codex-home mount retry ${attempt}/5: ${error instanceof Error ? error.message : String(error)}\n\n`,
|
||||
`codex-home mount retry ${attempt}/5: ${coerceErrorMessage(error)}\n\n`,
|
||||
);
|
||||
if (attempt < 5) {
|
||||
await sleep(2_000);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
||||
throw toStringifiedError(lastError);
|
||||
}
|
||||
|
||||
async function transferLiveProviderConfig(plan: QaMultipassPlan) {
|
||||
@@ -560,7 +561,7 @@ async function tryCopyGuestBootstrapLog(plan: QaMultipassPlan) {
|
||||
} catch (error) {
|
||||
await appendMultipassLog(
|
||||
plan.hostLogPath,
|
||||
`bootstrap log transfer skipped: ${error instanceof Error ? error.message : String(error)}\n\n`,
|
||||
`bootstrap log transfer skipped: ${coerceErrorMessage(error)}\n\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -605,10 +606,9 @@ export async function runQaMultipass(params: {
|
||||
await execFileAsync("multipass", ["version"]);
|
||||
} catch (error) {
|
||||
if ((error as ExecFileError).code !== "ENOENT") {
|
||||
throw new Error(
|
||||
`Unable to verify Multipass availability: ${error instanceof Error ? error.message : String(error)}.`,
|
||||
{ cause: error },
|
||||
);
|
||||
throw new Error(`Unable to verify Multipass availability: ${coerceErrorMessage(error)}.`, {
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
throw new Error(
|
||||
`Multipass is not installed on this host. Install it with '${resolveMultipassInstallHint()}', then rerun 'pnpm openclaw qa suite --runner multipass'.`,
|
||||
@@ -668,7 +668,7 @@ export async function runQaMultipass(params: {
|
||||
await tryCopyGuestBootstrapLog(plan);
|
||||
}
|
||||
throw new Error(
|
||||
`QA Multipass run failed: ${error instanceof Error ? error.message : String(error)}. See ${plan.hostLogPath}.`,
|
||||
`QA Multipass run failed: ${coerceErrorMessage(error)}. See ${plan.hostLogPath}.`,
|
||||
{ cause: error },
|
||||
);
|
||||
} finally {
|
||||
@@ -679,7 +679,7 @@ export async function runQaMultipass(params: {
|
||||
} catch (error) {
|
||||
await appendMultipassLog(
|
||||
plan.hostLogPath,
|
||||
`cleanup error: ${error instanceof Error ? error.message : String(error)}\n\n`,
|
||||
`cleanup error: ${coerceErrorMessage(error)}\n\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import { parseBooleanValue } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
export function parseQaProgressBooleanEnv(value: string | undefined): boolean | undefined {
|
||||
return parseBooleanValue(value);
|
||||
}
|
||||
|
||||
export function sanitizeQaProgressValue(value: string): string {
|
||||
let normalized = "";
|
||||
for (const char of value) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { OpenClawCrablineChannelDriverSelection } from "@openclaw/crabline";
|
||||
import { parseBooleanValue } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { QaSuiteChannelDriverSelection } from "./crabline-artifacts.js";
|
||||
import type { QaProviderMode } from "./model-selection.js";
|
||||
import { parseQaProgressBooleanEnv as parseQaSuiteBooleanEnv } from "./progress-format.js";
|
||||
import type { QaTransportId } from "./qa-transport-registry.js";
|
||||
import type { QaTransportAdapter } from "./qa-transport.js";
|
||||
import type { RuntimeId } from "./runtime-parity.js";
|
||||
@@ -127,7 +127,7 @@ export function appendNodeOption(raw: string | undefined, option: string) {
|
||||
}
|
||||
|
||||
export function shouldCaptureGatewayHeapCheckpoints(env: NodeJS.ProcessEnv = process.env) {
|
||||
return parseQaSuiteBooleanEnv(env.OPENCLAW_QA_GATEWAY_HEAP_CHECKPOINTS) === true;
|
||||
return parseBooleanValue(env.OPENCLAW_QA_GATEWAY_HEAP_CHECKPOINTS) === true;
|
||||
}
|
||||
|
||||
export function buildQaGatewayHeapCheckpointRuntimeEnvPatch(
|
||||
|
||||
@@ -301,14 +301,6 @@ describe("qa suite", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("parses progress env booleans", () => {
|
||||
expect(qaSuiteProgressTesting.parseQaSuiteBooleanEnv("true")).toBe(true);
|
||||
expect(qaSuiteProgressTesting.parseQaSuiteBooleanEnv("on")).toBe(true);
|
||||
expect(qaSuiteProgressTesting.parseQaSuiteBooleanEnv("false")).toBe(false);
|
||||
expect(qaSuiteProgressTesting.parseQaSuiteBooleanEnv("off")).toBe(false);
|
||||
expect(qaSuiteProgressTesting.parseQaSuiteBooleanEnv("maybe")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("stops an owned lab when readiness never becomes healthy", async () => {
|
||||
const stop = vi.fn(async () => {});
|
||||
fetchWithSsrFGuardMock.mockResolvedValue({
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { parseBooleanValue } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { QaEvidenceTiming, QaEvidenceSummaryJson } from "./evidence-summary.js";
|
||||
import type { QaCliBackendAuthMode, QaGatewayChildCommand } from "./gateway-child.js";
|
||||
import { startQaGatewayChild } from "./gateway-child.js";
|
||||
@@ -14,10 +15,7 @@ import { discardIgnoredResponseBody } from "./ignored-response-body.js";
|
||||
import type { QaLabServerHandle, QaLabServerStartParams } from "./lab-server.types.js";
|
||||
import { resolveQaLiveTurnTimeoutMs } from "./live-timeout.js";
|
||||
import type { QaProviderMode } from "./model-selection.js";
|
||||
import {
|
||||
parseQaProgressBooleanEnv as parseQaSuiteBooleanEnv,
|
||||
sanitizeQaProgressValue as sanitizeQaSuiteProgressValue,
|
||||
} from "./progress-format.js";
|
||||
import { sanitizeQaProgressValue as sanitizeQaSuiteProgressValue } from "./progress-format.js";
|
||||
import type { QaThinkingLevel } from "./qa-gateway-config.js";
|
||||
import {
|
||||
createQaTransportAdapter,
|
||||
@@ -169,11 +167,11 @@ export type QaSuiteRunParams = {
|
||||
};
|
||||
|
||||
export function shouldLogQaSuiteProgress(env: NodeJS.ProcessEnv = process.env) {
|
||||
const override = parseQaSuiteBooleanEnv(env.OPENCLAW_QA_SUITE_PROGRESS);
|
||||
const override = parseBooleanValue(env.OPENCLAW_QA_SUITE_PROGRESS);
|
||||
if (override !== undefined) {
|
||||
return override;
|
||||
}
|
||||
return parseQaSuiteBooleanEnv(env.CI) === true;
|
||||
return parseBooleanValue(env.CI) === true;
|
||||
}
|
||||
|
||||
export function resolveQaSuiteTransportReadyTimeoutMs(
|
||||
@@ -606,7 +604,6 @@ export const qaSuiteProgressTesting = {
|
||||
createScenarioStepRunner: createQaSuiteScenarioStepRunner,
|
||||
formatQaSuiteRunStartProgress,
|
||||
mergeQaRuntimeEnvPatches,
|
||||
parseQaSuiteBooleanEnv,
|
||||
remapModelRefForForcedRuntime,
|
||||
runQaFlowSuiteCleanupPlan,
|
||||
runQaSuiteCleanupSteps,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
|
||||
import { buildOpenAICompatibleLiveModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
||||
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { applyQwenNativeStreamingUsageCompat } from "./api.js";
|
||||
import { buildQwenMediaUnderstandingProvider } from "./media-understanding-provider.js";
|
||||
import {
|
||||
@@ -43,10 +44,6 @@ const QWEN_TOKEN_PLAN_GLM_NO_MAX_THINKING_LEVEL_IDS = QWEN_TOKEN_PLAN_THINKING_L
|
||||
(id) => id !== "max",
|
||||
);
|
||||
|
||||
function normalizeProviderId(value: string): string {
|
||||
return value.trim().toLowerCase();
|
||||
}
|
||||
|
||||
function resolveConfiguredQwenBaseUrl(
|
||||
config: { models?: { providers?: Record<string, { baseUrl?: string } | undefined> } } | undefined,
|
||||
): string | undefined {
|
||||
@@ -55,7 +52,7 @@ function resolveConfiguredQwenBaseUrl(
|
||||
return undefined;
|
||||
}
|
||||
for (const [providerId, provider] of Object.entries(providers)) {
|
||||
const normalized = normalizeProviderId(providerId);
|
||||
const normalized = normalizeLowercaseStringOrEmpty(providerId);
|
||||
if (normalized !== PROVIDER_ID && normalized !== LEGACY_PROVIDER_ID) {
|
||||
continue;
|
||||
}
|
||||
@@ -75,7 +72,7 @@ function resolveConfiguredQwenTokenPlanBaseUrl(
|
||||
return undefined;
|
||||
}
|
||||
for (const [providerId, provider] of Object.entries(providers)) {
|
||||
const normalized = normalizeProviderId(providerId);
|
||||
const normalized = normalizeLowercaseStringOrEmpty(providerId);
|
||||
if (normalized !== QWEN_TOKEN_PLAN_PROVIDER_ID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* to keep the two modes cleanly isolated.
|
||||
*/
|
||||
|
||||
import { toErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { coerceErrorMessage, toErrorObject } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { resolveFetch } from "openclaw/plugin-sdk/fetch-runtime";
|
||||
import {
|
||||
detectMime,
|
||||
@@ -243,7 +243,7 @@ export async function containerCheck(
|
||||
return {
|
||||
ok: false,
|
||||
status: null,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
error: coerceErrorMessage(err),
|
||||
};
|
||||
} finally {
|
||||
await releaseUnreadResponseBody(res);
|
||||
@@ -279,7 +279,7 @@ function containerReceiveCheck(
|
||||
settle({
|
||||
ok: false,
|
||||
status: null,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
error: coerceErrorMessage(err),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -301,7 +301,7 @@ function containerReceiveCheck(
|
||||
settle({
|
||||
ok: false,
|
||||
status: null,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
error: coerceErrorMessage(err),
|
||||
});
|
||||
});
|
||||
ws.once("close", (code, reason) => {
|
||||
@@ -478,9 +478,7 @@ export async function streamContainerEvents(params: {
|
||||
try {
|
||||
ws = new WebSocket(wsUrl, { maxPayload: WS_MAX_PAYLOAD, handshakeTimeout: WS_HANDSHAKE_MS });
|
||||
} catch (err) {
|
||||
logError(
|
||||
`[signal-ws] failed to create WebSocket: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
logError(`[signal-ws] failed to create WebSocket: ${coerceErrorMessage(err)}`);
|
||||
reject(toErrorObject(err, "Non-Error rejection"));
|
||||
return;
|
||||
}
|
||||
@@ -504,20 +502,18 @@ export async function streamContainerEvents(params: {
|
||||
await params.onEvent(envelope);
|
||||
});
|
||||
void eventChain.catch((err: unknown) => {
|
||||
logError(
|
||||
`[signal-ws] receive handler failed: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
logError(`[signal-ws] receive handler failed: ${coerceErrorMessage(err)}`);
|
||||
rejectOnce(err);
|
||||
ws.close();
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
logError(`[signal-ws] parse error: ${err instanceof Error ? err.message : String(err)}`);
|
||||
logError(`[signal-ws] parse error: ${coerceErrorMessage(err)}`);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on("error", (err) => {
|
||||
logError(`[signal-ws] error: ${err instanceof Error ? err.message : String(err)}`);
|
||||
logError(`[signal-ws] error: ${coerceErrorMessage(err)}`);
|
||||
// Don't resolve here - the close event will fire next
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { normalizeAccountId } from "openclaw/plugin-sdk/account-resolution";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { z } from "zod";
|
||||
import { resolveSlackAccount } from "./accounts.js";
|
||||
import type { SlackAuthoredTextPlacement } from "./authored-text.js";
|
||||
@@ -609,11 +610,6 @@ type SlackFileThreadShare = {
|
||||
threadTs?: string;
|
||||
};
|
||||
|
||||
function normalizeSlackScopeValue(value: string | undefined): string | undefined {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function collectSlackDirectShareChannelIds(file: SlackFileInfoSummary): Set<string> {
|
||||
const ids = new Set<string>();
|
||||
for (const group of [file.channels, file.groups, file.ims]) {
|
||||
@@ -624,7 +620,7 @@ function collectSlackDirectShareChannelIds(file: SlackFileInfoSummary): Set<stri
|
||||
if (typeof entry !== "string") {
|
||||
continue;
|
||||
}
|
||||
const normalized = normalizeSlackScopeValue(entry);
|
||||
const normalized = normalizeOptionalString(entry);
|
||||
if (normalized) {
|
||||
ids.add(normalized);
|
||||
}
|
||||
@@ -648,7 +644,7 @@ function collectSlackSharedChannelIds(file: SlackFileInfoSummary): Set<string> {
|
||||
const ids = new Set<string>();
|
||||
for (const shareMap of collectSlackShareMaps(file)) {
|
||||
for (const channelId of Object.keys(shareMap)) {
|
||||
const normalized = normalizeSlackScopeValue(channelId);
|
||||
const normalized = normalizeOptionalString(channelId);
|
||||
if (normalized) {
|
||||
ids.add(normalized);
|
||||
}
|
||||
@@ -672,9 +668,9 @@ function collectSlackThreadShares(
|
||||
continue;
|
||||
}
|
||||
const entry = rawEntry as Record<string, unknown>;
|
||||
const ts = typeof entry.ts === "string" ? normalizeSlackScopeValue(entry.ts) : undefined;
|
||||
const ts = typeof entry.ts === "string" ? normalizeOptionalString(entry.ts) : undefined;
|
||||
const threadTs =
|
||||
typeof entry.thread_ts === "string" ? normalizeSlackScopeValue(entry.thread_ts) : undefined;
|
||||
typeof entry.thread_ts === "string" ? normalizeOptionalString(entry.thread_ts) : undefined;
|
||||
matches.push({ channelId, ts, threadTs });
|
||||
}
|
||||
}
|
||||
@@ -686,11 +682,11 @@ function hasSlackScopeMismatch(params: {
|
||||
channelId?: string;
|
||||
threadId?: string;
|
||||
}): boolean {
|
||||
const channelId = normalizeSlackScopeValue(params.channelId);
|
||||
const channelId = normalizeOptionalString(params.channelId);
|
||||
if (!channelId) {
|
||||
return false;
|
||||
}
|
||||
const threadId = normalizeSlackScopeValue(params.threadId);
|
||||
const threadId = normalizeOptionalString(params.threadId);
|
||||
|
||||
const directIds = collectSlackDirectShareChannelIds(params.file);
|
||||
const sharedIds = collectSlackSharedChannelIds(params.file);
|
||||
|
||||
@@ -10,22 +10,8 @@ import {
|
||||
normalizeOptionalAccountId,
|
||||
} from "openclaw/plugin-sdk/account-id";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
|
||||
const DEFAULT_AGENT_ID = "main";
|
||||
|
||||
function normalizeAgentId(value: string | undefined | null): string {
|
||||
const normalized = (value ?? "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_-]+/g, "-")
|
||||
.replace(/^-+/g, "")
|
||||
.replace(/-+$/g, "");
|
||||
return normalized || DEFAULT_AGENT_ID;
|
||||
}
|
||||
|
||||
function normalizeChannelId(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
||||
}
|
||||
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
function resolveDefaultAgentId(cfg: OpenClawConfig): string {
|
||||
const agents = Array.isArray(cfg.agents?.list) ? cfg.agents.list : [];
|
||||
@@ -44,7 +30,7 @@ function resolveBindingAccount(params: {
|
||||
agentId?: unknown;
|
||||
match?: { channel?: unknown; accountId?: unknown };
|
||||
};
|
||||
if (normalizeChannelId(binding.match?.channel) !== params.channelId) {
|
||||
if (normalizeLowercaseStringOrEmpty(binding.match?.channel) !== params.channelId) {
|
||||
return null;
|
||||
}
|
||||
const accountId = typeof binding.match?.accountId === "string" ? binding.match.accountId : "";
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
// Telegram helper module supports agent config behavior.
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
||||
|
||||
type ReasoningDefault = "on" | "stream" | "off";
|
||||
|
||||
const DEFAULT_AGENT_ID = "main";
|
||||
|
||||
function normalizeAgentId(value: string | undefined | null): string {
|
||||
const normalized = (value ?? "").trim().toLowerCase();
|
||||
return normalized || DEFAULT_AGENT_ID;
|
||||
}
|
||||
|
||||
export function resolveTelegramConfigReasoningDefault(
|
||||
cfg: OpenClawConfig,
|
||||
agentId: string,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Telegram plugin module implements sticker cache store behavior.
|
||||
import type { PluginStateSyncKeyedStore } from "openclaw/plugin-sdk/plugin-state-runtime";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { getTelegramRuntime } from "./runtime.js";
|
||||
import {
|
||||
normalizeCachedStickerForStore,
|
||||
@@ -20,10 +21,6 @@ function openStickerCacheStore(): TelegramStickerCacheStore {
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeStickerSearchText(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
||||
}
|
||||
|
||||
function readStickerCacheStore<T>(
|
||||
operation: string,
|
||||
read: (store: TelegramStickerCacheStore) => T,
|
||||
@@ -61,7 +58,7 @@ export function cacheSticker(sticker: CachedSticker): void {
|
||||
* Search cached stickers by text query (fuzzy match on description + emoji + setName).
|
||||
*/
|
||||
export function searchStickers(query: string, limit = 10): CachedSticker[] {
|
||||
const queryLower = normalizeStickerSearchText(query);
|
||||
const queryLower = normalizeLowercaseStringOrEmpty(query);
|
||||
const results: Array<{ sticker: CachedSticker; score: number }> = [];
|
||||
|
||||
for (const { value: sticker } of readStickerCacheStore(
|
||||
@@ -70,7 +67,7 @@ export function searchStickers(query: string, limit = 10): CachedSticker[] {
|
||||
[],
|
||||
)) {
|
||||
let score = 0;
|
||||
const descLower = normalizeStickerSearchText(sticker.description);
|
||||
const descLower = normalizeLowercaseStringOrEmpty(sticker.description);
|
||||
|
||||
// Exact substring match in description
|
||||
if (descLower.includes(queryLower)) {
|
||||
@@ -92,7 +89,7 @@ export function searchStickers(query: string, limit = 10): CachedSticker[] {
|
||||
}
|
||||
|
||||
// Set name match
|
||||
if (normalizeStickerSearchText(sticker.setName).includes(queryLower)) {
|
||||
if (normalizeLowercaseStringOrEmpty(sticker.setName).includes(queryLower)) {
|
||||
score += 3;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Whatsapp plugin module implements message line behavior.
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
||||
|
||||
export {
|
||||
formatInboundEnvelope,
|
||||
@@ -8,10 +9,6 @@ export {
|
||||
|
||||
type WhatsAppMessagePrefixConfig = OpenClawConfig;
|
||||
|
||||
function normalizeAgentId(agentId: string): string {
|
||||
return agentId.trim().toLowerCase() || "main";
|
||||
}
|
||||
|
||||
function resolveIdentityNamePrefix(
|
||||
cfg: WhatsAppMessagePrefixConfig,
|
||||
agentId: string,
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
import {
|
||||
buildLiveModelProviderConfig,
|
||||
getCachedLiveProviderModelRows,
|
||||
readLiveModelCatalogBooleanField,
|
||||
readLiveModelCatalogPositiveSafeIntegerField,
|
||||
readLiveModelCatalogStringField,
|
||||
type LiveModelCatalogFetchGuard,
|
||||
} from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
||||
import type {
|
||||
@@ -106,10 +109,7 @@ function withXaiOAuthAutoModel(
|
||||
}
|
||||
|
||||
function readXaiOAuthDefaultModelId(value: unknown): string | undefined {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
return readLiveModelString(value, "default_model");
|
||||
return readLiveModelCatalogStringField(value, "default_model");
|
||||
}
|
||||
|
||||
async function fetchXaiOAuthDefaultModelId(params: {
|
||||
@@ -171,36 +171,6 @@ export async function buildLiveXaiProvider(params: {
|
||||
});
|
||||
}
|
||||
|
||||
function readLiveModelString(row: unknown, key: string): string | undefined {
|
||||
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
||||
return undefined;
|
||||
}
|
||||
const value = (row as Record<string, unknown>)[key];
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
function readLiveModelPositiveInteger(row: unknown, keys: readonly string[]): number | undefined {
|
||||
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
||||
return undefined;
|
||||
}
|
||||
const record = row as Record<string, unknown>;
|
||||
for (const key of keys) {
|
||||
const value = record[key];
|
||||
if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function readLiveModelBoolean(row: unknown, key: string): boolean | undefined {
|
||||
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
||||
return undefined;
|
||||
}
|
||||
const value = (row as Record<string, unknown>)[key];
|
||||
return typeof value === "boolean" ? value : undefined;
|
||||
}
|
||||
|
||||
function resolveXaiOauthMetadataFallback(modelId: string) {
|
||||
if (modelId === "grok-build") {
|
||||
return resolveXaiCatalogEntry("grok-build-0.1");
|
||||
@@ -209,14 +179,15 @@ function resolveXaiOauthMetadataFallback(modelId: string) {
|
||||
}
|
||||
|
||||
function isXaiOAuthResponsesModel(row: unknown, fallback: ModelDefinitionConfig | undefined) {
|
||||
const modelId = readLiveModelString(row, "id") ?? readLiveModelString(row, "model");
|
||||
const modelId =
|
||||
readLiveModelCatalogStringField(row, "id") ?? readLiveModelCatalogStringField(row, "model");
|
||||
if (modelId && (XAI_IMAGE_MODELS as readonly string[]).includes(modelId)) {
|
||||
return false;
|
||||
}
|
||||
const backend =
|
||||
readLiveModelString(row, "api_backend") ??
|
||||
readLiveModelString(row, "apiBackend") ??
|
||||
readLiveModelString(row, "backend");
|
||||
readLiveModelCatalogStringField(row, "api_backend") ??
|
||||
readLiveModelCatalogStringField(row, "apiBackend") ??
|
||||
readLiveModelCatalogStringField(row, "backend");
|
||||
if (backend) {
|
||||
const normalizedBackend = backend.toLowerCase();
|
||||
return (
|
||||
@@ -229,7 +200,8 @@ function isXaiOAuthResponsesModel(row: unknown, fallback: ModelDefinitionConfig
|
||||
}
|
||||
|
||||
function buildXaiOauthModelFromLiveRow(row: unknown): ModelDefinitionConfig | undefined {
|
||||
const modelId = readLiveModelString(row, "id") ?? readLiveModelString(row, "model");
|
||||
const modelId =
|
||||
readLiveModelCatalogStringField(row, "id") ?? readLiveModelCatalogStringField(row, "model");
|
||||
if (!modelId) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -238,16 +210,19 @@ function buildXaiOauthModelFromLiveRow(row: unknown): ModelDefinitionConfig | un
|
||||
return undefined;
|
||||
}
|
||||
const contextWindow =
|
||||
readLiveModelPositiveInteger(row, ["context_window", "contextWindow"]) ??
|
||||
readLiveModelCatalogPositiveSafeIntegerField(row, ["context_window", "contextWindow"]) ??
|
||||
fallback?.contextWindow ??
|
||||
XAI_DEFAULT_CONTEXT_WINDOW;
|
||||
const maxTokens =
|
||||
readLiveModelPositiveInteger(row, ["max_completion_tokens", "maxCompletionTokens"]) ??
|
||||
readLiveModelCatalogPositiveSafeIntegerField(row, [
|
||||
"max_completion_tokens",
|
||||
"maxCompletionTokens",
|
||||
]) ??
|
||||
fallback?.maxTokens ??
|
||||
XAI_DEFAULT_MAX_TOKENS;
|
||||
const supportsReasoningEffort =
|
||||
readLiveModelBoolean(row, "supports_reasoning_effort") ??
|
||||
readLiveModelBoolean(row, "supportsReasoningEffort");
|
||||
readLiveModelCatalogBooleanField(row, "supports_reasoning_effort") ??
|
||||
readLiveModelCatalogBooleanField(row, "supportsReasoningEffort");
|
||||
const reasoning =
|
||||
supportsReasoningEffort === true ||
|
||||
fallback?.reasoning === true ||
|
||||
@@ -255,7 +230,7 @@ function buildXaiOauthModelFromLiveRow(row: unknown): ModelDefinitionConfig | un
|
||||
|
||||
return {
|
||||
id: modelId,
|
||||
name: readLiveModelString(row, "name") ?? fallback?.name ?? modelId,
|
||||
name: readLiveModelCatalogStringField(row, "name") ?? fallback?.name ?? modelId,
|
||||
api: "openai-responses",
|
||||
baseUrl: XAI_GROK_OAUTH_BASE_URL,
|
||||
reasoning,
|
||||
|
||||
+1
-2
@@ -1666,7 +1666,6 @@
|
||||
"lint:extensions": "node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.extensions.json extensions",
|
||||
"lint:extensions:no-guarded-wildcard-reexports": "node --import tsx scripts/check-extension-wildcard-reexports.mts",
|
||||
"lint:extensions:no-normalization-core-bypass": "node --import tsx scripts/check-extension-plugin-sdk-boundary.mts --mode=normalization-core-bypass",
|
||||
"lint:extensions:no-plugin-sdk-internal": "node --import tsx scripts/check-extension-plugin-sdk-boundary.mts --mode=plugin-sdk-internal",
|
||||
"lint:extensions:no-plugin-sdk-wildcard-reexports": "node --import tsx scripts/check-plugin-sdk-wildcard-reexports.mts",
|
||||
"lint:extensions:no-relative-outside-package": "node --import tsx scripts/check-extension-plugin-sdk-boundary.mts --mode=relative-outside-package",
|
||||
"lint:extensions:no-src-outside-plugin-sdk": "node --import tsx scripts/check-extension-plugin-sdk-boundary.mts --mode=src-outside-plugin-sdk",
|
||||
@@ -1718,7 +1717,7 @@
|
||||
"plugin-sdk:usage": "node --max-old-space-size=8192 --import tsx scripts/analyze-plugin-sdk-usage.ts",
|
||||
"policy:config-coverage": "node --import tsx scripts/check-policy-config-coverage.ts",
|
||||
"plugins:boundary-report": "node --import tsx scripts/plugin-boundary-report.ts",
|
||||
"plugins:boundary-report:ci": "node --import tsx scripts/plugin-boundary-report.ts --summary --fail-on-cross-owner --fail-on-unclassified-unused-reserved --fail-on-eligible-compat",
|
||||
"plugins:boundary-report:ci": "node --import tsx scripts/plugin-boundary-report.ts --summary --fail-on-eligible-compat",
|
||||
"plugins:boundary-report:json": "node --import tsx scripts/plugin-boundary-report.ts --json",
|
||||
"plugins:boundary-report:summary": "node --import tsx scripts/plugin-boundary-report.ts --summary",
|
||||
"plugins:assets:build": "node --import tsx scripts/bundled-plugin-assets.mts --phase build",
|
||||
|
||||
@@ -154,7 +154,10 @@ export { parseAgentSessionKey } from "../../../../src/routing/session-key.js";
|
||||
export { hasInterSessionUserProvenance } from "../../../../src/sessions/input-provenance.js";
|
||||
export { isCronRunSessionKey } from "../../../../src/sessions/session-key-utils.js";
|
||||
export { onSessionTranscriptUpdate } from "../../../../src/sessions/transcript-events.js";
|
||||
export { CHARS_PER_TOKEN_ESTIMATE, estimateStringChars } from "../../../../src/utils/cjk-chars.js";
|
||||
export {
|
||||
CHARS_PER_TOKEN_ESTIMATE,
|
||||
estimateStringChars,
|
||||
} from "@openclaw/normalization-core/cjk-chars";
|
||||
export { runTasksWithConcurrency } from "../../../../src/utils/run-with-concurrency.js";
|
||||
export { splitShellArgs } from "../../../../src/utils/shell-argv.js";
|
||||
export {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Model Catalog Core module implements model catalog refs behavior.
|
||||
import { normalizeLowercaseStringOrEmpty } from "./provider-id.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
|
||||
|
||||
// Stable model catalog ref and merge-key builders.
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Model Catalog Core module implements provider id behavior.
|
||||
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
|
||||
|
||||
export { normalizeLowercaseStringOrEmpty };
|
||||
|
||||
export function normalizeProviderId(provider: string): string {
|
||||
return normalizeLowercaseStringOrEmpty(provider);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Model Catalog Core module implements provider model id normalization behavior.
|
||||
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
|
||||
import { parseModelCatalogRef } from "./model-catalog-refs.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "./provider-id.js";
|
||||
import {
|
||||
normalizeGooglePreviewModelId,
|
||||
normalizeTogetherModelId,
|
||||
|
||||
@@ -39,16 +39,21 @@
|
||||
"import": "./dist/expect.mjs",
|
||||
"default": "./dist/expect.mjs"
|
||||
},
|
||||
"./number-coercion": {
|
||||
"types": "./dist/number-coercion.d.mts",
|
||||
"import": "./dist/number-coercion.mjs",
|
||||
"default": "./dist/number-coercion.mjs"
|
||||
"./json-coercion": {
|
||||
"types": "./dist/json-coercion.d.mts",
|
||||
"import": "./dist/json-coercion.mjs",
|
||||
"default": "./dist/json-coercion.mjs"
|
||||
},
|
||||
"./json-schema": {
|
||||
"types": "./dist/json-schema.d.mts",
|
||||
"import": "./dist/json-schema.mjs",
|
||||
"default": "./dist/json-schema.mjs"
|
||||
},
|
||||
"./number-coercion": {
|
||||
"types": "./dist/number-coercion.d.mts",
|
||||
"import": "./dist/number-coercion.mjs",
|
||||
"default": "./dist/number-coercion.mjs"
|
||||
},
|
||||
"./phone-presentation": {
|
||||
"types": "./dist/phone-presentation.d.mts",
|
||||
"import": "./dist/phone-presentation.mjs",
|
||||
@@ -96,7 +101,7 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown src/index.ts src/agent-id.ts src/boolean-coercion.ts src/cjk-chars.ts src/error-coercion.ts src/expect.ts src/json-schema.ts src/number-coercion.ts src/phone-presentation.ts src/promise-like.ts src/record-coerce.ts src/result.ts src/stable-node-path.ts src/stable-stringify.ts src/string-coerce.ts src/string-normalization.ts src/utf16-slice.ts --no-config --platform node --format esm --dts --out-dir dist --clean"
|
||||
"build": "tsdown src/index.ts src/agent-id.ts src/boolean-coercion.ts src/cjk-chars.ts src/error-coercion.ts src/expect.ts src/json-coercion.ts src/json-schema.ts src/number-coercion.ts src/phone-presentation.ts src/promise-like.ts src/record-coerce.ts src/result.ts src/stable-node-path.ts src/stable-stringify.ts src/string-coerce.ts src/string-normalization.ts src/utf16-slice.ts --no-config --platform node --format esm --dts --out-dir dist --clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"libphonenumber-js": "1.13.9",
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import {
|
||||
safeParseJson as safeParseJsonFromRoot,
|
||||
safeParseJsonRecord as safeParseJsonRecordFromRoot,
|
||||
} from "@openclaw/normalization-core";
|
||||
import { safeParseJson, safeParseJsonRecord } from "@openclaw/normalization-core/json-coercion";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { safeParseJson, safeParseJsonRecord } from "./json-coercion.js";
|
||||
|
||||
describe("json-coercion", () => {
|
||||
it("preserves the root exports alongside the focused package subpath", () => {
|
||||
expect(safeParseJsonFromRoot).toBe(safeParseJson);
|
||||
expect(safeParseJsonRecordFromRoot).toBe(safeParseJsonRecord);
|
||||
});
|
||||
|
||||
it.each<[string, unknown]>([
|
||||
['{"ok":true}', { ok: true }],
|
||||
["[1]", [1]],
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import fs from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
type PackageManifest = {
|
||||
exports: Record<
|
||||
string,
|
||||
{
|
||||
default: string;
|
||||
import: string;
|
||||
types: string;
|
||||
}
|
||||
>;
|
||||
scripts: { build: string };
|
||||
};
|
||||
|
||||
const packageJsonPath = fileURLToPath(new URL("../package.json", import.meta.url));
|
||||
const manifest = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as PackageManifest;
|
||||
|
||||
describe("normalization-core package exports", () => {
|
||||
it("builds every focused export from its matching source entry", () => {
|
||||
for (const [subpath, target] of Object.entries(manifest.exports)) {
|
||||
const entryName = subpath === "." ? "index" : subpath.slice(2);
|
||||
expect(target).toEqual({
|
||||
types: `./dist/${entryName}.d.mts`,
|
||||
import: `./dist/${entryName}.mjs`,
|
||||
default: `./dist/${entryName}.mjs`,
|
||||
});
|
||||
expect(manifest.scripts.build.split(/\s+/u)).toContain(`src/${entryName}.ts`);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -9,6 +9,17 @@ const sanitizeSurrogates = (text: string) =>
|
||||
text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
|
||||
|
||||
describe("stableStringify", () => {
|
||||
it.each([
|
||||
['{"z":1,"a":2}', '{"a":2,"z":1}'],
|
||||
[
|
||||
'{"items":[3,null,{"z":false,"a":1.5}],"enabled":true}',
|
||||
'{"enabled":true,"items":[3,null,{"a":1.5,"z":false}]}',
|
||||
],
|
||||
['["text",0,-2.5,null,false]', '["text",0,-2.5,null,false]'],
|
||||
])("preserves deterministic bytes for parsed JSON %#", (json, expected) => {
|
||||
expect(stableStringify(JSON.parse(json))).toBe(expected);
|
||||
});
|
||||
|
||||
it("sorts object keys recursively", () => {
|
||||
expect(stableStringify({ b: { d: 4, c: 3 }, a: 1 })).toBe('{"a":1,"b":{"c":3,"d":4}}');
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Terminal Core module implements health style behavior.
|
||||
import { normalizeLowercaseStringOrEmpty } from "./string.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
|
||||
import { theme } from "./theme.js";
|
||||
|
||||
// Styles the status word in health output lines.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Terminal Core module implements note behavior.
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
import { note as clackNote } from "@clack/prompts";
|
||||
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
|
||||
import { splitGraphemes, visibleWidth } from "./ansi.js";
|
||||
import { stylePromptTitle } from "./prompt-style.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "./string.js";
|
||||
|
||||
const MIN_NOTE_COLUMNS = 80;
|
||||
const URL_PREFIX_RE = /^(https?:\/\/|file:\/\/)/i;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
// Shared terminal string normalization helpers.
|
||||
|
||||
export { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
|
||||
@@ -26,7 +26,7 @@ scenario:
|
||||
- scripts/lib/plugin-sdk-entrypoints.json
|
||||
- scripts/lib/plugin-sdk-private-local-only-subpaths.json
|
||||
- scripts/lib/plugin-sdk-deprecated-public-subpaths.json
|
||||
- src/plugin-sdk/entrypoints.ts
|
||||
- scripts/lib/plugin-sdk-entries.mts
|
||||
- src/plugins/contracts/plugin-sdk-subpaths.test.ts
|
||||
execution:
|
||||
kind: vitest
|
||||
|
||||
+7
-14
@@ -9,6 +9,7 @@ import { performance } from "node:perf_hooks";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { asRecord } from "@openclaw/normalization-core/record-coerce";
|
||||
import prettyMilliseconds from "pretty-ms";
|
||||
import { resolveBuildIdentityEnvironment } from "./lib/build-identity.mts";
|
||||
import {
|
||||
listPluginSdkDeclarationOutputs,
|
||||
pluginSdkEntrypoints,
|
||||
@@ -59,7 +60,6 @@ type BuildAllStepParams = {
|
||||
comSpec?: string;
|
||||
};
|
||||
type BuildAllCacheParams = { rootDir?: string; fs?: BuildAllFs; env?: NodeJS.ProcessEnv };
|
||||
const FULL_GIT_COMMIT_RE = /^[0-9a-f]{40}$/iu;
|
||||
const BUILD_CACHE_VERSION = 4;
|
||||
const TSDOWN_DECLARATION_EXTENSIONS = [".d.ts", ".d.mts", ".d.cts"];
|
||||
const TSDOWN_SOURCE_EXTENSIONS = [
|
||||
@@ -491,19 +491,12 @@ export function resolveBuildAllEnvironment(
|
||||
now: () => Date = () => new Date(),
|
||||
readGitCommit: () => string | null = readCurrentGitCommit,
|
||||
) {
|
||||
const explicitTimestamp = env.OPENCLAW_BUILD_TIMESTAMP?.trim();
|
||||
const explicitCommit = env.GIT_COMMIT?.trim() || env.GIT_SHA?.trim();
|
||||
const checkedOutCommit = explicitCommit ? null : readGitCommit()?.trim();
|
||||
// GITHUB_SHA names the workflow invocation and can differ from a checked-out tag.
|
||||
const commit = explicitCommit || checkedOutCommit || env.GITHUB_SHA?.trim();
|
||||
if (commit && !FULL_GIT_COMMIT_RE.test(commit)) {
|
||||
throw new Error("build commit must be a full 40-character hexadecimal SHA");
|
||||
}
|
||||
return {
|
||||
...env,
|
||||
OPENCLAW_BUILD_TIMESTAMP: explicitTimestamp || now().toISOString(),
|
||||
...(commit ? { GIT_COMMIT: commit.toLowerCase() } : {}),
|
||||
};
|
||||
return resolveBuildIdentityEnvironment({
|
||||
commitLabel: "build commit",
|
||||
env,
|
||||
now,
|
||||
readGitCommit,
|
||||
});
|
||||
}
|
||||
|
||||
function resolveStepEnv(step: BuildAllStep, env: NodeJS.ProcessEnv, platform: NodeJS.Platform) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
||||
import { stableStringify } from "../packages/normalization-core/src/stable-stringify.ts";
|
||||
import { booleanFlag, parseFlagArgs, stringFlag } from "./lib/arg-utils.mts";
|
||||
import { getChangedPathFacts, normalizeChangedPath } from "./lib/changed-path-facts.mjs";
|
||||
import { isDirectRunUrl } from "./lib/direct-run.mjs";
|
||||
@@ -448,8 +449,8 @@ export function isLiveDockerPackageScriptOnlyChange(before: string, after: strin
|
||||
const afterStripped = stripLiveDockerPackageScripts(afterPackage);
|
||||
|
||||
return (
|
||||
stableJson(beforeStripped) === stableJson(afterStripped) &&
|
||||
stableJson(beforeAllowed) !== stableJson(afterAllowed)
|
||||
stableStringify(beforeStripped) === stableStringify(afterStripped) &&
|
||||
stableStringify(beforeAllowed) !== stableStringify(afterAllowed)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -469,8 +470,8 @@ export function isPackageScriptOnlyChange(before: string, after: string): boolea
|
||||
const afterStripped = stripPackageScripts(afterPackage);
|
||||
|
||||
return (
|
||||
stableJson(beforeStripped) === stableJson(afterStripped) &&
|
||||
stableJson(beforeScripts) !== stableJson(afterScripts)
|
||||
stableStringify(beforeStripped) === stableStringify(afterStripped) &&
|
||||
stableStringify(beforeScripts) !== stableStringify(afterScripts)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -542,19 +543,6 @@ function stripPackageScripts(packageJson: Record<string, unknown>) {
|
||||
return clone;
|
||||
}
|
||||
|
||||
function stableJson(value: unknown): string {
|
||||
if (Array.isArray(value)) {
|
||||
return `[${value.map(stableJson).join(",")}]`;
|
||||
}
|
||||
if (isRecord(value)) {
|
||||
return `{${Object.keys(value)
|
||||
.toSorted((left, right) => left.localeCompare(right))
|
||||
.map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
|
||||
.join(",")}}`;
|
||||
}
|
||||
return JSON.stringify(value) ?? "undefined";
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes changed-lane booleans to the GitHub Actions output file.
|
||||
*/
|
||||
|
||||
+11
-11
@@ -20,7 +20,12 @@ import {
|
||||
listStagedChangedPaths,
|
||||
} from "./changed-lanes.mts";
|
||||
import type { ChangedLaneResult } from "./changed-lanes.mts";
|
||||
import { booleanFlag, parseFlagArgs, stringFlag } from "./lib/arg-utils.mts";
|
||||
import {
|
||||
booleanFlag,
|
||||
isOpenEndedTruthyValue,
|
||||
parseFlagArgs,
|
||||
stringFlag,
|
||||
} from "./lib/arg-utils.mts";
|
||||
import { getChangedPathFacts, normalizeChangedPath } from "./lib/changed-path-facts.mjs";
|
||||
import { printTimingSummary } from "./lib/check-timing-summary.mts";
|
||||
import { isDirectRunUrl } from "./lib/direct-run.mjs";
|
||||
@@ -157,11 +162,6 @@ export function createChangedCheckChildEnv(baseEnv: NodeJS.ProcessEnv = process.
|
||||
};
|
||||
}
|
||||
|
||||
function isTruthyEnvFlag(value: string | undefined) {
|
||||
const normalized = (value ?? "").trim().toLowerCase();
|
||||
return normalized !== "" && normalized !== "0" && normalized !== "false" && normalized !== "no";
|
||||
}
|
||||
|
||||
function hasAndroidVersionSyncPath(paths: string[]) {
|
||||
return paths.some((changedPath) =>
|
||||
ANDROID_VERSION_SYNC_PATHS.has(normalizeChangedPath(changedPath)),
|
||||
@@ -231,10 +231,10 @@ export function shouldDelegateChangedCheckToCrabbox(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
options: ChangedCheckDelegateOptions = {},
|
||||
) {
|
||||
if (isTruthyEnvFlag(env.OPENCLAW_CHECK_CHANGED_REMOTE_CHILD)) {
|
||||
if (isOpenEndedTruthyValue(env.OPENCLAW_CHECK_CHANGED_REMOTE_CHILD)) {
|
||||
return false;
|
||||
}
|
||||
if (isTruthyEnvFlag(env.CI) || isTruthyEnvFlag(env.GITHUB_ACTIONS)) {
|
||||
if (isOpenEndedTruthyValue(env.CI) || isOpenEndedTruthyValue(env.GITHUB_ACTIONS)) {
|
||||
return false;
|
||||
}
|
||||
if (argv.includes("--dry-run")) {
|
||||
@@ -247,7 +247,7 @@ export function shouldDelegateChangedCheckToCrabbox(
|
||||
if (result.paths.length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (isTruthyEnvFlag(env.OPENCLAW_TESTBOX)) {
|
||||
if (isOpenEndedTruthyValue(env.OPENCLAW_TESTBOX)) {
|
||||
return true;
|
||||
}
|
||||
// Release metadata plans diff the supplied commits after classification. A missing
|
||||
@@ -708,7 +708,7 @@ export function createChangedCheckPlan(
|
||||
add("package patch guard", ["deps:patches:check"]);
|
||||
if (
|
||||
hasDeadcodeScannedSource(result.paths) &&
|
||||
!isTruthyEnvFlag(baseEnv.OPENCLAW_CHECK_CHANGED_SKIP_DEADCODE)
|
||||
!isOpenEndedTruthyValue(baseEnv.OPENCLAW_CHECK_CHANGED_SKIP_DEADCODE)
|
||||
) {
|
||||
addCommand(
|
||||
"dead export scan (skip with OPENCLAW_CHECK_CHANGED_SKIP_DEADCODE=1)",
|
||||
@@ -1066,7 +1066,7 @@ export function createPnpmManagedCommand<T extends ChangedCheckCommand>(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
) {
|
||||
const commandEnv = command.env ?? resolveLocalHeavyCheckEnv(env);
|
||||
if (isTruthyEnvFlag(commandEnv.CI) || isTruthyEnvFlag(commandEnv.GITHUB_ACTIONS)) {
|
||||
if (isOpenEndedTruthyValue(commandEnv.CI) || isOpenEndedTruthyValue(commandEnv.GITHUB_ACTIONS)) {
|
||||
const shimmedEnv = prependCorepackPnpmShim(commandEnv);
|
||||
return {
|
||||
...command,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user