taskmeld 0.1.1

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 (204) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +172 -0
  3. package/README.zh-CN.md +172 -0
  4. package/dist/src/app/app-context-env.js +51 -0
  5. package/dist/src/app/create-app-context.js +127 -0
  6. package/dist/src/app/data-dir.js +29 -0
  7. package/dist/src/app/pipeline-config.js +105 -0
  8. package/dist/src/app/pipeline-plugin-config.js +2 -0
  9. package/dist/src/app/pipeline-registry.js +502 -0
  10. package/dist/src/app/pipeline-runtime.js +202 -0
  11. package/dist/src/app/runtime-store.js +151 -0
  12. package/dist/src/app/user-config.js +37 -0
  13. package/dist/src/artifacts/artifact-cleanup.js +192 -0
  14. package/dist/src/artifacts/artifact-index.js +262 -0
  15. package/dist/src/artifacts/artifact-rebuilder.js +120 -0
  16. package/dist/src/artifacts/storage-service.js +371 -0
  17. package/dist/src/cli/bootstrap.js +226 -0
  18. package/dist/src/cli/commands/agent.js +126 -0
  19. package/dist/src/cli/commands/artifact.js +175 -0
  20. package/dist/src/cli/commands/init.js +150 -0
  21. package/dist/src/cli/commands/pipeline/errors.js +37 -0
  22. package/dist/src/cli/commands/pipeline/result.js +179 -0
  23. package/dist/src/cli/commands/pipeline/selector.js +51 -0
  24. package/dist/src/cli/commands/pipeline/types.js +2 -0
  25. package/dist/src/cli/commands/pipeline/watch.js +67 -0
  26. package/dist/src/cli/commands/pipeline.js +339 -0
  27. package/dist/src/cli/commands/scheduler.js +81 -0
  28. package/dist/src/cli/commands/server.js +70 -0
  29. package/dist/src/cli/commands/system.js +21 -0
  30. package/dist/src/cli/errors.js +71 -0
  31. package/dist/src/cli/help.js +184 -0
  32. package/dist/src/cli/index.js +65 -0
  33. package/dist/src/cli/output.js +19 -0
  34. package/dist/src/cli/renderers/engine/json.js +67 -0
  35. package/dist/src/cli/renderers/engine/markdown.js +95 -0
  36. package/dist/src/cli/renderers/engine/types.js +2 -0
  37. package/dist/src/cli/renderers/engine/utils.js +32 -0
  38. package/dist/src/cli/renderers/index.js +27 -0
  39. package/dist/src/cli/renderers/specs/agent.js +78 -0
  40. package/dist/src/cli/renderers/specs/artifact.js +32 -0
  41. package/dist/src/cli/renderers/specs/index.js +36 -0
  42. package/dist/src/cli/renderers/specs/init.js +25 -0
  43. package/dist/src/cli/renderers/specs/pipeline.js +561 -0
  44. package/dist/src/cli/renderers/specs/scheduler.js +46 -0
  45. package/dist/src/cli/renderers/specs/server.js +38 -0
  46. package/dist/src/cli/renderers/specs/system.js +36 -0
  47. package/dist/src/cli/router.js +199 -0
  48. package/dist/src/cli/server-runtime-client.js +780 -0
  49. package/dist/src/cli/types.js +2 -0
  50. package/dist/src/gateway/frame-sanitizer.js +78 -0
  51. package/dist/src/gateway/gateway-client.js +462 -0
  52. package/dist/src/gateway/index.js +18 -0
  53. package/dist/src/gateway/types.js +2 -0
  54. package/dist/src/index.js +123 -0
  55. package/dist/src/logs/run-log-reader.js +141 -0
  56. package/dist/src/logs/run-log-service.js +42 -0
  57. package/dist/src/logs/run-log-types.js +2 -0
  58. package/dist/src/pipeline/agent-activity.js +191 -0
  59. package/dist/src/pipeline/artifact-storage.js +208 -0
  60. package/dist/src/pipeline/diagnostics/dependency-diagnostic.js +105 -0
  61. package/dist/src/pipeline/diagnostics/index.js +6 -0
  62. package/dist/src/pipeline/dispatch/pipeline-inbound-queue.js +215 -0
  63. package/dist/src/pipeline/dispatch/pipeline-link-dispatcher.js +66 -0
  64. package/dist/src/pipeline/dispatch/pipeline-link-store.js +94 -0
  65. package/dist/src/pipeline/dispatch/pipeline-queue-drainer.js +71 -0
  66. package/dist/src/pipeline/execution/dependency-check.js +52 -0
  67. package/dist/src/pipeline/execution/execution-result.js +2 -0
  68. package/dist/src/pipeline/execution/group-item-executor.js +128 -0
  69. package/dist/src/pipeline/execution/index.js +5 -0
  70. package/dist/src/pipeline/execution/node-item-executor.js +58 -0
  71. package/dist/src/pipeline/execution/node-runner.js +159 -0
  72. package/dist/src/pipeline/execution/readiness-state.js +10 -0
  73. package/dist/src/pipeline/execution/reject-handler.js +94 -0
  74. package/dist/src/pipeline/execution/rejected-artifact-archiver.js +45 -0
  75. package/dist/src/pipeline/execution/route-item-manager.js +253 -0
  76. package/dist/src/pipeline/execution/run-abort-controller.js +66 -0
  77. package/dist/src/pipeline/execution/run-state-helpers.js +257 -0
  78. package/dist/src/pipeline/execution/service.js +165 -0
  79. package/dist/src/pipeline/execution/session-registry.js +96 -0
  80. package/dist/src/pipeline/execution/structured-node-runner.js +411 -0
  81. package/dist/src/pipeline/execution-status.js +96 -0
  82. package/dist/src/pipeline/execution-timeout.js +21 -0
  83. package/dist/src/pipeline/identity/index.js +32 -0
  84. package/dist/src/pipeline/identity/types.js +2 -0
  85. package/dist/src/pipeline/item-batch-controller.js +227 -0
  86. package/dist/src/pipeline/output/pipeline-output-resolver.js +91 -0
  87. package/dist/src/pipeline/output/pipeline-output-store.js +60 -0
  88. package/dist/src/pipeline/runtime-model.js +173 -0
  89. package/dist/src/pipeline/scheduler/dependency-state.js +144 -0
  90. package/dist/src/pipeline/scheduler-service.js +314 -0
  91. package/dist/src/pipeline/state/group-item-state.js +50 -0
  92. package/dist/src/pipeline/state/group-run-state.js +41 -0
  93. package/dist/src/pipeline/state/index.js +20 -0
  94. package/dist/src/pipeline/state/node-item-state.js +67 -0
  95. package/dist/src/pipeline/state/node-run-state.js +51 -0
  96. package/dist/src/pipeline/state/types.js +2 -0
  97. package/dist/src/pipeline/state-machine.js +101 -0
  98. package/dist/src/pipeline/structured-output/contract.js +133 -0
  99. package/dist/src/pipeline/structured-output/index.js +22 -0
  100. package/dist/src/pipeline/structured-output/parser.js +214 -0
  101. package/dist/src/pipeline/structured-output/prompt.js +290 -0
  102. package/dist/src/pipeline/structured-output/waiter.js +139 -0
  103. package/dist/src/pipeline/template.js +135 -0
  104. package/dist/src/pipeline/timeline-log-store.js +57 -0
  105. package/dist/src/pipeline/tool-activity.js +94 -0
  106. package/dist/src/pipeline/types/pipeline-link.js +7 -0
  107. package/dist/src/pipeline/types/pipeline-output.js +11 -0
  108. package/dist/src/pipeline/types/workflow.js +2 -0
  109. package/dist/src/pipeline/workflow/branch-rules.js +74 -0
  110. package/dist/src/pipeline/workflow/defaults.js +48 -0
  111. package/dist/src/pipeline/workflow/io.js +89 -0
  112. package/dist/src/pipeline/workflow/normalize.js +347 -0
  113. package/dist/src/pipeline/workflow/routes.js +16 -0
  114. package/dist/src/pipeline/workflow/template-mapper.js +113 -0
  115. package/dist/src/pipeline/workflow/validate.js +312 -0
  116. package/dist/src/pipeline/workflow-graph.js +165 -0
  117. package/dist/src/server/api-handler.js +163 -0
  118. package/dist/src/server/http-utils.js +34 -0
  119. package/dist/src/server/middleware.js +61 -0
  120. package/dist/src/server/router.js +105 -0
  121. package/dist/src/server/routes/agents.js +189 -0
  122. package/dist/src/server/routes/artifacts.js +163 -0
  123. package/dist/src/server/routes/gateway.js +18 -0
  124. package/dist/src/server/routes/health.js +16 -0
  125. package/dist/src/server/routes/logs.js +73 -0
  126. package/dist/src/server/routes/pipeline-batch.js +163 -0
  127. package/dist/src/server/routes/pipeline-diagnostics.js +33 -0
  128. package/dist/src/server/routes/pipeline-identity.js +24 -0
  129. package/dist/src/server/routes/pipeline-links.js +117 -0
  130. package/dist/src/server/routes/pipeline-outputs.js +27 -0
  131. package/dist/src/server/routes/pipeline-queue.js +62 -0
  132. package/dist/src/server/routes/pipeline-runtime.js +162 -0
  133. package/dist/src/server/routes/pipeline-scheduler.js +69 -0
  134. package/dist/src/server/routes/pipeline-workflow.js +180 -0
  135. package/dist/src/server/routes/pipelines.js +96 -0
  136. package/dist/src/server/routes/sessions.js +244 -0
  137. package/dist/src/server/routes/timeline.js +14 -0
  138. package/dist/src/server/serve-static.js +42 -0
  139. package/dist/src/server/types.js +2 -0
  140. package/dist/src/services/agent-service.js +79 -0
  141. package/dist/src/services/artifact-service.js +74 -0
  142. package/dist/src/services/gateway-read-helpers.js +10 -0
  143. package/dist/src/services/index.js +23 -0
  144. package/dist/src/services/pipeline-service.js +529 -0
  145. package/dist/src/services/pipeline-status.js +93 -0
  146. package/dist/src/services/read-services.js +60 -0
  147. package/dist/src/services/scheduler-service.js +37 -0
  148. package/dist/src/services/session-service.js +227 -0
  149. package/dist/src/services/system-service.js +26 -0
  150. package/dist/src/transport/ws-broker.js +48 -0
  151. package/dist/src/utils/array.js +17 -0
  152. package/dist/src/utils/guards.js +5 -0
  153. package/dist/src/utils/session.js +60 -0
  154. package/dist/src/version.js +5 -0
  155. package/package.json +61 -0
  156. package/web/dist/assets/index-CWnfhkn-.js +65 -0
  157. package/web/dist/assets/index-gZ0xOfSO.css +1 -0
  158. package/web/dist/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  159. package/web/dist/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  160. package/web/dist/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
  161. package/web/dist/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
  162. package/web/dist/assets/jetbrains-mono-cyrillic-700-normal-BWTpRfYl.woff2 +0 -0
  163. package/web/dist/assets/jetbrains-mono-cyrillic-700-normal-CEoEElIJ.woff +0 -0
  164. package/web/dist/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  165. package/web/dist/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  166. package/web/dist/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
  167. package/web/dist/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
  168. package/web/dist/assets/jetbrains-mono-greek-700-normal-C6CZE3T8.woff2 +0 -0
  169. package/web/dist/assets/jetbrains-mono-greek-700-normal-DEigVDxa.woff +0 -0
  170. package/web/dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  171. package/web/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  172. package/web/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  173. package/web/dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  174. package/web/dist/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
  175. package/web/dist/assets/jetbrains-mono-latin-700-normal-D3wTyLJW.woff +0 -0
  176. package/web/dist/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  177. package/web/dist/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  178. package/web/dist/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
  179. package/web/dist/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
  180. package/web/dist/assets/jetbrains-mono-latin-ext-700-normal-CZipNAKV.woff2 +0 -0
  181. package/web/dist/assets/jetbrains-mono-latin-ext-700-normal-CxPITLHs.woff +0 -0
  182. package/web/dist/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  183. package/web/dist/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
  184. package/web/dist/assets/jetbrains-mono-vietnamese-700-normal-BDLVIk2r.woff +0 -0
  185. package/web/dist/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
  186. package/web/dist/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
  187. package/web/dist/assets/space-grotesk-latin-500-normal-CNSSEhBt.woff +0 -0
  188. package/web/dist/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
  189. package/web/dist/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
  190. package/web/dist/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
  191. package/web/dist/assets/space-grotesk-latin-ext-400-normal-CfP_5XZW.woff2 +0 -0
  192. package/web/dist/assets/space-grotesk-latin-ext-400-normal-DRPE3kg4.woff +0 -0
  193. package/web/dist/assets/space-grotesk-latin-ext-500-normal-3dgZTiw9.woff +0 -0
  194. package/web/dist/assets/space-grotesk-latin-ext-500-normal-DUe3BAxM.woff2 +0 -0
  195. package/web/dist/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
  196. package/web/dist/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
  197. package/web/dist/assets/space-grotesk-vietnamese-400-normal-B7xT_GF5.woff2 +0 -0
  198. package/web/dist/assets/space-grotesk-vietnamese-400-normal-BIWiOVfw.woff +0 -0
  199. package/web/dist/assets/space-grotesk-vietnamese-500-normal-BTqKIpxg.woff +0 -0
  200. package/web/dist/assets/space-grotesk-vietnamese-500-normal-BmEvtly_.woff2 +0 -0
  201. package/web/dist/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
  202. package/web/dist/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
  203. package/web/dist/favicon.svg +10 -0
  204. package/web/dist/index.html +14 -0
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sanitizeDiagnosticPayload = exports.sanitizeGatewayFrame = void 0;
4
+ const guards_1 = require("../utils/guards");
5
+ const TARGET_FIELDS = new Set([
6
+ "args",
7
+ "body",
8
+ "content",
9
+ "delta",
10
+ "html",
11
+ "input",
12
+ "markdown",
13
+ "message",
14
+ "output",
15
+ "pagecontent",
16
+ "prompt",
17
+ "result",
18
+ "stderr",
19
+ "stdout",
20
+ "summary",
21
+ "text",
22
+ ]);
23
+ const resolveOptions = (options) => ({
24
+ maxTextHeadChars: options?.maxTextHeadChars ?? 256,
25
+ maxTextTailChars: options?.maxTextTailChars ?? 2048,
26
+ maxObjectDepth: options?.maxObjectDepth ?? 8,
27
+ maxArrayItems: options?.maxArrayItems ?? 20,
28
+ maxTextLength: options?.maxTextLength ?? 2048,
29
+ });
30
+ const maybeTruncateString = (value, key, opts) => {
31
+ if (!TARGET_FIELDS.has(key.toLowerCase()))
32
+ return value;
33
+ if (value.length <= opts.maxTextLength)
34
+ return value;
35
+ return {
36
+ __truncated: true,
37
+ length: value.length,
38
+ head: value.slice(0, opts.maxTextHeadChars),
39
+ tail: value.slice(-opts.maxTextTailChars),
40
+ };
41
+ };
42
+ const sanitizeAny = (value, parentKey, depth, opts) => {
43
+ if (depth > opts.maxObjectDepth) {
44
+ return { __depthLimitReached: true };
45
+ }
46
+ if (typeof value === "string") {
47
+ if (parentKey !== null) {
48
+ return maybeTruncateString(value, parentKey, opts);
49
+ }
50
+ return value;
51
+ }
52
+ if (Array.isArray(value)) {
53
+ const kept = value.slice(0, opts.maxArrayItems);
54
+ const result = kept.map((item) => sanitizeAny(item, parentKey, depth, opts));
55
+ if (value.length > opts.maxArrayItems) {
56
+ result.push({ __omittedItems: value.length - opts.maxArrayItems });
57
+ }
58
+ return result;
59
+ }
60
+ if ((0, guards_1.isRecord)(value)) {
61
+ const result = {};
62
+ for (const [k, v] of Object.entries(value)) {
63
+ result[k] = sanitizeAny(v, k, depth + 1, opts);
64
+ }
65
+ return result;
66
+ }
67
+ return value;
68
+ };
69
+ const sanitizeGatewayFrame = (frame, options) => {
70
+ const opts = resolveOptions(options);
71
+ return sanitizeAny(frame, null, 0, opts);
72
+ };
73
+ exports.sanitizeGatewayFrame = sanitizeGatewayFrame;
74
+ const sanitizeDiagnosticPayload = (value, options) => {
75
+ const opts = resolveOptions(options);
76
+ return sanitizeAny(value, null, 0, opts);
77
+ };
78
+ exports.sanitizeDiagnosticPayload = sanitizeDiagnosticPayload;
@@ -0,0 +1,462 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createGatewayClient = void 0;
4
+ const ws_1 = require("ws");
5
+ const node_crypto_1 = require("node:crypto");
6
+ const node_fs_1 = require("node:fs");
7
+ const frame_sanitizer_1 = require("./frame-sanitizer");
8
+ const data_dir_1 = require("../app/data-dir");
9
+ const STORAGE_DIR = (0, data_dir_1.resolveTaskMeldDataPath)();
10
+ const STORAGE_FILE = (0, data_dir_1.resolveTaskMeldDataPath)("openclaw-device.json");
11
+ const CONTROL_UI_ORIGIN = "http://localhost:3000";
12
+ const ED25519_SPKI_PREFIX = Buffer.from([
13
+ 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00,
14
+ ]);
15
+ const MIN_PROTOCOL = 3;
16
+ const MAX_PROTOCOL = 4;
17
+ const CHALLENGE_TIMEOUT_MS = 5_000;
18
+ const HELLO_TIMEOUT_MS = 8_000;
19
+ const BASE_RECONNECT_MS = 1_000;
20
+ const MAX_RECONNECT_MS = 30_000;
21
+ const base64UrlEncode = (data) => data
22
+ .toString("base64")
23
+ .replace(/\+/g, "-")
24
+ .replace(/\//g, "_")
25
+ .replace(/=+$/, "");
26
+ const base64UrlDecode = (value) => {
27
+ const padded = value.padEnd(Math.ceil(value.length / 4) * 4, "=");
28
+ return Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64");
29
+ };
30
+ const sha256Hex = (data) => (0, node_crypto_1.createHash)("sha256").update(data).digest("hex");
31
+ const publicKeyRawBase64Url = (spkiBuffer) => {
32
+ if (spkiBuffer.length === ED25519_SPKI_PREFIX.length + 32 &&
33
+ spkiBuffer.subarray(0, ED25519_SPKI_PREFIX.length).equals(ED25519_SPKI_PREFIX)) {
34
+ return base64UrlEncode(spkiBuffer.subarray(ED25519_SPKI_PREFIX.length));
35
+ }
36
+ return base64UrlEncode(spkiBuffer);
37
+ };
38
+ const fingerprintPublicKey = (spkiBuffer) => {
39
+ let rawKey = spkiBuffer;
40
+ if (spkiBuffer.length === ED25519_SPKI_PREFIX.length + 32 &&
41
+ spkiBuffer.subarray(0, ED25519_SPKI_PREFIX.length).equals(ED25519_SPKI_PREFIX)) {
42
+ rawKey = spkiBuffer.subarray(ED25519_SPKI_PREFIX.length);
43
+ }
44
+ return sha256Hex(rawKey);
45
+ };
46
+ const buildDeviceAuthPayload = ({ deviceId, clientId, clientMode, role, scopes, signedAt, token, nonce, }) => {
47
+ const version = nonce ? "v2" : "v1";
48
+ const scopeStr = scopes.join(",");
49
+ const base = [
50
+ version,
51
+ deviceId,
52
+ clientId,
53
+ clientMode,
54
+ role,
55
+ scopeStr,
56
+ String(signedAt),
57
+ token || "",
58
+ ];
59
+ if (version === "v2")
60
+ base.push(nonce || "");
61
+ return {
62
+ version,
63
+ value: base.join("|"),
64
+ };
65
+ };
66
+ const signDevicePayload = (privateKeyBase64Url, payload) => {
67
+ const message = Buffer.from(payload, "utf8");
68
+ const privateKeyBuffer = base64UrlDecode(privateKeyBase64Url);
69
+ const key = (0, node_crypto_1.createPrivateKey)({ key: privateKeyBuffer, format: "der", type: "pkcs8" });
70
+ const signature = (0, node_crypto_1.sign)(null, message, key);
71
+ return base64UrlEncode(signature);
72
+ };
73
+ const loadOrCreateIdentity = async () => {
74
+ try {
75
+ const stored = await node_fs_1.promises.readFile(STORAGE_FILE, "utf8");
76
+ const parsed = JSON.parse(stored);
77
+ if (!parsed?.deviceId || !parsed?.publicKey || !parsed?.privateKey) {
78
+ throw new Error("invalid_device_identity");
79
+ }
80
+ return parsed;
81
+ }
82
+ catch {
83
+ await node_fs_1.promises.mkdir(STORAGE_DIR, { recursive: true });
84
+ const { publicKey, privateKey } = (0, node_crypto_1.generateKeyPairSync)("ed25519");
85
+ const publicKeyBuffer = publicKey.export({ type: "spki", format: "der" });
86
+ const privateKeyBuffer = privateKey.export({ type: "pkcs8", format: "der" });
87
+ const identity = {
88
+ deviceId: fingerprintPublicKey(publicKeyBuffer),
89
+ publicKey: publicKeyRawBase64Url(publicKeyBuffer),
90
+ privateKey: base64UrlEncode(privateKeyBuffer),
91
+ };
92
+ await node_fs_1.promises.writeFile(STORAGE_FILE, JSON.stringify(identity, null, 2), "utf8");
93
+ return identity;
94
+ }
95
+ };
96
+ const getBackoffMs = (attempt) => {
97
+ const base = Math.min(MAX_RECONNECT_MS, BASE_RECONNECT_MS * 2 ** attempt);
98
+ const jitter = Math.floor(base * Math.random() * 0.2);
99
+ return base + jitter;
100
+ };
101
+ const createGatewayClient = (options) => {
102
+ let socket = null;
103
+ let status = "idle";
104
+ let lastError = null;
105
+ let lastHelloAt = null;
106
+ let protocol = null;
107
+ let activeScopes = [...(options.scopes ?? ["operator.read", "operator.write"])];
108
+ let isManualClose = false;
109
+ let reconnectAttempt = 0;
110
+ let reconnectTimer = null;
111
+ let challengeTimer = null;
112
+ let helloTimer = null;
113
+ let connectRequestId = null;
114
+ const connectWaiters = new Set();
115
+ const pending = new Map();
116
+ const eventHandlers = new Set();
117
+ const clearTimer = (timer) => {
118
+ if (timer) {
119
+ clearTimeout(timer);
120
+ }
121
+ };
122
+ const clearHandshakeTimers = () => {
123
+ clearTimer(challengeTimer);
124
+ clearTimer(helloTimer);
125
+ challengeTimer = null;
126
+ helloTimer = null;
127
+ };
128
+ const getStatus = () => ({
129
+ status,
130
+ lastError,
131
+ lastHelloAt,
132
+ protocol,
133
+ scopes: [...activeScopes],
134
+ });
135
+ const updateStatus = (next, error) => {
136
+ status = next;
137
+ if (error !== undefined) {
138
+ lastError = error;
139
+ }
140
+ options.onStatus?.(getStatus());
141
+ };
142
+ const rejectAllPending = (reason) => {
143
+ for (const [id, entry] of pending.entries()) {
144
+ clearTimeout(entry.timer);
145
+ entry.reject(new Error(reason));
146
+ pending.delete(id);
147
+ }
148
+ };
149
+ const failConnectWaiters = (reason) => {
150
+ for (const waiter of connectWaiters) {
151
+ waiter.reject(new Error(reason));
152
+ }
153
+ connectWaiters.clear();
154
+ };
155
+ const shouldReconnect = (failedStatus, reason) => {
156
+ if (isManualClose) {
157
+ return false;
158
+ }
159
+ if (failedStatus === "failed_auth" || failedStatus === "failed_protocol") {
160
+ return false;
161
+ }
162
+ return options.shouldReconnect?.(failedStatus, reason) ?? true;
163
+ };
164
+ const scheduleReconnect = (failedStatus, reason) => {
165
+ if (!shouldReconnect(failedStatus, reason)) {
166
+ return;
167
+ }
168
+ if (reconnectTimer) {
169
+ return;
170
+ }
171
+ const delay = getBackoffMs(reconnectAttempt++);
172
+ reconnectTimer = setTimeout(() => {
173
+ reconnectTimer = null;
174
+ void openSocket();
175
+ }, delay);
176
+ };
177
+ const failAndMaybeReconnect = (failedStatus, reason) => {
178
+ clearHandshakeTimers();
179
+ updateStatus(failedStatus, reason);
180
+ rejectAllPending(reason);
181
+ failConnectWaiters(reason);
182
+ scheduleReconnect(failedStatus, reason);
183
+ };
184
+ const makeConnectParams = async (nonce) => {
185
+ const identity = await loadOrCreateIdentity();
186
+ const signedAt = Date.now();
187
+ const clientId = options.clientId ?? "openclaw-control-ui";
188
+ const clientMode = options.clientMode ?? "webchat";
189
+ const role = "operator";
190
+ const authPayload = buildDeviceAuthPayload({
191
+ deviceId: identity.deviceId,
192
+ clientId,
193
+ clientMode,
194
+ role,
195
+ scopes: activeScopes,
196
+ signedAt,
197
+ token: options.token,
198
+ nonce,
199
+ });
200
+ const signature = signDevicePayload(identity.privateKey, authPayload.value);
201
+ return {
202
+ minProtocol: MIN_PROTOCOL,
203
+ maxProtocol: MAX_PROTOCOL,
204
+ client: {
205
+ id: clientId,
206
+ version: options.clientVersion ?? "0.1.0",
207
+ platform: options.platform ?? "web",
208
+ mode: clientMode,
209
+ instanceId: (0, node_crypto_1.randomUUID)(),
210
+ },
211
+ role,
212
+ scopes: [...activeScopes],
213
+ caps: ["tool-events"],
214
+ commands: [],
215
+ permissions: {
216
+ "device.auth.version.v1": authPayload.version === "v1",
217
+ "device.auth.version.v2": authPayload.version === "v2",
218
+ },
219
+ auth: { token: options.token },
220
+ locale: options.locale ?? "en-US",
221
+ userAgent: `openclaw-control/${process.version} (${process.platform})`,
222
+ device: {
223
+ id: identity.deviceId,
224
+ publicKey: identity.publicKey,
225
+ signature,
226
+ signedAt,
227
+ nonce,
228
+ },
229
+ };
230
+ };
231
+ const sendConnectRequest = async (nonce) => {
232
+ if (!socket || socket.readyState !== ws_1.WebSocket.OPEN) {
233
+ throw new Error("socket_not_open");
234
+ }
235
+ if (connectRequestId) {
236
+ return;
237
+ }
238
+ const reqId = `connect-${Date.now()}-${(0, node_crypto_1.randomUUID)()}`;
239
+ const connectParams = await makeConnectParams(nonce);
240
+ const req = {
241
+ type: "req",
242
+ id: reqId,
243
+ method: "connect",
244
+ params: connectParams,
245
+ };
246
+ connectRequestId = reqId;
247
+ updateStatus("connect_sent", null);
248
+ socket.send(JSON.stringify(req));
249
+ helloTimer = setTimeout(() => {
250
+ failAndMaybeReconnect("failed_timeout", "hello_timeout");
251
+ socket?.close();
252
+ }, HELLO_TIMEOUT_MS);
253
+ };
254
+ const handleHandshakeResponse = (frame) => {
255
+ if (!connectRequestId || frame.id !== connectRequestId) {
256
+ return;
257
+ }
258
+ clearTimer(helloTimer);
259
+ helloTimer = null;
260
+ if (!frame.ok) {
261
+ const msg = `connect_failed:${JSON.stringify(frame.error ?? {})}`;
262
+ failAndMaybeReconnect("failed_auth", msg);
263
+ socket?.close();
264
+ return;
265
+ }
266
+ const payload = (frame.payload ?? {});
267
+ if (payload?.type && payload.type !== "hello-ok") {
268
+ failAndMaybeReconnect("failed_protocol", `invalid_hello_type:${String(payload.type)}`);
269
+ return;
270
+ }
271
+ const nextProtocol = Number(payload?.protocol ?? 0);
272
+ if (!Number.isFinite(nextProtocol) || nextProtocol < MIN_PROTOCOL || nextProtocol > MAX_PROTOCOL) {
273
+ failAndMaybeReconnect("failed_protocol", `protocol_mismatch:${String(payload?.protocol)}`);
274
+ return;
275
+ }
276
+ protocol = nextProtocol;
277
+ lastHelloAt = Date.now();
278
+ reconnectAttempt = 0;
279
+ connectRequestId = null;
280
+ updateStatus("ready", null);
281
+ for (const waiter of connectWaiters) {
282
+ waiter.resolve(payload);
283
+ }
284
+ connectWaiters.clear();
285
+ };
286
+ const handleMessage = async (raw) => {
287
+ let frame;
288
+ try {
289
+ frame = JSON.parse(raw.toString());
290
+ }
291
+ catch (error) {
292
+ options.onError?.(error);
293
+ return;
294
+ }
295
+ const rawFrame = frame;
296
+ const diagnosticFrame = (0, frame_sanitizer_1.sanitizeGatewayFrame)(rawFrame, { maxTextLength: 16384, maxTextTailChars: 16384 });
297
+ options.onRawFrame?.(rawFrame);
298
+ options.onFrame?.(diagnosticFrame);
299
+ if (frame.type === "event") {
300
+ const event = frame;
301
+ if (event.event === "connect.challenge") {
302
+ if (status !== "ws_open") {
303
+ return;
304
+ }
305
+ clearTimer(challengeTimer);
306
+ challengeTimer = null;
307
+ updateStatus("challenged", null);
308
+ const nonce = event.payload?.nonce;
309
+ try {
310
+ await sendConnectRequest(nonce);
311
+ }
312
+ catch (error) {
313
+ options.onError?.(error);
314
+ failAndMaybeReconnect("failed_transport", "connect_send_failed");
315
+ socket?.close();
316
+ }
317
+ }
318
+ for (const handler of eventHandlers) {
319
+ handler(event);
320
+ }
321
+ return;
322
+ }
323
+ if (frame.type === "res") {
324
+ const response = frame;
325
+ if (connectRequestId && response.id === connectRequestId) {
326
+ handleHandshakeResponse(response);
327
+ return;
328
+ }
329
+ const entry = pending.get(response.id);
330
+ if (entry) {
331
+ clearTimeout(entry.timer);
332
+ pending.delete(response.id);
333
+ entry.resolve(response);
334
+ }
335
+ }
336
+ };
337
+ const openSocket = async () => {
338
+ clearTimer(reconnectTimer);
339
+ reconnectTimer = null;
340
+ // S7 fix: 旧 socket 必须显式解绑 listener + close,避免重连时旧实例和监听器闭包滞留
341
+ if (socket) {
342
+ try {
343
+ socket.removeAllListeners();
344
+ }
345
+ catch { /* noop */ }
346
+ try {
347
+ socket.close();
348
+ }
349
+ catch { /* noop */ }
350
+ socket = null;
351
+ }
352
+ updateStatus("connecting", null);
353
+ connectRequestId = null;
354
+ socket = new ws_1.WebSocket(options.gatewayUrl, {
355
+ headers: { origin: CONTROL_UI_ORIGIN },
356
+ maxPayload: 8 * 1024 * 1024,
357
+ });
358
+ socket.on("open", () => {
359
+ updateStatus("ws_open", null);
360
+ options.onOpen?.();
361
+ challengeTimer = setTimeout(() => {
362
+ if (!connectRequestId) {
363
+ failAndMaybeReconnect("failed_timeout", "challenge_timeout");
364
+ socket?.close();
365
+ }
366
+ }, CHALLENGE_TIMEOUT_MS);
367
+ });
368
+ socket.on("message", (data) => {
369
+ void handleMessage(data);
370
+ });
371
+ socket.on("error", (error) => {
372
+ options.onError?.(error);
373
+ failAndMaybeReconnect("failed_transport", "socket_error");
374
+ socket?.close();
375
+ });
376
+ socket.on("close", (code, reason) => {
377
+ clearHandshakeTimers();
378
+ rejectAllPending(`socket_closed:${code}`);
379
+ if (status !== "failed_auth" && status !== "failed_protocol") {
380
+ updateStatus(isManualClose ? "closed" : "failed_transport", `closed:${code}`);
381
+ }
382
+ options.onClose?.(code, reason.toString());
383
+ // 握手阶段如果已经判定为鉴权/协议错误,close 只是前面主动断开的收尾动作。
384
+ // 这里不能再按 failed_transport 继续重连,否则 pairing required / scope-upgrade
385
+ // 会触发本地无限重连,持续打 127.0.0.1:18789。
386
+ if (!isManualClose && status !== "failed_auth" && status !== "failed_protocol") {
387
+ scheduleReconnect("failed_transport", `closed:${code}`);
388
+ }
389
+ });
390
+ };
391
+ const connect = async () => {
392
+ isManualClose = false;
393
+ if (status === "ready") {
394
+ return {
395
+ type: "hello-ok",
396
+ protocol: protocol ?? MIN_PROTOCOL,
397
+ };
398
+ }
399
+ const promise = new Promise((resolve, reject) => {
400
+ connectWaiters.add({ resolve, reject });
401
+ });
402
+ if (status === "idle" || status === "closed" || status.startsWith("failed")) {
403
+ await openSocket();
404
+ }
405
+ return promise;
406
+ };
407
+ const sendReq = async (method, params = {}, opts = {}) => {
408
+ if (status !== "ready" || !socket || socket.readyState !== ws_1.WebSocket.OPEN) {
409
+ throw new Error("gateway_not_ready");
410
+ }
411
+ const id = `${method}-${Date.now()}-${(0, node_crypto_1.randomUUID)()}`;
412
+ const timeoutMs = opts.timeoutMs ?? 15_000;
413
+ const finalParams = { ...params };
414
+ if (opts.sideEffect) {
415
+ finalParams.idempotencyKey = opts.idempotencyKey ?? (0, node_crypto_1.randomUUID)();
416
+ }
417
+ const request = {
418
+ type: "req",
419
+ id,
420
+ method,
421
+ params: finalParams,
422
+ };
423
+ options.onFrame?.((0, frame_sanitizer_1.sanitizeGatewayFrame)(request, { maxTextLength: 16384, maxTextTailChars: 16384 }));
424
+ const response = await new Promise((resolve, reject) => {
425
+ const timer = setTimeout(() => {
426
+ pending.delete(id);
427
+ reject(new Error(`request_timeout:${method}`));
428
+ }, timeoutMs);
429
+ pending.set(id, { resolve, reject, timer });
430
+ socket?.send(JSON.stringify(request));
431
+ });
432
+ if (!response.ok) {
433
+ throw new Error(`request_failed:${method}:${JSON.stringify(response.error ?? {})}`);
434
+ }
435
+ return response.payload;
436
+ };
437
+ const close = () => {
438
+ isManualClose = true;
439
+ clearHandshakeTimers();
440
+ clearTimer(reconnectTimer);
441
+ reconnectTimer = null;
442
+ rejectAllPending("gateway_closed");
443
+ failConnectWaiters("gateway_closed");
444
+ socket?.close();
445
+ updateStatus("closed", null);
446
+ };
447
+ const onEvent = (handler) => {
448
+ eventHandlers.add(handler);
449
+ return () => {
450
+ eventHandlers.delete(handler);
451
+ };
452
+ };
453
+ return {
454
+ connect,
455
+ close,
456
+ sendReq,
457
+ onEvent,
458
+ getStatus,
459
+ getSocket: () => socket,
460
+ };
461
+ };
462
+ exports.createGatewayClient = createGatewayClient;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./gateway-client"), exports);
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createGatewayClient = void 0;
4
+ require("dotenv/config");
5
+ const node_http_1 = require("node:http");
6
+ const node_crypto_1 = require("node:crypto");
7
+ const promises_1 = require("node:fs/promises");
8
+ const node_path_1 = require("node:path");
9
+ const gateway_1 = require("./gateway");
10
+ Object.defineProperty(exports, "createGatewayClient", { enumerable: true, get: function () { return gateway_1.createGatewayClient; } });
11
+ const create_app_context_1 = require("./app/create-app-context");
12
+ const user_config_1 = require("./app/user-config");
13
+ const api_handler_1 = require("./server/api-handler");
14
+ const ws_broker_1 = require("./transport/ws-broker");
15
+ const data_dir_1 = require("./app/data-dir");
16
+ const serverRuntimeIdentity = {
17
+ serverId: (0, node_crypto_1.randomUUID)(),
18
+ startedAt: new Date().toISOString(),
19
+ };
20
+ const getServerRuntimeDir = () => {
21
+ const override = process.env.TASKMELD_SERVER_RUNTIME_DIR?.trim();
22
+ if (override)
23
+ return override;
24
+ return (0, data_dir_1.resolveTaskMeldDataPath)("server");
25
+ };
26
+ const getServerRuntimeMetadataPath = () => (0, node_path_1.join)(getServerRuntimeDir(), "runtime.json");
27
+ const writeServerRuntimeMetadata = async (port) => {
28
+ const metadataPath = getServerRuntimeMetadataPath();
29
+ await (0, promises_1.mkdir)(getServerRuntimeDir(), { recursive: true });
30
+ await (0, promises_1.writeFile)(metadataPath, JSON.stringify({
31
+ serverId: serverRuntimeIdentity.serverId,
32
+ pid: process.pid,
33
+ port,
34
+ endpoint: `http://127.0.0.1:${port}`,
35
+ startedAt: serverRuntimeIdentity.startedAt,
36
+ }, null, 2));
37
+ };
38
+ const removeServerRuntimeMetadata = async () => {
39
+ await (0, promises_1.rm)(getServerRuntimeMetadataPath(), { force: true });
40
+ };
41
+ if (require.main === module) {
42
+ (async () => {
43
+ const gatewayConfig = await (0, user_config_1.resolveGatewayConfig)();
44
+ const appContext = (0, create_app_context_1.createAppContext)({
45
+ gatewayUrl: gatewayConfig.url ?? undefined,
46
+ gatewayToken: gatewayConfig.token ?? undefined,
47
+ });
48
+ const app = appContext.app;
49
+ appContext.gateway.setHandlers({
50
+ onStatus: (_status) => {
51
+ // silence intermediate status transitions
52
+ },
53
+ onError: (error) => {
54
+ console.error("gateway-error", error);
55
+ },
56
+ });
57
+ const apiServer = (0, node_http_1.createServer)((0, api_handler_1.createApiHandler)({
58
+ apiPort: appContext.api.port,
59
+ webOrigin: appContext.api.webOrigin,
60
+ app,
61
+ serverRuntimeIdentity: {
62
+ serverId: serverRuntimeIdentity.serverId,
63
+ pid: process.pid,
64
+ port: appContext.api.port,
65
+ endpoint: `http://127.0.0.1:${appContext.api.port}`,
66
+ startedAt: serverRuntimeIdentity.startedAt,
67
+ },
68
+ }));
69
+ const wsBroker = (0, ws_broker_1.createWsBroker)({
70
+ server: apiServer,
71
+ path: "/api/ws",
72
+ getBootstrapPayload: app.getBootstrapPayload,
73
+ });
74
+ app.runtime.setBroadcast(wsBroker.broadcast);
75
+ void appContext.initialize();
76
+ apiServer.listen(appContext.api.port, appContext.api.host, () => {
77
+ void writeServerRuntimeMetadata(appContext.api.port).catch((error) => {
78
+ console.error("server-runtime-metadata-write-failed", error);
79
+ });
80
+ console.log(`api-server-ready v${process.env.npm_package_version ?? "?.?.?"} http://${appContext.api.host}:${appContext.api.port}`);
81
+ });
82
+ if (appContext.gateway.url && appContext.gateway.token) {
83
+ appContext.gateway.connect()
84
+ .then((hello) => {
85
+ const sv = hello?.server;
86
+ console.log(`gateway-ready server=${sv?.version ?? "?"} conn=${sv?.connId ?? "?"} proto=v${hello?.protocol ?? "?"}`);
87
+ console.log(`taskmeld v${process.env.npm_package_version ?? "?.?.?"} running, Ctrl+C to stop`);
88
+ })
89
+ .catch((error) => {
90
+ console.error("gateway-connect-failed", error);
91
+ console.log(`taskmeld v${process.env.npm_package_version ?? "?.?.?"} running (gateway disconnected), Ctrl+C to stop`);
92
+ process.exitCode = 1;
93
+ });
94
+ }
95
+ else {
96
+ console.warn("gateway-connect-skipped missing OPENCLAW_GATEWAY_URL or OPENCLAW_GATEWAY_TOKEN");
97
+ console.log(`taskmeld v${process.env.npm_package_version ?? "?.?.?"} running (no gateway), Ctrl+C to stop`);
98
+ }
99
+ let shutdownStarted = false;
100
+ const shutdown = () => {
101
+ if (shutdownStarted)
102
+ return;
103
+ shutdownStarted = true;
104
+ wsBroker.close();
105
+ appContext.dispose();
106
+ void removeServerRuntimeMetadata().catch(() => { });
107
+ };
108
+ process.on("SIGTERM", () => {
109
+ shutdown();
110
+ process.exit(0);
111
+ });
112
+ process.on("SIGINT", () => {
113
+ shutdown();
114
+ process.exit(0);
115
+ });
116
+ process.on("exit", () => {
117
+ shutdown();
118
+ });
119
+ })().catch((error) => {
120
+ console.error("app-context-create-failed", error);
121
+ process.exit(1);
122
+ });
123
+ }