@elisym/cli 0.17.2 → 0.18.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.
package/dist/index.js CHANGED
@@ -27,6 +27,14 @@ var __export = (target, all) => {
27
27
  };
28
28
 
29
29
  // src/llm/providers/http.ts
30
+ function resolveLlmTimeoutMs() {
31
+ const raw = process.env.ELISYM_LLM_TIMEOUT_MS;
32
+ if (raw === void 0) {
33
+ return DEFAULT_LLM_TIMEOUT_MS;
34
+ }
35
+ const parsed = Number(raw);
36
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_LLM_TIMEOUT_MS;
37
+ }
30
38
  function createAbortError() {
31
39
  const err = new Error("The operation was aborted");
32
40
  err.name = "AbortError";
@@ -92,10 +100,11 @@ async function fetchWithRetry(url, init, signal) {
92
100
  await sleepWithSignal(delay, signal);
93
101
  }
94
102
  }
95
- var LLM_TIMEOUT_MS, MAX_RETRIES, RETRYABLE_STATUSES;
103
+ var DEFAULT_LLM_TIMEOUT_MS, LLM_TIMEOUT_MS, MAX_RETRIES, RETRYABLE_STATUSES;
96
104
  var init_http = __esm({
97
105
  "src/llm/providers/http.ts"() {
98
- LLM_TIMEOUT_MS = 12e4;
106
+ DEFAULT_LLM_TIMEOUT_MS = 6e5;
107
+ LLM_TIMEOUT_MS = resolveLlmTimeoutMs();
99
108
  MAX_RETRIES = 2;
100
109
  RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
101
110
  }
@@ -1977,7 +1986,6 @@ function createLogger(options = {}) {
1977
1986
  var payment = new SolanaPaymentStrategy();
1978
1987
  var LEDGER_GC_INTERVAL_MS = 60 * 60 * 1e3;
1979
1988
  var LEDGER_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
1980
- var TOTAL_JOB_TIMEOUT_MS = 5 * 60 * 1e3;
1981
1989
  var MAX_PAID_AGE_MS = 24 * 60 * 60 * 1e3;
1982
1990
  var SIG_PATH_TIMEOUT_MS = 60 * 1e3;
1983
1991
  function resolveJobPrice(tags, skills) {
@@ -2012,6 +2020,12 @@ var AgentUnavailableError = class extends Error {
2012
2020
  this.name = "AgentUnavailableError";
2013
2021
  }
2014
2022
  };
2023
+ var ExecutionBudgetExceededError = class extends Error {
2024
+ constructor(budgetMs) {
2025
+ super(`Execution exceeded budget (${Math.round(budgetMs / 1e3)}s)`);
2026
+ this.name = "ExecutionBudgetExceededError";
2027
+ }
2028
+ };
2015
2029
  function bodyLooksLikeBilling3(body) {
2016
2030
  const lower = body.toLowerCase();
2017
2031
  return BILLING_BODY_MARKERS3.some((marker) => lower.includes(marker));
@@ -2366,21 +2380,26 @@ var AgentRuntime = class {
2366
2380
  }
2367
2381
  this.transport.stop();
2368
2382
  }
2369
- /** Wrapper with total job timeout and error handling. */
2383
+ /**
2384
+ * Resolve a job's execution budget in milliseconds. Per-skill
2385
+ * `executionTimeoutSecs` wins over the agent-level `config.executionTimeoutSecs`;
2386
+ * `0` (explicit unlimited) and `undefined` both collapse to `0` => no timer.
2387
+ */
2388
+ resolveExecutionBudgetMs(skill) {
2389
+ const secs = skill.executionTimeoutSecs ?? this.config.executionTimeoutSecs ?? 0;
2390
+ return secs > 0 ? secs * 1e3 : 0;
2391
+ }
2392
+ /**
2393
+ * Wrapper with error handling. The execution budget (if any) is enforced
2394
+ * around `skill.execute` inside `executeJob`, not here - payment collection
2395
+ * and result delivery run on their own bounds. `jobAbort` is retained so
2396
+ * `stop()` can still abort an in-flight job.
2397
+ */
2370
2398
  async processJob(job) {
2371
- let timeoutId;
2372
2399
  const jobAbort = new AbortController();
2373
2400
  this.jobAbortControllers.add(jobAbort);
2374
2401
  try {
2375
- await Promise.race([
2376
- this.executeJob(job, jobAbort.signal),
2377
- new Promise((_, reject) => {
2378
- timeoutId = setTimeout(() => {
2379
- jobAbort.abort();
2380
- reject(new Error("Job processing timeout"));
2381
- }, TOTAL_JOB_TIMEOUT_MS);
2382
- })
2383
- ]);
2402
+ await this.executeJob(job, jobAbort.signal);
2384
2403
  } catch (e) {
2385
2404
  const log = this.callbacks.onLog ?? console.log;
2386
2405
  log(`[${job.jobId.slice(0, 8)}] Error: ${e.message}`);
@@ -2406,7 +2425,6 @@ var AgentRuntime = class {
2406
2425
  await this.transport.sendFeedback(job, { type: "error", message: safeMessage }).catch(() => {
2407
2426
  });
2408
2427
  } finally {
2409
- clearTimeout(timeoutId);
2410
2428
  this.jobAbortControllers.delete(jobAbort);
2411
2429
  }
2412
2430
  }
@@ -2468,6 +2486,21 @@ var AgentRuntime = class {
2468
2486
  }
2469
2487
  log(`[${job.jobId.slice(0, 8)}] Executing skill: ${skill.name}`);
2470
2488
  let output;
2489
+ const budgetMs = this.resolveExecutionBudgetMs(skill);
2490
+ let budgetExceeded = false;
2491
+ const execAbort = new AbortController();
2492
+ const onOuterAbort = () => execAbort.abort();
2493
+ if (signal) {
2494
+ if (signal.aborted) {
2495
+ execAbort.abort();
2496
+ } else {
2497
+ signal.addEventListener("abort", onOuterAbort);
2498
+ }
2499
+ }
2500
+ const budgetTimer = budgetMs > 0 ? setTimeout(() => {
2501
+ budgetExceeded = true;
2502
+ execAbort.abort();
2503
+ }, budgetMs) : void 0;
2471
2504
  try {
2472
2505
  output = await skill.execute(
2473
2506
  {
@@ -2476,14 +2509,25 @@ var AgentRuntime = class {
2476
2509
  tags: job.tags,
2477
2510
  jobId: job.jobId
2478
2511
  },
2479
- { ...this.skillCtx, signal }
2512
+ { ...this.skillCtx, signal: execAbort.signal }
2480
2513
  );
2481
2514
  } catch (err) {
2515
+ if (budgetExceeded) {
2516
+ log(`[${job.jobId.slice(0, 8)}] Execution exceeded budget (${budgetMs / 1e3}s)`);
2517
+ throw new ExecutionBudgetExceededError(budgetMs);
2518
+ }
2482
2519
  const flippedToUnhealthy = this.markHealthFromExecuteError(skill, err, log, job.jobId);
2483
2520
  if (flippedToUnhealthy) {
2484
2521
  throw new AgentUnavailableError();
2485
2522
  }
2486
2523
  throw err;
2524
+ } finally {
2525
+ if (budgetTimer) {
2526
+ clearTimeout(budgetTimer);
2527
+ }
2528
+ if (signal) {
2529
+ signal.removeEventListener("abort", onOuterAbort);
2530
+ }
2487
2531
  }
2488
2532
  this.ledger.markExecuted(job.jobId, output.data);
2489
2533
  log(`[${job.jobId.slice(0, 8)}] Skill completed, delivering result`);
@@ -2731,7 +2775,6 @@ var AgentRuntime = class {
2731
2775
  async recoverSingleJob(entry, log) {
2732
2776
  const recoveryAbort = new AbortController();
2733
2777
  this.jobAbortControllers.add(recoveryAbort);
2734
- const timeout = setTimeout(() => recoveryAbort.abort(), TOTAL_JOB_TIMEOUT_MS);
2735
2778
  try {
2736
2779
  const rawEvent = JSON.parse(entry.raw_event_json);
2737
2780
  const fakeJob = {
@@ -2788,22 +2831,30 @@ var AgentRuntime = class {
2788
2831
  return;
2789
2832
  }
2790
2833
  }
2791
- const output = await skill.execute(
2792
- {
2793
- data: entry.input,
2794
- inputType: entry.input_type,
2795
- tags: entry.tags,
2796
- jobId: entry.job_id
2797
- },
2798
- { ...this.skillCtx, signal: recoveryAbort.signal }
2799
- );
2834
+ const recoveryBudgetMs = this.resolveExecutionBudgetMs(skill);
2835
+ const budgetTimer = recoveryBudgetMs > 0 ? setTimeout(() => recoveryAbort.abort(), recoveryBudgetMs) : void 0;
2836
+ let output;
2837
+ try {
2838
+ output = await skill.execute(
2839
+ {
2840
+ data: entry.input,
2841
+ inputType: entry.input_type,
2842
+ tags: entry.tags,
2843
+ jobId: entry.job_id
2844
+ },
2845
+ { ...this.skillCtx, signal: recoveryAbort.signal }
2846
+ );
2847
+ } finally {
2848
+ if (budgetTimer) {
2849
+ clearTimeout(budgetTimer);
2850
+ }
2851
+ }
2800
2852
  this.ledger.markExecuted(entry.job_id, output.data);
2801
2853
  await this.transport.deliverResult(fakeJob, output.data, entry.net_amount);
2802
2854
  this.ledger.markDelivered(entry.job_id);
2803
2855
  log(`[${entry.job_id.slice(0, 8)}] Recovery: re-executed and delivered`);
2804
2856
  }
2805
2857
  } finally {
2806
- clearTimeout(timeout);
2807
2858
  this.jobAbortControllers.delete(recoveryAbort);
2808
2859
  }
2809
2860
  }
@@ -3163,6 +3214,9 @@ function buildCliSkill(parsed, entryPath, scriptEnv) {
3163
3214
  if (parsed.rateLimit) {
3164
3215
  skill.rateLimit = parsed.rateLimit;
3165
3216
  }
3217
+ if (parsed.executionTimeoutSecs !== void 0) {
3218
+ skill.executionTimeoutSecs = parsed.executionTimeoutSecs;
3219
+ }
3166
3220
  return skill;
3167
3221
  }
3168
3222
  function loadSkillsFromDir(skillsDir, options = {}) {
@@ -3940,7 +3994,10 @@ async function cmdStart(nameArg, options = {}) {
3940
3994
  recoveryMaxRetries: RECOVERY_MAX_RETRIES,
3941
3995
  recoveryIntervalSecs: RECOVERY_INTERVAL_SECS,
3942
3996
  network: walletNetwork,
3943
- solanaAddress
3997
+ solanaAddress,
3998
+ // Agent-level default execution budget; per-skill `max_execution_secs`
3999
+ // overrides it. Undefined => unlimited (operator-owned, no protocol default).
4000
+ executionTimeoutSecs: loaded.yaml.execution_timeout_secs
3944
4001
  };
3945
4002
  const rpcUrlForLog = stripRpcSecrets(process.env.SOLANA_RPC_URL ?? getRpcUrl());
3946
4003
  logger.debug(