@relayflows/sdk 2.0.21 → 2.0.23

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 (247) hide show
  1. package/dist/agent-artifacts.d.ts +18 -9
  2. package/dist/agent-artifacts.d.ts.map +1 -1
  3. package/dist/agent-artifacts.js +27 -11
  4. package/dist/agent-artifacts.js.map +1 -1
  5. package/dist/agent-transcript.d.ts +186 -0
  6. package/dist/agent-transcript.d.ts.map +1 -0
  7. package/dist/agent-transcript.js +585 -0
  8. package/dist/agent-transcript.js.map +1 -0
  9. package/dist/authored-flow-error.d.ts +33 -1
  10. package/dist/authored-flow-error.d.ts.map +1 -1
  11. package/dist/authored-flow-error.js +20 -1
  12. package/dist/authored-flow-error.js.map +1 -1
  13. package/dist/authored-flow-executor.d.ts.map +1 -1
  14. package/dist/authored-flow-executor.js +4 -1
  15. package/dist/authored-flow-executor.js.map +1 -1
  16. package/dist/authored-node-entry.js +5 -1
  17. package/dist/authored-node-entry.js.map +1 -1
  18. package/dist/authored-node-runner.d.ts +11 -0
  19. package/dist/authored-node-runner.d.ts.map +1 -1
  20. package/dist/authored-node-runner.js +30 -1
  21. package/dist/authored-node-runner.js.map +1 -1
  22. package/dist/authored-root.js +3 -1
  23. package/dist/authored-root.js.map +1 -1
  24. package/dist/authored-source-authority.js +13 -0
  25. package/dist/authored-source-authority.js.map +1 -1
  26. package/dist/authored-step-index.d.ts +82 -0
  27. package/dist/authored-step-index.d.ts.map +1 -0
  28. package/dist/authored-step-index.js +120 -0
  29. package/dist/authored-step-index.js.map +1 -0
  30. package/dist/authored-step-output.d.ts +23 -2
  31. package/dist/authored-step-output.d.ts.map +1 -1
  32. package/dist/authored-step-output.js +47 -5
  33. package/dist/authored-step-output.js.map +1 -1
  34. package/dist/authored-worker-step.d.ts +10 -2
  35. package/dist/authored-worker-step.d.ts.map +1 -1
  36. package/dist/authored-worker-step.js +81 -7
  37. package/dist/authored-worker-step.js.map +1 -1
  38. package/dist/cli/check.d.ts.map +1 -1
  39. package/dist/cli/check.js +67 -12
  40. package/dist/cli/check.js.map +1 -1
  41. package/dist/cli/cloud-read.d.ts +31 -0
  42. package/dist/cli/cloud-read.d.ts.map +1 -0
  43. package/dist/cli/cloud-read.js +482 -0
  44. package/dist/cli/cloud-read.js.map +1 -0
  45. package/dist/cli/run.d.ts +2 -0
  46. package/dist/cli/run.d.ts.map +1 -1
  47. package/dist/cli/run.js +53 -24
  48. package/dist/cli/run.js.map +1 -1
  49. package/dist/cli/status.d.ts +43 -0
  50. package/dist/cli/status.d.ts.map +1 -0
  51. package/dist/cli/status.js +321 -0
  52. package/dist/cli/status.js.map +1 -0
  53. package/dist/cli/step-failure.d.ts +26 -0
  54. package/dist/cli/step-failure.d.ts.map +1 -1
  55. package/dist/cli/step-failure.js +99 -10
  56. package/dist/cli/step-failure.js.map +1 -1
  57. package/dist/cli-commands.d.ts +41 -0
  58. package/dist/cli-commands.d.ts.map +1 -1
  59. package/dist/cli-commands.js +33 -0
  60. package/dist/cli-commands.js.map +1 -1
  61. package/dist/cli.d.ts +4 -2
  62. package/dist/cli.d.ts.map +1 -1
  63. package/dist/cli.js +26 -0
  64. package/dist/cli.js.map +1 -1
  65. package/dist/cloud-http.d.ts +12 -0
  66. package/dist/cloud-http.d.ts.map +1 -1
  67. package/dist/cloud-http.js +14 -6
  68. package/dist/cloud-http.js.map +1 -1
  69. package/dist/cloud-read.d.ts +154 -0
  70. package/dist/cloud-read.d.ts.map +1 -0
  71. package/dist/cloud-read.js +373 -0
  72. package/dist/cloud-read.js.map +1 -0
  73. package/dist/cloud-transcript.d.ts +94 -0
  74. package/dist/cloud-transcript.d.ts.map +1 -0
  75. package/dist/cloud-transcript.js +342 -0
  76. package/dist/cloud-transcript.js.map +1 -0
  77. package/dist/communication/environment.d.ts +3 -0
  78. package/dist/communication/environment.d.ts.map +1 -0
  79. package/dist/communication/environment.js +34 -0
  80. package/dist/communication/environment.js.map +1 -0
  81. package/dist/communication/history.d.ts +6 -0
  82. package/dist/communication/history.d.ts.map +1 -0
  83. package/dist/communication/history.js +38 -0
  84. package/dist/communication/history.js.map +1 -0
  85. package/dist/communication/local.d.ts +6 -0
  86. package/dist/communication/local.d.ts.map +1 -0
  87. package/dist/communication/local.js +56 -0
  88. package/dist/communication/local.js.map +1 -0
  89. package/dist/communication/preflight.d.ts +6 -0
  90. package/dist/communication/preflight.d.ts.map +1 -0
  91. package/dist/communication/preflight.js +37 -0
  92. package/dist/communication/preflight.js.map +1 -0
  93. package/dist/communication/projection.d.ts +8 -0
  94. package/dist/communication/projection.d.ts.map +1 -0
  95. package/dist/communication/projection.js +23 -0
  96. package/dist/communication/projection.js.map +1 -0
  97. package/dist/communication/relay.d.ts +78 -0
  98. package/dist/communication/relay.d.ts.map +1 -0
  99. package/dist/communication/relay.js +89 -0
  100. package/dist/communication/relay.js.map +1 -0
  101. package/dist/communication/session.d.ts +24 -0
  102. package/dist/communication/session.d.ts.map +1 -0
  103. package/dist/communication/session.js +100 -0
  104. package/dist/communication/session.js.map +1 -0
  105. package/dist/communication/spec.d.ts +20 -0
  106. package/dist/communication/spec.d.ts.map +1 -0
  107. package/dist/communication/spec.js +113 -0
  108. package/dist/communication/spec.js.map +1 -0
  109. package/dist/communication/tool.d.ts +3 -0
  110. package/dist/communication/tool.d.ts.map +1 -0
  111. package/dist/communication/tool.js +23 -0
  112. package/dist/communication/tool.js.map +1 -0
  113. package/dist/communication/tools.d.ts +11 -0
  114. package/dist/communication/tools.d.ts.map +1 -0
  115. package/dist/communication/tools.js +65 -0
  116. package/dist/communication/tools.js.map +1 -0
  117. package/dist/communication/worker.d.ts +6 -0
  118. package/dist/communication/worker.d.ts.map +1 -0
  119. package/dist/communication/worker.js +120 -0
  120. package/dist/communication/worker.js.map +1 -0
  121. package/dist/compile.d.ts.map +1 -1
  122. package/dist/compile.js +8 -1
  123. package/dist/compile.js.map +1 -1
  124. package/dist/failure-kinds.d.ts +30 -2
  125. package/dist/failure-kinds.d.ts.map +1 -1
  126. package/dist/failure-kinds.js +9 -0
  127. package/dist/failure-kinds.js.map +1 -1
  128. package/dist/index.d.ts +3 -1
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +2 -0
  131. package/dist/index.js.map +1 -1
  132. package/dist/journal-client.d.ts +4 -1
  133. package/dist/journal-client.d.ts.map +1 -1
  134. package/dist/journal-client.js +11 -2
  135. package/dist/journal-client.js.map +1 -1
  136. package/dist/journal-reader.d.ts +1 -1
  137. package/dist/journal-reader.d.ts.map +1 -1
  138. package/dist/journal-reader.js +3 -1
  139. package/dist/journal-reader.js.map +1 -1
  140. package/dist/llm-worker.d.ts.map +1 -1
  141. package/dist/llm-worker.js +12 -1
  142. package/dist/llm-worker.js.map +1 -1
  143. package/dist/permissions-preflight.d.ts +22 -0
  144. package/dist/permissions-preflight.d.ts.map +1 -0
  145. package/dist/permissions-preflight.js +49 -0
  146. package/dist/permissions-preflight.js.map +1 -0
  147. package/dist/preflight.d.ts +13 -2
  148. package/dist/preflight.d.ts.map +1 -1
  149. package/dist/preflight.js +29 -6
  150. package/dist/preflight.js.map +1 -1
  151. package/dist/protocol.d.ts +39 -1
  152. package/dist/protocol.d.ts.map +1 -1
  153. package/dist/pty-sidechannel.d.ts +7 -0
  154. package/dist/pty-sidechannel.d.ts.map +1 -1
  155. package/dist/pty-sidechannel.js.map +1 -1
  156. package/dist/redact.d.ts +16 -0
  157. package/dist/redact.d.ts.map +1 -0
  158. package/dist/redact.js +104 -0
  159. package/dist/redact.js.map +1 -0
  160. package/dist/run-state.d.ts +124 -0
  161. package/dist/run-state.d.ts.map +1 -0
  162. package/dist/run-state.js +368 -0
  163. package/dist/run-state.js.map +1 -0
  164. package/dist/spec.d.ts +24 -1
  165. package/dist/spec.d.ts.map +1 -1
  166. package/dist/spec.js.map +1 -1
  167. package/dist/step-env.d.ts +25 -0
  168. package/dist/step-env.d.ts.map +1 -0
  169. package/dist/step-env.js +34 -0
  170. package/dist/step-env.js.map +1 -0
  171. package/dist/transcript-tail.d.ts +54 -0
  172. package/dist/transcript-tail.d.ts.map +1 -0
  173. package/dist/transcript-tail.js +176 -0
  174. package/dist/transcript-tail.js.map +1 -0
  175. package/dist/worker-cli.d.ts +10 -0
  176. package/dist/worker-cli.d.ts.map +1 -1
  177. package/dist/worker-cli.js +138 -17
  178. package/dist/worker-cli.js.map +1 -1
  179. package/dist/worker-usage.d.ts +7 -2
  180. package/dist/worker-usage.d.ts.map +1 -1
  181. package/dist/worker-usage.js +21 -3
  182. package/dist/worker-usage.js.map +1 -1
  183. package/dist/worker.d.ts +1 -0
  184. package/dist/worker.d.ts.map +1 -1
  185. package/dist/worker.js +41 -7
  186. package/dist/worker.js.map +1 -1
  187. package/dist/wrapper-runtime.d.ts +7 -1
  188. package/dist/wrapper-runtime.d.ts.map +1 -1
  189. package/dist/wrapper-runtime.js +7 -1
  190. package/dist/wrapper-runtime.js.map +1 -1
  191. package/dist/wrapper-session.d.ts +8 -0
  192. package/dist/wrapper-session.d.ts.map +1 -1
  193. package/dist/wrapper-session.js +156 -23
  194. package/dist/wrapper-session.js.map +1 -1
  195. package/package.json +14 -2
  196. package/src/agent-artifacts.ts +27 -11
  197. package/src/agent-transcript.ts +651 -0
  198. package/src/authored-flow-error.ts +17 -0
  199. package/src/authored-flow-executor.ts +4 -1
  200. package/src/authored-node-entry.ts +5 -1
  201. package/src/authored-node-runner.ts +33 -1
  202. package/src/authored-root.ts +2 -1
  203. package/src/authored-source-authority.ts +13 -0
  204. package/src/authored-step-index.ts +157 -0
  205. package/src/authored-step-output.ts +66 -14
  206. package/src/authored-worker-step.ts +88 -15
  207. package/src/cli/check.ts +69 -11
  208. package/src/cli/cloud-read.ts +518 -0
  209. package/src/cli/run.ts +43 -24
  210. package/src/cli/status.ts +349 -0
  211. package/src/cli/step-failure.ts +100 -10
  212. package/src/cli-commands.ts +33 -0
  213. package/src/cli.ts +27 -0
  214. package/src/cloud-http.ts +32 -6
  215. package/src/cloud-read.ts +515 -0
  216. package/src/cloud-transcript.ts +422 -0
  217. package/src/communication/environment.ts +33 -0
  218. package/src/communication/history.ts +38 -0
  219. package/src/communication/local.ts +41 -0
  220. package/src/communication/preflight.ts +34 -0
  221. package/src/communication/projection.ts +23 -0
  222. package/src/communication/relay.ts +102 -0
  223. package/src/communication/session.ts +84 -0
  224. package/src/communication/spec.ts +106 -0
  225. package/src/communication/tool.ts +22 -0
  226. package/src/communication/tools.ts +50 -0
  227. package/src/communication/worker.ts +95 -0
  228. package/src/compile.ts +4 -1
  229. package/src/failure-kinds.ts +31 -1
  230. package/src/index.ts +10 -1
  231. package/src/journal-client.ts +12 -2
  232. package/src/journal-reader.ts +4 -2
  233. package/src/llm-worker.ts +12 -1
  234. package/src/permissions-preflight.ts +53 -0
  235. package/src/preflight.ts +42 -7
  236. package/src/protocol.ts +15 -0
  237. package/src/pty-sidechannel.ts +7 -0
  238. package/src/redact.ts +106 -0
  239. package/src/run-state.ts +468 -0
  240. package/src/spec.ts +18 -1
  241. package/src/step-env.ts +47 -0
  242. package/src/transcript-tail.ts +193 -0
  243. package/src/worker-cli.ts +139 -18
  244. package/src/worker-usage.ts +26 -3
  245. package/src/worker.ts +42 -6
  246. package/src/wrapper-runtime.ts +7 -1
  247. package/src/wrapper-session.ts +159 -24
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Scrub secret env values and known token shapes out of `text`.
3
+ *
4
+ * An env value is replaced by `[redacted:<NAME>]` so the reader learns which
5
+ * variable leaked without learning its value. Token shapes are replaced by
6
+ * `[redacted]`; header and `NAME=value` shapes keep their name.
7
+ */
8
+ export declare function redact(text: string, env?: NodeJS.ProcessEnv): string;
9
+ /**
10
+ * The relay-transport error scrub `worker-cli.ts` has always applied: the two
11
+ * relay credential names by exact match, at any length, spelled `[redacted]`.
12
+ * Everything `redact` adds is applied after, so the historical output for
13
+ * those two names and the token pattern is unchanged.
14
+ */
15
+ export declare function redactRelayError(message: string, env?: NodeJS.ProcessEnv): string;
16
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AA4EA;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAQjF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAM9F"}
package/dist/redact.js ADDED
@@ -0,0 +1,104 @@
1
+ // Redaction for free text an agent-facing surface may print.
2
+ //
3
+ // `flows status` renders gate `detail`, transcript tails and error messages
4
+ // to a process that may be an agent, in a sandbox whose stdout is captured.
5
+ // A direct agent inherits the worker's whole environment (worker-cli.ts), so
6
+ // anything the operator exported can be echoed back into that text. This
7
+ // module is the one place that decides what never reaches the page.
8
+ //
9
+ // Applied to free text only. Identifiers — run ids, step ids, hashes and env
10
+ // NAMES — are never rewritten, so the view stays greppable.
11
+ /** Env names whose values are treated as secret when they are long enough. */
12
+ const SECRET_NAME = /TOKEN|SECRET|KEY|PASSWORD|PASSWD|CREDENTIAL|AUTH/i;
13
+ /** Shorter values are flags and modes (`AUTH_MODE=off`), not material. */
14
+ const MIN_SECRET_LENGTH = 8;
15
+ /**
16
+ * Token shapes replaced wholesale. The first is the relay token family
17
+ * `redactRelayError` in worker-cli.ts already scrubbed; the rest are the
18
+ * bearer, header and vendor shapes a gate render can carry.
19
+ */
20
+ const TOKEN_PATTERNS = [
21
+ /\b(?:at|rk|nt|ot|br|arr)_(?:live_)?[A-Za-z0-9_-]+/g,
22
+ /\bsk-[A-Za-z0-9_-]{16,}/g,
23
+ /\bghp_[A-Za-z0-9]{20,}/g,
24
+ /\bxox[abp]-[A-Za-z0-9-]+/g,
25
+ ];
26
+ /**
27
+ * Header and assignment shapes: keep the name (and an auth scheme), replace
28
+ * the value. A value already replaced by an earlier pass is left as it is,
29
+ * so the reader still sees which env name it came from.
30
+ */
31
+ const NAMED_VALUE_PATTERNS = [
32
+ /\b(authorization:\s*(?:\w+\s+)?)(?!\[redacted)\S+/gi,
33
+ /\b(Bearer\s+)(?!\[redacted)\S+/g,
34
+ /\b(x-callback-token:\s*)(?!\[redacted)\S+/gi,
35
+ /\b(x-nightcto-evidence-token:\s*)(?!\[redacted)\S+/gi,
36
+ /\b([A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD)=)(?!\[redacted)\S+/gi,
37
+ ];
38
+ /**
39
+ * Credential fields in serialized JSON. A gate detail or a transcript line
40
+ * often carries a request or config body, where the value is an opaque string
41
+ * that matches no vendor token shape and — for anything this process did not
42
+ * export — no environment value either: `{"authToken":"opaque-secret"}` went
43
+ * through untouched. The field name is kept, so the reader knows what was cut.
44
+ *
45
+ * Quoted string values only — including escaped ones: a serialized private
46
+ * key or nested JSON reaches these fields as `\n` and `\"`, and a value
47
+ * class that stopped at the first backslash left every one of them intact.
48
+ * Only where the field NAME ends in a credential noun. Containing one is not enough: `monkey` and `keyboard` are
49
+ * ordinary fields, so the name is split on separators and camelCase humps and
50
+ * the last word decides.
51
+ */
52
+ const JSON_STRING_FIELD = /("([A-Za-z0-9_.\-]{1,64})"\s*:\s*")(?!\[redacted)((?:[^"\\]|\\.){4,})(")/g;
53
+ const CREDENTIAL_NOUN = /^(?:token|secret|key|password|passwd|credential|cookie)s?$/i;
54
+ function isCredentialName(name) {
55
+ if (name.toLowerCase() === 'authorization')
56
+ return true;
57
+ const words = name.split(/[_\-.]+/).flatMap((word) => word.split(/(?<=[a-z0-9])(?=[A-Z])/u));
58
+ const last = words.at(-1);
59
+ return last !== undefined && CREDENTIAL_NOUN.test(last);
60
+ }
61
+ /** Secret env values, longest first so a value that contains another is replaced whole. */
62
+ function secretEnvValues(env) {
63
+ const secrets = [];
64
+ for (const [name, value] of Object.entries(env)) {
65
+ if (typeof value !== 'string' || value.length < MIN_SECRET_LENGTH)
66
+ continue;
67
+ if (SECRET_NAME.test(name))
68
+ secrets.push([name, value]);
69
+ }
70
+ return secrets.sort((a, b) => b[1].length - a[1].length);
71
+ }
72
+ /**
73
+ * Scrub secret env values and known token shapes out of `text`.
74
+ *
75
+ * An env value is replaced by `[redacted:<NAME>]` so the reader learns which
76
+ * variable leaked without learning its value. Token shapes are replaced by
77
+ * `[redacted]`; header and `NAME=value` shapes keep their name.
78
+ */
79
+ export function redact(text, env = process.env) {
80
+ let out = text;
81
+ for (const [name, value] of secretEnvValues(env))
82
+ out = out.replaceAll(value, `[redacted:${name}]`);
83
+ for (const pattern of TOKEN_PATTERNS)
84
+ out = out.replace(pattern, '[redacted]');
85
+ for (const pattern of NAMED_VALUE_PATTERNS)
86
+ out = out.replace(pattern, '$1[redacted]');
87
+ out = out.replace(JSON_STRING_FIELD, (match, open, name, _value, close) => isCredentialName(name) ? `${open}[redacted]${close}` : match);
88
+ return out;
89
+ }
90
+ /**
91
+ * The relay-transport error scrub `worker-cli.ts` has always applied: the two
92
+ * relay credential names by exact match, at any length, spelled `[redacted]`.
93
+ * Everything `redact` adds is applied after, so the historical output for
94
+ * those two names and the token pattern is unchanged.
95
+ */
96
+ export function redactRelayError(message, env = process.env) {
97
+ for (const key of ['RELAY_AGENT_TOKEN', 'RELAY_API_KEY']) {
98
+ const secret = env[key];
99
+ if (secret)
100
+ message = message.replaceAll(secret, '[redacted]');
101
+ }
102
+ return redact(message, env);
103
+ }
104
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ import type { JournalEvent } from './journal-reader.js';
2
+ export type RunStatus = 'running' | 'parked' | 'completed' | 'failed' | 'cancelling' | 'cancelled';
3
+ /** `StepState` variant names from state.rs, snake_case. */
4
+ export type StepStateName = 'pending' | 'runnable' | 'running' | 'backoff' | 'waiting' | 'needs_human' | 'done';
5
+ export interface Spend {
6
+ tokens_in: number;
7
+ tokens_out: number;
8
+ /** Decimal string, as the journal stores it; never a float. */
9
+ dollars: string;
10
+ dollars_unmetered: boolean;
11
+ }
12
+ export interface StepCounts {
13
+ total: number;
14
+ done: number;
15
+ running: number;
16
+ /** `pending` and `runnable` both: neither has started an attempt. */
17
+ pending: number;
18
+ backoff: number;
19
+ waiting: number;
20
+ needs_human: number;
21
+ }
22
+ /**
23
+ * The part of the attempt's journaled transcript digest (flows#491, written to
24
+ * `trajectory_tail.transcript`) that a status view shows. Not the whole digest:
25
+ * the tool roster and per-call excerpts are the transcript file's job, and this
26
+ * view has to stay small and greppable. Every string here was redacted when the
27
+ * digest was built and is redacted again on the way out.
28
+ */
29
+ export interface AttemptTranscript {
30
+ /** Where the full per-attempt transcript is, so a reader can go and open it. */
31
+ path: string | null;
32
+ bytes: number | null;
33
+ truncated: boolean;
34
+ model: string | null;
35
+ num_turns: number | null;
36
+ total_cost_usd: number | null;
37
+ tool_calls: number | null;
38
+ /** Why the attempt failed, as the digest recorded it; already bounded. */
39
+ failure: {
40
+ kind: string;
41
+ excerpt: string;
42
+ } | null;
43
+ }
44
+ export interface LastAttempt {
45
+ attempt: number;
46
+ completion_reason: string;
47
+ disposition: string;
48
+ verification: {
49
+ gate: string;
50
+ verdict: string;
51
+ detail: string;
52
+ } | null;
53
+ human_intervention: boolean;
54
+ /** Count only; the refs carry surface paths and idempotency keys. */
55
+ effects: number;
56
+ ended_at_ms: number;
57
+ /** Null when the attempt journaled no digest — an llm step, or a pre-#491 run. */
58
+ transcript: AttemptTranscript | null;
59
+ }
60
+ export interface StepView {
61
+ id: string;
62
+ type: string;
63
+ state: StepStateName;
64
+ attempt: number;
65
+ max_iterations: number | null;
66
+ started_at_ms: number | null;
67
+ elapsed_ms: number | null;
68
+ lease: {
69
+ deadline_ms: number;
70
+ overdue_ms: number;
71
+ } | null;
72
+ backoff_until_ms: number | null;
73
+ wait: {
74
+ wait_id: string;
75
+ kind: 'human' | 'event' | 'timer';
76
+ timeout_at_ms: number | null;
77
+ } | null;
78
+ last_attempt: LastAttempt | null;
79
+ /**
80
+ * Why this step finished, once it is `done`. Separate from `last_attempt`
81
+ * because an epoch summary carries `steps_done[id].completionReason`
82
+ * (kernel `entry.rs` `StepDoneSummary`) but no attempt record — so after a
83
+ * compaction this is the only place the fact survives. Dependency readiness
84
+ * and the failure glyph both read this, never `last_attempt`.
85
+ */
86
+ completion_reason: string | null;
87
+ /** Paths the worker journaled for the last attempt, when its output carried them. */
88
+ artifacts: {
89
+ paths: string[];
90
+ journaled: boolean;
91
+ };
92
+ }
93
+ export interface RunView {
94
+ run_id: string;
95
+ name: string;
96
+ status: RunStatus;
97
+ completion_reason: string | null;
98
+ spawned_at_ms: number;
99
+ now_ms: number;
100
+ spend: Spend;
101
+ counts: StepCounts;
102
+ steps: StepView[];
103
+ }
104
+ export declare class RunStateError extends Error {
105
+ constructor(message: string);
106
+ }
107
+ /**
108
+ * Sum two decimal dollar strings exactly, at whatever scale they carry — the
109
+ * kernel's budget fold (`relayflowd-core/src/state/budget.rs`) aligns every
110
+ * fractional digit before adding, and it accepts arbitrary precision. Scaling
111
+ * to a fixed six fractional digits dropped a charge of `0.0000001` to zero, so
112
+ * `flows status` underreported spend the kernel had totalled exactly.
113
+ *
114
+ * A value that is not a decimal number is left out of the sum rather than
115
+ * counted as zero, and the caller is told by `malformed`.
116
+ */
117
+ export declare function addDollars(left: string, right: string): string;
118
+ /**
119
+ * Fold journal events, in sequence order, into a {@link RunView} as of
120
+ * `now_ms`. Throws {@link RunStateError} on a journal the kernel would also
121
+ * refuse (no `run.spawned` first, an unknown step).
122
+ */
123
+ export declare function foldRunState(events: readonly JournalEvent[], now_ms: number): RunView;
124
+ //# sourceMappingURL=run-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-state.d.ts","sourceRoot":"","sources":["../src/run-state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,368 @@
1
+ // Pure fold of a run's journal into the view `flows status` renders.
2
+ //
3
+ // Mirrors the kernel's `RunState::fold` (kernel/relayflowd-core/src/state.rs)
4
+ // transition for transition, but keeps only what a status view needs: step
5
+ // state, attempt, lease, backoff, wait, the last completion's verdict, and
6
+ // spend. Nothing here reads a prompt, an input binding, an output body, a
7
+ // wake context or a pin — the model has no field for them, so their absence
8
+ // from the rendered view is structural rather than a matter of care.
9
+ //
10
+ // No I/O, no clock: the caller passes `now_ms`, so a test folds a hand-built
11
+ // journal against a fixed instant and asserts exact elapsed and overdue values.
12
+ export class RunStateError extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = 'RunStateError';
16
+ }
17
+ }
18
+ function payloadOf(event) {
19
+ return event.payload !== null && typeof event.payload === 'object' && !Array.isArray(event.payload)
20
+ ? event.payload : {};
21
+ }
22
+ function integer(value) {
23
+ return typeof value === 'number' && Number.isSafeInteger(value) ? value : null;
24
+ }
25
+ function text(value, fallback = '') {
26
+ return typeof value === 'string' ? value : fallback;
27
+ }
28
+ /**
29
+ * Sum two decimal dollar strings exactly, at whatever scale they carry — the
30
+ * kernel's budget fold (`relayflowd-core/src/state/budget.rs`) aligns every
31
+ * fractional digit before adding, and it accepts arbitrary precision. Scaling
32
+ * to a fixed six fractional digits dropped a charge of `0.0000001` to zero, so
33
+ * `flows status` underreported spend the kernel had totalled exactly.
34
+ *
35
+ * A value that is not a decimal number is left out of the sum rather than
36
+ * counted as zero, and the caller is told by `malformed`.
37
+ */
38
+ export function addDollars(left, right) {
39
+ const parsed = [left, right].map((value) => /^\d+(?:\.\d+)?$/.test(value) ? value : null);
40
+ const usable = parsed.filter((value) => value !== null);
41
+ if (usable.length === 0)
42
+ return '0';
43
+ if (usable.length === 1)
44
+ return normalizeDollars(usable[0]);
45
+ const scale = Math.max(...usable.map((value) => (value.split('.')[1] ?? '').length));
46
+ const scaled = usable.map((value) => {
47
+ const [whole, fraction = ''] = value.split('.');
48
+ return BigInt(`${whole}${fraction.padEnd(scale, '0')}`);
49
+ });
50
+ return fromScaled(scaled[0] + scaled[1], scale);
51
+ }
52
+ /** `12.3400` -> `12.34`, `0.000` -> `0`; the journal's own spelling otherwise. */
53
+ function normalizeDollars(value) {
54
+ const [whole, fraction = ''] = value.split('.');
55
+ return fromScaled(BigInt(`${whole}${fraction}`), fraction.length);
56
+ }
57
+ function fromScaled(total, scale) {
58
+ if (scale === 0)
59
+ return String(total);
60
+ const digits = String(total).padStart(scale + 1, '0');
61
+ const whole = digits.slice(0, digits.length - scale);
62
+ const fraction = digits.slice(digits.length - scale).replace(/0+$/, '');
63
+ return fraction.length === 0 ? whole : `${whole}.${fraction}`;
64
+ }
65
+ /**
66
+ * Reduce `trajectory_tail.transcript` (the digest flows#491 journals) to the
67
+ * handful of facts a status view shows. Every field is taken defensively: the
68
+ * journal is evidence written by a worker that may predate this reader, so a
69
+ * missing or wrong-typed field becomes null rather than throwing.
70
+ */
71
+ function attemptTranscript(trajectoryTail) {
72
+ // Arrays are objects too, and a malformed digest is exactly the shape that
73
+ // would otherwise reach the view as `{ kind: 'unknown', excerpt: '' }`.
74
+ const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null;
75
+ const digest = object(object(trajectoryTail)?.['transcript']);
76
+ if (digest === null)
77
+ return null;
78
+ const file = object(digest['file']) ?? {};
79
+ const result = object(digest['result']) ?? {};
80
+ const tools = object(digest['tools']) ?? {};
81
+ const failure = object(digest['failure']);
82
+ return {
83
+ path: typeof file['path'] === 'string' ? file['path'] : null,
84
+ bytes: integer(file['bytes_kept']),
85
+ truncated: file['truncated'] === true,
86
+ model: typeof result['model'] === 'string' ? result['model'] : null,
87
+ num_turns: integer(result['num_turns']),
88
+ total_cost_usd: typeof result['total_cost_usd'] === 'number' && Number.isFinite(result['total_cost_usd'])
89
+ ? result['total_cost_usd'] : null,
90
+ tool_calls: integer(tools['total_calls']),
91
+ failure: failure === null ? null : {
92
+ kind: text(failure['kind'], 'unknown'),
93
+ excerpt: text(failure['excerpt']),
94
+ },
95
+ };
96
+ }
97
+ function addSpend(total, charge) {
98
+ const budget = charge !== null && typeof charge === 'object' ? charge : {};
99
+ return {
100
+ tokens_in: total.tokens_in + (integer(budget['tokens_in']) ?? 0),
101
+ tokens_out: total.tokens_out + (integer(budget['tokens_out']) ?? 0),
102
+ dollars: addDollars(total.dollars, text(budget['dollars'], '0')),
103
+ dollars_unmetered: total.dollars_unmetered || budget['dollars_unmetered'] === true,
104
+ };
105
+ }
106
+ const ZERO_SPEND = { tokens_in: 0, tokens_out: 0, dollars: '0', dollars_unmetered: false };
107
+ function freshStep(id, type, maxIterations, dependsOn) {
108
+ return {
109
+ depends_on: dependsOn,
110
+ view: {
111
+ id, type, state: 'pending', attempt: 0, max_iterations: maxIterations,
112
+ started_at_ms: null, elapsed_ms: null, lease: null, backoff_until_ms: null, wait: null,
113
+ last_attempt: null, completion_reason: null, artifacts: { paths: [], journaled: false },
114
+ },
115
+ };
116
+ }
117
+ /** Leave the transient facts of one state behind when entering another. */
118
+ function enter(view, state) {
119
+ view.state = state;
120
+ view.lease = null;
121
+ view.backoff_until_ms = null;
122
+ view.wait = null;
123
+ }
124
+ function artifactsOf(output) {
125
+ // worker.ts journals `artifacts` inside the CliResult wrapper only when the
126
+ // agent's stdout was not a JSON object; on the JSON path the author's
127
+ // object is the output and carries no such key.
128
+ if (output === null || typeof output !== 'object' || Array.isArray(output))
129
+ return { paths: [], journaled: false };
130
+ const paths = output['artifacts'];
131
+ if (!Array.isArray(paths) || !paths.every((path) => typeof path === 'string'))
132
+ return { paths: [], journaled: false };
133
+ return { paths: [...paths], journaled: true };
134
+ }
135
+ /**
136
+ * Fold journal events, in sequence order, into a {@link RunView} as of
137
+ * `now_ms`. Throws {@link RunStateError} on a journal the kernel would also
138
+ * refuse (no `run.spawned` first, an unknown step).
139
+ */
140
+ export function foldRunState(events, now_ms) {
141
+ const first = events[0];
142
+ if (first === undefined || first.entry_type !== 'run.spawned') {
143
+ throw new RunStateError('Journal does not begin with run.spawned.');
144
+ }
145
+ const spawned = payloadOf(first);
146
+ const spec = spawned['spec'] !== null && typeof spawned['spec'] === 'object' ? spawned['spec'] : {};
147
+ const declared = Array.isArray(spec['steps']) ? spec['steps'] : [];
148
+ const steps = new Map();
149
+ for (const entry of declared) {
150
+ const step = entry !== null && typeof entry === 'object' ? entry : {};
151
+ const id = text(step['id']);
152
+ if (id.length === 0)
153
+ throw new RunStateError('Spec step without an id.');
154
+ const dependsOn = Array.isArray(step['depends_on']) ? step['depends_on'].filter((d) => typeof d === 'string') : [];
155
+ steps.set(id, freshStep(id, text(step['type'], 'unknown'), integer(step['max_iterations']), dependsOn));
156
+ }
157
+ const budget = spec['budget'] !== null && typeof spec['budget'] === 'object' ? spec['budget'] : {};
158
+ // An authored child run carries its parent's totals so ceilings hold across
159
+ // the family (state.rs `prior_spend`); the view reports the same running total.
160
+ let spend = addSpend(ZERO_SPEND, budget['prior_spend']);
161
+ let completion = null;
162
+ let cancelRequested = false;
163
+ const stepOf = (event) => {
164
+ if (event.step_id === null)
165
+ throw new RunStateError(`Entry seq ${event.seq} (${event.entry_type}) names no step.`);
166
+ const step = steps.get(event.step_id);
167
+ if (step === undefined)
168
+ throw new RunStateError(`Entry seq ${event.seq} names unknown step ${JSON.stringify(event.step_id)}.`);
169
+ return step.view;
170
+ };
171
+ for (const event of events.slice(1)) {
172
+ const payload = payloadOf(event);
173
+ switch (event.entry_type) {
174
+ case 'step.attempt.started': {
175
+ const view = stepOf(event);
176
+ const attempt = event.attempt ?? view.attempt;
177
+ view.attempt = Math.max(view.attempt, attempt);
178
+ view.max_iterations = integer(payload['max_iterations']) ?? view.max_iterations;
179
+ view.started_at_ms = event.at_ms;
180
+ enter(view, 'running');
181
+ const deadline = integer(payload['lease_deadline_ms']) ?? event.at_ms;
182
+ view.lease = { deadline_ms: deadline, overdue_ms: 0 };
183
+ break;
184
+ }
185
+ case 'step.completed': {
186
+ const view = stepOf(event);
187
+ const attempt = event.attempt ?? view.attempt;
188
+ view.attempt = Math.max(view.attempt, attempt);
189
+ spend = addSpend(spend, payload['budget']);
190
+ const verification = payload['verification'] !== null && typeof payload['verification'] === 'object'
191
+ ? payload['verification'] : undefined;
192
+ view.last_attempt = {
193
+ attempt,
194
+ completion_reason: text(payload['completionReason'], 'unknown'),
195
+ disposition: text(payload['disposition'], 'unknown'),
196
+ verification: verification === undefined ? null : {
197
+ gate: text(verification['gate']), verdict: text(verification['verdict']), detail: text(verification['detail']),
198
+ },
199
+ human_intervention: payload['human_intervention'] === true,
200
+ effects: Array.isArray(payload['effects']) ? payload['effects'].length : 0,
201
+ ended_at_ms: event.at_ms,
202
+ transcript: attemptTranscript(payload['trajectory_tail']),
203
+ };
204
+ view.artifacts = artifactsOf(payload['output']);
205
+ const disposition = payload['disposition'];
206
+ if (disposition === 'retry') {
207
+ enter(view, 'backoff');
208
+ view.backoff_until_ms = integer(payload['next_attempt_at_ms']) ?? event.at_ms;
209
+ }
210
+ else if (disposition === 'park') {
211
+ enter(view, 'needs_human');
212
+ view.wait = { wait_id: `park-${view.id}-${attempt}`, kind: 'human', timeout_at_ms: null };
213
+ }
214
+ else {
215
+ enter(view, 'done');
216
+ view.completion_reason = view.last_attempt.completion_reason;
217
+ }
218
+ break;
219
+ }
220
+ case 'sleep.until': {
221
+ const view = stepOf(event);
222
+ enter(view, 'backoff');
223
+ view.backoff_until_ms = integer(payload['wake_at_ms']) ?? event.at_ms;
224
+ break;
225
+ }
226
+ case 'wait.event': {
227
+ const view = stepOf(event);
228
+ enter(view, 'waiting');
229
+ view.wait = { wait_id: text(payload['wait_id']), kind: 'event', timeout_at_ms: integer(payload['timeout_at_ms']) };
230
+ break;
231
+ }
232
+ case 'wait.human': {
233
+ const view = stepOf(event);
234
+ enter(view, 'needs_human');
235
+ view.wait = { wait_id: text(payload['wait_id']), kind: 'human', timeout_at_ms: integer(payload['timeout_at_ms']) };
236
+ break;
237
+ }
238
+ case 'wait.completed': {
239
+ const view = stepOf(event);
240
+ if (payload['completionReason'] === 'canceled') {
241
+ enter(view, 'done');
242
+ view.last_attempt = view.last_attempt ?? {
243
+ attempt: view.attempt, completion_reason: 'canceled', disposition: 'step_done',
244
+ verification: null, human_intervention: false, effects: 0, ended_at_ms: event.at_ms,
245
+ transcript: null,
246
+ };
247
+ view.completion_reason = 'canceled';
248
+ }
249
+ else {
250
+ enter(view, 'runnable');
251
+ }
252
+ break;
253
+ }
254
+ case 'epoch.summary': {
255
+ // A new epoch restarts every step (state.rs `apply_epoch`); the
256
+ // summary then restores the ones it carries forward.
257
+ for (const step of steps.values()) {
258
+ const { id, type, max_iterations } = step.view;
259
+ step.view = freshStep(id, type, max_iterations, step.depends_on).view;
260
+ }
261
+ spend = addSpend(ZERO_SPEND, payload['budget_spent']);
262
+ const done = payload['steps_done'] !== null && typeof payload['steps_done'] === 'object' ? payload['steps_done'] : {};
263
+ for (const [id, summary] of Object.entries(done)) {
264
+ const view = steps.get(id)?.view;
265
+ if (view === undefined)
266
+ throw new RunStateError(`Epoch summary names unknown step ${JSON.stringify(id)}.`);
267
+ enter(view, 'done');
268
+ // `StepDoneSummary` (kernel entry.rs) carries `completionReason` and
269
+ // nothing else about the attempt. Keep the reason, which dependency
270
+ // readiness and the glyph need; invent no attempt record around it.
271
+ const facts = summary !== null && typeof summary === 'object' ? summary : {};
272
+ view.completion_reason = text(facts['completionReason'], 'unknown');
273
+ }
274
+ const open = payload['steps_open'] !== null && typeof payload['steps_open'] === 'object' ? payload['steps_open'] : {};
275
+ for (const [id, summary] of Object.entries(open)) {
276
+ const view = steps.get(id)?.view;
277
+ if (view === undefined)
278
+ throw new RunStateError(`Epoch summary names unknown step ${JSON.stringify(id)}.`);
279
+ const facts = summary !== null && typeof summary === 'object' ? summary : {};
280
+ view.attempt = integer(facts['attempt']) ?? 0;
281
+ const state = facts['state'];
282
+ if (state === 'running') {
283
+ enter(view, 'running');
284
+ // The summary carries no original attempt start, and the kernel
285
+ // does not track one. Stamping the epoch's own time here made the
286
+ // live `attempt-<n>.*.tail` header look stale to
287
+ // `readTranscriptTail` and restarted elapsed at the compaction.
288
+ // Unknown is the honest value: elapsed goes unrendered and the
289
+ // tail's staleness check is skipped rather than failed.
290
+ view.started_at_ms = null;
291
+ view.lease = { deadline_ms: integer(facts['lease_deadline_ms']) ?? event.at_ms, overdue_ms: 0 };
292
+ }
293
+ else if (state === 'backoff') {
294
+ enter(view, 'backoff');
295
+ view.backoff_until_ms = integer(facts['wake_at_ms']) ?? event.at_ms;
296
+ }
297
+ else if (state === 'needs_human') {
298
+ enter(view, 'needs_human');
299
+ view.wait = { wait_id: `epoch-${integer(payload['epoch']) ?? 0}-${id}`, kind: 'human', timeout_at_ms: null };
300
+ }
301
+ else if (state === 'waiting') {
302
+ enter(view, 'waiting');
303
+ view.wait = { wait_id: text(facts['wait_id']), kind: 'event', timeout_at_ms: null };
304
+ }
305
+ }
306
+ break;
307
+ }
308
+ case 'run.completed':
309
+ completion = text(payload['completionReason'], 'unknown');
310
+ spend = addSpend(ZERO_SPEND, payload['budget_total']);
311
+ break;
312
+ case 'run.cancel.requested':
313
+ cancelRequested = true;
314
+ break;
315
+ default:
316
+ // Routing, subscriptions, channels, streams, effects, memory and
317
+ // segment records change nothing a status view shows.
318
+ break;
319
+ }
320
+ }
321
+ // state.rs `refresh_runnable`: a pending step whose dependencies all
322
+ // succeeded is runnable, whether or not a worker has picked it up.
323
+ for (const step of steps.values()) {
324
+ if (step.view.state !== 'pending')
325
+ continue;
326
+ const ready = step.depends_on.every((dependency) => {
327
+ const upstream = steps.get(dependency)?.view;
328
+ return upstream?.state === 'done' && upstream.completion_reason === 'success';
329
+ });
330
+ if (ready)
331
+ step.view.state = 'runnable';
332
+ }
333
+ const views = [];
334
+ const counts = { total: 0, done: 0, running: 0, pending: 0, backoff: 0, waiting: 0, needs_human: 0 };
335
+ for (const { view } of steps.values()) {
336
+ if (view.started_at_ms !== null) {
337
+ // `ended_at_ms` belongs to the attempt that ended. A retry, wait or
338
+ // sleep starts a newer attempt whose `started_at_ms` is later than that,
339
+ // and measuring to the older end clamped a live wait's elapsed to zero.
340
+ const ended = view.last_attempt?.ended_at_ms ?? null;
341
+ const end = ended === null || ended < view.started_at_ms ? now_ms : ended;
342
+ view.elapsed_ms = Math.max(0, (view.state === 'running' ? now_ms : end) - view.started_at_ms);
343
+ }
344
+ if (view.lease !== null)
345
+ view.lease.overdue_ms = Math.max(0, now_ms - view.lease.deadline_ms);
346
+ counts.total += 1;
347
+ if (view.state === 'pending' || view.state === 'runnable')
348
+ counts.pending += 1;
349
+ else
350
+ counts[view.state] += 1;
351
+ views.push(view);
352
+ }
353
+ const status = completion === null
354
+ ? cancelRequested ? 'cancelling' : counts.needs_human > 0 ? 'parked' : 'running'
355
+ : completion === 'success' ? 'completed' : completion === 'canceled' ? 'cancelled' : 'failed';
356
+ return {
357
+ run_id: first.run_id,
358
+ name: text(spec['name'], first.run_id),
359
+ status,
360
+ completion_reason: completion,
361
+ spawned_at_ms: first.at_ms,
362
+ now_ms,
363
+ spend,
364
+ counts,
365
+ steps: views,
366
+ };
367
+ }
368
+ //# sourceMappingURL=run-state.js.map