@coinrithm/mcp-trading 0.7.15 → 0.7.17

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 (76) hide show
  1. package/CHANGELOG.md +885 -692
  2. package/README.md +486 -400
  3. package/dist/agent/bench/bench.d.ts +59 -0
  4. package/dist/agent/bench/bench.js +319 -0
  5. package/dist/agent/bench/cassette.d.ts +97 -0
  6. package/dist/agent/bench/cassette.js +181 -0
  7. package/dist/agent/bench/costs.d.ts +28 -0
  8. package/dist/agent/bench/costs.js +60 -0
  9. package/dist/agent/bench/labelBuilder.d.ts +123 -0
  10. package/dist/agent/bench/labelBuilder.js +282 -0
  11. package/dist/agent/bench/labels.d.ts +75 -0
  12. package/dist/agent/bench/labels.js +259 -0
  13. package/dist/agent/bench/recordingClient.d.ts +51 -0
  14. package/dist/agent/bench/recordingClient.js +193 -0
  15. package/dist/agent/bench/replayClient.d.ts +41 -0
  16. package/dist/agent/bench/replayClient.js +262 -0
  17. package/dist/agent/bench/report.d.ts +115 -0
  18. package/dist/agent/bench/report.js +435 -0
  19. package/dist/agent/capitalSizing.d.ts +10 -1
  20. package/dist/agent/capitalSizing.js +28 -4
  21. package/dist/agent/cli.d.ts +25 -0
  22. package/dist/agent/cli.js +300 -2
  23. package/dist/agent/client.d.ts +14 -0
  24. package/dist/agent/client.js +23 -6
  25. package/dist/agent/configurationWarnings.js +11 -0
  26. package/dist/agent/decision.d.ts +24 -16
  27. package/dist/agent/decision.js +92 -3
  28. package/dist/agent/decisionValidator.d.ts +31 -0
  29. package/dist/agent/decisionValidator.js +148 -3
  30. package/dist/agent/engine.d.ts +5 -3
  31. package/dist/agent/engine.js +5 -3
  32. package/dist/agent/futuresCloseOutcome.d.ts +19 -0
  33. package/dist/agent/futuresCloseOutcome.js +54 -0
  34. package/dist/agent/futuresEligibility.d.ts +13 -0
  35. package/dist/agent/futuresEligibility.js +43 -5
  36. package/dist/agent/observe.d.ts +28 -2
  37. package/dist/agent/observe.js +530 -90
  38. package/dist/agent/pmPaperCandidates.d.ts +17 -0
  39. package/dist/agent/pmPaperCandidates.js +125 -0
  40. package/dist/agent/pmPaperCycle.d.ts +16 -0
  41. package/dist/agent/pmPaperCycle.js +372 -0
  42. package/dist/agent/pmPaperDecision.d.ts +187 -0
  43. package/dist/agent/pmPaperDecision.js +101 -0
  44. package/dist/agent/pmPaperPolicy.d.ts +71 -0
  45. package/dist/agent/pmPaperPolicy.js +46 -0
  46. package/dist/agent/prompt.d.ts +15 -1
  47. package/dist/agent/prompt.js +187 -9
  48. package/dist/agent/promptTables.d.ts +1 -0
  49. package/dist/agent/promptTables.js +33 -0
  50. package/dist/agent/providerCapabilities.d.ts +7 -0
  51. package/dist/agent/providerCapabilities.js +27 -1
  52. package/dist/agent/providers.d.ts +26 -7
  53. package/dist/agent/providers.js +193 -48
  54. package/dist/agent/resolve.js +3 -0
  55. package/dist/agent/runner.d.ts +17 -1
  56. package/dist/agent/runner.js +210 -38
  57. package/dist/agent/setups.d.ts +2 -1
  58. package/dist/agent/setups.js +17 -16
  59. package/dist/agent/signals.d.ts +33 -0
  60. package/dist/agent/signals.js +89 -0
  61. package/dist/agent/skill.js +46 -0
  62. package/dist/agent/skillValidator.js +56 -0
  63. package/dist/agent/strictLint.js +30 -0
  64. package/dist/agent/templates.js +30 -30
  65. package/dist/agent/types.d.ts +180 -12
  66. package/dist/agent/universe.d.ts +78 -0
  67. package/dist/agent/universe.js +215 -0
  68. package/dist/agent/version.d.ts +1 -1
  69. package/dist/agent/version.js +1 -1
  70. package/dist/client.d.ts +14 -0
  71. package/dist/client.js +31 -1
  72. package/dist/http.js +1 -1
  73. package/dist/pmPaperV2.d.ts +81 -0
  74. package/dist/pmPaperV2.js +53 -0
  75. package/dist/tools.js +84 -6
  76. package/package.json +100 -97
@@ -3,7 +3,64 @@
3
3
  // single structured-JSON decision (parsed in decision.ts). No free-form tool
4
4
  // execution — the model only proposes; the runner disposes.
5
5
  import { retryAfterSeconds } from "../retryAfter.js";
6
- import { chatShapeFor, buildChatBody, DECISION_TOOL_NAME, NVIDIA_BASE_URL as CAP_NVIDIA_BASE_URL, } from "./providerCapabilities.js";
6
+ import { parseDecisionForContract, } from "./decision.js";
7
+ import { chatShapeFor, buildChatBody, withJsonContentTransport, canRetrySuperToolOutput, DECISION_TOOL_NAME, NVIDIA_BASE_URL as CAP_NVIDIA_BASE_URL, } from "./providerCapabilities.js";
8
+ // A token count as Anthropic reports it: a non-negative safe integer. Zero
9
+ // is a real count; anything else (missing, null, negative, NaN, fractional,
10
+ // a string) is malformed.
11
+ function exactCount(value) {
12
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0
13
+ ? value
14
+ : undefined;
15
+ }
16
+ // Anthropic usage, including prompt-cache reads and writes (and the 5m/1h
17
+ // write split) when reported. Strict, because paid metering prices it (root
18
+ // review of #118): input_tokens and output_tokens are mandatory, and a
19
+ // malformed reported field (mandatory or cache) makes the WHOLE usage unknown
20
+ // (undefined), never zero, so the call is metered uncertain instead of free.
21
+ // Cache fields the response does not carry at all stay absent. A reported
22
+ // write total must equal its 5m/1h split; a split without a total implies it.
23
+ export function anthropicUsage(raw) {
24
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
25
+ return undefined;
26
+ const u = raw;
27
+ const promptTokens = exactCount(u.input_tokens);
28
+ const completionTokens = exactCount(u.output_tokens);
29
+ if (promptTokens === undefined || completionTokens === undefined)
30
+ return undefined;
31
+ const usage = { promptTokens, completionTokens };
32
+ const optional = (value) => value === undefined ? undefined : (exactCount(value) ?? null);
33
+ const cacheRead = optional(u.cache_read_input_tokens);
34
+ const cacheWrite = optional(u.cache_creation_input_tokens);
35
+ let write5m;
36
+ let write1h;
37
+ if (u.cache_creation !== undefined) {
38
+ const split = u.cache_creation;
39
+ if (typeof split !== "object" || split === null || Array.isArray(split))
40
+ return undefined;
41
+ const s = split;
42
+ write5m = optional(s.ephemeral_5m_input_tokens);
43
+ write1h = optional(s.ephemeral_1h_input_tokens);
44
+ }
45
+ if ([cacheRead, cacheWrite, write5m, write1h].includes(null))
46
+ return undefined;
47
+ if (write5m != null || write1h != null) {
48
+ const splitTotal = (write5m ?? 0) + (write1h ?? 0);
49
+ if (cacheWrite != null && cacheWrite !== splitTotal)
50
+ return undefined;
51
+ usage.cacheWriteTokens = splitTotal;
52
+ if (write5m != null)
53
+ usage.cacheWrite5mTokens = write5m;
54
+ if (write1h != null)
55
+ usage.cacheWrite1hTokens = write1h;
56
+ }
57
+ else if (cacheWrite != null) {
58
+ usage.cacheWriteTokens = cacheWrite;
59
+ }
60
+ if (cacheRead != null)
61
+ usage.cacheReadTokens = cacheRead;
62
+ return usage;
63
+ }
7
64
  // NVIDIA NIM is OpenAI-compatible; the `nvidia` preset hard-wires the hosted
8
65
  // endpoint so an agent only needs `{ provider: nvidia, name: "<model id>" }`.
9
66
  const NVIDIA_BASE_URL = CAP_NVIDIA_BASE_URL;
@@ -31,6 +88,8 @@ const DEFAULT_TIMEOUT_MS = 300_000;
31
88
  const RETRYABLE_SERVER_STATUSES = new Set([500, 502, 503, 504]);
32
89
  /** Keep direct and routed cycle evidence consistent about NVIDIA backpressure. */
33
90
  export function classifyProviderFailure(result) {
91
+ if (result.failureClass === "malformed")
92
+ return "malformed";
34
93
  if (result.status === 429 ||
35
94
  (result.status === 503 &&
36
95
  /resourceexhausted|worker local total request limit/i.test(result.error ?? "")))
@@ -186,15 +245,27 @@ class AnthropicProvider {
186
245
  }
187
246
  const json = (await res.json());
188
247
  const text = json.content?.map((c) => c.text ?? "").join("") ?? "";
189
- const usage = json.usage
190
- ? {
191
- promptTokens: json.usage.input_tokens ?? 0,
192
- completionTokens: json.usage.output_tokens ?? 0,
193
- }
194
- : undefined;
248
+ // Absent or malformed usage is undefined (unknown), never zero.
249
+ const usage = anthropicUsage(json.usage);
250
+ if (json.stop_reason === "max_tokens" ||
251
+ json.stop_reason === "model_context_window_exceeded") {
252
+ return {
253
+ ok: false,
254
+ failureClass: "malformed",
255
+ error: json.stop_reason === "max_tokens"
256
+ ? "provider returned incomplete decision (output token limit)"
257
+ : "provider returned incomplete decision (context window limit)",
258
+ usage,
259
+ };
260
+ }
195
261
  return text
196
262
  ? { ok: true, text, usage, responseSource: "content" }
197
- : { ok: false, error: "anthropic returned empty content" };
263
+ : // Answered (and billed) with nothing usable: keep the usage.
264
+ {
265
+ ok: false,
266
+ error: "anthropic returned empty content",
267
+ ...(usage ? { usage } : {}),
268
+ };
198
269
  });
199
270
  }
200
271
  catch (err) {
@@ -213,24 +284,40 @@ class AnthropicProvider {
213
284
  }
214
285
  }
215
286
  }
287
+ function wantsJsonContent(options) {
288
+ const v = options.nemotronJsonContent;
289
+ if (typeof v !== "function")
290
+ return v === true;
291
+ try {
292
+ return v() === true;
293
+ }
294
+ catch {
295
+ return false;
296
+ }
297
+ }
216
298
  class OpenAiCompatProvider {
217
299
  provider;
218
300
  model;
219
301
  apiKey;
220
302
  baseUrl;
221
303
  fetchFn;
304
+ options;
222
305
  label;
223
- constructor(provider, model, apiKey, baseUrl, fetchFn) {
306
+ constructor(provider, model, apiKey, baseUrl, fetchFn, options = {}) {
224
307
  this.provider = provider;
225
308
  this.model = model;
226
309
  this.apiKey = apiKey;
227
310
  this.baseUrl = baseUrl;
228
311
  this.fetchFn = fetchFn;
312
+ this.options = options;
229
313
  this.label = `${baseUrl}/${model}`;
230
314
  }
231
315
  async decide(input) {
232
316
  const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
233
- const shape = chatShapeFor(this.provider, this.model, this.baseUrl);
317
+ const baseShape = chatShapeFor(this.provider, this.model, this.baseUrl);
318
+ const shape = wantsJsonContent(this.options)
319
+ ? withJsonContentTransport(baseShape)
320
+ : baseShape;
234
321
  let failureResponse;
235
322
  try {
236
323
  return await withProviderTimeout(timeoutMs, async (signal) => {
@@ -247,6 +334,7 @@ class OpenAiCompatProvider {
247
334
  user: input.user,
248
335
  maxTokens: input.maxTokens ?? 1024,
249
336
  excludeActionTypes: input.excludeActionTypes,
337
+ decisionContract: input.decisionContract,
250
338
  })),
251
339
  });
252
340
  if (!res.ok) {
@@ -273,6 +361,14 @@ class OpenAiCompatProvider {
273
361
  completionTokens: json.usage.completion_tokens ?? 0,
274
362
  }
275
363
  : undefined;
364
+ if (json.choices?.[0]?.finish_reason === "length") {
365
+ return {
366
+ ok: false,
367
+ failureClass: "malformed",
368
+ error: "provider returned incomplete decision (output token limit)",
369
+ usage,
370
+ };
371
+ }
276
372
  return text
277
373
  ? {
278
374
  ok: true,
@@ -303,20 +399,24 @@ class OpenAiCompatProvider {
303
399
  }
304
400
  }
305
401
  }
306
- // A direct NVIDIA 5xx previously lost the whole cycle until the next cadence.
307
- // Retry only an explicit server refusal, once, on the identical configured
308
- // route. The shared router owns its own attempt/capacity budget and MUST NOT
309
- // receive this wrapper. No auth/404/429, network, timeout or decision repair
310
- // retries; no model substitution or request-parameter changes.
402
+ // Direct NVIDIA recovery has one shared two-call/deadline budget. Explicit
403
+ // server refusals retry the original request; rejected Super tool encoding
404
+ // retries JSON content on the same configured route. The shared router owns
405
+ // its own admission budget and MUST NOT receive this wrapper. No model
406
+ // substitution or repair/coercion of a rejected decision is allowed.
311
407
  class SameModelRetryProvider {
312
408
  delegate;
313
409
  provider;
314
410
  model;
411
+ contentRetry;
412
+ baseUrl;
315
413
  label;
316
- constructor(delegate, provider, model) {
414
+ constructor(delegate, provider, model, contentRetry, baseUrl) {
317
415
  this.delegate = delegate;
318
416
  this.provider = provider;
319
417
  this.model = model;
418
+ this.contentRetry = contentRetry;
419
+ this.baseUrl = baseUrl;
320
420
  this.label = delegate.label;
321
421
  }
322
422
  async decide(input) {
@@ -324,56 +424,99 @@ class SameModelRetryProvider {
324
424
  const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
325
425
  const first = await this.delegate.decide(input);
326
426
  const firstFinished = Date.now();
327
- if (first.ok || !first.retryableHttpFailure) {
427
+ const parsedFirst = first.ok
428
+ ? parseDecisionForContract(first.text, input.decisionContract)
429
+ : undefined;
430
+ const retryContent = first.ok &&
431
+ parsedFirst?.ok === false &&
432
+ this.contentRetry !== undefined &&
433
+ canRetrySuperToolOutput({ provider: this.provider, model: this.model, baseUrl: this.baseUrl }, first.responseSource, parsedFirst.actionsStringDiagnostic);
434
+ if (!retryContent && (first.ok || !first.retryableHttpFailure)) {
328
435
  return first;
329
436
  }
330
- const delayMs = Math.max(1000, first.retryAfterMs ?? 0);
437
+ const delayMs = retryContent
438
+ ? 0
439
+ : Math.max(1000, first.ok ? 0 : (first.retryAfterMs ?? 0));
331
440
  // Honor the provider's cooldown; a long one waits for the next cycle. Both
332
441
  // attempts and this backoff share the ORIGINAL deadline, never two 5m caps.
333
442
  if (delayMs > 5000 || delayMs >= timeoutMs - (firstFinished - started)) {
334
443
  return first;
335
444
  }
336
- await new Promise((resolve) => setTimeout(resolve, delayMs));
445
+ if (delayMs > 0)
446
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
337
447
  const retryStarted = Date.now();
338
448
  const remainingMs = timeoutMs - (retryStarted - started);
339
449
  if (remainingMs <= 0)
340
450
  return first;
341
- const result = await this.delegate.decide({
451
+ const retryResult = await (retryContent ? this.contentRetry : this.delegate).decide({
342
452
  ...input,
343
453
  timeoutMs: remainingMs,
344
454
  });
345
- const attempt = (res, latencyMs) => ({
346
- provider: this.provider,
347
- model: this.model,
348
- outcome: res.ok ? "success" : "failed",
349
- latencyMs,
350
- ...(!res.ok
351
- ? {
352
- error: res.error,
353
- status: res.status,
354
- retryAfterMs: res.retryAfterMs,
355
- failureClass: classifyProviderFailure(res) === "capacity"
356
- ? "capacity"
357
- : res.status !== undefined && res.status < 500
358
- ? "permanent"
359
- : "transient",
360
- }
361
- : {}),
362
- });
455
+ const parsedRetry = retryResult.ok
456
+ ? parseDecisionForContract(retryResult.text, input.decisionContract)
457
+ : undefined;
458
+ const result = retryResult.ok && parsedRetry?.ok === false
459
+ ? {
460
+ ok: false,
461
+ failureClass: "malformed",
462
+ error: parsedRetry.error,
463
+ usage: retryResult.usage,
464
+ }
465
+ : retryResult;
466
+ const attempt = (res, latencyMs) => {
467
+ const parsed = res.ok
468
+ ? parseDecisionForContract(res.text, input.decisionContract)
469
+ : undefined;
470
+ return {
471
+ provider: this.provider,
472
+ model: this.model,
473
+ outcome: res.ok && parsed?.ok ? "success" : "failed",
474
+ latencyMs,
475
+ ...(res.ok ? { responseSource: res.responseSource } : {}),
476
+ ...(parsed?.ok === false
477
+ ? {
478
+ failureClass: "malformed",
479
+ error: parsed.error,
480
+ actionsStringDiagnostic: parsed.actionsStringDiagnostic,
481
+ }
482
+ : {}),
483
+ ...(!res.ok
484
+ ? {
485
+ error: res.error,
486
+ status: res.status,
487
+ retryAfterMs: res.retryAfterMs,
488
+ failureClass: res.failureClass ??
489
+ (classifyProviderFailure(res) === "capacity"
490
+ ? "capacity"
491
+ : res.status !== undefined && res.status < 500
492
+ ? "permanent"
493
+ : "transient"),
494
+ }
495
+ : {}),
496
+ };
497
+ };
498
+ // Sum reported usage from both physical calls, including rejected output.
499
+ // Missing usage is unknown, not evidence that a request consumed no tokens.
500
+ const usage = first.usage || result.usage
501
+ ? {
502
+ promptTokens: (first.usage?.promptTokens ?? 0) +
503
+ (result.usage?.promptTokens ?? 0),
504
+ completionTokens: (first.usage?.completionTokens ?? 0) +
505
+ (result.usage?.completionTokens ?? 0),
506
+ }
507
+ : undefined;
363
508
  return {
364
- // Usage, when present, describes only the final response. The preceding
365
- // 5xx did not report usage; attempt evidence is not total billed tokens
366
- // or a certificate that returned text passed the trading-decision parser.
367
509
  ...result,
510
+ ...(usage ? { usage } : {}),
368
511
  route: {
369
- policyVersion: "coinrithm.configured-same-model-retry.v2",
512
+ policyVersion: "coinrithm.configured-same-model-retry.v3",
370
513
  profile: "configured",
371
514
  effectiveProvider: this.provider,
372
515
  effectiveModel: this.model,
373
516
  reason: "configured_direct",
374
517
  attempts: [
375
518
  attempt(first, firstFinished - started),
376
- attempt(result, Date.now() - retryStarted),
519
+ attempt(retryResult, Date.now() - retryStarted),
377
520
  ],
378
521
  },
379
522
  };
@@ -382,7 +525,7 @@ class SameModelRetryProvider {
382
525
  // Build the provider from the spec's model block + env key. Throws a clear
383
526
  // error if no model is configured (self-host requires one) or the env key is
384
527
  // missing. fetch is injectable for tests.
385
- export function selectProvider(spec, env, fetchFn = fetch) {
528
+ export function selectProvider(spec, env, fetchFn = fetch, options = {}) {
386
529
  if (!spec.model) {
387
530
  throw new Error("no model configured: set model.provider + model.name in the agent (self-host needs an explicit model)");
388
531
  }
@@ -411,15 +554,17 @@ export function selectProvider(spec, env, fetchFn = fetch) {
411
554
  if (!resolvedBase) {
412
555
  throw new Error("openai-compatible provider needs model.baseUrl");
413
556
  }
414
- const direct = new OpenAiCompatProvider(provider, name, key, resolvedBase, fetchFn);
557
+ const direct = new OpenAiCompatProvider(provider, name, key, resolvedBase, fetchFn, options);
415
558
  return provider === "nvidia"
416
- ? new SameModelRetryProvider(direct, provider, name)
559
+ ? new SameModelRetryProvider(direct, provider, name, new OpenAiCompatProvider(provider, name, key, resolvedBase, fetchFn, {
560
+ nemotronJsonContent: true,
561
+ }), baseUrl)
417
562
  : direct;
418
563
  }
419
564
  // Build a provider for an EXPLICIT route + raw key (no spec, no env) — the
420
565
  // decision probe's entry point. Same classes as selectProvider, so a probe
421
566
  // exercises byte-identical request shapes to a real cycle.
422
- export function providerForRoute(route, apiKey, fetchFn = fetch) {
567
+ export function providerForRoute(route, apiKey, fetchFn = fetch, options = {}) {
423
568
  if (route.provider === "mechanical")
424
569
  return new MechanicalProvider(route.model);
425
570
  if (route.provider === "anthropic")
@@ -428,5 +573,5 @@ export function providerForRoute(route, apiKey, fetchFn = fetch) {
428
573
  if (!resolvedBase) {
429
574
  throw new Error("openai-compatible route needs a baseUrl");
430
575
  }
431
- return new OpenAiCompatProvider(route.provider, route.model, apiKey, resolvedBase, fetchFn);
576
+ return new OpenAiCompatProvider(route.provider, route.model, apiKey, resolvedBase, fetchFn, options);
432
577
  }
@@ -43,6 +43,9 @@ const CONFIG_BLOCKS = [
43
43
  "killSwitch",
44
44
  "objective",
45
45
  "capabilities",
46
+ "signals",
47
+ "data",
48
+ "universe",
46
49
  ];
47
50
  const JOURNAL_MAX_LINES = 200;
48
51
  const JOURNAL_MAX_BYTES = 8_000;
@@ -1,6 +1,7 @@
1
1
  import { CoinRithmClient, ProvenanceReport } from "./client.js";
2
2
  import { Provider } from "./providers.js";
3
- import { AgentSpec, RunState, CycleResult, Decision, ProposedAction, PmMarket, PostedOpportunity, QuoteEvidence } from "./types.js";
3
+ import { AgentSpec, RunState, CycleResult, Decision, PlannedAction, ProposedAction, PmMarket, PostedOpportunity, QuoteEvidence, LastRejection, TakeProfitRepair } from "./types.js";
4
+ import { type ObserveOptions } from "./observe.js";
4
5
  import { type DecisionInputRecord } from "./decisionReceipt.js";
5
6
  export interface RunnerDeps {
6
7
  client: CoinRithmClient;
@@ -13,6 +14,13 @@ export interface RunnerDeps {
13
14
  log?: (line: string) => void;
14
15
  /** Optional private storage hook. Its failure never changes cycle execution. */
15
16
  onDecisionInputRecord?: (record: DecisionInputRecord) => void;
17
+ /** Hosted shared-pool admission only; protective checks still run every cycle. */
18
+ minModelIntervalSeconds?: number;
19
+ /** Opt-in hosted canary; changes presentation only, preserving all values. */
20
+ compactPromptTables?: boolean;
21
+ /** Bench replay only (bench.ts): observation overrides. Live runs never
22
+ * set it. */
23
+ observeOptions?: ObserveOptions;
16
24
  }
17
25
  export declare function houseAgentForecastEnabled(): boolean;
18
26
  export declare function agentOpportunityCaptureEnabled(): boolean;
@@ -22,7 +30,15 @@ export declare function sanitizeForecastProbability(raw: unknown): number | unde
22
30
  export declare function repairFuturesTakeProfit(action: ProposedAction, quote?: QuoteEvidence, capitalMinimumRewardRisk?: number): {
23
31
  action: ProposedAction;
24
32
  repaired: boolean;
33
+ reason?: TakeProfitRepair["reason"];
25
34
  };
35
+ export declare const LAST_REJECTIONS_MAX = 5;
36
+ /**
37
+ * Pure: the rejected actions of one validated decision, newest decision only,
38
+ * for the next prompt. Undefined when nothing was rejected, so a fixed
39
+ * mistake drops out at once.
40
+ */
41
+ export declare function lastRejectionsOf(planned: readonly PlannedAction[], at: string): LastRejection[] | undefined;
26
42
  export declare function rationaleForAction(a: ProposedAction, decisionRationale: string | undefined, perActionSummary: string | undefined, totalActions: number): string | undefined;
27
43
  export declare function buildSkipOpportunity(decision: Decision, pmMarkets: PmMarket[], forecastEnabled: boolean): PostedOpportunity | null;
28
44
  export declare function runCycle(deps: RunnerDeps): Promise<CycleResult>;