@clawhive/openclaw-plugin 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +217 -0
  2. package/dist/api.d.ts +6 -0
  3. package/dist/api.js +6 -0
  4. package/dist/index.d.ts +4 -0
  5. package/dist/index.js +324 -0
  6. package/dist/runtime-api.d.ts +1 -0
  7. package/dist/runtime-api.js +1 -0
  8. package/dist/setup-entry.d.ts +4 -0
  9. package/dist/setup-entry.js +3 -0
  10. package/dist/src/agency-install/claimLoop.d.ts +41 -0
  11. package/dist/src/agency-install/claimLoop.js +188 -0
  12. package/dist/src/agent-panel/claimLoop.d.ts +32 -0
  13. package/dist/src/agent-panel/claimLoop.js +118 -0
  14. package/dist/src/agent-panel/executor.d.ts +8 -0
  15. package/dist/src/agent-panel/executor.js +368 -0
  16. package/dist/src/agent-panel/plan.d.ts +20 -0
  17. package/dist/src/agent-panel/plan.js +111 -0
  18. package/dist/src/agent-panel/prompts.d.ts +38 -0
  19. package/dist/src/agent-panel/prompts.js +87 -0
  20. package/dist/src/agent-panel/types.d.ts +45 -0
  21. package/dist/src/agent-panel/types.js +1 -0
  22. package/dist/src/agents.d.ts +44 -0
  23. package/dist/src/agents.js +195 -0
  24. package/dist/src/authorization.d.ts +34 -0
  25. package/dist/src/authorization.js +183 -0
  26. package/dist/src/channel.d.ts +5 -0
  27. package/dist/src/channel.js +93 -0
  28. package/dist/src/claimPolling.d.ts +9 -0
  29. package/dist/src/claimPolling.js +13 -0
  30. package/dist/src/client.d.ts +386 -0
  31. package/dist/src/client.js +595 -0
  32. package/dist/src/config.d.ts +28 -0
  33. package/dist/src/config.js +94 -0
  34. package/dist/src/defaults.d.ts +40 -0
  35. package/dist/src/defaults.js +52 -0
  36. package/dist/src/deviceCode.d.ts +16 -0
  37. package/dist/src/deviceCode.js +65 -0
  38. package/dist/src/heartbeat.d.ts +25 -0
  39. package/dist/src/heartbeat.js +65 -0
  40. package/dist/src/imageAttachments.d.ts +14 -0
  41. package/dist/src/imageAttachments.js +81 -0
  42. package/dist/src/inbound.d.ts +10 -0
  43. package/dist/src/inbound.js +140 -0
  44. package/dist/src/installMutex.d.ts +4 -0
  45. package/dist/src/installMutex.js +18 -0
  46. package/dist/src/market-install/claimLoop.d.ts +41 -0
  47. package/dist/src/market-install/claimLoop.js +183 -0
  48. package/dist/src/market-install/downloader.d.ts +36 -0
  49. package/dist/src/market-install/downloader.js +133 -0
  50. package/dist/src/market-install/executor.d.ts +28 -0
  51. package/dist/src/market-install/executor.js +352 -0
  52. package/dist/src/market-install/types.d.ts +62 -0
  53. package/dist/src/market-install/types.js +1 -0
  54. package/dist/src/market-install/verifier.d.ts +32 -0
  55. package/dist/src/market-install/verifier.js +60 -0
  56. package/dist/src/market-publish/packager.d.ts +34 -0
  57. package/dist/src/market-publish/packager.js +168 -0
  58. package/dist/src/market-publish/publishFlow.d.ts +70 -0
  59. package/dist/src/market-publish/publishFlow.js +107 -0
  60. package/dist/src/market-publish/uploader.d.ts +73 -0
  61. package/dist/src/market-publish/uploader.js +132 -0
  62. package/dist/src/openclawVersion.d.ts +4 -0
  63. package/dist/src/openclawVersion.js +13 -0
  64. package/dist/src/outbound.d.ts +13 -0
  65. package/dist/src/outbound.js +41 -0
  66. package/dist/src/pairing.d.ts +32 -0
  67. package/dist/src/pairing.js +64 -0
  68. package/dist/src/runtime.d.ts +52 -0
  69. package/dist/src/runtime.js +26 -0
  70. package/dist/src/telemetry.d.ts +34 -0
  71. package/dist/src/telemetry.js +89 -0
  72. package/dist/src/transport/realtime.d.ts +49 -0
  73. package/dist/src/transport/realtime.js +273 -0
  74. package/dist/src/transport.d.ts +38 -0
  75. package/dist/src/transport.js +15 -0
  76. package/dist/src/wake.d.ts +31 -0
  77. package/dist/src/wake.js +101 -0
  78. package/openclaw.config.example.yaml +10 -0
  79. package/openclaw.plugin.json +122 -0
  80. package/package.json +84 -0
@@ -0,0 +1,168 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync } from "node:fs";
3
+ import { readdir } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { create as createTar } from "tar";
6
+ export class AgentManifestNotFoundError extends Error {
7
+ constructor(agentDir) {
8
+ super(`Agent manifest (openclaw.agent.json) not found in ${agentDir}`);
9
+ this.name = "AgentManifestNotFoundError";
10
+ }
11
+ }
12
+ export class PackageTooLargeError extends Error {
13
+ constructor(sizeBytes, limitBytes) {
14
+ super(`Package size ${sizeBytes} bytes exceeds limit of ${limitBytes} bytes (${(limitBytes / 1024 / 1024).toFixed(1)} MiB)`);
15
+ this.name = "PackageTooLargeError";
16
+ }
17
+ }
18
+ export class PackagingError extends Error {
19
+ constructor(message, cause) {
20
+ super(message);
21
+ this.name = "PackagingError";
22
+ if (cause instanceof Error) {
23
+ this.cause = cause;
24
+ }
25
+ }
26
+ }
27
+ const MAX_PACKAGE_SIZE_BYTES = 10 * 1024 * 1024; // 10 MiB
28
+ /**
29
+ * Determines if a file should be included in the agent package.
30
+ * Uses a strict whitelist approach per D-02: only manifest and definition files.
31
+ */
32
+ function shouldInclude(path) {
33
+ const normalized = path.replace(/\\/g, "/");
34
+ // Always include manifest
35
+ if (normalized === "openclaw.agent.json") {
36
+ return true;
37
+ }
38
+ // Include common definition directories
39
+ if (normalized.startsWith("prompts/") ||
40
+ normalized.startsWith("tools/") ||
41
+ normalized.startsWith("knowledge/") ||
42
+ normalized.startsWith("definitions/")) {
43
+ return true;
44
+ }
45
+ // Include common definition file extensions
46
+ const allowedExtensions = [
47
+ ".md",
48
+ ".txt",
49
+ ".json",
50
+ ".yaml",
51
+ ".yml",
52
+ ".py",
53
+ ".js",
54
+ ".ts",
55
+ ];
56
+ if (allowedExtensions.some((ext) => normalized.endsWith(ext))) {
57
+ return true;
58
+ }
59
+ // Exclude everything else
60
+ return false;
61
+ }
62
+ /**
63
+ * Determines if a file should be excluded from the agent package.
64
+ * Blacklist for common unwanted files and directories.
65
+ */
66
+ function shouldExclude(path) {
67
+ const normalized = path.replace(/\\/g, "/");
68
+ // Exclude build/runtime artifacts
69
+ if (normalized.includes("node_modules/") ||
70
+ normalized.includes(".git/") ||
71
+ normalized.includes("__pycache__/") ||
72
+ normalized.includes("dist/") ||
73
+ normalized.includes("build/")) {
74
+ return true;
75
+ }
76
+ // Exclude specific files
77
+ if (normalized.endsWith(".log") ||
78
+ normalized.endsWith(".pyc") ||
79
+ normalized === ".DS_Store" ||
80
+ normalized === "package.json" ||
81
+ normalized === "package-lock.json" ||
82
+ normalized === "tsconfig.json") {
83
+ return true;
84
+ }
85
+ // Apply whitelist - if not included, exclude
86
+ return !shouldInclude(normalized);
87
+ }
88
+ /**
89
+ * Packages an agent directory into a tar.gz archive.
90
+ *
91
+ * Per D-01, D-02, D-03, D-04:
92
+ * - Format: tar.gz (standard Unix archive with gzip compression)
93
+ * - Contents: manifest (openclaw.agent.json) + definition files only
94
+ * - Manifest: embedded at tar.gz root
95
+ * - Fingerprint: SHA-256 over the tar.gz byte stream
96
+ *
97
+ * @param options - Packaging options
98
+ * @returns Package result with buffer, size, and fingerprint
99
+ * @throws {AgentManifestNotFoundError} if openclaw.agent.json is missing
100
+ * @throws {PackageTooLargeError} if package exceeds 10 MiB limit
101
+ * @throws {PackagingError} if tar operation fails
102
+ */
103
+ export async function packageAgentDirectory(options) {
104
+ const { agentDir, manifestPath = "openclaw.agent.json" } = options;
105
+ // Verify manifest exists
106
+ const manifestFullPath = join(agentDir, manifestPath);
107
+ if (!existsSync(manifestFullPath)) {
108
+ throw new AgentManifestNotFoundError(agentDir);
109
+ }
110
+ try {
111
+ // Collect all files to include
112
+ const filesToInclude = [];
113
+ await collectFiles(agentDir, "", filesToInclude);
114
+ // Create tar.gz in memory
115
+ const chunks = [];
116
+ const tarStream = createTar({
117
+ gzip: true,
118
+ cwd: agentDir,
119
+ filter: (path) => !shouldExclude(path),
120
+ }, filesToInclude);
121
+ for await (const chunk of tarStream) {
122
+ chunks.push(Buffer.from(chunk));
123
+ }
124
+ const buffer = Buffer.concat(chunks);
125
+ const sizeBytes = buffer.length;
126
+ // Check size limit (client-side fast fail)
127
+ if (sizeBytes > MAX_PACKAGE_SIZE_BYTES) {
128
+ throw new PackageTooLargeError(sizeBytes, MAX_PACKAGE_SIZE_BYTES);
129
+ }
130
+ // Compute SHA-256 fingerprint
131
+ const hash = createHash("sha256");
132
+ hash.update(buffer);
133
+ const fingerprint = hash.digest("hex");
134
+ return {
135
+ buffer,
136
+ sizeBytes,
137
+ fingerprint,
138
+ };
139
+ }
140
+ catch (error) {
141
+ if (error instanceof AgentManifestNotFoundError ||
142
+ error instanceof PackageTooLargeError) {
143
+ throw error;
144
+ }
145
+ throw new PackagingError(`Failed to package agent directory: ${error instanceof Error ? error.message : String(error)}`, error);
146
+ }
147
+ }
148
+ /**
149
+ * Recursively collects files from a directory that should be included in the package.
150
+ */
151
+ async function collectFiles(baseDir, relativeDir, result) {
152
+ const fullDir = join(baseDir, relativeDir);
153
+ const entries = await readdir(fullDir, { withFileTypes: true });
154
+ for (const entry of entries) {
155
+ const relativePath = relativeDir
156
+ ? join(relativeDir, entry.name)
157
+ : entry.name;
158
+ if (shouldExclude(relativePath)) {
159
+ continue;
160
+ }
161
+ if (entry.isDirectory()) {
162
+ await collectFiles(baseDir, relativePath, result);
163
+ }
164
+ else if (entry.isFile()) {
165
+ result.push(relativePath);
166
+ }
167
+ }
168
+ }
@@ -0,0 +1,70 @@
1
+ import { ClawHiveCloudApi } from "../client.js";
2
+ import type { ClawHiveResolvedAccount } from "../config.js";
3
+ import { packageAgentDirectory } from "./packager.js";
4
+ export type AgentManifest = {
5
+ name?: string;
6
+ displayName?: string;
7
+ version?: string;
8
+ description?: string;
9
+ summary?: string;
10
+ author?: {
11
+ name?: string;
12
+ };
13
+ compatibility?: {
14
+ minOpenClawVersion?: string;
15
+ minPluginVersion?: string;
16
+ };
17
+ metadata?: {
18
+ category?: string;
19
+ tags?: string[];
20
+ license?: string;
21
+ };
22
+ };
23
+ export type PublishAgentInfo = {
24
+ name: string;
25
+ path: string;
26
+ manifest: AgentManifest;
27
+ };
28
+ export type PublishDefaults = {
29
+ name: string;
30
+ slug: string;
31
+ summary: string | undefined;
32
+ authorDisplayName: string | undefined;
33
+ category: string | undefined;
34
+ license: string | undefined;
35
+ tags: string[];
36
+ version: string;
37
+ sourceRef: string;
38
+ minOpenClawVersion: string | undefined;
39
+ minPluginVersion: string | undefined;
40
+ };
41
+ export type PublishOverrides = Partial<PublishDefaults> & {
42
+ listingId?: string;
43
+ };
44
+ export type PublishFlowResult = {
45
+ listingId: string;
46
+ releaseId: string | null;
47
+ version: string;
48
+ storagePath: string;
49
+ fingerprint: string;
50
+ listing: Record<string, unknown>;
51
+ release: Record<string, unknown>;
52
+ };
53
+ export declare function slugifyForListing(value: string): string;
54
+ export declare function inferPublishDefaults(input: {
55
+ folderName: string;
56
+ manifest: AgentManifest;
57
+ sourceRef: string;
58
+ account?: Pick<ClawHiveResolvedAccount, "minOpenClawVersion" | "minPluginVersion">;
59
+ }): PublishDefaults;
60
+ export declare function runPublishFlow(input: {
61
+ agent: PublishAgentInfo;
62
+ account: ClawHiveResolvedAccount;
63
+ sourceRef: string;
64
+ overrides: PublishOverrides;
65
+ onProgress?: (uploaded: number, total: number) => void;
66
+ deps?: {
67
+ createClient?: (account: ClawHiveResolvedAccount) => Pick<ClawHiveCloudApi, "registerPluginNode" | "createMarketListing" | "uploadReleasePackage" | "createMarketRelease">;
68
+ packageAgentDirectory?: typeof packageAgentDirectory;
69
+ };
70
+ }): Promise<PublishFlowResult>;
@@ -0,0 +1,107 @@
1
+ import { basename } from "node:path";
2
+ import { ClawHiveCloudApi, } from "../client.js";
3
+ import { packageAgentDirectory } from "./packager.js";
4
+ export function slugifyForListing(value) {
5
+ const slug = value
6
+ .toLowerCase()
7
+ .trim()
8
+ .replace(/[^a-z0-9\s-]/g, "")
9
+ .replace(/\s+/g, "-")
10
+ .replace(/-+/g, "-")
11
+ .replace(/^-|-$/g, "");
12
+ return slug || "agent";
13
+ }
14
+ function normalizeVersion(value) {
15
+ const raw = value?.trim() || "1.0.0";
16
+ return raw.startsWith("v") ? raw : `v${raw}`;
17
+ }
18
+ export function inferPublishDefaults(input) {
19
+ const manifest = input.manifest;
20
+ const name = manifest.displayName ?? manifest.name ?? input.folderName;
21
+ return {
22
+ name,
23
+ slug: slugifyForListing(manifest.name ?? name),
24
+ summary: manifest.summary ?? manifest.description,
25
+ authorDisplayName: manifest.author?.name,
26
+ category: manifest.metadata?.category,
27
+ license: manifest.metadata?.license,
28
+ tags: Array.isArray(manifest.metadata?.tags) ? manifest.metadata.tags : [],
29
+ version: normalizeVersion(manifest.version),
30
+ sourceRef: input.sourceRef,
31
+ minOpenClawVersion: manifest.compatibility?.minOpenClawVersion ?? input.account?.minOpenClawVersion ?? undefined,
32
+ minPluginVersion: manifest.compatibility?.minPluginVersion ?? input.account?.minPluginVersion ?? undefined,
33
+ };
34
+ }
35
+ export async function runPublishFlow(input) {
36
+ if (!input.account.pluginToken) {
37
+ throw new Error("ClawHive is not authorized. Run `openclaw clawhive authorize` first.");
38
+ }
39
+ const defaults = inferPublishDefaults({
40
+ folderName: input.agent.name || basename(input.agent.path),
41
+ manifest: input.agent.manifest,
42
+ sourceRef: input.sourceRef,
43
+ account: input.account,
44
+ });
45
+ const publish = { ...defaults, ...input.overrides };
46
+ const createClient = input.deps?.createClient ??
47
+ ((account) => new ClawHiveCloudApi({
48
+ projectUrl: account.projectUrl,
49
+ anonKey: account.anonKey,
50
+ pluginToken: account.pluginToken,
51
+ userId: account.userId,
52
+ pluginNodeId: account.pluginNodeId,
53
+ nodeName: account.nodeName,
54
+ pluginVersion: account.pluginVersion,
55
+ }));
56
+ const client = createClient(input.account);
57
+ const registration = await client.registerPluginNode();
58
+ const accessToken = registration.user_session.access_token;
59
+ const listingInput = {
60
+ accessToken,
61
+ listingId: input.overrides.listingId,
62
+ name: publish.name,
63
+ slug: publish.slug,
64
+ authorDisplayName: publish.authorDisplayName,
65
+ summary: publish.summary,
66
+ category: publish.category,
67
+ license: publish.license,
68
+ tags: publish.tags,
69
+ status: "draft",
70
+ };
71
+ const listingResponse = await client.createMarketListing(listingInput);
72
+ const listing = listingResponse.listing;
73
+ const listingId = String(listing.id);
74
+ const packageFn = input.deps?.packageAgentDirectory ?? packageAgentDirectory;
75
+ const packageResult = await packageFn({ agentDir: input.agent.path });
76
+ const upload = await client.uploadReleasePackage({
77
+ listingId,
78
+ version: publish.version,
79
+ buffer: packageResult.buffer,
80
+ fingerprint: packageResult.fingerprint,
81
+ accessToken,
82
+ onProgress: input.onProgress,
83
+ });
84
+ const releaseInput = {
85
+ accessToken,
86
+ listingId,
87
+ version: publish.version,
88
+ storagePath: upload.storage_path,
89
+ fingerprint: upload.fingerprint,
90
+ sourceType: "manifest",
91
+ sourceRef: publish.sourceRef,
92
+ publish: true,
93
+ minPluginVersion: publish.minPluginVersion,
94
+ minOpenClawVersion: publish.minOpenClawVersion,
95
+ };
96
+ const releaseResponse = await client.createMarketRelease(releaseInput);
97
+ const release = releaseResponse.release;
98
+ return {
99
+ listingId,
100
+ releaseId: release.id ? String(release.id) : null,
101
+ version: publish.version,
102
+ storagePath: upload.storage_path,
103
+ fingerprint: upload.fingerprint,
104
+ listing,
105
+ release,
106
+ };
107
+ }
@@ -0,0 +1,73 @@
1
+ import type { ClawHiveCloudApi } from "../client.js";
2
+ export type UploadResult = {
3
+ storagePath: string;
4
+ fingerprint: string;
5
+ sizeBytes: number;
6
+ };
7
+ export type UploadOptions = {
8
+ listingId: string;
9
+ version: string;
10
+ buffer: Buffer;
11
+ fingerprint: string;
12
+ accessToken: string;
13
+ onProgress?: (bytesUploaded: number, totalBytes: number) => void;
14
+ };
15
+ export declare class UploadFailedError extends Error {
16
+ readonly attempts: number;
17
+ constructor(message: string, attempts: number, cause?: unknown);
18
+ }
19
+ /**
20
+ * Uploads a release package to Supabase Storage with retry logic.
21
+ *
22
+ * Per D-08, D-09, D-10, D-11:
23
+ * - Two-step publish flow: upload first, then create release record
24
+ * - Storage path: releases/{listing_id}/{version}.tar.gz
25
+ * - Size limit: 10 MiB (checked by edge function)
26
+ * - Retry: exponential backoff with jitter, max 3 attempts
27
+ * - Idempotency: uploading same content to same path is safe
28
+ *
29
+ * @param client - ClawHiveCloudApi instance
30
+ * @param options - Upload options
31
+ * @returns Upload result with storage path and fingerprint
32
+ * @throws {UploadFailedError} if all retry attempts fail
33
+ */
34
+ export declare function uploadReleasePackage(client: ClawHiveCloudApi, options: UploadOptions): Promise<UploadResult>;
35
+ /**
36
+ * Two-step publish flow helper that packages, uploads, and returns data for release creation.
37
+ *
38
+ * This is a convenience wrapper that combines packaging and upload.
39
+ * The caller must still call market-releases-create with the returned data.
40
+ *
41
+ * Example usage:
42
+ * ```typescript
43
+ * import { packageAndUpload } from "./market-publish/uploader.js";
44
+ * import { packageAgentDirectory } from "./market-publish/packager.js";
45
+ *
46
+ * // Step 1: Package and upload
47
+ * const uploadResult = await packageAndUpload(client, {
48
+ * agentDir: "/path/to/agent",
49
+ * listingId: "listing-uuid",
50
+ * version: "v1.0.0",
51
+ * accessToken: "user-jwt",
52
+ * });
53
+ *
54
+ * // Step 2: Create release record (caller's responsibility)
55
+ * await client.createMarketRelease({
56
+ * listingId: uploadResult.listingId,
57
+ * version: uploadResult.version,
58
+ * storagePath: uploadResult.storagePath,
59
+ * fingerprint: uploadResult.fingerprint,
60
+ * // ... other release metadata
61
+ * });
62
+ * ```
63
+ */
64
+ export declare function packageAndUpload(client: ClawHiveCloudApi, options: {
65
+ agentDir: string;
66
+ listingId: string;
67
+ version: string;
68
+ accessToken: string;
69
+ onProgress?: (bytesUploaded: number, totalBytes: number) => void;
70
+ }): Promise<UploadResult & {
71
+ listingId: string;
72
+ version: string;
73
+ }>;
@@ -0,0 +1,132 @@
1
+ export class UploadFailedError extends Error {
2
+ attempts;
3
+ constructor(message, attempts, cause) {
4
+ super(message);
5
+ this.attempts = attempts;
6
+ this.name = "UploadFailedError";
7
+ if (cause instanceof Error) {
8
+ this.cause = cause;
9
+ }
10
+ }
11
+ }
12
+ const MAX_RETRY_ATTEMPTS = 3;
13
+ const INITIAL_BACKOFF_MS = 1000;
14
+ const BACKOFF_MULTIPLIER = 2;
15
+ const JITTER_MAX_MS = 500;
16
+ /**
17
+ * Uploads a release package to Supabase Storage with retry logic.
18
+ *
19
+ * Per D-08, D-09, D-10, D-11:
20
+ * - Two-step publish flow: upload first, then create release record
21
+ * - Storage path: releases/{listing_id}/{version}.tar.gz
22
+ * - Size limit: 10 MiB (checked by edge function)
23
+ * - Retry: exponential backoff with jitter, max 3 attempts
24
+ * - Idempotency: uploading same content to same path is safe
25
+ *
26
+ * @param client - ClawHiveCloudApi instance
27
+ * @param options - Upload options
28
+ * @returns Upload result with storage path and fingerprint
29
+ * @throws {UploadFailedError} if all retry attempts fail
30
+ */
31
+ export async function uploadReleasePackage(client, options) {
32
+ const { listingId, version, buffer, fingerprint, accessToken, onProgress } = options;
33
+ let lastError;
34
+ for (let attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
35
+ try {
36
+ // Report progress at start of attempt
37
+ if (onProgress) {
38
+ onProgress(0, buffer.length);
39
+ }
40
+ const result = await client.uploadReleasePackage({
41
+ listingId,
42
+ version,
43
+ buffer,
44
+ fingerprint,
45
+ accessToken,
46
+ });
47
+ // Report progress at completion
48
+ if (onProgress) {
49
+ onProgress(buffer.length, buffer.length);
50
+ }
51
+ return {
52
+ storagePath: result.storage_path,
53
+ fingerprint: result.fingerprint,
54
+ sizeBytes: buffer.length,
55
+ };
56
+ }
57
+ catch (error) {
58
+ lastError = error;
59
+ // Don't retry on client errors (4xx) - these are permanent failures
60
+ if (error &&
61
+ typeof error === "object" &&
62
+ "status" in error &&
63
+ typeof error.status === "number" &&
64
+ error.status >= 400 &&
65
+ error.status < 500) {
66
+ throw new UploadFailedError(`Upload failed with client error (${error.status}): ${error instanceof Error ? error.message : String(error)}`, attempt, error);
67
+ }
68
+ // If this was the last attempt, throw
69
+ if (attempt === MAX_RETRY_ATTEMPTS) {
70
+ break;
71
+ }
72
+ // Calculate backoff with exponential increase and jitter
73
+ const backoffMs = INITIAL_BACKOFF_MS * Math.pow(BACKOFF_MULTIPLIER, attempt - 1);
74
+ const jitterMs = Math.random() * JITTER_MAX_MS;
75
+ const delayMs = backoffMs + jitterMs;
76
+ // Wait before retry
77
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
78
+ }
79
+ }
80
+ // All attempts failed
81
+ throw new UploadFailedError(`Upload failed after ${MAX_RETRY_ATTEMPTS} attempts: ${lastError instanceof Error ? lastError.message : String(lastError)}`, MAX_RETRY_ATTEMPTS, lastError);
82
+ }
83
+ /**
84
+ * Two-step publish flow helper that packages, uploads, and returns data for release creation.
85
+ *
86
+ * This is a convenience wrapper that combines packaging and upload.
87
+ * The caller must still call market-releases-create with the returned data.
88
+ *
89
+ * Example usage:
90
+ * ```typescript
91
+ * import { packageAndUpload } from "./market-publish/uploader.js";
92
+ * import { packageAgentDirectory } from "./market-publish/packager.js";
93
+ *
94
+ * // Step 1: Package and upload
95
+ * const uploadResult = await packageAndUpload(client, {
96
+ * agentDir: "/path/to/agent",
97
+ * listingId: "listing-uuid",
98
+ * version: "v1.0.0",
99
+ * accessToken: "user-jwt",
100
+ * });
101
+ *
102
+ * // Step 2: Create release record (caller's responsibility)
103
+ * await client.createMarketRelease({
104
+ * listingId: uploadResult.listingId,
105
+ * version: uploadResult.version,
106
+ * storagePath: uploadResult.storagePath,
107
+ * fingerprint: uploadResult.fingerprint,
108
+ * // ... other release metadata
109
+ * });
110
+ * ```
111
+ */
112
+ export async function packageAndUpload(client, options) {
113
+ const { packageAgentDirectory } = await import("./packager.js");
114
+ // Package the agent directory
115
+ const packageResult = await packageAgentDirectory({
116
+ agentDir: options.agentDir,
117
+ });
118
+ // Upload to Storage
119
+ const uploadResult = await uploadReleasePackage(client, {
120
+ listingId: options.listingId,
121
+ version: options.version,
122
+ buffer: packageResult.buffer,
123
+ fingerprint: packageResult.fingerprint,
124
+ accessToken: options.accessToken,
125
+ onProgress: options.onProgress,
126
+ });
127
+ return {
128
+ ...uploadResult,
129
+ listingId: options.listingId,
130
+ version: options.version,
131
+ };
132
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Resolve the current OpenClaw host version from runtime exports or env overrides.
3
+ */
4
+ export declare function resolveOpenClawVersion(): string | null;
@@ -0,0 +1,13 @@
1
+ import { OPENCLAW_VERSION } from "openclaw/plugin-sdk/agent-harness";
2
+ /**
3
+ * Resolve the current OpenClaw host version from runtime exports or env overrides.
4
+ */
5
+ export function resolveOpenClawVersion() {
6
+ const fromEnv = process.env.OPENCLAW_VERSION?.trim();
7
+ if (fromEnv) {
8
+ return fromEnv;
9
+ }
10
+ return typeof OPENCLAW_VERSION === "string" && OPENCLAW_VERSION.trim()
11
+ ? OPENCLAW_VERSION
12
+ : null;
13
+ }
@@ -0,0 +1,13 @@
1
+ type SendTextContext = {
2
+ cfg: unknown;
3
+ to: string;
4
+ text: string;
5
+ accountId?: string | null;
6
+ };
7
+ type SendResult = {
8
+ messageId: string;
9
+ meta?: Record<string, unknown>;
10
+ };
11
+ export declare function sendAgentText(ctx: SendTextContext): Promise<SendResult>;
12
+ export declare function rejectMedia(): Promise<never>;
13
+ export {};
@@ -0,0 +1,41 @@
1
+ import { clawhiveRuntimeStore, lookupSession, } from "./runtime.js";
2
+ import { CLAWHIVE_CHANNEL_ID } from "./config.js";
3
+ function stripChannelPrefix(raw) {
4
+ if (raw.startsWith(`${CLAWHIVE_CHANNEL_ID}:`)) {
5
+ return raw.slice(CLAWHIVE_CHANNEL_ID.length + 1);
6
+ }
7
+ return raw;
8
+ }
9
+ function resolveBinding(runtime, to) {
10
+ const normalized = stripChannelPrefix(to);
11
+ return (lookupSession(runtime, to) ??
12
+ lookupSession(runtime, normalized) ??
13
+ lookupSession(runtime, `clawhive:default:${normalized}`));
14
+ }
15
+ export async function sendAgentText(ctx) {
16
+ const runtime = clawhiveRuntimeStore.getRuntime();
17
+ const binding = resolveBinding(runtime, ctx.to);
18
+ if (!binding) {
19
+ throw new Error(`ClawHive outbound: no session binding for target "${ctx.to}"`);
20
+ }
21
+ const content = ctx.text?.trim();
22
+ if (!content) {
23
+ throw new Error("ClawHive outbound: refusing to send empty text");
24
+ }
25
+ const { message } = await runtime.api.writeAgentMessage({
26
+ pluginNodeId: runtime.pluginNodeId,
27
+ sessionId: binding.sessionId,
28
+ agentId: binding.cloudAgentId,
29
+ content,
30
+ });
31
+ return {
32
+ messageId: message.id,
33
+ meta: {
34
+ sequence: message.sequence,
35
+ createdAt: message.created_at,
36
+ },
37
+ };
38
+ }
39
+ export async function rejectMedia() {
40
+ throw new Error("ClawHive plugin does not yet support outbound media attachments (MVP scope).");
41
+ }
@@ -0,0 +1,32 @@
1
+ import type { ClawHiveCloudApi, PluginRegisterResult } from "./client.js";
2
+ export type PairingPrintResult = {
3
+ pairToken: string;
4
+ expiresAt: string;
5
+ payloadUrl: string;
6
+ qrText: string;
7
+ };
8
+ export type PairingPrintOptions = {
9
+ api: ClawHiveCloudApi;
10
+ pluginNodeId: string;
11
+ ttlSeconds?: number;
12
+ ascii?: (text: string) => void;
13
+ /** Optional banner printed above the QR. */
14
+ banner?: string;
15
+ };
16
+ export declare function generatePairingPayload(api: ClawHiveCloudApi, pluginNodeId: string, ttlSeconds?: number): Promise<{
17
+ pairToken: string;
18
+ expiresAt: string;
19
+ payloadUrl: string;
20
+ }>;
21
+ export declare function renderQrText(payloadUrl: string): Promise<string>;
22
+ export declare function printPairingQr(options: PairingPrintOptions): Promise<PairingPrintResult>;
23
+ /**
24
+ * Return the number of active paired devices for the current user.
25
+ * Uses the user-scoped session minted by plugin-register so the query
26
+ * passes RLS without bringing service-role credentials into the plugin.
27
+ */
28
+ export declare function getPairedDeviceCount(params: {
29
+ registration: PluginRegisterResult;
30
+ projectUrl: string;
31
+ anonKey: string;
32
+ }): Promise<number>;