@foldrun/core 0.2.0 → 0.4.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 (85) hide show
  1. package/README.md +31 -10
  2. package/dist/src/approvals.d.ts +104 -1
  3. package/dist/src/approvals.js +192 -2
  4. package/dist/src/budget.d.ts +32 -0
  5. package/dist/src/budget.js +177 -0
  6. package/dist/src/catalog.js +2 -1
  7. package/dist/src/confine.js +6 -1
  8. package/dist/src/deploy.js +4 -2
  9. package/dist/src/egress.d.ts +19 -2
  10. package/dist/src/egress.js +2 -1
  11. package/dist/src/evals.d.ts +4 -0
  12. package/dist/src/evals.js +20 -11
  13. package/dist/src/flow-lint.d.ts +4 -0
  14. package/dist/src/flow-lint.js +108 -0
  15. package/dist/src/host-env.d.ts +8 -0
  16. package/dist/src/host-env.js +48 -0
  17. package/dist/src/json-schema.d.ts +12 -0
  18. package/dist/src/json-schema.js +202 -0
  19. package/dist/src/library.js +13 -1
  20. package/dist/src/notify.d.ts +79 -1
  21. package/dist/src/notify.js +243 -9
  22. package/dist/src/paths.d.ts +5 -0
  23. package/dist/src/paths.js +32 -0
  24. package/dist/src/platform.d.ts +112 -2
  25. package/dist/src/platform.js +20 -2
  26. package/dist/src/provider-health.d.ts +55 -0
  27. package/dist/src/provider-health.js +232 -0
  28. package/dist/src/run-container.d.ts +80 -4
  29. package/dist/src/run-container.js +176 -37
  30. package/dist/src/runner.d.ts +41 -5
  31. package/dist/src/runner.js +513 -81
  32. package/dist/src/script-tools.d.ts +12 -0
  33. package/dist/src/script-tools.js +10 -2
  34. package/dist/src/secret-health.d.ts +59 -0
  35. package/dist/src/secret-health.js +155 -0
  36. package/dist/src/secrets.d.ts +19 -1
  37. package/dist/src/secrets.js +44 -12
  38. package/dist/src/starter.js +4 -0
  39. package/dist/src/step-exec.d.ts +62 -6
  40. package/dist/src/step-exec.js +199 -68
  41. package/dist/src/store.d.ts +300 -3
  42. package/dist/src/store.js +574 -26
  43. package/dist/src/tar.js +2 -1
  44. package/dist/src/test-mode.d.ts +111 -0
  45. package/dist/src/test-mode.js +285 -0
  46. package/dist/src/tool-test.js +48 -21
  47. package/dist/src/translator.js +2 -1
  48. package/dist/src/trigger-log.d.ts +43 -0
  49. package/dist/src/trigger-log.js +122 -0
  50. package/dist/src/triggers.d.ts +20 -0
  51. package/dist/src/triggers.js +91 -10
  52. package/dist/src/webhook.d.ts +15 -3
  53. package/dist/src/webhook.js +11 -11
  54. package/package.json +6 -5
  55. package/src/approvals.ts +253 -2
  56. package/src/budget.ts +178 -0
  57. package/src/catalog.ts +2 -1
  58. package/src/confine.ts +6 -1
  59. package/src/deploy.ts +4 -1
  60. package/src/egress.ts +20 -4
  61. package/src/evals.ts +25 -9
  62. package/src/flow-lint.ts +118 -0
  63. package/src/host-env.ts +51 -0
  64. package/src/json-schema.ts +184 -0
  65. package/src/library.ts +13 -1
  66. package/src/notify.ts +272 -11
  67. package/src/paths.ts +29 -0
  68. package/src/platform.ts +119 -3
  69. package/src/provider-health.ts +274 -0
  70. package/src/run-container.ts +215 -35
  71. package/src/runner.ts +525 -73
  72. package/src/script-tools.ts +18 -2
  73. package/src/secret-health.ts +185 -0
  74. package/src/secrets.ts +44 -10
  75. package/src/starter.ts +4 -0
  76. package/src/step-exec.ts +176 -21
  77. package/src/storage.ts +8 -2
  78. package/src/store.ts +785 -25
  79. package/src/tar.ts +2 -1
  80. package/src/test-mode.ts +311 -0
  81. package/src/tool-test.ts +49 -20
  82. package/src/translator.ts +2 -1
  83. package/src/trigger-log.ts +159 -0
  84. package/src/triggers.ts +92 -10
  85. package/src/webhook.ts +17 -11
@@ -1,4 +1,5 @@
1
1
  import { singleWorkspace } from "./paths.ts";
2
+ import type { TestEffect } from "./test-mode.ts";
2
3
  /**
3
4
  * The frontmatter of one markdown file, as the runtime reads it. Exported
4
5
  * for the CLI: `foldrun probe` used to import gray-matter itself, which is
@@ -249,6 +250,12 @@ export interface AgentInfo {
249
250
  * list withholds all of them. */
250
251
  skills: string[] | null;
251
252
  secrets: string[];
253
+ /** `budget:` — the most this agent may spend in ONE run, in USD, across
254
+ * every step it takes in it; null is no cap. Per run and nothing else:
255
+ * an agent has no calendar, the workspace and the account do. */
256
+ budget: number | null;
257
+ /** What is wrong with the `budget:` line, or null. */
258
+ budgetProblem: string | null;
252
259
  }
253
260
  export declare const MODEL_TIERS: Record<string, string>;
254
261
  export type Tier = keyof typeof MODEL_TIERS;
@@ -291,11 +298,14 @@ export interface FlowStep {
291
298
  effort?: string;
292
299
  /** Pause for a human before running this step. */
293
300
  approve?: boolean;
294
- /** Run only if the previous results contain this text (case-insensitive).
295
- * Independent: every matching `when:` step in a group runs. */
301
+ /** Run only if the previous group's result has a line beginning with
302
+ * this marker (case-insensitive). The previous group is the nearest
303
+ * earlier one that produced a result. Independent: every matching
304
+ * `when:` step in a group runs. */
296
305
  when?: string;
297
306
  /** Exclusive routing: of a group's `case:` steps, only the FIRST whose
298
- * text appears in the previous results runs; the rest are routed past. */
307
+ * marker leads a line of the previous group's result runs; the rest are
308
+ * routed past. */
299
309
  case?: string;
300
310
  /** The route of last resort: runs only when none of its group's `case:`
301
311
  * steps matched. */
@@ -357,9 +367,29 @@ export interface FlowStep {
357
367
  * cannot, and hands the value to the next group losslessly — the narrow
358
368
  * move the grammar ADR allowed instead of variables. */
359
369
  output?: "json";
370
+ /** `schema:` — the JSON Schema an `output: json` value must satisfy,
371
+ * written inline (one-line JSON, or a YAML block under the key) and
372
+ * checked where the reply is; a value that does not fit fails the step
373
+ * with the field named. */
374
+ schema?: Record<string, unknown> | boolean;
375
+ /** `schema: <path>` — the same, in a file under the workspace, read when
376
+ * the step runs. */
377
+ schemaPath?: string;
378
+ /** `parallel:` — how many of an `each:` step's instances run at once.
379
+ * Unset is all of them, which is what fan-out always did. */
380
+ parallel?: number;
381
+ /** `max_turns:` — the most model turns this step may take before it is
382
+ * stopped, beside `budget:` (money) and `timeout:` (time). */
383
+ maxTurns?: number;
360
384
  /** 1-indexed line in the flow file. Diagnostics without a line make you
361
385
  * search; every real linter emits file:line. */
362
386
  line?: number;
387
+ /** What is wrong with an option's VALUE, in the author's words — `timeout:
388
+ * abc`, `retry: 9`, `each: columns`. The parser keeps the step (a run
389
+ * still has a shape) and the lint reports each of these as an error, so
390
+ * `foldrun check` fails on a value that would otherwise be read as
391
+ * something the author never wrote: `timeout: 15m` used to be one second. */
392
+ problems?: string[];
363
393
  }
364
394
  export interface FlowInfo {
365
395
  name: string;
@@ -378,12 +408,20 @@ export interface FlowInfo {
378
408
  * overlap) — a default that changed underfoot would surprise every flow
379
409
  * that legitimately runs in parallel. */
380
410
  overlap: "skip" | "queue" | null;
411
+ /** Where this flow's runs stand in the queue against everyone else's:
412
+ * "high" is claimed before the round-robin, "low" after it. Adds to the
413
+ * account's own priority; the account's cap still bounds how many run
414
+ * at once, so a high lane never starves the rest outright. Unset is
415
+ * normal — first come, fair across accounts. */
416
+ priority: "high" | "normal" | "low" | null;
381
417
  /** The most one run of this flow may spend, in USD. A literal, never an
382
418
  * expression — the cap is readable off the file, which is the property
383
419
  * the whole grammar exists to keep. Checked between groups: the group
384
420
  * that crosses it is the last one that runs. Null: no cap of its own
385
421
  * (the workspace's monthly `budget:` still applies). */
386
422
  budget: number | null;
423
+ /** What is wrong with the `budget:` line, or null. */
424
+ budgetProblem: string | null;
387
425
  /** `trigger: flow` — start when this other flow of the workspace finishes,
388
426
  * with `on:` saying which endings count. The finished run's summary and
389
427
  * result become this run's task. */
@@ -406,6 +444,50 @@ export interface FlowInfo {
406
444
  /** `trigger: storage` — a path prefix under storage/; a file landing
407
445
  * there starts the flow with the path as its task. */
408
446
  path: string | null;
447
+ /** `idempotency:` — the name of the field that identifies a delivery, so
448
+ * the same one twice starts one run. A header on the request
449
+ * ("x-github-delivery"), or "body:<key>" for a top-level field of a JSON
450
+ * body. A name, never an expression: the value is read from the delivery,
451
+ * not computed from it. Null: every delivery is its own event. */
452
+ idempotency: string | null;
453
+ /** `debounce:` — seconds of quiet before a fired trigger becomes a run.
454
+ * A burst of deliveries, storage writes or watched changes collapses into
455
+ * one run once they stop. The task is the LAST event's, since that is the
456
+ * state the flow is reacting to. Null: every event starts a run. */
457
+ debounce: number | null;
458
+ /** `throttle:` — the least time between two runs of this flow. A trigger
459
+ * arriving inside the window is dropped, and the trace of the run that
460
+ * held the window says how many. Null: no floor. */
461
+ throttle: number | null;
462
+ /** `catchup:` — what a scheduled flow does about a fire it missed while
463
+ * the platform was not running. "last" is the behaviour that always
464
+ * applied: one make-up run for however many were missed. "none" skips
465
+ * them, for a flow whose moment has passed by the time anyone notices.
466
+ * Null reads as "last". */
467
+ catchup: "last" | "none" | null;
468
+ /** `disable_after:` — consecutive failed runs after which the platform
469
+ * stops firing this flow's schedule and says so. A nightly desk that has
470
+ * failed all week is not going to fix itself, and every one of those runs
471
+ * costs money and sends mail. Null: never quarantined. */
472
+ disableAfter: number | null;
473
+ /** `sla:` — seconds a run of this flow is expected to take. A run still
474
+ * going well past it is reported once, as a notification, and keeps
475
+ * running: this is a smoke alarm, not a timeout. Null: no expectation. */
476
+ sla: number | null;
477
+ /** `approvers:` — the email addresses that may decide this flow's gates,
478
+ * or the single word "admins" for anyone at admin or above. Empty means
479
+ * the account's existing rule: any member who can run the workspace.
480
+ * Nobody may approve their own run's gate whatever this says. */
481
+ approvers: string[] | null;
482
+ /** `approve_within:` — how long a gate waits for a person before the run
483
+ * is rejected and fails. A gate with no expiry holds its run, and its
484
+ * place in the account's concurrency, for as long as nobody looks.
485
+ * Null: it waits indefinitely, which stays the default. */
486
+ approveWithin: number | null;
487
+ /** `live: true` — this flow's runs are real even where the platform would
488
+ * default them to test: in a preview workspace, and as an eval's subject.
489
+ * Only ever an opt-out of the safe default, never the other way round. */
490
+ live: boolean;
409
491
  steps: FlowStep[];
410
492
  }
411
493
  export declare const FLOW_TRIGGERS: readonly ["manual", "schedule", "webhook", "email", "flow", "once", "watch", "storage"];
@@ -416,9 +498,25 @@ export interface AgentAssets {
416
498
  }
417
499
  export declare function agentAssets(tenant: string, workspace: string, agent: string): AgentAssets;
418
500
  export declare function listAgents(tenant: string, workspace: string): AgentInfo[];
501
+ /** The most fan-out instances `parallel:` may run at once — the fan-out's
502
+ * own hard cap, since more slots than items is no cap. */
503
+ export declare const PARALLEL_CAP = 20;
504
+ /** The most turns `max_turns:` may allow. A turn is one model reply, tool
505
+ * calls included; a step that needs more than this is not one step. */
506
+ export declare const MAX_TURNS_CAP = 500;
419
507
  /** "90s", "30m", "4h", "3d" — or a bare number of seconds. Clamped to 30
420
508
  * days: a wait is a pause in a flow, not a second scheduler. */
421
509
  export declare function parseWait(value: string): number | undefined;
510
+ /** The one duration grammar every key shares — `wait:`, `timeout:`, the
511
+ * flow's `throttle:`/`debounce:`/`sla:` — as whole seconds, at least 1.
512
+ * Undefined for anything that is not one: the caller says what the key
513
+ * was, because "not a duration" is only useful with the line it names. */
514
+ export declare function parseDuration(value: string): number | undefined;
515
+ /** `timeout:` — the same units `wait:` takes, and no ceiling: the platform
516
+ * sets no clock of its own, so the author's number stands as written.
517
+ * `timeout: 15m` was `Math.max(1, Number("15m") || 0)` — one second — and
518
+ * every step wearing it was cut off before its first tool call returned. */
519
+ export declare function parseTimeout(value: string): number | undefined;
422
520
  /** An ISO 8601 instant (YAML may already have parsed it into a Date), or
423
521
  * null for anything that is not one — a `once` flow with an unreadable
424
522
  * `at:` never fires, and `foldrun check` says so. */
@@ -443,7 +541,48 @@ export declare function parseInstant(raw: unknown): string | null;
443
541
  * they are two pairs, not one wrapper.
444
542
  */
445
543
  export declare function unquote(raw: string): string;
544
+ /**
545
+ * Does this text carry the marker a `when:` is looking for?
546
+ *
547
+ * At the START OF A LINE, not anywhere in the prose. A plain substring
548
+ * search is the obvious implementation and it is wrong in a way that is
549
+ * very hard to see: an agent writing
550
+ *
551
+ * There are no BLOCKED items this week.
552
+ *
553
+ * opened a `when: BLOCKED` gate, because the word is right there. Saying a
554
+ * marker is absent necessarily names it, so the more carefully an agent
555
+ * explains itself the more likely it is to trip its own condition. It bit
556
+ * one desk on its first run and sat latent in another.
557
+ *
558
+ * A verdict is written as a headline — the line leads with it — so leading
559
+ * position is what distinguishes "this is my verdict" from "here is a word
560
+ * I am using in a sentence". Markdown decoration in front of it is fine: a
561
+ * heading, a bullet or bold is still a line that leads with the marker.
562
+ *
563
+ * The marker must also END at a boundary, so BLOCKED does not match
564
+ * BLOCKEDBY.
565
+ */
566
+ export declare function markerPresent(text: string | null | undefined, marker: string): boolean;
446
567
  export declare function parseFlow(file: string, raw: string): FlowInfo;
568
+ /** A schema written out: JSON, or YAML (parsed as frontmatter is, by the
569
+ * same engine). The result must be an object — a schema is a document,
570
+ * and `schema: 3` or `schema: [a, b]` describes nothing. */
571
+ export declare function parseSchemaText(text: string): {
572
+ schema: Record<string, unknown> | boolean;
573
+ } | {
574
+ problem: string;
575
+ };
576
+ /** May this person decide a gate of this flow? `approvers:` is an allowlist
577
+ * of addresses plus the word "admins"; an empty list defers to whatever
578
+ * permission the caller already needed to reach the route.
579
+ *
580
+ * Self-approval is refused separately (see approvals.ts): who STARTED a run
581
+ * is a property of the run, not of the flow file. */
582
+ export declare function mayApprove(flow: Pick<FlowInfo, "approvers"> | null | undefined, who: {
583
+ email?: string | null;
584
+ role?: string | null;
585
+ }): boolean;
447
586
  /** A live run of this flow — the fact overlap: decisions are made on. */
448
587
  export declare function flowHasLiveRun(tenant: string, workspace: string, flowName: string): boolean;
449
588
  /**
@@ -489,6 +628,10 @@ export declare function addFlowStep(raw: string, step: {
489
628
  /** Drop one step, by its index in the flow's parsed steps. */
490
629
  export declare function removeFlowStep(raw: string, index: number): string;
491
630
  export declare function listFlows(tenant: string, workspace: string): FlowInfo[];
631
+ /** One flow of a workspace by name, or null. The same read listFlows makes,
632
+ * named so callers that want a single flow — the approval rules, the
633
+ * runner's budget stamp — do not each re-implement the directory walk. */
634
+ export declare function readFlow(tenant: string, workspace: string, flowName: string): FlowInfo | null;
492
635
  export interface WorkspaceSummary {
493
636
  name: string;
494
637
  description: string;
@@ -565,6 +708,9 @@ export interface ChangeMeta {
565
708
  message?: string;
566
709
  }
567
710
  export declare function writeWorkspaceFile(tenant: string, workspace: string, rel: string, content: string, meta?: ChangeMeta): void;
711
+ /** Tell the platform, and never let its listener fail a write. */
712
+ declare function workspaceChanged(tenant: string, workspace: string, why: "write" | "deploy" | "push" | "delete"): void;
713
+ export { workspaceChanged as notifyWorkspaceChanged };
568
714
  export declare function syncBundleFor(file: string, change?: "Creation" | "Update"): void;
569
715
  /**
570
716
  * Rename one editable file. Both ends pass the same gate as a write — a
@@ -633,7 +779,32 @@ export interface RunEvent {
633
779
  call?: string;
634
780
  ms?: number;
635
781
  err?: boolean;
782
+ /** Set on a test run's event that reports what the platform did instead
783
+ * of the real thing — a refused send, a redirected email, a diverted
784
+ * state write, a withheld secret. The run page lists these; the text is
785
+ * still the trace's line. See test-mode.ts. */
786
+ effect?: TestEffect;
636
787
  }
788
+ /** One attempt of a step — see StepRecord.tries. */
789
+ export interface StepAttempt {
790
+ /** Which attempt, 1-based: `attempts` on the step at the time. */
791
+ n: number;
792
+ status: "completed" | "failed";
793
+ costUsd: number | null;
794
+ tokens: {
795
+ input: number;
796
+ output: number;
797
+ } | null;
798
+ computeSecs: number | null;
799
+ startedAt: string;
800
+ finishedAt: string;
801
+ /** The last error event this attempt wrote, when it failed. */
802
+ error?: string;
803
+ }
804
+ /** The step's totals across its attempts, from the rows. Null where no
805
+ * attempt reported the figure — an in-process step rents no sandbox, and
806
+ * billing reads null as zero, not as "unknown, charge something". */
807
+ export declare function sumAttempts(tries: StepAttempt[]): Pick<StepAttempt, "costUsd" | "tokens" | "computeSecs">;
637
808
  export interface StepRecord {
638
809
  agent: string;
639
810
  instruction: string;
@@ -653,6 +824,32 @@ export interface StepRecord {
653
824
  verify?: string;
654
825
  /** Attempts made so far, for the run trace. */
655
826
  attempts?: number;
827
+ /**
828
+ * One row per attempt, oldest first: what each cost and how it ended.
829
+ * `costUsd`, `tokens` and `computeSecs` on the step are the SUM of these
830
+ * rows — a retried step spent every attempt's money, and the run's spend,
831
+ * the flow's `budget:` and the platform's bill all read the step's
832
+ * figure, so the figure is the total. Before this the step carried only
833
+ * the last attempt's cost and a `retry: 2` step could spend three shares
834
+ * of the budget while reporting one. A loop cycle that re-runs the step
835
+ * appends here too, so its earlier cycles are not lost from the bill.
836
+ */
837
+ tries?: StepAttempt[];
838
+ /** The sandbox this step is executing in, while it is: which executor,
839
+ * its handle (a pod name), and how many of its output lines the driver
840
+ * has already applied to this record. What a driver that inherits the
841
+ * run — after a deploy rolled the last one, or it crashed — needs to
842
+ * re-attach and carry on rather than run the step again. Cleared when
843
+ * the step settles. */
844
+ sandbox?: {
845
+ kind: string;
846
+ ref: string;
847
+ consumed: number;
848
+ since: string;
849
+ } | null;
850
+ /** A size class a retry moved the step up to (an evicted or OOM-killed
851
+ * attempt), overriding the agent's own `size:` for the attempts after. */
852
+ sizeUp?: "large" | "heavy";
656
853
  /** When the step actually started and finished executing — not when the
657
854
  * run reached it. A step's events approximate this (first and last), but
658
855
  * a model can think for minutes before its first tool call, and a step
@@ -675,6 +872,10 @@ export interface StepRecord {
675
872
  max?: number;
676
873
  /** Carried from the flow — see FlowStep. */
677
874
  output?: "json";
875
+ schema?: Record<string, unknown> | boolean;
876
+ schemaPath?: string;
877
+ parallel?: number;
878
+ maxTurns?: number;
678
879
  /** The parsed JSON an `output: json` step returned. Kept beside the prose
679
880
  * result rather than instead of it: the reply is still what a person
680
881
  * reads on the run page, and the data is what the next step computes on. */
@@ -808,6 +1009,26 @@ export interface RunRecord {
808
1009
  * the agents decided was worth remembering, listed so a person can review
809
1010
  * the lesson without diffing the folder. */
810
1011
  memoryWrites?: string[];
1012
+ /** Who asked for this run, when a person did: their email address. A
1013
+ * schedule, a webhook and a chained flow have no one, and leave it unset.
1014
+ * Kept so a gate can refuse the one approval nobody should be able to
1015
+ * give — your own. */
1016
+ startedBy?: string | null;
1017
+ /** When this run's gate stops waiting, stamped the moment it parks from
1018
+ * the flow's `approve_within:`. A gate nobody answers by then is rejected
1019
+ * and the run fails, rather than holding a concurrency slot forever. */
1020
+ approveBy?: string | null;
1021
+ /** A test run: every step runs for real, against the real files and the
1022
+ * real read APIs, but nothing outward happens — sends are refused or
1023
+ * sunk at the egress proxy, send-capable secrets never enter the
1024
+ * sandbox, and state/ and storage/ writes land under this run's own
1025
+ * directory. On the record, not in an env var, so a script cannot turn
1026
+ * it off; carried onto every rerun of this run. See test-mode.ts. */
1027
+ test?: boolean;
1028
+ /** Digests of the emailed approval links that have already decided a
1029
+ * step of this run — never the tokens. A link is good once; the second
1030
+ * click is a 409, not a second approval. Written by approvals.ts. */
1031
+ approvalLinksUsed?: string[];
811
1032
  steps: StepRecord[];
812
1033
  }
813
1034
  export declare function runFilePath(tenant: string, workspace: string, runId: string): string;
@@ -854,7 +1075,81 @@ export declare function writeRun(tenant: string, workspace: string, run: RunReco
854
1075
  export interface RunWithWorkspace extends RunRecord {
855
1076
  workspace: string;
856
1077
  }
1078
+ /**
1079
+ * Every run in the account, newest first, off the per-workspace index.
1080
+ *
1081
+ * A finished run comes back as the index knows it: id, flow, status,
1082
+ * summary, times, and each step's agent, group, status, cost, tokens,
1083
+ * seconds, skip reason and last error — with `instruction` empty, `events`
1084
+ * holding only that error, and `result` null. That is what every list and
1085
+ * every rollup reads (runCost, runFailure, runDurationSecs, the usage
1086
+ * window, the graph's stats). A run that is still live — queued, running,
1087
+ * or parked at a gate — is read whole, because the gate's question and its
1088
+ * events are what the approvals list shows. Anything wanting a finished
1089
+ * run's reply or trace opens it with readRun.
1090
+ *
1091
+ * This used to parse every run file of every workspace on every call, and
1092
+ * the dashboard calls it every twenty seconds per open tab.
1093
+ */
857
1094
  export declare function listAllRuns(tenant: string): RunWithWorkspace[];
1095
+ /** What a list knows about one step. */
1096
+ export interface RunSummaryStep {
1097
+ agent: string;
1098
+ group: number;
1099
+ status: StepRecord["status"];
1100
+ costUsd: number | null;
1101
+ tokens?: {
1102
+ input: number;
1103
+ output: number;
1104
+ } | null;
1105
+ computeSecs?: number | null;
1106
+ skipReason?: string;
1107
+ /** The last error event, for a failed step — what runFailure reads. */
1108
+ error?: string;
1109
+ item?: string;
1110
+ waitFor?: "event";
1111
+ ask?: string;
1112
+ /** The step's instruction, kept for a parked step so the approvals banner can show the question without the record. */
1113
+ instruction?: string;
1114
+ /** When a parked step last spoke — the moment the banner sorts by. */
1115
+ waitingSince?: string;
1116
+ startedAt?: string;
1117
+ finishedAt?: string;
1118
+ }
1119
+ /** What a list knows about one run. */
1120
+ export interface RunSummary {
1121
+ id: string;
1122
+ flow: string;
1123
+ status: RunRecord["status"];
1124
+ summary: string | null;
1125
+ startedAt: string;
1126
+ finishedAt: string | null;
1127
+ stopRequested?: boolean;
1128
+ test?: boolean;
1129
+ budgetUsd?: number | null;
1130
+ tags?: string[];
1131
+ startedBy?: string | null;
1132
+ parkedAt?: string | null;
1133
+ /** A step is parked for a person — what the approvals banner counts. */
1134
+ awaitingApproval: boolean;
1135
+ steps: RunSummaryStep[];
1136
+ }
1137
+ /** One run's row. */
1138
+ export declare function summarizeRun(run: RunRecord): RunSummary;
1139
+ /** A RunRecord with the shape a list reads and nothing else — see listAllRuns. */
1140
+ export declare function runFromSummary(s: RunSummary): RunRecord;
1141
+ /**
1142
+ * The workspace's runs as the index knows them, newest first — the call a
1143
+ * list makes. Reconciled against the directory on every read: rows for
1144
+ * records that are gone are dropped, records with no row or a moved mtime
1145
+ * are re-read, and the index is rewritten if anything changed. A missing
1146
+ * or unreadable index is a full scan that leaves one behind.
1147
+ */
1148
+ export declare function listRunSummaries(tenant: string, workspace: string): RunSummary[];
1149
+ /** Every workspace's runs as summaries, tagged with the workspace. */
1150
+ export declare function listAllRunSummaries(tenant: string): (RunSummary & {
1151
+ workspace: string;
1152
+ })[];
858
1153
  /**
859
1154
  * The status a person should see. "failed" and "someone stopped it" are
860
1155
  * different facts — the record keeps them apart (stopRequested), and the
@@ -973,6 +1268,8 @@ export interface AgentStep {
973
1268
  * step-io-model, which is the pure copy the client can import. */
974
1269
  export declare function deniedPaths(step: Pick<StepRecord, "events">): number;
975
1270
  export declare function listAgentSteps(tenant: string, workspace: string, agent: string): AgentStep[];
1271
+ /** Every run of the workspace, whole, newest first. Parses every record;
1272
+ * a list that needs only what a list needs reads listRunSummaries. */
976
1273
  export declare function listRuns(tenant: string, workspace: string): RunRecord[];
977
1274
  /**
978
1275
  * `provider:` — which endpoint the model calls run against, and the two