mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
fix(clawhub): accept live artifact resolver field aliases
Accept live ClawHub artifact resolver kind/sha256 aliases for npm-pack and legacy ZIP installs.\n\nVerified locally after rebase:\n- pnpm exec oxfmt --check --threads=1 src/plugins/clawhub.ts src/plugins/clawhub.test.ts CHANGELOG.md\n- pnpm test:serial src/plugins/clawhub.test.ts
This commit is contained in:
@@ -39,6 +39,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Memory/status: keep plain `openclaw memory status` and `openclaw memory status --json` on the cheap read-only path by reserving vector and embedding provider probes for `--deep` or `--index`. Fixes #76769. Thanks @daruire.
|
||||
- Telegram: suppress stale same-session replies when a newer accepted message arrives before an older in-flight Telegram dispatch finalizes. Fixes #76642. Thanks @chinar-amrutkar.
|
||||
- Gateway/diagnostics: abort-drain embedded runs after an extended no-progress stall so a single dead session no longer leaves queued Discord/channel turns blocked behind repeated `recovery=none` liveness warnings.
|
||||
- Plugins/ClawHub: accept the live artifact resolver `kind`/`sha256` field names alongside the typed `artifactKind`/`artifactSha256` form so `clawhub:` installs of npm-pack and legacy ZIP packages no longer miss downloadable artifacts. Thanks @romneyda.
|
||||
- Control UI/Sessions: avoid full `sessions.list` reloads for chat-turn `sessions.changed` payloads, so large session stores no longer add multi-second delays while chat responses are being delivered. (#76676) Thanks @VACInc.
|
||||
- Gateway/watch: run `doctor --fix --non-interactive` once and retry when the dev Gateway child exits during startup, so stale local plugin install/config state does not leave the tmux watch session disappearing without a repair attempt.
|
||||
- Doctor/Telegram: warn when selected Telegram quote replies can suppress `streaming.preview.toolProgress`, and document the `replyToMode` trade-off without changing runtime delivery. Fixes #73487. Thanks @GodsBoy.
|
||||
|
||||
@@ -51,10 +51,12 @@ vi.mock("../infra/archive.js", async () => {
|
||||
});
|
||||
|
||||
const { ClawHubRequestError } = await import("../infra/clawhub.js");
|
||||
type ClawHubResolvedArtifact = import("../infra/clawhub.js").ClawHubResolvedArtifact;
|
||||
const { CLAWHUB_INSTALL_ERROR_CODE, formatClawHubSpecifier, installPluginFromClawHub } =
|
||||
await import("./clawhub.js");
|
||||
|
||||
const DEMO_ARCHIVE_INTEGRITY = "sha256-qerEjGEpvES2+Tyan0j2xwDRkbcnmh4ZFfKN9vWbsa8=";
|
||||
const DEMO_ARCHIVE_SHA256 = "a9eac48c6129bc44b6f93c9a9f48f6c700d191b7279a1e1915f28df6f59bb1af";
|
||||
const DEMO_CLAWPACK_SHA256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
const DEMO_CLAWPACK_INTEGRITY = `sha256-${Buffer.from(DEMO_CLAWPACK_SHA256, "hex").toString(
|
||||
"base64",
|
||||
@@ -463,6 +465,102 @@ describe("installPluginFromClawHub", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts the live ClawHub artifact resolver shape with kind/sha256 field names", async () => {
|
||||
fetchClawHubPackageVersionMock.mockClear();
|
||||
fetchClawHubPackageArtifactMock.mockResolvedValueOnce({
|
||||
package: {
|
||||
name: "demo",
|
||||
displayName: "Demo",
|
||||
family: "code-plugin",
|
||||
},
|
||||
version: "2026.3.22",
|
||||
artifact: {
|
||||
kind: "npm-pack",
|
||||
sha256: DEMO_CLAWPACK_SHA256,
|
||||
npmIntegrity: "sha512-clawpack",
|
||||
npmShasum: "1".repeat(40),
|
||||
} as unknown as ClawHubResolvedArtifact,
|
||||
});
|
||||
downloadClawHubPackageArchiveMock.mockResolvedValueOnce({
|
||||
archivePath: "/tmp/clawhub-demo/demo-2026.3.22.tgz",
|
||||
integrity: DEMO_CLAWPACK_INTEGRITY,
|
||||
sha256Hex: DEMO_CLAWPACK_SHA256,
|
||||
artifact: "clawpack",
|
||||
clawpackHeaderSha256: DEMO_CLAWPACK_SHA256,
|
||||
npmIntegrity: "sha512-clawpack",
|
||||
npmShasum: "1".repeat(40),
|
||||
cleanup: archiveCleanupMock,
|
||||
});
|
||||
|
||||
const result = await installPluginFromClawHub({
|
||||
spec: "clawhub:demo",
|
||||
baseUrl: "https://clawhub.ai",
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
ok: true,
|
||||
clawhub: {
|
||||
artifactKind: "npm-pack",
|
||||
artifactFormat: "tgz",
|
||||
npmIntegrity: "sha512-clawpack",
|
||||
npmShasum: "1".repeat(40),
|
||||
clawpackSha256: DEMO_CLAWPACK_SHA256,
|
||||
},
|
||||
});
|
||||
expect(fetchClawHubPackageVersionMock).not.toHaveBeenCalled();
|
||||
expect(downloadClawHubPackageArchiveMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
artifact: "clawpack",
|
||||
name: "demo",
|
||||
version: "2026.3.22",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts the live ClawHub legacy zip resolver shape with kind/sha256 field names", async () => {
|
||||
fetchClawHubPackageVersionMock.mockClear();
|
||||
fetchClawHubPackageArtifactMock.mockResolvedValueOnce({
|
||||
package: {
|
||||
name: "demo",
|
||||
displayName: "Demo",
|
||||
family: "code-plugin",
|
||||
},
|
||||
version: "2026.3.22",
|
||||
artifact: {
|
||||
kind: "legacy-zip",
|
||||
sha256: DEMO_ARCHIVE_SHA256,
|
||||
} as unknown as ClawHubResolvedArtifact,
|
||||
});
|
||||
downloadClawHubPackageArchiveMock.mockResolvedValueOnce({
|
||||
archivePath: "/tmp/clawhub-demo/archive.zip",
|
||||
integrity: DEMO_ARCHIVE_INTEGRITY,
|
||||
cleanup: archiveCleanupMock,
|
||||
});
|
||||
|
||||
const result = await installPluginFromClawHub({
|
||||
spec: "clawhub:demo",
|
||||
baseUrl: "https://clawhub.ai",
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
ok: true,
|
||||
pluginId: "demo",
|
||||
clawhub: {
|
||||
artifactKind: "legacy-zip",
|
||||
artifactFormat: "zip",
|
||||
integrity: DEMO_ARCHIVE_INTEGRITY,
|
||||
},
|
||||
});
|
||||
expect(fetchClawHubPackageVersionMock).not.toHaveBeenCalled();
|
||||
expect(downloadClawHubPackageArchiveMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
artifact: "archive",
|
||||
name: "demo",
|
||||
version: "2026.3.22",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to version metadata when the ClawHub artifact resolver route is missing", async () => {
|
||||
fetchClawHubPackageArtifactMock.mockRejectedValueOnce(
|
||||
new ClawHubRequestError({
|
||||
|
||||
@@ -265,29 +265,47 @@ function normalizeArtifactResolverFiles(
|
||||
return files as NonNullable<ClawHubPackageVersion["version"]>["files"];
|
||||
}
|
||||
|
||||
type ClawHubResolvedArtifactWire = {
|
||||
artifactKind?: string | null;
|
||||
kind?: string | null;
|
||||
artifactSha256?: string | null;
|
||||
sha256?: string | null;
|
||||
npmIntegrity?: string | null;
|
||||
npmShasum?: string | null;
|
||||
downloadUrl?: string | null;
|
||||
};
|
||||
|
||||
function resolveTopLevelNpmPackArtifact(
|
||||
artifact: ClawHubResolvedArtifact | null | undefined,
|
||||
): ClawHubPackageArtifactSummary | null {
|
||||
if (artifact?.artifactKind !== "npm-pack") {
|
||||
const wire = artifact as ClawHubResolvedArtifactWire | null | undefined;
|
||||
const artifactKind = wire?.artifactKind ?? wire?.kind;
|
||||
if (artifactKind !== "npm-pack") {
|
||||
return null;
|
||||
}
|
||||
if (typeof wire?.npmIntegrity !== "string") {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
kind: "npm-pack",
|
||||
format: "tgz",
|
||||
sha256: artifact.artifactSha256 ?? null,
|
||||
npmIntegrity: artifact.npmIntegrity,
|
||||
npmShasum: artifact.npmShasum ?? null,
|
||||
downloadUrl: artifact.downloadUrl ?? null,
|
||||
sha256: wire.artifactSha256 ?? wire.sha256 ?? null,
|
||||
npmIntegrity: wire.npmIntegrity,
|
||||
npmShasum: wire.npmShasum ?? null,
|
||||
downloadUrl: wire.downloadUrl ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveTopLevelLegacyArchiveVerification(
|
||||
artifact: ClawHubResolvedArtifact | null | undefined,
|
||||
): ClawHubArchiveVerification | null {
|
||||
if (artifact?.artifactKind !== "legacy-zip" || typeof artifact.artifactSha256 !== "string") {
|
||||
const wire = artifact as ClawHubResolvedArtifactWire | null | undefined;
|
||||
const artifactKind = wire?.artifactKind ?? wire?.kind;
|
||||
const artifactSha256 = wire?.artifactSha256 ?? wire?.sha256;
|
||||
if (artifactKind !== "legacy-zip" || typeof artifactSha256 !== "string") {
|
||||
return null;
|
||||
}
|
||||
const integrity = normalizeClawHubSha256Integrity(artifact.artifactSha256);
|
||||
const integrity = normalizeClawHubSha256Integrity(artifactSha256);
|
||||
return integrity ? { kind: "archive-integrity", integrity } : null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user