@toren-run/core 0.1.1 → 0.1.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.
package/dist/loop.js CHANGED
@@ -129,14 +129,24 @@ async function runTaskLoopImpl(args) {
129
129
  else {
130
130
  // Crash window: call was issued but the response never landed. Re-issue (at-least-once).
131
131
  ptr += 1;
132
- response = await withSpan("toren.llm", { "gen_ai.request.model": request.model }, () => provider.complete(request));
132
+ response = await withSpan("toren.llm", { "gen_ai.request.model": request.model }, async (span) => {
133
+ const r = await provider.complete(request);
134
+ if (r.usage)
135
+ span.setAttributes({ "gen_ai.usage.input_tokens": r.usage.inputTokens, "gen_ai.usage.output_tokens": r.usage.outputTokens });
136
+ return r;
137
+ });
133
138
  await append([ev("LlmCallCompleted", { stepId: next.payload.stepId, response, usage: response.usage })]);
134
139
  }
135
140
  }
136
141
  else {
137
142
  const stepId = `s${head + 1}`;
138
143
  await append([ev("LlmCallStarted", { stepId, requestDigest: digest, model: request.model })]);
139
- response = await withSpan("toren.llm", { "gen_ai.request.model": request.model }, () => provider.complete(request));
144
+ response = await withSpan("toren.llm", { "gen_ai.request.model": request.model }, async (span) => {
145
+ const r = await provider.complete(request);
146
+ if (r.usage)
147
+ span.setAttributes({ "gen_ai.usage.input_tokens": r.usage.inputTokens, "gen_ai.usage.output_tokens": r.usage.outputTokens });
148
+ return r;
149
+ });
140
150
  await append([ev("LlmCallCompleted", { stepId, response, usage: response.usage })]);
141
151
  }
142
152
  if (response.usage) {
@@ -107,14 +107,15 @@ async function tickImpl(deps, runId) {
107
107
  const r = await deps.store.append(runId, "run", session.head, [ev("RunCompleted", { output })]);
108
108
  if (!r.ok)
109
109
  throw new RunLeaseLostError("run stream advanced concurrently");
110
- await deps.store.updateRun(runId, { status: "completed", output });
110
+ await deps.store.updateRun(runId, { status: "completed", output, error: null });
111
111
  await flush();
112
112
  return "completed";
113
113
  }
114
114
  catch (e) {
115
115
  if (e instanceof WorkflowBlocked) {
116
116
  await flush();
117
- await deps.store.updateRun(runId, { status: "running" });
117
+ // Parking cleanly is progress: a transient error recorded by a worker retry is stale now.
118
+ await deps.store.updateRun(runId, { status: "running", error: null });
118
119
  return "blocked";
119
120
  }
120
121
  if (e instanceof RunLeaseLostError)
package/dist/worker.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { type TickDeps } from "./orchestrator.js";
2
+ /** Exponential backoff for task retries: 0.2s doubling per attempt, capped at 60s. */
3
+ export declare const retryDelaySeconds: (attempt: number) => number;
2
4
  export interface WorkerOpts {
3
5
  concurrency?: number;
4
6
  visibilitySeconds?: number;
package/dist/worker.js CHANGED
@@ -2,6 +2,8 @@ import { randomUUID } from "node:crypto";
2
2
  import { InvalidationStormError, runTaskLoop, TaskLeaseLostError } from "./loop.js";
3
3
  import { findTaskSpec, tick } from "./orchestrator.js";
4
4
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
5
+ /** Exponential backoff for task retries: 0.2s doubling per attempt, capped at 60s. */
6
+ export const retryDelaySeconds = (attempt) => Math.min(60, 0.2 * 2 ** Math.max(0, attempt - 1));
5
7
  /**
6
8
  * Local worker runtime: in-process pollers over the orchestrator and task
7
9
  * queues (local binding). One instance serves one agent or a whole
@@ -165,7 +167,18 @@ export class LocalWorkerRuntime {
165
167
  await this.shared.queue.nack(d, { delaySeconds: 20 });
166
168
  return;
167
169
  }
168
- await this.shared.queue.nack(d, { delaySeconds: 0.2 });
170
+ // An outside dependency failed (a provider, a tool, the network). The
171
+ // retry is durability doing its job; the reason must not vanish into
172
+ // it. Record it on the run so jobs/console answer "why is it stuck"
173
+ // while the retries continue; a later success clears it.
174
+ try {
175
+ const reason = e instanceof Error ? e.message : String(e);
176
+ await deps.store.updateRun(msg.runId, {
177
+ error: `${reason}${msg.taskId ? ` (task ${msg.taskId}, attempt ${d.attempt})` : ""}`,
178
+ });
179
+ }
180
+ catch { /* the store may be the thing that failed */ }
181
+ await this.shared.queue.nack(d, { delaySeconds: retryDelaySeconds(d.attempt) });
169
182
  }
170
183
  catch {
171
184
  // Queue unreachable too: do nothing. The visibility timeout redelivers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toren-run/core",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {