runling 0.1.1

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 (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -0
  3. package/bin/runling.js +26 -0
  4. package/bin/workflow-loader.js +10 -0
  5. package/dist/extensions/web-fetch.d.ts +8 -0
  6. package/dist/extensions/web-fetch.js +245 -0
  7. package/dist/src/agent.d.ts +78 -0
  8. package/dist/src/agent.js +450 -0
  9. package/dist/src/ansi.d.ts +1 -0
  10. package/dist/src/ansi.js +9 -0
  11. package/dist/src/cli.d.ts +8 -0
  12. package/dist/src/cli.js +46 -0
  13. package/dist/src/config-reloader.d.ts +20 -0
  14. package/dist/src/config-reloader.js +110 -0
  15. package/dist/src/events.d.ts +82 -0
  16. package/dist/src/events.js +21 -0
  17. package/dist/src/git.d.ts +9 -0
  18. package/dist/src/git.js +82 -0
  19. package/dist/src/id.d.ts +1 -0
  20. package/dist/src/id.js +7 -0
  21. package/dist/src/index.d.ts +15 -0
  22. package/dist/src/index.js +14 -0
  23. package/dist/src/input.d.ts +14 -0
  24. package/dist/src/input.js +49 -0
  25. package/dist/src/log.d.ts +35 -0
  26. package/dist/src/log.js +92 -0
  27. package/dist/src/markdown.d.ts +2 -0
  28. package/dist/src/markdown.js +32 -0
  29. package/dist/src/model.d.ts +5 -0
  30. package/dist/src/model.js +10 -0
  31. package/dist/src/paths.d.ts +10 -0
  32. package/dist/src/paths.js +18 -0
  33. package/dist/src/runner.d.ts +43 -0
  34. package/dist/src/runner.js +200 -0
  35. package/dist/src/runtime.d.ts +62 -0
  36. package/dist/src/runtime.js +44 -0
  37. package/dist/src/schema.d.ts +3 -0
  38. package/dist/src/schema.js +17 -0
  39. package/dist/src/shell.d.ts +47 -0
  40. package/dist/src/shell.js +170 -0
  41. package/dist/src/step.d.ts +5 -0
  42. package/dist/src/step.js +39 -0
  43. package/dist/src/system-prompt.d.ts +2 -0
  44. package/dist/src/system-prompt.js +23 -0
  45. package/dist/src/text.d.ts +2 -0
  46. package/dist/src/text.js +6 -0
  47. package/dist/src/tui.d.ts +46 -0
  48. package/dist/src/tui.js +477 -0
  49. package/dist/src/usage.d.ts +35 -0
  50. package/dist/src/usage.js +90 -0
  51. package/dist/src/utils.d.ts +2 -0
  52. package/dist/src/utils.js +16 -0
  53. package/dist/src/web-config.d.ts +16 -0
  54. package/dist/src/web-config.js +22 -0
  55. package/dist/src/web-server.d.ts +8 -0
  56. package/dist/src/web-server.js +91 -0
  57. package/dist/src/workflow.d.ts +11 -0
  58. package/dist/src/workflow.js +39 -0
  59. package/dist/web/client/_app/immutable/assets/0.DkxYLsNr.css +1 -0
  60. package/dist/web/client/_app/immutable/assets/0.DkxYLsNr.css.br +0 -0
  61. package/dist/web/client/_app/immutable/assets/0.DkxYLsNr.css.gz +0 -0
  62. package/dist/web/client/_app/immutable/assets/2.D0D6RatL.css +1 -0
  63. package/dist/web/client/_app/immutable/assets/2.D0D6RatL.css.br +0 -0
  64. package/dist/web/client/_app/immutable/assets/2.D0D6RatL.css.gz +0 -0
  65. package/dist/web/client/_app/immutable/assets/plus-jakarta-sans-latin-ext-wght-italic.DJWiFoht.woff2 +0 -0
  66. package/dist/web/client/_app/immutable/assets/plus-jakarta-sans-latin-ext-wght-normal.DmpS2jIq.woff2 +0 -0
  67. package/dist/web/client/_app/immutable/assets/plus-jakarta-sans-latin-wght-italic.DnD1KgkH.woff2 +0 -0
  68. package/dist/web/client/_app/immutable/assets/plus-jakarta-sans-latin-wght-normal.eXO_dkmS.woff2 +0 -0
  69. package/dist/web/client/_app/immutable/assets/plus-jakarta-sans-vietnamese-wght-italic.CPBsCcxN.woff2 +0 -0
  70. package/dist/web/client/_app/immutable/assets/plus-jakarta-sans-vietnamese-wght-normal.qRpaaN48.woff2 +0 -0
  71. package/dist/web/client/_app/immutable/chunks/DVu8M-Ny.js +1 -0
  72. package/dist/web/client/_app/immutable/chunks/DVu8M-Ny.js.br +0 -0
  73. package/dist/web/client/_app/immutable/chunks/DVu8M-Ny.js.gz +0 -0
  74. package/dist/web/client/_app/immutable/chunks/DZ8VO57n.js +2 -0
  75. package/dist/web/client/_app/immutable/chunks/DZ8VO57n.js.br +0 -0
  76. package/dist/web/client/_app/immutable/chunks/DZ8VO57n.js.gz +0 -0
  77. package/dist/web/client/_app/immutable/chunks/DcSF4jBT.js +2 -0
  78. package/dist/web/client/_app/immutable/chunks/DcSF4jBT.js.br +0 -0
  79. package/dist/web/client/_app/immutable/chunks/DcSF4jBT.js.gz +0 -0
  80. package/dist/web/client/_app/immutable/chunks/DhqrxzQW.js +1 -0
  81. package/dist/web/client/_app/immutable/chunks/DhqrxzQW.js.br +0 -0
  82. package/dist/web/client/_app/immutable/chunks/DhqrxzQW.js.gz +0 -0
  83. package/dist/web/client/_app/immutable/chunks/DqKc9n9r.js +1 -0
  84. package/dist/web/client/_app/immutable/chunks/DqKc9n9r.js.br +0 -0
  85. package/dist/web/client/_app/immutable/chunks/DqKc9n9r.js.gz +0 -0
  86. package/dist/web/client/_app/immutable/chunks/EYhLy6Dm.js +1 -0
  87. package/dist/web/client/_app/immutable/chunks/EYhLy6Dm.js.br +0 -0
  88. package/dist/web/client/_app/immutable/chunks/EYhLy6Dm.js.gz +0 -0
  89. package/dist/web/client/_app/immutable/chunks/Jt-Bo-2J.js +1 -0
  90. package/dist/web/client/_app/immutable/chunks/Jt-Bo-2J.js.br +0 -0
  91. package/dist/web/client/_app/immutable/chunks/Jt-Bo-2J.js.gz +0 -0
  92. package/dist/web/client/_app/immutable/chunks/N_quh2N-.js +1 -0
  93. package/dist/web/client/_app/immutable/chunks/N_quh2N-.js.br +0 -0
  94. package/dist/web/client/_app/immutable/chunks/N_quh2N-.js.gz +0 -0
  95. package/dist/web/client/_app/immutable/chunks/wbPk3Yxo.js +81 -0
  96. package/dist/web/client/_app/immutable/chunks/wbPk3Yxo.js.br +0 -0
  97. package/dist/web/client/_app/immutable/chunks/wbPk3Yxo.js.gz +0 -0
  98. package/dist/web/client/_app/immutable/entry/app.YNAAR_x0.js +2 -0
  99. package/dist/web/client/_app/immutable/entry/app.YNAAR_x0.js.br +0 -0
  100. package/dist/web/client/_app/immutable/entry/app.YNAAR_x0.js.gz +0 -0
  101. package/dist/web/client/_app/immutable/entry/start.BQmDILhe.js +1 -0
  102. package/dist/web/client/_app/immutable/entry/start.BQmDILhe.js.br +0 -0
  103. package/dist/web/client/_app/immutable/entry/start.BQmDILhe.js.gz +0 -0
  104. package/dist/web/client/_app/immutable/nodes/0.DNWnBVC3.js +1 -0
  105. package/dist/web/client/_app/immutable/nodes/0.DNWnBVC3.js.br +0 -0
  106. package/dist/web/client/_app/immutable/nodes/0.DNWnBVC3.js.gz +0 -0
  107. package/dist/web/client/_app/immutable/nodes/1.CZEgzXeT.js +1 -0
  108. package/dist/web/client/_app/immutable/nodes/1.CZEgzXeT.js.br +0 -0
  109. package/dist/web/client/_app/immutable/nodes/1.CZEgzXeT.js.gz +0 -0
  110. package/dist/web/client/_app/immutable/nodes/2.BSE8F3Ef.js +19 -0
  111. package/dist/web/client/_app/immutable/nodes/2.BSE8F3Ef.js.br +0 -0
  112. package/dist/web/client/_app/immutable/nodes/2.BSE8F3Ef.js.gz +0 -0
  113. package/dist/web/client/_app/version.json +1 -0
  114. package/dist/web/client/_app/version.json.br +0 -0
  115. package/dist/web/client/_app/version.json.gz +0 -0
  116. package/dist/web/env.js +70 -0
  117. package/dist/web/env.js.map +1 -0
  118. package/dist/web/handler.js +23 -0
  119. package/dist/web/handler.js.map +1 -0
  120. package/dist/web/index.js +146 -0
  121. package/dist/web/index.js.map +1 -0
  122. package/dist/web/server/chunks/chunks/exports.js-BZBK1HC9.js +161 -0
  123. package/dist/web/server/chunks/chunks/exports.js-BZBK1HC9.js.map +1 -0
  124. package/dist/web/server/chunks/chunks/internal.js-pcXelyRW.js +126 -0
  125. package/dist/web/server/chunks/chunks/internal.js-pcXelyRW.js.map +1 -0
  126. package/dist/web/server/chunks/chunks/root.js-HNgH7zyW.js +4349 -0
  127. package/dist/web/server/chunks/chunks/root.js-HNgH7zyW.js.map +1 -0
  128. package/dist/web/server/chunks/chunks/run-store.js-Dgbf5-M9.js +174 -0
  129. package/dist/web/server/chunks/chunks/run-store.js-Dgbf5-M9.js.map +1 -0
  130. package/dist/web/server/chunks/chunks/runs.js-vPPJr_ta.js +20 -0
  131. package/dist/web/server/chunks/chunks/runs.js-vPPJr_ta.js.map +1 -0
  132. package/dist/web/server/chunks/chunks/server.js-Bf8x1V_n.js +16 -0
  133. package/dist/web/server/chunks/chunks/server.js-Bf8x1V_n.js.map +1 -0
  134. package/dist/web/server/chunks/chunks/state.svelte.js-lAA5-aPf.js +12 -0
  135. package/dist/web/server/chunks/chunks/state.svelte.js-lAA5-aPf.js.map +1 -0
  136. package/dist/web/server/chunks/chunks/stream.js-CHB18nO5.js +51 -0
  137. package/dist/web/server/chunks/chunks/stream.js-CHB18nO5.js.map +1 -0
  138. package/dist/web/server/chunks/chunks/uneval.js-5Y6J9rDU.js +530 -0
  139. package/dist/web/server/chunks/chunks/uneval.js-5Y6J9rDU.js.map +1 -0
  140. package/dist/web/server/chunks/chunks/utils.js-BQzn9ikS.js +38 -0
  141. package/dist/web/server/chunks/chunks/utils.js-BQzn9ikS.js.map +1 -0
  142. package/dist/web/server/chunks/chunks/utils2.js-CPBdgO4h.js +1627 -0
  143. package/dist/web/server/chunks/chunks/utils2.js-CPBdgO4h.js.map +1 -0
  144. package/dist/web/server/chunks/chunks/web-config.js-CAwLL_g4.js +12 -0
  145. package/dist/web/server/chunks/chunks/web-config.js-CAwLL_g4.js.map +1 -0
  146. package/dist/web/server/chunks/chunks/webhook.js-nEb5r1eG.js +62 -0
  147. package/dist/web/server/chunks/chunks/webhook.js-nEb5r1eG.js.map +1 -0
  148. package/dist/web/server/chunks/entries/endpoints/api/config/events/_server.ts.js-DwgI16MG.js +20 -0
  149. package/dist/web/server/chunks/entries/endpoints/api/config/events/_server.ts.js-DwgI16MG.js.map +1 -0
  150. package/dist/web/server/chunks/entries/endpoints/api/runs/_id_/_server.ts.js-BSKmJv3K.js +14 -0
  151. package/dist/web/server/chunks/entries/endpoints/api/runs/_id_/_server.ts.js-BSKmJv3K.js.map +1 -0
  152. package/dist/web/server/chunks/entries/endpoints/api/runs/_id_/events/_server.ts.js-CWWee51l.js +23 -0
  153. package/dist/web/server/chunks/entries/endpoints/api/runs/_id_/events/_server.ts.js-CWWee51l.js.map +1 -0
  154. package/dist/web/server/chunks/entries/endpoints/api/runs/events/_server.ts.js-CY2mz9bH.js +28 -0
  155. package/dist/web/server/chunks/entries/endpoints/api/runs/events/_server.ts.js-CY2mz9bH.js.map +1 -0
  156. package/dist/web/server/chunks/entries/endpoints/api/runs/start/_name_/_server.ts.js-CD4RKiK6.js +30 -0
  157. package/dist/web/server/chunks/entries/endpoints/api/runs/start/_name_/_server.ts.js-CD4RKiK6.js.map +1 -0
  158. package/dist/web/server/chunks/entries/endpoints/api/webhooks/_name_/_server.ts.js-Dgh42cDJ.js +28 -0
  159. package/dist/web/server/chunks/entries/endpoints/api/webhooks/_name_/_server.ts.js-Dgh42cDJ.js.map +1 -0
  160. package/dist/web/server/chunks/entries/fallbacks/error.svelte.js-LEaHcOM2.js +27 -0
  161. package/dist/web/server/chunks/entries/fallbacks/error.svelte.js-LEaHcOM2.js.map +1 -0
  162. package/dist/web/server/chunks/entries/pages/_layout.svelte.js-D_wzPsk8.js +8 -0
  163. package/dist/web/server/chunks/entries/pages/_layout.svelte.js-D_wzPsk8.js.map +1 -0
  164. package/dist/web/server/chunks/entries/pages/_page.server.ts.js-oVQ0hBaN.js +27 -0
  165. package/dist/web/server/chunks/entries/pages/_page.server.ts.js-oVQ0hBaN.js.map +1 -0
  166. package/dist/web/server/chunks/entries/pages/_page.svelte.js-B7fSvh9U.js +141 -0
  167. package/dist/web/server/chunks/entries/pages/_page.svelte.js-B7fSvh9U.js.map +1 -0
  168. package/dist/web/server/chunks/handler-BpKA6eGq.js +1734 -0
  169. package/dist/web/server/chunks/handler-BpKA6eGq.js.map +1 -0
  170. package/dist/web/server/chunks/index.js-CVS44OGr.js +4157 -0
  171. package/dist/web/server/chunks/index.js-CVS44OGr.js.map +1 -0
  172. package/dist/web/server/chunks/manifest.js-CnB5rB3W.js +84 -0
  173. package/dist/web/server/chunks/manifest.js-CnB5rB3W.js.map +1 -0
  174. package/dist/web/server/chunks/nodes/0.js-BqA14fLU.js +9 -0
  175. package/dist/web/server/chunks/nodes/0.js-BqA14fLU.js.map +1 -0
  176. package/dist/web/server/chunks/nodes/1.js-DzvhRJCS.js +9 -0
  177. package/dist/web/server/chunks/nodes/1.js-DzvhRJCS.js.map +1 -0
  178. package/dist/web/server/chunks/nodes/2.js-DmujW_AW.js +20 -0
  179. package/dist/web/server/chunks/nodes/2.js-DmujW_AW.js.map +1 -0
  180. package/dist/web/shims.js +33 -0
  181. package/dist/web/shims.js.map +1 -0
  182. package/package.json +69 -0
@@ -0,0 +1,477 @@
1
+ import { ansiColor } from "./ansi.js";
2
+ import { stripVTControlCharacters } from "node:util";
3
+ import { Input as TextInput, Key, ProcessTerminal, matchesKey, truncateToWidth, TuiMainScreen, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
4
+ import { renderMarkdown } from "./markdown.js";
5
+ import { totalTokens } from "./usage.js";
6
+ const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
7
+ const paint = (color, text) => {
8
+ const ansi = ansiColor(color);
9
+ return ansi ? `${ansi}${text}\x1b[39m` : text;
10
+ };
11
+ const bold = (text) => `\x1b[1m${text}\x1b[22m`;
12
+ const dim = (text) => `\x1b[2m${text}\x1b[22m`;
13
+ export class RunlingDashboard {
14
+ title;
15
+ startedAt;
16
+ nodes = [];
17
+ nodesById = new Map();
18
+ agents = new Map();
19
+ inputEditors = new Map();
20
+ logSequence = 0;
21
+ usage = {
22
+ input: 0,
23
+ output: 0,
24
+ cacheRead: 0,
25
+ cacheWrite: 0,
26
+ };
27
+ execution;
28
+ constructor(title, startedAt = performance.now()) {
29
+ this.title = title;
30
+ this.startedAt = startedAt;
31
+ }
32
+ handle = (event) => {
33
+ switch (event.type) {
34
+ case "log":
35
+ if (event.source !== "step" &&
36
+ event.source !== "agent" &&
37
+ event.source !== "command" &&
38
+ event.source !== "input") {
39
+ this.addNode({
40
+ type: "log",
41
+ id: `log:${this.logSequence++}`,
42
+ parentId: event.activityId,
43
+ event,
44
+ });
45
+ }
46
+ break;
47
+ case "step.started":
48
+ this.addNode({
49
+ type: "step",
50
+ id: event.id,
51
+ parentId: event.activityId,
52
+ label: event.label,
53
+ status: "running",
54
+ });
55
+ break;
56
+ case "step.finished": {
57
+ const step = this.nodesById.get(event.id);
58
+ if (step?.type === "step") {
59
+ step.status = event.status;
60
+ step.durationMs = event.durationMs;
61
+ }
62
+ break;
63
+ }
64
+ case "command.started":
65
+ this.addNode({
66
+ type: "command",
67
+ id: event.id,
68
+ parentId: event.activityId,
69
+ command: event.command,
70
+ status: "running",
71
+ });
72
+ break;
73
+ case "command.finished": {
74
+ const command = this.nodesById.get(event.id);
75
+ if (command?.type === "command") {
76
+ command.status = event.status;
77
+ command.durationMs = event.durationMs;
78
+ command.output = event.output;
79
+ }
80
+ break;
81
+ }
82
+ case "input.requested":
83
+ this.addNode({
84
+ type: "input",
85
+ id: event.id,
86
+ parentId: event.activityId,
87
+ message: event.message,
88
+ status: "waiting",
89
+ });
90
+ break;
91
+ case "input.finished": {
92
+ const input = this.nodesById.get(event.id);
93
+ if (input?.type === "input") {
94
+ input.status = event.status;
95
+ if (event.status === "answered")
96
+ input.value = event.value;
97
+ input.durationMs = event.durationMs;
98
+ }
99
+ break;
100
+ }
101
+ case "agent.started": {
102
+ const existing = this.agents.get(event.agentId);
103
+ if (existing !== undefined) {
104
+ existing.parentId = event.activityId;
105
+ existing.model = event.model;
106
+ existing.color = event.color;
107
+ existing.action = "Starting";
108
+ existing.outcome = undefined;
109
+ break;
110
+ }
111
+ const agent = {
112
+ type: "agent",
113
+ id: `agent:${event.agentId}`,
114
+ parentId: event.activityId,
115
+ agentId: event.agentId,
116
+ model: event.model,
117
+ color: event.color,
118
+ action: "Starting",
119
+ };
120
+ this.agents.set(event.agentId, agent);
121
+ this.addNode(agent);
122
+ break;
123
+ }
124
+ case "agent.action": {
125
+ const agent = this.agents.get(event.agentId);
126
+ if (agent !== undefined)
127
+ agent.action = event.action;
128
+ break;
129
+ }
130
+ case "agent.finished": {
131
+ const agent = this.agents.get(event.agentId);
132
+ if (agent !== undefined)
133
+ agent.outcome = event.outcome;
134
+ break;
135
+ }
136
+ case "usage.updated":
137
+ this.usage = event.usage;
138
+ break;
139
+ }
140
+ };
141
+ finish(execution) {
142
+ this.execution = execution;
143
+ this.usage = execution.usage;
144
+ }
145
+ invalidate() { }
146
+ setInputEditor(id, editor) {
147
+ if (editor === undefined)
148
+ this.inputEditors.delete(id);
149
+ else
150
+ this.inputEditors.set(id, editor);
151
+ }
152
+ render(width) {
153
+ return [
154
+ ...this.renderHeader(width),
155
+ ...this.renderTranscript(width),
156
+ ...this.renderFooter(width),
157
+ ];
158
+ }
159
+ renderHeader(width) {
160
+ const columns = Math.max(1, Math.floor(width));
161
+ const status = this.execution
162
+ ? this.execution.ok
163
+ ? paint("limegreen", "✓")
164
+ : paint("crimson", "✗")
165
+ : paint("dodgerblue", this.spinner());
166
+ return [
167
+ fit(`${bold(paint("dodgerblue", "◆ Runling"))} ${this.title} ${status}`, columns),
168
+ ];
169
+ }
170
+ renderTranscript(width) {
171
+ const columns = Math.max(1, Math.floor(width));
172
+ const lines = [];
173
+ for (const node of this.nodes) {
174
+ if (!this.hasParent(node)) {
175
+ lines.push(...this.renderNode(node, 1, columns));
176
+ }
177
+ }
178
+ const result = this.execution?.result;
179
+ if (result !== undefined && result !== null) {
180
+ const markdown = result.details === undefined
181
+ ? result.summary
182
+ : `${result.summary}\n\n${result.details}`;
183
+ lines.push("", ...renderMarkdown(markdown, columns).split("\n"));
184
+ }
185
+ return lines.map((line) => fit(line, columns));
186
+ }
187
+ renderFooter(width) {
188
+ const columns = Math.max(1, Math.floor(width));
189
+ const elapsed = formatElapsed(this.execution?.durationMs ?? performance.now() - this.startedAt);
190
+ return [
191
+ dim("─".repeat(columns)),
192
+ fit(this.footer(elapsed), columns),
193
+ ];
194
+ }
195
+ footer(elapsed) {
196
+ const steps = this.nodes.filter((node) => node.type === "step");
197
+ const completed = steps.filter(({ status }) => status === "completed").length;
198
+ const failed = steps.filter(({ status }) => status === "failed").length;
199
+ const waitingForInput = this.nodes.some((node) => node.type === "input" && node.status === "waiting");
200
+ const status = this.execution
201
+ ? this.execution.ok
202
+ ? "Completed"
203
+ : "Failed"
204
+ : waitingForInput
205
+ ? "Waiting for input…"
206
+ : "Working…";
207
+ const tokens = totalTokens(this.usage);
208
+ const parts = [
209
+ status,
210
+ elapsed,
211
+ `${tokens.toLocaleString("en-US")} tokens`,
212
+ ];
213
+ const cacheableInput = this.usage.input + this.usage.cacheRead;
214
+ if (this.usage.cacheRead > 0 && cacheableInput > 0) {
215
+ parts.push(`${Math.round((this.usage.cacheRead / cacheableInput) * 100)}% cached`);
216
+ }
217
+ parts.push(formatCost(this.usage.cost ?? 0));
218
+ if (steps.length > 0) {
219
+ parts.push(failed > 0
220
+ ? `${failed} failed, ${completed}/${steps.length} steps`
221
+ : `${completed}/${steps.length} steps`);
222
+ }
223
+ return dim(parts.join(" · "));
224
+ }
225
+ addNode(node) {
226
+ this.nodes.push(node);
227
+ this.nodesById.set(node.id, node);
228
+ }
229
+ hasParent(node) {
230
+ return node.parentId !== undefined && this.nodesById.has(node.parentId);
231
+ }
232
+ renderNode(node, depth, width) {
233
+ const indent = " ".repeat(depth);
234
+ let lines;
235
+ switch (node.type) {
236
+ case "step": {
237
+ const marker = node.status === "completed"
238
+ ? paint("limegreen", "✓")
239
+ : node.status === "failed"
240
+ ? paint("crimson", "✗")
241
+ : paint("dodgerblue", "●");
242
+ const duration = node.durationMs === undefined
243
+ ? ""
244
+ : ` ${dim(`· ${formatStepDuration(node.durationMs)}`)}`;
245
+ lines = [fit(`${indent}${marker} ${node.label}${duration}`, width)];
246
+ break;
247
+ }
248
+ case "command": {
249
+ const marker = node.status === "completed"
250
+ ? paint("limegreen", "✓")
251
+ : node.status === "failed"
252
+ ? paint("crimson", "✗")
253
+ : paint("#ae3ec9", "●");
254
+ const verb = node.status === "running" ? "Running" : "Ran";
255
+ const duration = node.durationMs === undefined
256
+ ? ""
257
+ : ` ${dim(`· ${formatStepDuration(node.durationMs)}`)}`;
258
+ lines = this.wrapNode(`${indent}${marker} `, `${bold(paint("#ae3ec9", verb))} ${node.command}${duration}`, width);
259
+ if (node.status === "failed" && node.output !== undefined) {
260
+ for (const outputLine of failureOutput(node.output)) {
261
+ lines.push(...this.wrapNode(`${indent} ${paint("crimson", "│")} `, dim(outputLine), width));
262
+ }
263
+ }
264
+ break;
265
+ }
266
+ case "agent": {
267
+ const marker = node.outcome === undefined
268
+ ? paint(node.color, this.spinner())
269
+ : node.outcome === "completed"
270
+ ? paint("limegreen", "✓")
271
+ : node.outcome === "blocked"
272
+ ? paint("#f59f00", "!")
273
+ : paint("crimson", "✗");
274
+ lines = [
275
+ fit(`${indent}${marker} ${paint(node.color, `[${node.agentId}]`)} ${dim(`· ${node.model}`)}`, width),
276
+ ];
277
+ if (node.outcome === undefined) {
278
+ lines.push(...this.wrapNode(`${indent} ${paint(node.color, "↳")} `, node.action, width));
279
+ }
280
+ break;
281
+ }
282
+ case "input": {
283
+ const marker = node.status === "answered"
284
+ ? paint("limegreen", "✓")
285
+ : node.status === "failed"
286
+ ? paint("crimson", "✗")
287
+ : paint("#f59f00", "?");
288
+ const duration = node.durationMs === undefined
289
+ ? ""
290
+ : ` ${dim(`· ${formatStepDuration(node.durationMs)}`)}`;
291
+ lines = this.wrapNode(`${indent}${marker} `, `${node.message}${node.value === undefined ? "" : ` ${dim("→")} ${node.value}`}${duration}`, width);
292
+ const editor = this.inputEditors.get(node.id);
293
+ if (node.status === "waiting" && editor !== undefined) {
294
+ const prefix = `${indent} `;
295
+ const continuation = " ".repeat(visibleWidth(prefix));
296
+ const editorLines = editor.render(Math.max(1, width - visibleWidth(prefix)));
297
+ lines.push(...editorLines.map((line, index) => fit(`${index === 0 ? prefix : continuation}${line}`, width)));
298
+ }
299
+ break;
300
+ }
301
+ case "log": {
302
+ const { event } = node;
303
+ const marker = event.level === "success"
304
+ ? "✓"
305
+ : event.level === "error"
306
+ ? "✗"
307
+ : event.level === "debug"
308
+ ? "·"
309
+ : "●";
310
+ lines = this.wrapNode(`${indent}${paint(event.color, marker)} `, event.message, width);
311
+ break;
312
+ }
313
+ }
314
+ if (node.type !== "step" || node.status !== "completed") {
315
+ for (const child of this.nodes) {
316
+ if (child.parentId === node.id) {
317
+ lines.push(...this.renderNode(child, depth + 1, width));
318
+ }
319
+ }
320
+ }
321
+ return lines;
322
+ }
323
+ wrapNode(prefix, text, width) {
324
+ const continuation = " ".repeat(visibleWidth(prefix));
325
+ const wrapped = wrapTextWithAnsi(text, Math.max(1, width - visibleWidth(prefix)));
326
+ return wrapped.map((line, index) => fit(`${index === 0 ? prefix : continuation}${line}`, width));
327
+ }
328
+ spinner() {
329
+ return SPINNER[Math.floor((performance.now() - this.startedAt) / 80) % SPINNER.length];
330
+ }
331
+ }
332
+ export class TuiReporter {
333
+ onCancel;
334
+ dashboard;
335
+ tui;
336
+ timer;
337
+ started = false;
338
+ stopped = false;
339
+ pendingInputs = new Set();
340
+ pendingEditors = new Set();
341
+ constructor(title, terminal = new ProcessTerminal(), onCancel = () => {
342
+ process.kill(process.pid, "SIGINT");
343
+ }) {
344
+ this.onCancel = onCancel;
345
+ this.dashboard = new RunlingDashboard(title);
346
+ this.tui = new TuiMainScreen(terminal);
347
+ this.tui.addChild(this.dashboard);
348
+ this.tui.addInputListener((data) => {
349
+ if (!matchesKey(data, Key.ctrl("c")))
350
+ return;
351
+ this.stop();
352
+ this.onCancel();
353
+ return { consume: true };
354
+ });
355
+ }
356
+ handle = (event) => {
357
+ this.dashboard.handle(event);
358
+ if (event.type === "input.finished")
359
+ this.tui.renderNow();
360
+ else
361
+ this.tui.requestRender();
362
+ };
363
+ input = (request) => {
364
+ if (!this.started || this.stopped) {
365
+ return Promise.reject(new Error("The TUI cannot request input while stopped"));
366
+ }
367
+ return new Promise((resolve, reject) => {
368
+ let editor;
369
+ const settle = (result) => {
370
+ request.signal?.removeEventListener("abort", abort);
371
+ this.pendingInputs.delete(cancel);
372
+ this.pendingEditors.delete(editor);
373
+ this.dashboard.setInputEditor(request.id, undefined);
374
+ this.tui.setFocus([...this.pendingEditors].at(-1) ?? null);
375
+ this.tui.requestRender();
376
+ if ("value" in result)
377
+ resolve(result.value);
378
+ else
379
+ reject(result.error);
380
+ };
381
+ const cancel = (error) => settle({ error });
382
+ const abort = () => cancel(request.signal?.reason instanceof Error
383
+ ? request.signal.reason
384
+ : new Error("Input cancelled"));
385
+ editor = new InlineInput(request, (value) => settle({ value }), () => cancel(new Error(`Input cancelled: ${request.message}`)));
386
+ this.pendingInputs.add(cancel);
387
+ this.pendingEditors.add(editor);
388
+ request.signal?.addEventListener("abort", abort, { once: true });
389
+ this.dashboard.setInputEditor(request.id, editor);
390
+ this.tui.setFocus(editor);
391
+ this.tui.requestRender();
392
+ });
393
+ };
394
+ start() {
395
+ this.started = true;
396
+ this.tui.start();
397
+ this.timer = setInterval(() => this.tui.requestRender(), 80);
398
+ this.timer.unref();
399
+ }
400
+ finish(execution) {
401
+ this.dashboard.finish(execution);
402
+ this.tui.renderNow();
403
+ }
404
+ stop() {
405
+ if (!this.started || this.stopped)
406
+ return;
407
+ this.stopped = true;
408
+ for (const cancel of [...this.pendingInputs]) {
409
+ cancel(new Error("The TUI stopped while waiting for input"));
410
+ }
411
+ if (this.timer !== undefined)
412
+ clearInterval(this.timer);
413
+ this.tui.stop();
414
+ }
415
+ }
416
+ class InlineInput {
417
+ request;
418
+ input = new TextInput();
419
+ constructor(request, onSubmit, onCancel) {
420
+ this.request = request;
421
+ if (request.defaultValue !== undefined) {
422
+ for (const character of request.defaultValue) {
423
+ this.input.handleInput(character);
424
+ }
425
+ }
426
+ this.input.onSubmit = onSubmit;
427
+ this.input.onEscape = onCancel;
428
+ }
429
+ get focused() {
430
+ return this.input.focused;
431
+ }
432
+ set focused(value) {
433
+ this.input.focused = value;
434
+ }
435
+ handleInput(data) {
436
+ this.input.handleInput(data);
437
+ }
438
+ invalidate() {
439
+ this.input.invalidate();
440
+ }
441
+ render(width) {
442
+ return this.input.render(Math.max(1, Math.floor(width)));
443
+ }
444
+ }
445
+ const fit = (line, width) => truncateToWidth(line, width, "");
446
+ const formatElapsed = (durationMs) => {
447
+ const seconds = Math.max(0, Math.floor(durationMs / 1000));
448
+ const minutes = Math.floor(seconds / 60);
449
+ return minutes > 0
450
+ ? `${minutes}m ${String(seconds % 60).padStart(2, "0")}s`
451
+ : `${seconds}s`;
452
+ };
453
+ const formatStepDuration = (durationMs) => durationMs < 1000
454
+ ? `${Math.round(durationMs)}ms`
455
+ : `${(durationMs / 1000).toFixed(1)}s`;
456
+ const formatCost = (cost) => cost === 0
457
+ ? "$0.00"
458
+ : cost < 0.0001
459
+ ? "<$0.0001"
460
+ : `$${cost.toFixed(cost < 0.01 ? 4 : 2)}`;
461
+ const COMMAND_FAILURE_LINES = 8;
462
+ const failureOutput = ({ stdout, stderr, }) => {
463
+ const lines = stripVTControlCharacters([stdout.trimEnd(), stderr.trimEnd()].filter(Boolean).join("\n"))
464
+ .split("\n")
465
+ .map((line) => line.trimEnd());
466
+ while (lines[0]?.trim() === "")
467
+ lines.shift();
468
+ while (lines.at(-1)?.trim() === "")
469
+ lines.pop();
470
+ if (lines.length <= COMMAND_FAILURE_LINES)
471
+ return lines;
472
+ const omitted = lines.length - COMMAND_FAILURE_LINES;
473
+ return [
474
+ `… ${omitted.toLocaleString("en-US")} earlier lines omitted`,
475
+ ...lines.slice(-COMMAND_FAILURE_LINES),
476
+ ];
477
+ };
@@ -0,0 +1,35 @@
1
+ export interface TokenUsage {
2
+ /** Non-cached input tokens. */
3
+ input: number;
4
+ /** Output tokens (including reasoning tokens when reported). */
5
+ output: number;
6
+ /** Input tokens served from the prompt cache. */
7
+ cacheRead: number;
8
+ /** Input tokens written to the prompt cache. */
9
+ cacheWrite: number;
10
+ /** Provider/model-aware cost in US dollars, when reported by Pi. */
11
+ cost?: number;
12
+ /** Some recorded tokens have no reported price. */
13
+ costIncomplete?: boolean;
14
+ }
15
+ type TokenUsageInput = Omit<TokenUsage, "cost"> & {
16
+ cost?: number | {
17
+ total?: number;
18
+ };
19
+ };
20
+ export declare function emptyTokenUsage(): TokenUsage;
21
+ /** Accumulate `usage` into `total` in place. Missing or malformed usage is ignored. */
22
+ export declare function accumulateTokenUsage(total: TokenUsage, usage: TokenUsageInput | undefined): TokenUsage;
23
+ /** Sum of all tracked token counts; zero means nothing was recorded. */
24
+ export declare function totalTokens(usage: TokenUsage): number;
25
+ export declare function formatTokenUsage(usage: TokenUsage): string;
26
+ export declare function isTokenUsage(value: unknown): value is TokenUsage;
27
+ /** Keep token totals local to one execution, including its parallel agents. */
28
+ export declare const withTokenUsage: <T>(work: () => T) => T;
29
+ /** Add one agent interaction's usage to the workflow-wide totals. */
30
+ export declare function recordTokenUsage(usage: TokenUsageInput): void;
31
+ /** Workflow-wide token usage accumulated via `recordTokenUsage`. */
32
+ export declare function getRecordedTokenUsage(): TokenUsage;
33
+ /** Reset workflow-wide totals, e.g. at the start of a workflow execution. */
34
+ export declare function resetTokenUsage(): void;
35
+ export {};
@@ -0,0 +1,90 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { emitRunlingEvent } from "./events.js";
3
+ export function emptyTokenUsage() {
4
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
5
+ }
6
+ /** Accumulate `usage` into `total` in place. Missing or malformed usage is ignored. */
7
+ export function accumulateTokenUsage(total, usage) {
8
+ if (!hasValidTokenCounts(usage)) {
9
+ return total;
10
+ }
11
+ total.input += usage.input;
12
+ total.output += usage.output;
13
+ total.cacheRead += usage.cacheRead;
14
+ total.cacheWrite += usage.cacheWrite;
15
+ const cost = readCost(usage.cost);
16
+ if (cost !== undefined)
17
+ total.cost = (total.cost ?? 0) + cost;
18
+ if (usage.costIncomplete ||
19
+ (cost === undefined &&
20
+ usage.input + usage.output + usage.cacheRead + usage.cacheWrite > 0))
21
+ total.costIncomplete = true;
22
+ return total;
23
+ }
24
+ /** Sum of all tracked token counts; zero means nothing was recorded. */
25
+ export function totalTokens(usage) {
26
+ return usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
27
+ }
28
+ function formatCount(value) {
29
+ return value.toLocaleString("en-US");
30
+ }
31
+ export function formatTokenUsage(usage) {
32
+ const parts = [
33
+ `in ${formatCount(usage.input)}`,
34
+ `out ${formatCount(usage.output)}`,
35
+ ];
36
+ if (usage.cacheRead > 0 || usage.cacheWrite > 0) {
37
+ parts.push(`cache read ${formatCount(usage.cacheRead)}`);
38
+ parts.push(`cache write ${formatCount(usage.cacheWrite)}`);
39
+ }
40
+ return parts.join(", ");
41
+ }
42
+ export function isTokenUsage(value) {
43
+ if (!hasValidTokenCounts(value))
44
+ return false;
45
+ const usage = value;
46
+ return ((usage.costIncomplete === undefined ||
47
+ typeof usage.costIncomplete === "boolean") &&
48
+ (usage.cost === undefined ||
49
+ (Number.isFinite(usage.cost) && usage.cost >= 0)));
50
+ }
51
+ const hasValidTokenCounts = (value) => {
52
+ if (typeof value !== "object" || value === null)
53
+ return false;
54
+ const usage = value;
55
+ return [usage.input, usage.output, usage.cacheRead, usage.cacheWrite].every((count) => Number.isSafeInteger(count) && count >= 0);
56
+ };
57
+ const readCost = (cost) => {
58
+ const value = typeof cost === "number" ? cost : cost?.total;
59
+ return value !== undefined && Number.isFinite(value) && value >= 0
60
+ ? value
61
+ : undefined;
62
+ };
63
+ let recordedUsage = emptyTokenUsage();
64
+ const executionUsage = new AsyncLocalStorage();
65
+ /** Keep token totals local to one execution, including its parallel agents. */
66
+ export const withTokenUsage = (work) => executionUsage.run(emptyTokenUsage(), work);
67
+ /** Add one agent interaction's usage to the workflow-wide totals. */
68
+ export function recordTokenUsage(usage) {
69
+ accumulateTokenUsage(executionUsage.getStore() ?? recordedUsage, usage);
70
+ emitRunlingEvent({
71
+ type: "usage.updated",
72
+ usage: getRecordedTokenUsage(),
73
+ });
74
+ }
75
+ /** Workflow-wide token usage accumulated via `recordTokenUsage`. */
76
+ export function getRecordedTokenUsage() {
77
+ return { ...(executionUsage.getStore() ?? recordedUsage) };
78
+ }
79
+ /** Reset workflow-wide totals, e.g. at the start of a workflow execution. */
80
+ export function resetTokenUsage() {
81
+ const current = executionUsage.getStore();
82
+ if (current) {
83
+ Object.assign(current, emptyTokenUsage());
84
+ delete current.cost;
85
+ delete current.costIncomplete;
86
+ }
87
+ else {
88
+ recordedUsage = emptyTokenUsage();
89
+ }
90
+ }
@@ -0,0 +1,2 @@
1
+ export type ConcatPart = string | readonly ConcatPart[];
2
+ export declare function concat(...parts: ConcatPart[]): string;
@@ -0,0 +1,16 @@
1
+ export function concat(...parts) {
2
+ const lines = [];
3
+ const append = (part) => {
4
+ if (typeof part === "string") {
5
+ lines.push(part);
6
+ return;
7
+ }
8
+ for (const nestedPart of part) {
9
+ append(nestedPart);
10
+ }
11
+ };
12
+ for (const part of parts) {
13
+ append(part);
14
+ }
15
+ return lines.join("\n");
16
+ }
@@ -0,0 +1,16 @@
1
+ import { type Workflow } from "./workflow.ts";
2
+ import type { TSchema } from "typebox";
3
+ export interface WebhookDefinition<WorkflowInputSchema extends TSchema, WorkflowOutputSchema extends TSchema> {
4
+ workflow: Workflow<WorkflowInputSchema, WorkflowOutputSchema>;
5
+ }
6
+ type AnyWebhookDefinition = {
7
+ workflow: ((...args: any[]) => unknown) & Pick<Workflow, "name" | "input" | "output">;
8
+ };
9
+ export interface WebConfig<Webhooks extends Record<string, AnyWebhookDefinition> = Record<string, AnyWebhookDefinition>> {
10
+ webhooks: Webhooks;
11
+ }
12
+ /** Preserve webhook names and schema types in a Runling web configuration. */
13
+ export declare function defineWebConfig<const Webhooks extends Record<string, AnyWebhookDefinition>>(config: WebConfig<Webhooks>): WebConfig<Webhooks>;
14
+ /** Check the runtime shape of a Runling web configuration. */
15
+ export declare function isWebConfig(value: unknown): value is WebConfig;
16
+ export {};
@@ -0,0 +1,22 @@
1
+ import { isWorkflow } from "./workflow.js";
2
+ /** Preserve webhook names and schema types in a Runling web configuration. */
3
+ export function defineWebConfig(config) {
4
+ return config;
5
+ }
6
+ /** Check the runtime shape of a Runling web configuration. */
7
+ export function isWebConfig(value) {
8
+ if (typeof value !== "object" ||
9
+ value === null ||
10
+ !("webhooks" in value) ||
11
+ typeof value.webhooks !== "object" ||
12
+ value.webhooks === null ||
13
+ Array.isArray(value.webhooks)) {
14
+ return false;
15
+ }
16
+ return Object.values(value.webhooks).every((definition) => typeof definition === "object" &&
17
+ definition !== null &&
18
+ "workflow" in definition &&
19
+ isWorkflow(definition.workflow) &&
20
+ !("body" in definition) &&
21
+ !("input" in definition));
22
+ }
@@ -0,0 +1,8 @@
1
+ export declare function parseRunlingWebArguments(argv: readonly string[]): {
2
+ port: number;
3
+ config: string;
4
+ host: string;
5
+ open: boolean;
6
+ help: boolean;
7
+ };
8
+ export declare function runRunlingWeb(argv?: string[]): Promise<void>;