@nifrajs/coding-agent 3.2.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/context.js +29 -1
  2. package/dist/context.js.map +1 -1
  3. package/dist/diff.js +38 -2
  4. package/dist/diff.js.map +1 -1
  5. package/dist/host.d.ts.map +1 -1
  6. package/dist/host.js +217 -24
  7. package/dist/host.js.map +1 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/isolated.js +37 -2
  13. package/dist/isolated.js.map +1 -1
  14. package/dist/native.d.ts +17 -0
  15. package/dist/native.d.ts.map +1 -1
  16. package/dist/native.js +265 -41
  17. package/dist/native.js.map +1 -1
  18. package/dist/replay.d.ts +4 -0
  19. package/dist/replay.d.ts.map +1 -1
  20. package/dist/replay.js +189 -24
  21. package/dist/replay.js.map +1 -1
  22. package/dist/rpc.d.ts +10 -0
  23. package/dist/rpc.d.ts.map +1 -1
  24. package/dist/rpc.js +208 -29
  25. package/dist/rpc.js.map +1 -1
  26. package/dist/sessions.d.ts +14 -2
  27. package/dist/sessions.d.ts.map +1 -1
  28. package/dist/sessions.js +253 -59
  29. package/dist/sessions.js.map +1 -1
  30. package/dist/verification.d.ts +23 -0
  31. package/dist/verification.d.ts.map +1 -1
  32. package/dist/verification.js +113 -1
  33. package/dist/verification.js.map +1 -1
  34. package/dist/workflows.d.ts +1 -0
  35. package/dist/workflows.d.ts.map +1 -1
  36. package/dist/workflows.js +43 -21
  37. package/dist/workflows.js.map +1 -1
  38. package/package.json +6 -5
  39. package/src/context.ts +32 -1
  40. package/src/diff.ts +40 -2
  41. package/src/host.ts +219 -24
  42. package/src/index.ts +5 -0
  43. package/src/isolated.ts +39 -2
  44. package/src/native.ts +408 -42
  45. package/src/replay.ts +206 -25
  46. package/src/rpc.ts +261 -33
  47. package/src/sessions.ts +271 -56
  48. package/src/verification.ts +140 -1
  49. package/src/workflows.ts +37 -25
package/src/workflows.ts CHANGED
@@ -88,6 +88,7 @@ export interface WorkflowResult {
88
88
  export class WorkflowRunner {
89
89
  private readonly options: Required<Pick<WorkflowRunnerOptions, "maxSteps" | "maxDepth">> &
90
90
  WorkflowRunnerOptions
91
+ private readonly signal: AbortSignal
91
92
  private readonly values = new Map<string, unknown>()
92
93
  private readonly completed: string[] = []
93
94
  private steps = 0
@@ -98,6 +99,7 @@ export class WorkflowRunner {
98
99
  maxSteps: options.maxSteps ?? 128,
99
100
  maxDepth: options.maxDepth ?? 8,
100
101
  }
102
+ this.signal = options.signal ?? new AbortController().signal
101
103
  if (!Number.isSafeInteger(this.options.maxSteps) || this.options.maxSteps < 1)
102
104
  throw new RangeError("workflow: maxSteps must be positive")
103
105
  if (!Number.isSafeInteger(this.options.maxDepth) || this.options.maxDepth < 1)
@@ -128,12 +130,12 @@ export class WorkflowRunner {
128
130
  }
129
131
  }
130
132
 
131
- private context(): WorkflowContext {
133
+ private context(depth: number): WorkflowContext {
132
134
  return {
133
- signal: this.options.signal ?? new AbortController().signal,
135
+ signal: this.signal,
134
136
  values: this.values,
135
137
  set: (name, value) => this.values.set(name, value),
136
- run: (step) => this.execute(step, 1),
138
+ run: (step) => this.execute(step, depth + 1),
137
139
  }
138
140
  }
139
141
 
@@ -143,51 +145,55 @@ export class WorkflowRunner {
143
145
  if (this.options.signal?.aborted) throw new Error("workflow cancelled")
144
146
  switch (step.type) {
145
147
  case "task":
146
- return this.single(step.id, () => step.run(this.context()))
148
+ return this.single(step.id, () => step.run(this.context(depth)))
147
149
  case "verify": {
148
- const result = await this.single(step.id, () => step.run(this.context()))
150
+ const result = await this.single(step.id, () => step.run(this.context(depth)))
149
151
  const ok = typeof result === "boolean" ? result : isVerificationResult(result) && result.ok
150
152
  if (!ok) throw new Error(`verification failed: ${step.id}`)
151
153
  return result
152
154
  }
153
155
  case "approve": {
154
156
  await this.emit({ type: "approval.required", id: step.id, reason: step.reason })
155
- const approved = await this.single(step.id, () => step.run(this.context()))
157
+ const approved = await this.single(step.id, () => step.run(this.context(depth)))
156
158
  if (approved !== true) throw new Error(`approval denied: ${step.id}`)
157
159
  return approved
158
160
  }
159
161
  case "checkpoint": {
160
- const result = await this.single(step.id, () => step.run(this.context()))
162
+ const result = await this.single(step.id, () => step.run(this.context(depth)))
161
163
  await this.emit({ type: "checkpoint.created", id: step.id })
162
164
  return result
163
165
  }
164
166
  case "handoff":
165
- return this.single(step.id, () => step.run(this.context()))
167
+ return this.single(step.id, () => step.run(this.context(depth)))
166
168
  case "sequence": {
167
169
  const results: unknown[] = []
168
170
  for (const child of step.steps) results.push(await this.execute(child, depth + 1))
169
171
  return results
170
172
  }
171
173
  case "parallel":
174
+ if (step.steps.length === 0) return []
172
175
  return this.parallel(step.steps, step.maxConcurrency ?? step.steps.length, depth)
173
176
  case "retry": {
174
177
  const attempts = step.attempts ?? 3
175
178
  if (!Number.isSafeInteger(attempts) || attempts < 1 || attempts > 16)
176
179
  throw new RangeError("workflow: attempts must be between 1 and 16")
180
+ const backoffMs = step.backoffMs ?? 0
181
+ if (!Number.isSafeInteger(backoffMs) || backoffMs < 0)
182
+ throw new RangeError("workflow: backoffMs must be a non-negative safe integer")
177
183
  let last: unknown
178
184
  for (let attempt = 0; attempt < attempts; attempt++) {
179
185
  try {
180
186
  return await this.execute(step.step, depth + 1)
181
187
  } catch (error) {
182
188
  last = error
183
- if (attempt + 1 < attempts && (step.backoffMs ?? 0) > 0)
184
- await delay(Math.min(step.backoffMs! * (attempt + 1), 10_000), this.options.signal)
189
+ if (attempt + 1 < attempts && backoffMs > 0)
190
+ await delay(Math.min(backoffMs * (attempt + 1), 10_000), this.options.signal)
185
191
  }
186
192
  }
187
193
  throw last instanceof Error ? last : new Error(String(last))
188
194
  }
189
195
  case "branch": {
190
- const selected = await this.single(step.id, () => step.when(this.context()))
196
+ const selected = await this.single(step.id, () => step.when(this.context(depth)))
191
197
  return selected === true
192
198
  ? this.execute(step.then, depth + 1)
193
199
  : step.otherwise === undefined
@@ -249,21 +255,27 @@ export class WorkflowRunner {
249
255
 
250
256
  async function delay(ms: number, signal: AbortSignal | undefined): Promise<void> {
251
257
  await new Promise<void>((resolve, reject) => {
252
- const timer = setTimeout(resolve, ms)
253
- if (signal === undefined) return
254
- if (signal.aborted) {
255
- clearTimeout(timer)
256
- reject(new Error("workflow cancelled"))
257
- return
258
+ let settled = false
259
+ let timer: ReturnType<typeof setTimeout> | undefined
260
+ let onAbort: (() => void) | undefined
261
+ const cleanup = (): void => {
262
+ if (timer !== undefined) clearTimeout(timer)
263
+ if (signal !== undefined && onAbort !== undefined)
264
+ signal.removeEventListener("abort", onAbort)
258
265
  }
259
- signal.addEventListener(
260
- "abort",
261
- () => {
262
- clearTimeout(timer)
263
- reject(new Error("workflow cancelled"))
264
- },
265
- { once: true },
266
- )
266
+ const settle = (error?: Error): void => {
267
+ if (settled) return
268
+ settled = true
269
+ cleanup()
270
+ if (error === undefined) resolve()
271
+ else reject(error)
272
+ }
273
+ timer = setTimeout(() => settle(), ms)
274
+ if (signal === undefined) return
275
+ onAbort = () => settle(new Error("workflow cancelled"))
276
+ signal.addEventListener("abort", onAbort, { once: true })
277
+ // Abort can race listener registration; check again after subscribing.
278
+ if (signal.aborted) onAbort()
267
279
  })
268
280
  }
269
281