@method-ai-hq/sdk 0.0.0-stage → 0.14.2

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 (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -2
  3. package/dist/packages/contracts/src/identity.d.ts +16 -0
  4. package/dist/packages/contracts/src/identity.js +41 -0
  5. package/dist/packages/contracts/src/method-package.d.ts +71 -0
  6. package/dist/packages/contracts/src/method-package.js +22 -0
  7. package/dist/packages/method-document/src/conversion-report.d.ts +138 -0
  8. package/dist/packages/method-document/src/conversion-report.js +23 -0
  9. package/dist/packages/sdk/examples/daily-briefing/README.md +31 -0
  10. package/dist/packages/sdk/examples/daily-briefing/TASK.md +3 -0
  11. package/dist/packages/sdk/examples/daily-briefing/approved-report.md +138 -0
  12. package/dist/packages/sdk/examples/daily-briefing/briefing_artifacts.py +39 -0
  13. package/dist/packages/sdk/examples/daily-briefing/briefing_check_inputs.py +47 -0
  14. package/dist/packages/sdk/examples/daily-briefing/briefing_files.py +31 -0
  15. package/dist/packages/sdk/examples/daily-briefing/briefing_manifest.py +23 -0
  16. package/dist/packages/sdk/examples/daily-briefing/briefing_markdown.py +67 -0
  17. package/dist/packages/sdk/examples/daily-briefing/briefing_progress.py +18 -0
  18. package/dist/packages/sdk/examples/daily-briefing/briefing_render.py +71 -0
  19. package/dist/packages/sdk/examples/daily-briefing/briefing_sessions.py +37 -0
  20. package/dist/packages/sdk/examples/daily-briefing/briefing_times.py +158 -0
  21. package/dist/packages/sdk/examples/daily-briefing/briefing_validation.py +101 -0
  22. package/dist/packages/sdk/examples/daily-briefing/briefing_website.py +215 -0
  23. package/dist/packages/sdk/examples/daily-briefing/checks.json +19 -0
  24. package/dist/packages/sdk/examples/daily-briefing/daily-briefing.method +158 -0
  25. package/dist/packages/sdk/examples/daily-briefing/inputs.json +1 -0
  26. package/dist/packages/sdk/examples/daily-briefing/package-lock.json +10 -0
  27. package/dist/packages/sdk/examples/daily-briefing/package.json +4 -0
  28. package/dist/packages/sdk/examples/daily-briefing/pyproject.toml +8 -0
  29. package/dist/packages/sdk/examples/daily-briefing/reader/reader.css +35 -0
  30. package/dist/packages/sdk/examples/daily-briefing/reader/reader.js +42 -0
  31. package/dist/packages/sdk/examples/daily-briefing/sample/context.json +1 -0
  32. package/dist/packages/sdk/examples/daily-briefing/sample/documents/document-001.md +219 -0
  33. package/dist/packages/sdk/examples/daily-briefing/sample/media.json +1 -0
  34. package/dist/packages/sdk/examples/daily-briefing/sample/metadata/documents.json +39 -0
  35. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0001.json +49 -0
  36. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0002.json +49 -0
  37. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0003.json +49 -0
  38. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0004.json +49 -0
  39. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0005.json +49 -0
  40. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0006.json +49 -0
  41. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0007.json +49 -0
  42. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0008.json +49 -0
  43. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0009.json +49 -0
  44. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0010.json +49 -0
  45. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0011.json +49 -0
  46. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0012.json +49 -0
  47. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0013.json +49 -0
  48. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0014.json +49 -0
  49. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0015.json +49 -0
  50. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0016.json +49 -0
  51. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0017.json +49 -0
  52. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0018.json +49 -0
  53. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0019.json +49 -0
  54. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0020.json +49 -0
  55. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0021.json +49 -0
  56. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0022.json +49 -0
  57. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0023.json +49 -0
  58. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0024.json +49 -0
  59. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0025.json +49 -0
  60. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0026.json +49 -0
  61. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0027.json +49 -0
  62. package/dist/packages/sdk/examples/daily-briefing/sample/records/chatgpt-0028.json +49 -0
  63. package/dist/packages/sdk/examples/daily-briefing/sample/sessions.json +1 -0
  64. package/dist/packages/sdk/examples/daily-briefing/sample/timeline.jsonl +28 -0
  65. package/dist/packages/sdk/examples/daily-briefing/sample/untimed.jsonl +0 -0
  66. package/dist/packages/sdk/examples/daily-briefing/sample-output.zip +0 -0
  67. package/dist/packages/sdk/examples/daily-briefing/sample-report.md +51 -0
  68. package/dist/packages/sdk/examples/daily-briefing/uv.lock +128 -0
  69. package/dist/packages/sdk/examples/daily-briefing/vendor/marked-LICENSE.md +44 -0
  70. package/dist/packages/sdk/examples/daily-briefing/vendor/marked.mjs +77 -0
  71. package/dist/packages/sdk/examples/files.json +126 -0
  72. package/dist/packages/sdk/examples/message-routing/README.md +23 -0
  73. package/dist/packages/sdk/examples/message-routing/TASK.md +5 -0
  74. package/dist/packages/sdk/examples/message-routing/choose-destination.mjs +10 -0
  75. package/dist/packages/sdk/examples/message-routing/create-ticket.mjs +15 -0
  76. package/dist/packages/sdk/examples/message-routing/inputs.json +1 -0
  77. package/dist/packages/sdk/examples/message-routing/message-routing.method +45 -0
  78. package/dist/packages/sdk/examples/message-routing/result.fixture.json +6 -0
  79. package/dist/packages/sdk/examples/message-routing/routing.mjs +29 -0
  80. package/dist/packages/sdk/examples/message-routing/routing.test.mjs +27 -0
  81. package/dist/packages/sdk/examples/message-routing/ticket.method +88 -0
  82. package/dist/packages/sdk/examples/message-routing/ticket.test.mjs +64 -0
  83. package/dist/packages/sdk/examples/notes-summary/README.md +23 -0
  84. package/dist/packages/sdk/examples/notes-summary/TASK.md +3 -0
  85. package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/case.json +36 -0
  86. package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/examples.json +16 -0
  87. package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/files/0 +10 -0
  88. package/dist/packages/sdk/examples/notes-summary/cases/dates-in-decisions/recording.json +195 -0
  89. package/dist/packages/sdk/examples/notes-summary/notes/2026-10-05-standup.md +4 -0
  90. package/dist/packages/sdk/examples/notes-summary/notes/2026-10-07-planning.md +3 -0
  91. package/dist/packages/sdk/examples/notes-summary/notes-summary.method +54 -0
  92. package/dist/packages/sdk/examples/notes-summary/out/.keep +0 -0
  93. package/dist/packages/sdk/examples/notes-summary/read-notes.mjs +4 -0
  94. package/dist/packages/sdk/examples/notes-summary/save.mjs +7 -0
  95. package/dist/packages/sdk/examples/outbound-management/README.md +119 -0
  96. package/dist/packages/sdk/examples/outbound-management/TASK.md +5 -0
  97. package/dist/packages/sdk/examples/outbound-management/check_plan.py +16 -0
  98. package/dist/packages/sdk/examples/outbound-management/check_sources.py +14 -0
  99. package/dist/packages/sdk/examples/outbound-management/crm.py +153 -0
  100. package/dist/packages/sdk/examples/outbound-management/crm_test.py +141 -0
  101. package/dist/packages/sdk/examples/outbound-management/fixtures/README.md +15 -0
  102. package/dist/packages/sdk/examples/outbound-management/fixtures/crm.json +54 -0
  103. package/dist/packages/sdk/examples/outbound-management/fixtures/day-1/messages.json +29 -0
  104. package/dist/packages/sdk/examples/outbound-management/fixtures/day-2/messages.json +38 -0
  105. package/dist/packages/sdk/examples/outbound-management/fixtures/observations.json +12 -0
  106. package/dist/packages/sdk/examples/outbound-management/fixtures/plan.json +69 -0
  107. package/dist/packages/sdk/examples/outbound-management/fixtures/receipt.json +1 -0
  108. package/dist/packages/sdk/examples/outbound-management/fixtures/state.json +99 -0
  109. package/dist/packages/sdk/examples/outbound-management/fixtures/tasks.md +16 -0
  110. package/dist/packages/sdk/examples/outbound-management/inputs.json +4 -0
  111. package/dist/packages/sdk/examples/outbound-management/outbound.method +277 -0
  112. package/dist/packages/sdk/examples/outbound-management/read_crm.py +4 -0
  113. package/dist/packages/sdk/examples/outbound-management/save_day.py +4 -0
  114. package/dist/packages/sdk/examples/outbound-management/starter/crm.json +11 -0
  115. package/dist/packages/sdk/examples/social-briefing/README.md +41 -0
  116. package/dist/packages/sdk/examples/social-briefing/TASK.md +5 -0
  117. package/dist/packages/sdk/examples/social-briefing/inputs.json +5 -0
  118. package/dist/packages/sdk/examples/social-briefing/result.fixture.json +14 -0
  119. package/dist/packages/sdk/examples/social-briefing/social-briefing.method +110 -0
  120. package/dist/packages/sdk/examples/support-triage/README.md +77 -0
  121. package/dist/packages/sdk/examples/support-triage/TASK.md +3 -0
  122. package/dist/packages/sdk/examples/support-triage/cases/double-charge-to-billing/case.json +40 -0
  123. package/dist/packages/sdk/examples/support-triage/cases/double-charge-to-billing/recording.json +460 -0
  124. package/dist/packages/sdk/examples/support-triage/fake-helpdesk.mjs +38 -0
  125. package/dist/packages/sdk/examples/support-triage/helpdesk.mjs +33 -0
  126. package/dist/packages/sdk/examples/support-triage/inputs.json +1 -0
  127. package/dist/packages/sdk/examples/support-triage/read-ticket.mjs +8 -0
  128. package/dist/packages/sdk/examples/support-triage/route.mjs +4 -0
  129. package/dist/packages/sdk/examples/support-triage/sample-tickets.json +5 -0
  130. package/dist/packages/sdk/examples/support-triage/set-ticket.mjs +9 -0
  131. package/dist/packages/sdk/examples/support-triage/support-triage.method +165 -0
  132. package/dist/packages/sdk/examples/support-triage/triage.mjs +38 -0
  133. package/dist/packages/sdk/examples/support-triage/triage.test.mjs +68 -0
  134. package/dist/packages/sdk/observers/mail.delivery/fetch.mjs +87 -0
  135. package/dist/packages/sdk/observers/mail.delivery/fixtures/bounce-by-recipient.json +1 -0
  136. package/dist/packages/sdk/observers/mail.delivery/fixtures/bounce.json +1 -0
  137. package/dist/packages/sdk/observers/mail.delivery/fixtures/delayed.json +1 -0
  138. package/dist/packages/sdk/observers/mail.delivery/fixtures/delivered.json +1 -0
  139. package/dist/packages/sdk/observers/mail.delivery/fixtures/empty.json +1 -0
  140. package/dist/packages/sdk/observers/mail.delivery/fixtures/other-message.json +1 -0
  141. package/dist/packages/sdk/observers/mail.delivery/judge.mjs +13 -0
  142. package/dist/packages/sdk/observers/mail.delivery/observer.json +14 -0
  143. package/dist/packages/sdk/package.json +232 -0
  144. package/dist/packages/sdk/src/authoring-example.d.ts +91 -0
  145. package/dist/packages/sdk/src/authoring-example.js +50 -0
  146. package/dist/packages/sdk/src/authoring-instructions.d.ts +20 -0
  147. package/dist/packages/sdk/src/authoring-instructions.js +151 -0
  148. package/dist/packages/sdk/src/authoring-prompt.d.ts +2 -0
  149. package/dist/packages/sdk/src/authoring-prompt.js +8 -0
  150. package/dist/packages/sdk/src/authoring.d.ts +10 -0
  151. package/dist/packages/sdk/src/authoring.js +287 -0
  152. package/dist/packages/sdk/src/bindings.d.ts +4 -0
  153. package/dist/packages/sdk/src/bindings.js +124 -0
  154. package/dist/packages/sdk/src/bridge.d.ts +2 -0
  155. package/dist/packages/sdk/src/bridge.js +23 -0
  156. package/dist/packages/sdk/src/browser-runtime/pyproject.toml +5 -0
  157. package/dist/packages/sdk/src/browser-runtime/tools.json +189 -0
  158. package/dist/packages/sdk/src/browser-runtime/uv.lock +3894 -0
  159. package/dist/packages/sdk/src/browser-service.py +127 -0
  160. package/dist/packages/sdk/src/browser.d.ts +38 -0
  161. package/dist/packages/sdk/src/browser.js +203 -0
  162. package/dist/packages/sdk/src/capabilities.d.ts +13 -0
  163. package/dist/packages/sdk/src/capabilities.js +68 -0
  164. package/dist/packages/sdk/src/classification-client.d.ts +4 -0
  165. package/dist/packages/sdk/src/classification-client.js +27 -0
  166. package/dist/packages/sdk/src/cloud.d.ts +23 -0
  167. package/dist/packages/sdk/src/cloud.js +43 -0
  168. package/dist/packages/sdk/src/computer-settings.d.ts +28 -0
  169. package/dist/packages/sdk/src/computer-settings.js +57 -0
  170. package/dist/packages/sdk/src/connect.d.ts +47 -0
  171. package/dist/packages/sdk/src/connect.js +203 -0
  172. package/dist/packages/sdk/src/current-inspection.d.ts +3 -0
  173. package/dist/packages/sdk/src/current-inspection.js +146 -0
  174. package/dist/packages/sdk/src/current-runtime.d.ts +14 -0
  175. package/dist/packages/sdk/src/current-runtime.js +167 -0
  176. package/dist/packages/sdk/src/doctor.d.ts +17 -0
  177. package/dist/packages/sdk/src/doctor.js +68 -0
  178. package/dist/packages/sdk/src/executable.d.ts +2 -0
  179. package/dist/packages/sdk/src/executable.js +22 -0
  180. package/dist/packages/sdk/src/explain-effects.d.ts +32 -0
  181. package/dist/packages/sdk/src/explain-effects.js +192 -0
  182. package/dist/packages/sdk/src/explain.d.ts +115 -0
  183. package/dist/packages/sdk/src/explain.js +441 -0
  184. package/dist/packages/sdk/src/files.d.ts +9 -0
  185. package/dist/packages/sdk/src/files.js +50 -0
  186. package/dist/packages/sdk/src/guard-keys.d.ts +5 -0
  187. package/dist/packages/sdk/src/guard-keys.js +42 -0
  188. package/dist/packages/sdk/src/hosted-models.d.ts +49 -0
  189. package/dist/packages/sdk/src/hosted-models.js +81 -0
  190. package/dist/packages/sdk/src/improve.d.ts +55 -0
  191. package/dist/packages/sdk/src/improve.js +159 -0
  192. package/dist/packages/sdk/src/index.d.ts +13 -0
  193. package/dist/packages/sdk/src/index.js +13 -0
  194. package/dist/packages/sdk/src/inspect.d.ts +6 -0
  195. package/dist/packages/sdk/src/inspect.js +17 -0
  196. package/dist/packages/sdk/src/link.d.ts +12 -0
  197. package/dist/packages/sdk/src/link.js +53 -0
  198. package/dist/packages/sdk/src/local-cli.d.ts +24 -0
  199. package/dist/packages/sdk/src/local-cli.js +146 -0
  200. package/dist/packages/sdk/src/local-setup.d.ts +15 -0
  201. package/dist/packages/sdk/src/local-setup.js +42 -0
  202. package/dist/packages/sdk/src/method-client.d.ts +30 -0
  203. package/dist/packages/sdk/src/method-client.js +250 -0
  204. package/dist/packages/sdk/src/method-files.d.ts +29 -0
  205. package/dist/packages/sdk/src/method-files.js +93 -0
  206. package/dist/packages/sdk/src/method-help.d.ts +19 -0
  207. package/dist/packages/sdk/src/method-help.js +404 -0
  208. package/dist/packages/sdk/src/method-issues.d.ts +86 -0
  209. package/dist/packages/sdk/src/method-issues.js +192 -0
  210. package/dist/packages/sdk/src/method-sync.d.ts +46 -0
  211. package/dist/packages/sdk/src/method-sync.js +293 -0
  212. package/dist/packages/sdk/src/method.d.ts +5 -0
  213. package/dist/packages/sdk/src/method.js +359 -0
  214. package/dist/packages/sdk/src/model-checks.d.ts +96 -0
  215. package/dist/packages/sdk/src/model-checks.js +181 -0
  216. package/dist/packages/sdk/src/outbox.d.ts +120 -0
  217. package/dist/packages/sdk/src/outbox.js +297 -0
  218. package/dist/packages/sdk/src/own-model-key.d.ts +22 -0
  219. package/dist/packages/sdk/src/own-model-key.js +76 -0
  220. package/dist/packages/sdk/src/prepare.d.ts +24 -0
  221. package/dist/packages/sdk/src/prepare.js +245 -0
  222. package/dist/packages/sdk/src/process.d.ts +15 -0
  223. package/dist/packages/sdk/src/process.js +107 -0
  224. package/dist/packages/sdk/src/production.d.ts +182 -0
  225. package/dist/packages/sdk/src/production.js +284 -0
  226. package/dist/packages/sdk/src/progress.d.ts +12 -0
  227. package/dist/packages/sdk/src/progress.js +48 -0
  228. package/dist/packages/sdk/src/proposals.d.ts +73 -0
  229. package/dist/packages/sdk/src/proposals.js +284 -0
  230. package/dist/packages/sdk/src/quality.d.ts +82 -0
  231. package/dist/packages/sdk/src/quality.js +294 -0
  232. package/dist/packages/sdk/src/result-files.d.ts +5 -0
  233. package/dist/packages/sdk/src/result-files.js +149 -0
  234. package/dist/packages/sdk/src/result-transfer.d.ts +21 -0
  235. package/dist/packages/sdk/src/result-transfer.js +37 -0
  236. package/dist/packages/sdk/src/run-data.d.ts +11 -0
  237. package/dist/packages/sdk/src/run-data.js +80 -0
  238. package/dist/packages/sdk/src/run-method.d.ts +2 -0
  239. package/dist/packages/sdk/src/run-method.js +2 -0
  240. package/dist/packages/sdk/src/run-saved.d.ts +5 -0
  241. package/dist/packages/sdk/src/run-saved.js +138 -0
  242. package/dist/packages/sdk/src/run-worker.d.ts +6 -0
  243. package/dist/packages/sdk/src/run-worker.js +131 -0
  244. package/dist/packages/sdk/src/secrets.d.ts +37 -0
  245. package/dist/packages/sdk/src/secrets.js +196 -0
  246. package/dist/packages/sdk/src/update.d.ts +5 -0
  247. package/dist/packages/sdk/src/update.js +39 -0
  248. package/dist/packages/sdk/src/versions.d.ts +58 -0
  249. package/dist/packages/sdk/src/versions.js +144 -0
  250. package/dist/packages/sdk/src/worker.d.ts +120 -0
  251. package/dist/packages/sdk/src/worker.js +443 -0
  252. package/dist/packages/workflow-language/src/execution.d.ts +1 -0
  253. package/dist/packages/workflow-language/src/execution.js +1 -0
  254. package/dist/packages/workflow-language/src/inspection.d.ts +990 -0
  255. package/dist/packages/workflow-language/src/inspection.js +130 -0
  256. package/dist/packages/workflow-language/src/result-files.d.ts +160 -0
  257. package/dist/packages/workflow-language/src/result-files.js +67 -0
  258. package/dist/packages/workflow-language/src/schema.d.ts +207 -0
  259. package/dist/packages/workflow-language/src/schema.js +56 -0
  260. package/dist/packages/workflow-language/src/validate.d.ts +11 -0
  261. package/dist/packages/workflow-language/src/validate.js +77 -0
  262. package/package.json +230 -4
@@ -0,0 +1,443 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { hostname } from "node:os";
5
+ import { dirname, join, relative, resolve, sep } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { parseArgs } from "node:util";
8
+ import { gunzipSync, gzipSync } from "node:zlib";
9
+ import { writePrivateJson } from "./files.js";
10
+ import { DEFAULT_SERVER } from "./method-client.js";
11
+ import { methodCache } from "./prepare.js";
12
+ import { open, openBytes, seal, sealBytes } from "./run-data.js";
13
+ const sleep = (ms, signal) => new Promise(done => {
14
+ const timer = setTimeout(done, ms);
15
+ signal?.addEventListener("abort", () => { clearTimeout(timer); done(); }, { once: true });
16
+ });
17
+ export class ProductionWorker {
18
+ api;
19
+ options;
20
+ id;
21
+ done = Promise.resolve();
22
+ stopping = new AbortController();
23
+ active = new Map();
24
+ cancelActive = false;
25
+ constructor(api, options = {}) {
26
+ this.api = api;
27
+ this.options = options;
28
+ this.id = options.workerId ?? `${hostname()}-${process.pid}-${randomBytes(3).toString("hex")}`;
29
+ }
30
+ get running() { return !this.stopping.signal.aborted; }
31
+ start() {
32
+ const slots = Math.max(1, Math.min(32, Math.floor(this.options.concurrency ?? 1)));
33
+ this.done = Promise.all(Array.from({ length: slots }, () => this.loop())).then(() => undefined);
34
+ return this;
35
+ }
36
+ /** Stop claiming. Running runs finish, unless cancel is true: then they stop and go back to the queue. */
37
+ async stop(options = {}) {
38
+ this.stopping.abort();
39
+ if (options.cancel) {
40
+ this.cancelActive = true;
41
+ for (const controller of this.active.values())
42
+ controller.abort();
43
+ }
44
+ await this.done;
45
+ }
46
+ report(error) {
47
+ const value = error instanceof Error ? error : new Error(String(error));
48
+ if (this.options.onError)
49
+ this.options.onError(value);
50
+ else
51
+ process.stderr.write(`Method worker: ${value.message}\n`);
52
+ }
53
+ async loop() {
54
+ let backoff = 1_000;
55
+ const waitMs = this.options.waitMs ?? 25_000;
56
+ while (!this.stopping.signal.aborted) {
57
+ let claim;
58
+ try {
59
+ const methods = [...this.options.methods ?? []];
60
+ const response = await this.api.request("/v1/worker/claim", "POST", { worker_id: this.id, wait_ms: waitMs, ...(methods.length ? { methods } : {}) }, waitMs + 15_000, this.stopping.signal);
61
+ backoff = 1_000;
62
+ if (response.status === 204) {
63
+ if (!waitMs)
64
+ await sleep(3_000, this.stopping.signal);
65
+ continue;
66
+ }
67
+ claim = response.body;
68
+ }
69
+ catch (error) {
70
+ if (this.stopping.signal.aborted)
71
+ break;
72
+ // A key without the worker scope, or a revoked key, cannot recover by retrying.
73
+ if (error?.status === 401 || error?.status === 403) {
74
+ this.report(error);
75
+ this.stopping.abort();
76
+ break;
77
+ }
78
+ this.report(error);
79
+ await sleep(backoff, this.stopping.signal);
80
+ backoff = Math.min(backoff * 2, 30_000);
81
+ continue;
82
+ }
83
+ await this.handle(claim);
84
+ }
85
+ }
86
+ async handle(claim) {
87
+ const controller = new AbortController(), directory = resolve(this.options.directory ?? join(methodCache(), "worker"));
88
+ this.active.set(claim.run_id, controller);
89
+ let lost = false, cancelled = false, runDir = join(directory, claim.run_id);
90
+ const path = `/v1/worker/runs/${encodeURIComponent(claim.run_id)}`;
91
+ const heartbeat = setInterval(async () => {
92
+ try {
93
+ const response = await this.api.request(`${path}/heartbeat`, "POST", { lease_id: claim.lease_id });
94
+ if (response.body.cancel_requested && !cancelled) {
95
+ cancelled = true;
96
+ controller.abort();
97
+ }
98
+ }
99
+ catch (error) {
100
+ // Another worker holds the run now. Stop this copy and do not complete it.
101
+ if (error?.status === 409) {
102
+ lost = true;
103
+ controller.abort();
104
+ }
105
+ else
106
+ this.report(error);
107
+ }
108
+ }, Math.max(1_000, Math.floor(claim.lease_ms / 3)));
109
+ // The run itself keeps the process alive; the heartbeat alone must not.
110
+ heartbeat.unref();
111
+ let outcome;
112
+ try {
113
+ const opened = this.open(claim);
114
+ // A run that waited continues its folder. A worker that does not have the folder gets it from the server.
115
+ if (claim.resume && !existsSync(join(runDir, "checkpoint.json"))) {
116
+ if (!claim.snapshot)
117
+ throw Object.assign(Error("The run folder of this waiting run is not on this worker and was not saved."), { code: "run_folder_missing" });
118
+ runDir = await this.restore(claim, runDir);
119
+ }
120
+ outcome = await (this.options.execute ?? cliExecutor())(opened, { directory, runDir, signal: controller.signal, api: this.api });
121
+ }
122
+ catch (error) {
123
+ const final = ["wrong_key", "run_folder_missing", "invalid_sealed"].includes(error?.code);
124
+ outcome = { status: "failed", error: { code: error?.code ?? "worker_failed", message: String(error?.message ?? error) }, retryable: !final };
125
+ }
126
+ finally {
127
+ clearInterval(heartbeat);
128
+ this.active.delete(claim.run_id);
129
+ }
130
+ if (lost)
131
+ return;
132
+ if (outcome.status === "waiting" && !cancelled && !this.cancelActive) {
133
+ await this.wait(claim, runDir, outcome);
134
+ return;
135
+ }
136
+ if (outcome.status === "waiting")
137
+ outcome = cancelled ? { status: "cancelled", error: { code: "cancelled", message: "The run was cancelled." } }
138
+ : { status: "failed", error: { code: "worker_stopped", message: "The worker stopped." }, retryable: true };
139
+ if (cancelled && outcome.status !== "succeeded")
140
+ outcome = { status: "cancelled", error: { code: "cancelled", message: "The run was cancelled." } };
141
+ else if (this.cancelActive && outcome.status !== "succeeded")
142
+ outcome = { status: "failed", error: { code: "worker_stopped", message: "The worker stopped." }, retryable: true };
143
+ const done = outcome;
144
+ const finished = { run_id: claim.run_id, method_id: claim.method_id, version_id: claim.version_id, status: done.status,
145
+ run_data: claim.run_data, run_dir: runDir,
146
+ ...(done.result !== undefined ? { result: done.result } : {}), ...(done.error ? { error: done.error } : {}) };
147
+ // A requeued run is not finished; it runs again.
148
+ if (!(done.status === "failed" && done.retryable && claim.attempt < 3)) {
149
+ try {
150
+ await this.options.onResult?.(finished);
151
+ }
152
+ catch (error) {
153
+ this.report(error);
154
+ }
155
+ }
156
+ const body = { lease_id: claim.lease_id, status: done.status, ...(done.error ? { error: done.error } : {}),
157
+ ...(done.retryable ? { retryable: true } : {}),
158
+ // A device run's result stays on this computer.
159
+ ...(claim.run_data === "account" && done.result !== undefined ? { result: done.result } : {}) };
160
+ await this.send(`${path}/complete`, body);
161
+ }
162
+ async send(path, body) {
163
+ for (let attempt = 0;; attempt++) {
164
+ try {
165
+ await this.api.request(path, "POST", body);
166
+ return true;
167
+ }
168
+ catch (error) {
169
+ if (error?.status && error.status < 500 && error.status !== 429 || attempt >= 4) {
170
+ this.report(error);
171
+ return false;
172
+ }
173
+ await sleep(1_000 * 2 ** attempt);
174
+ }
175
+ }
176
+ }
177
+ /** Open sealed inputs and answers with this worker's credential. */
178
+ open(claim) {
179
+ const key = this.api.apiKey;
180
+ const inputs = claim.sealed_inputs ? open(key, "inputs", claim.method_id, claim.sealed_inputs) : claim.inputs;
181
+ const answers = Object.fromEntries(Object.entries(claim.answers ?? {}).map(([step, answer]) => [step, "sealed" in answer ? { outputs: open(key, "answer", claim.method_id, answer.sealed) } : answer]));
182
+ const { sealed_inputs: _sealed, ...rest } = claim;
183
+ return { ...rest, inputs, answers };
184
+ }
185
+ /** Save the run folder, report the question, and let go of the run. */
186
+ async wait(claim, runDir, outcome) {
187
+ const path = `/v1/worker/runs/${encodeURIComponent(claim.run_id)}`;
188
+ try {
189
+ const packed = packFolder(runDir);
190
+ const bytes = claim.run_data === "device" ? sealBytes(this.api.apiKey, claim.method_id, packed) : packed;
191
+ await this.api.bytes(`${path}/snapshot`, "PUT", bytes, { "method-lease": claim.lease_id });
192
+ }
193
+ catch (error) {
194
+ // Without a saved folder only this worker can continue the run. Report the question anyway.
195
+ this.report(new Error(`The run folder was not saved, so only this worker can continue run ${claim.run_id}: ${error.message}`));
196
+ }
197
+ const asked = claim.run_data === "device"
198
+ ? { sealed_question: seal(this.api.apiKey, "question", claim.method_id, { question: outcome.question.question, form: outcome.question.form }) }
199
+ : { question: outcome.question.question, form: outcome.question.form };
200
+ const sent = await this.send(`${path}/wait`, { lease_id: claim.lease_id, step: outcome.question.step, ...asked,
201
+ ...(outcome.timeoutMs ? { timeout_ms: outcome.timeoutMs } : {}) });
202
+ if (sent)
203
+ try {
204
+ await this.options.onWaiting?.({ run_id: claim.run_id, method_id: claim.method_id, question: outcome.question });
205
+ }
206
+ catch (error) {
207
+ this.report(error);
208
+ }
209
+ }
210
+ /** Get the saved run folder. It goes back to the path it had, when this computer can write there, so the run can resume. */
211
+ async restore(claim, fallback) {
212
+ const response = await this.api.bytes(`/v1/worker/runs/${encodeURIComponent(claim.run_id)}/snapshot`, "GET", undefined, { "method-lease": claim.lease_id });
213
+ const packed = claim.run_data === "device" ? openBytes(this.api.apiKey, claim.method_id, response) : response;
214
+ return unpackFolder(packed, fallback);
215
+ }
216
+ }
217
+ const skipped = (path) => /(^|\/)(source|node_modules|\.venv|__pycache__)(\/|$)/.test(path) || /\.log$/.test(path);
218
+ /** The run folder as gzip JSON: its own path and each file. */
219
+ export function packFolder(dir) {
220
+ const files = [];
221
+ const visit = (folder) => {
222
+ for (const name of readdirSync(folder)) {
223
+ const full = join(folder, name), rel = relative(dir, full).split(sep).join("/");
224
+ if (skipped(rel))
225
+ continue;
226
+ const stat = lstatSync(full);
227
+ if (stat.isDirectory())
228
+ visit(full);
229
+ else if (stat.isFile()) {
230
+ files.push({ path: rel, data: readFileSync(full).toString("base64") });
231
+ }
232
+ }
233
+ };
234
+ visit(dir);
235
+ return gzipSync(JSON.stringify({ schema: "method-run-folder/1", dir, files }));
236
+ }
237
+ export function unpackFolder(packed, fallback) {
238
+ const folder = JSON.parse(gunzipSync(packed).toString("utf8"));
239
+ if (folder.schema !== "method-run-folder/1")
240
+ throw Error("Unknown run folder format.");
241
+ let target = fallback;
242
+ // The run's saved configuration names its folder, so the original path lets the runtime resume it.
243
+ try {
244
+ mkdirSync(folder.dir, { recursive: true, mode: 0o700 });
245
+ target = folder.dir;
246
+ }
247
+ catch {
248
+ mkdirSync(fallback, { recursive: true, mode: 0o700 });
249
+ }
250
+ for (const file of folder.files) {
251
+ const path = resolve(target, file.path);
252
+ if (!path.startsWith(resolve(target) + sep))
253
+ throw Error("The saved run folder has a file outside the folder.");
254
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
255
+ writeFileSync(path, Buffer.from(file.data, "base64"), { mode: 0o600 });
256
+ }
257
+ return target;
258
+ }
259
+ /** The last question the runtime asked in a run folder: the human.required event. */
260
+ export function lastQuestion(dir) {
261
+ let lines;
262
+ try {
263
+ lines = readFileSync(join(dir, "events.jsonl"), "utf8").trim().split("\n");
264
+ }
265
+ catch {
266
+ return null;
267
+ }
268
+ for (const line of lines.reverse()) {
269
+ let event;
270
+ try {
271
+ event = JSON.parse(line);
272
+ }
273
+ catch {
274
+ continue;
275
+ }
276
+ if (event.event !== "human.required")
277
+ continue;
278
+ let form = event.schema;
279
+ if (!form) {
280
+ // A runtime that does not record the form: derive it from the step's out types.
281
+ try {
282
+ form = outputForm(JSON.parse(readFileSync(join(dir, "saved.method"), "utf8")).steps?.[event.step]?.out ?? {});
283
+ }
284
+ catch {
285
+ form = { type: "object", properties: {} };
286
+ }
287
+ }
288
+ return { step: `${event.step}:${event.iteration ?? 0}`, question: String(event.prompt ?? ""), form };
289
+ }
290
+ return null;
291
+ }
292
+ function outputForm(out) {
293
+ const one = (def) => {
294
+ const d = typeof def === "string" ? { type: def } : def ?? {};
295
+ const base = d.type === "number" ? { type: "number" } : d.type === "boolean" ? { type: "boolean" } : d.type === "list" ? { type: "array", items: d.fields ? outputForm(d.fields) : one(d.items) }
296
+ : d.type === "record" ? outputForm(d.fields ?? {}) : { type: "string" };
297
+ return d.description ? { ...base, description: d.description } : base;
298
+ };
299
+ return { type: "object", properties: Object.fromEntries(Object.entries(out).map(([k, v]) => [k, one(v)])), required: Object.keys(out), additionalProperties: false };
300
+ }
301
+ const finalCodes = new Set(["needs_input", "needs_update", "missing_secret", "invalid_inputs", "model_not_private"]);
302
+ /** Run a claim with `method run METHOD_ID --version VERSION_ID` in a child process, then read its summary. */
303
+ export function cliExecutor(options = {}) {
304
+ return async (claim, { runDir: dir, signal, api }) => {
305
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
306
+ // The run record uses the production run ID, so the dashboard links both.
307
+ if (!existsSync(join(dir, "request.json")))
308
+ writePrivateJson(join(dir, "request.json"), { run_id: claim.run_id, workflow_id: claim.method_id, version_id: claim.version_id, server: api.server });
309
+ const inputs = join(dir, "production-inputs.json");
310
+ writePrivateJson(inputs, claim.inputs);
311
+ // Answers of ask steps, in the runtime's --human format.
312
+ const answers = Object.entries(claim.answers ?? {});
313
+ const human = join(dir, "production-answers.json");
314
+ if (answers.length)
315
+ writePrivateJson(human, { steps: Object.fromEntries(answers.map(([step, answer]) => [step, { outputs: answer.outputs }])) });
316
+ const command = options.command ?? methodCommand();
317
+ const out = openSync(join(dir, "worker.stdout.log"), "a", 0o600), err = openSync(join(dir, "worker.stderr.log"), "a", 0o600);
318
+ // The CLI sign-in of this computer reaches the child through its saved credential, not the environment.
319
+ const credential = api.apiKey.startsWith("mk_live_") ? { METHOD_API_KEY: api.apiKey } : {};
320
+ const child = spawn(command[0], [...command.slice(1), "run", claim.method_id, "--version", claim.version_id, "--server", api.server,
321
+ "--run-dir", dir, "--inputs", inputs, "--resume", ...(answers.length ? ["--human", human] : [])], {
322
+ detached: true, stdio: ["ignore", out, err],
323
+ env: { ...process.env, ...credential, METHOD_RUN_WORKER: "1", METHOD_NO_EXPLAIN: "1" },
324
+ });
325
+ closeSync(out);
326
+ closeSync(err);
327
+ const stop = () => {
328
+ try {
329
+ process.kill(-child.pid, "SIGTERM");
330
+ }
331
+ catch { /* Already stopped. */ }
332
+ setTimeout(() => { try {
333
+ process.kill(-child.pid, "SIGKILL");
334
+ }
335
+ catch { /* Stopped. */ } }, 10_000).unref();
336
+ };
337
+ signal.addEventListener("abort", stop, { once: true });
338
+ const code = await new Promise((done, fail) => { child.once("error", fail); child.once("exit", done); })
339
+ .finally(() => signal.removeEventListener("abort", stop));
340
+ const read = (name) => { try {
341
+ return JSON.parse(readFileSync(join(dir, name), "utf8"));
342
+ }
343
+ catch {
344
+ return null;
345
+ } };
346
+ const summary = read("summary.json");
347
+ if (summary?.status === "completed" || summary?.status === "unconfirmed")
348
+ return { status: "succeeded", result: summary.result };
349
+ if (signal.aborted)
350
+ return { status: "cancelled", error: { code: "cancelled", message: "The run was stopped." } };
351
+ if (summary?.status === "needs_input") {
352
+ // An ask step: the run waits for an answer, until it is answered or cancelled, or for the step's limits.timeout_ms.
353
+ const question = lastQuestion(dir);
354
+ if (question) {
355
+ let timeoutMs;
356
+ try {
357
+ timeoutMs = JSON.parse(readFileSync(join(dir, "saved.method"), "utf8")).steps?.[question.step.split(":")[0]]?.limits?.timeout_ms;
358
+ }
359
+ catch { /* Default. */ }
360
+ return { status: "waiting", question, ...(typeof timeoutMs === "number" ? { timeoutMs } : {}) };
361
+ }
362
+ return { status: "failed", error: { code: "needs_input", message: String(summary.error ?? "The run needs input that a production worker cannot give.") } };
363
+ }
364
+ if (summary?.status === "failed")
365
+ return { status: "failed", error: { code: summary.code ?? "execution_failed", message: String(summary.error ?? "The run failed.") } };
366
+ const setup = read("setup-error.json");
367
+ if (setup)
368
+ return { status: "failed", error: { code: setup.code, message: String(setup.error) }, retryable: !finalCodes.has(setup.code) };
369
+ return { status: "failed", error: { code: "worker_failed", message: `The run process stopped (exit ${code}). See ${join(dir, "worker.stderr.log")}.` }, retryable: true };
370
+ };
371
+ }
372
+ function methodCommand() {
373
+ const source = import.meta.url.endsWith(".ts");
374
+ return [process.execPath, ...(source ? process.execArgv : []), fileURLToPath(new URL(`./method.${source ? "ts" : "js"}`, import.meta.url))];
375
+ }
376
+ /** `method worker` and `method keys`. */
377
+ export async function productionCommand(args, clientFactory) {
378
+ const print = (value) => process.stdout.write(JSON.stringify(value, null, 2) + "\n");
379
+ if (args[0] === "worker") {
380
+ const { values } = parseArgs({ args: args.slice(1), options: { method: { type: "string", multiple: true }, concurrency: { type: "string" }, server: { type: "string" },
381
+ // The Python library sends more Method IDs on stdin, one a line, as its app starts runs of them.
382
+ "methods-from-stdin": { type: "boolean" } } });
383
+ const concurrency = values.concurrency === undefined ? 1 : Number(values.concurrency);
384
+ if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 32)
385
+ throw Error("Use --concurrency 1 to 32.");
386
+ const { Method } = await import("./production.js");
387
+ // METHOD_API_KEY, or the CLI sign-in of this computer.
388
+ const method = new Method({ server: values.server ?? DEFAULT_SERVER, worker: false });
389
+ const methods = new Set(values.method ?? []);
390
+ // One JSON line for each finished run, and one for each run that waits for an answer. A device run's result is printed here only.
391
+ const worker = method.worker.start({ concurrency, ...(methods.size || values["methods-from-stdin"] ? { methods } : {}),
392
+ onResult: result => { process.stdout.write(JSON.stringify(result) + "\n"); },
393
+ onWaiting: run => { process.stdout.write(JSON.stringify({ run_id: run.run_id, status: "waiting", question: run.question }) + "\n"); } });
394
+ if (values["methods-from-stdin"]) {
395
+ process.stdin.setEncoding("utf8");
396
+ let pending = "";
397
+ process.stdin.on("data", chunk => {
398
+ pending += chunk;
399
+ const lines = pending.split("\n");
400
+ pending = lines.pop() ?? "";
401
+ for (const line of lines)
402
+ if (/^wf_[A-Za-z0-9_]+$/.test(line.trim()))
403
+ methods.add(line.trim());
404
+ });
405
+ }
406
+ process.stderr.write(`Method worker ${worker.id} is waiting for runs on ${method.api.server}. Press Ctrl+C to stop after the current runs.\n`);
407
+ let signals = 0;
408
+ const stop = () => { signals++; void worker.stop({ cancel: signals > 1 }); if (signals === 1)
409
+ process.stderr.write("Stopping after the current runs. Press Ctrl+C again to stop them now.\n"); };
410
+ process.on("SIGINT", stop);
411
+ process.on("SIGTERM", stop);
412
+ try {
413
+ await worker.done;
414
+ }
415
+ finally {
416
+ process.removeListener("SIGINT", stop);
417
+ process.removeListener("SIGTERM", stop);
418
+ process.stdin.pause();
419
+ }
420
+ return;
421
+ }
422
+ const { values, positionals } = parseArgs({ args: args.slice(1), allowPositionals: true, options: { name: { type: "string" }, server: { type: "string" } } });
423
+ const client = clientFactory(values.server ?? DEFAULT_SERVER);
424
+ if (!client.token())
425
+ await client.login();
426
+ const action = positionals[0];
427
+ if (action === "create") {
428
+ if (!values.name)
429
+ throw Error("Use method keys create --name NAME.");
430
+ const created = await client.request("/api/service-keys", "POST", { name: values.name });
431
+ print({ ...created, next: "Save key as METHOD_API_KEY and webhook_secret as METHOD_WEBHOOK_SECRET in your app's secret store. They are shown only once. To write the key to an app's .env without showing it, use method connect APP_FOLDER." });
432
+ return;
433
+ }
434
+ if (action === "list" || !action) {
435
+ print(await client.request("/api/service-keys"));
436
+ return;
437
+ }
438
+ if (action === "revoke" && positionals[1]) {
439
+ print(await client.request(`/api/service-keys/${encodeURIComponent(positionals[1])}/revoke`, "POST", {}));
440
+ return;
441
+ }
442
+ throw Error("Use method keys create --name NAME, method keys list, or method keys revoke KEY_ID.");
443
+ }
@@ -0,0 +1 @@
1
+ export { stepDependencies } from "./validate.js";
@@ -0,0 +1 @@
1
+ export { stepDependencies } from "./validate.js";