@llm4ts/runner 2.21.0 → 2.22.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.
- package/dist/AgentTree.d.ts +32 -0
- package/dist/AgentTree.d.ts.map +1 -1
- package/dist/AgentTree.js +94 -10
- package/dist/AgentTree.js.map +1 -1
- package/dist/AgentTreeSurface.d.ts +22 -1
- package/dist/AgentTreeSurface.d.ts.map +1 -1
- package/dist/AgentTreeSurface.js +50 -11
- package/dist/AgentTreeSurface.js.map +1 -1
- package/dist/FlowRunner.d.ts.map +1 -1
- package/dist/FlowRunner.js +10 -7
- package/dist/FlowRunner.js.map +1 -1
- package/dist/Profile.d.ts +166 -0
- package/dist/Profile.d.ts.map +1 -0
- package/dist/Profile.js +497 -0
- package/dist/Profile.js.map +1 -0
- package/dist/Terminal.d.ts.map +1 -1
- package/dist/Terminal.js +2 -0
- package/dist/Terminal.js.map +1 -1
- package/dist/Watch.d.ts +15 -0
- package/dist/Watch.d.ts.map +1 -1
- package/dist/Watch.js +26 -12
- package/dist/Watch.js.map +1 -1
- package/package.json +4 -3
- package/src/AgentTree.ts +136 -10
- package/src/AgentTreeSurface.ts +76 -17
- package/src/FlowRunner.ts +19 -11
- package/src/Profile.ts +645 -0
- package/src/Terminal.ts +2 -0
- package/src/Watch.ts +50 -17
package/src/Watch.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readdirSync, existsSync, statSync } from "node:fs"
|
|
2
2
|
import { join } from "node:path"
|
|
3
|
+
import * as Clock from "effect/Clock"
|
|
3
4
|
import * as Duration from "effect/Duration"
|
|
4
5
|
import * as Effect from "effect/Effect"
|
|
5
6
|
import * as Queue from "effect/Queue"
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
type TreeState,
|
|
23
24
|
type TreeView
|
|
24
25
|
} from "./AgentTree.ts"
|
|
25
|
-
import { nodeTreeKeys } from "./AgentTreeSurface.ts"
|
|
26
|
+
import { enterScreen, leaveScreen, makeScreenPainter, nodeTreeKeys } from "./AgentTreeSurface.ts"
|
|
26
27
|
import { nodeListTraces, TraceDirectoryName } from "./Costs.ts"
|
|
27
28
|
import { nodePlainFileStore } from "./NodePlainFileStore.ts"
|
|
28
29
|
|
|
@@ -42,6 +43,8 @@ export interface WatchOptions {
|
|
|
42
43
|
readonly replay?: boolean
|
|
43
44
|
/** Replay speed-up (default 10). */
|
|
44
45
|
readonly speed?: number
|
|
46
|
+
/** A live lane with no event for this long is marked idle (`LLM4TS_IDLE_AFTER`). */
|
|
47
|
+
readonly idleAfterMs?: number
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
export interface WatchOutput {
|
|
@@ -68,7 +71,13 @@ export class WatchTargetMissing extends Schema.TaggedError<WatchTargetMissing>()
|
|
|
68
71
|
{ message: Schema.String }
|
|
69
72
|
) {}
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
/** Where a run's trace and its epic are read from. */
|
|
75
|
+
export type TraceSources = Pick<WatchDependencies, "files" | "listTraces" | "listEpics">
|
|
76
|
+
|
|
77
|
+
/** A trace to read: given, an epic's latest run, or the newest in the repository. */
|
|
78
|
+
export type TraceChoice = Pick<WatchOptions, "repo" | "trace" | "epic">
|
|
79
|
+
|
|
80
|
+
export interface WatchTarget {
|
|
72
81
|
readonly tracePath: string
|
|
73
82
|
readonly epicDir: string | undefined
|
|
74
83
|
readonly run: EpicRun | undefined
|
|
@@ -80,7 +89,7 @@ const epicsDirOf = (repo: string): string => join(repo, TraceDirectoryName, "epi
|
|
|
80
89
|
const epicOfTrace = (
|
|
81
90
|
tracePath: string,
|
|
82
91
|
repo: string,
|
|
83
|
-
dependencies:
|
|
92
|
+
dependencies: TraceSources
|
|
84
93
|
): Effect.Effect<{ readonly dir: string; readonly run: EpicRun } | undefined, FlowError> =>
|
|
85
94
|
Effect.gen(function* () {
|
|
86
95
|
const epicsDir = epicsDirOf(repo)
|
|
@@ -96,9 +105,9 @@ const epicOfTrace = (
|
|
|
96
105
|
return undefined
|
|
97
106
|
})
|
|
98
107
|
|
|
99
|
-
const
|
|
100
|
-
options:
|
|
101
|
-
dependencies:
|
|
108
|
+
export const resolveTraceTarget = (
|
|
109
|
+
options: TraceChoice,
|
|
110
|
+
dependencies: TraceSources
|
|
102
111
|
): Effect.Effect<WatchTarget, FlowError | WatchTargetMissing> =>
|
|
103
112
|
Effect.gen(function* () {
|
|
104
113
|
if (options.epic !== undefined) {
|
|
@@ -143,12 +152,14 @@ const actionWords = (run: EpicRun | undefined): string | undefined => {
|
|
|
143
152
|
/** The tree before any event: the epic's board, when the trace belongs to one. */
|
|
144
153
|
const startingTree = (
|
|
145
154
|
target: WatchTarget,
|
|
146
|
-
files: PlainFileStoreShape
|
|
155
|
+
files: PlainFileStoreShape,
|
|
156
|
+
idleAfterMs: number | undefined
|
|
147
157
|
): Effect.Effect<TreeState, FlowError> =>
|
|
148
158
|
Effect.gen(function* () {
|
|
149
159
|
const action = actionWords(target.run)
|
|
160
|
+
const idle = idleAfterMs === undefined ? {} : { idleAfterMs }
|
|
150
161
|
if (target.epicDir === undefined) {
|
|
151
|
-
return emptyTree(action === undefined ? {} : { action })
|
|
162
|
+
return emptyTree({ ...idle, ...(action === undefined ? {} : { action }) })
|
|
152
163
|
}
|
|
153
164
|
const boardDir =
|
|
154
165
|
target.run?.round === undefined
|
|
@@ -159,6 +170,7 @@ const startingTree = (
|
|
|
159
170
|
return emptyTree({
|
|
160
171
|
title: board === undefined ? `epic ${epicId}` : board.title.replace(/^Epic:\s*/u, "epic "),
|
|
161
172
|
stories: (board?.items ?? []).map((item) => ({ id: item.id, status: item.status })),
|
|
173
|
+
...idle,
|
|
162
174
|
...(action === undefined ? {} : { action })
|
|
163
175
|
})
|
|
164
176
|
})
|
|
@@ -169,9 +181,6 @@ const readInputs = (
|
|
|
169
181
|
): Effect.Effect<ReadonlyArray<TreeInput>, FlowError> =>
|
|
170
182
|
Effect.map(readTrace(files, target.tracePath), treeInputsOfTrace)
|
|
171
183
|
|
|
172
|
-
const enterScreen = "\u001b[?1049h\u001b[?25l"
|
|
173
|
-
const leaveScreen = "\u001b[?25h\u001b[?1049l"
|
|
174
|
-
const home = "\u001b[H\u001b[2J"
|
|
175
184
|
const pollInterval = Duration.millis(500)
|
|
176
185
|
const replayGapCap = 2_000
|
|
177
186
|
|
|
@@ -184,17 +193,22 @@ const draw = (state: TreeState, view: TreeView, output: WatchOutput, height?: nu
|
|
|
184
193
|
}).join("\n")
|
|
185
194
|
|
|
186
195
|
/** A full-screen frame: no taller than the terminal. */
|
|
187
|
-
const
|
|
188
|
-
|
|
196
|
+
const screenLines = (
|
|
197
|
+
state: TreeState,
|
|
198
|
+
view: TreeView,
|
|
199
|
+
output: WatchOutput
|
|
200
|
+
): ReadonlyArray<string> => draw(state, view, output, output.rows()).split("\n")
|
|
189
201
|
|
|
190
202
|
export const makeWatchProgram = Effect.fn("@llm4ts/runner/Watch.make")(function* (
|
|
191
203
|
options: WatchOptions,
|
|
192
204
|
dependencies: WatchDependencies
|
|
193
205
|
): Effect.fn.Return<void, FlowError | WatchTargetMissing> {
|
|
194
|
-
const target = yield*
|
|
206
|
+
const target = yield* resolveTraceTarget(options, dependencies)
|
|
195
207
|
const { files, output } = dependencies
|
|
196
208
|
const fold = (inputs: ReadonlyArray<TreeInput>): Effect.Effect<TreeState, FlowError> =>
|
|
197
|
-
Effect.map(startingTree(target, files), (start) =>
|
|
209
|
+
Effect.map(startingTree(target, files, options.idleAfterMs), (start) =>
|
|
210
|
+
inputs.reduce(reduceTree, start)
|
|
211
|
+
)
|
|
198
212
|
|
|
199
213
|
if (!output.interactive) {
|
|
200
214
|
const state = yield* Effect.flatMap(readInputs(target, files), fold)
|
|
@@ -236,13 +250,26 @@ export const makeWatchProgram = Effect.fn("@llm4ts/runner/Watch.make")(function*
|
|
|
236
250
|
Effect.as(Effect.sleep(wait), false)
|
|
237
251
|
)
|
|
238
252
|
|
|
253
|
+
const painter = yield* makeScreenPainter
|
|
239
254
|
yield* output.write(enterScreen)
|
|
240
255
|
yield* Effect.gen(function* () {
|
|
241
256
|
while (true) {
|
|
242
257
|
const current = yield* Ref.get(inputs)
|
|
243
258
|
const count = yield* Ref.get(shown)
|
|
244
|
-
const
|
|
245
|
-
|
|
259
|
+
const folded = yield* fold(current.slice(0, count))
|
|
260
|
+
// Following a live run, the clock moves between events; a replay
|
|
261
|
+
// and a finished run keep the trace's own time.
|
|
262
|
+
const state =
|
|
263
|
+
options.replay !== true && folded.ended === undefined
|
|
264
|
+
? reduceTree(folded, { _tag: "Tick", at: yield* Clock.currentTimeMillis })
|
|
265
|
+
: folded
|
|
266
|
+
const text = yield* painter.paint(
|
|
267
|
+
screenLines(state, yield* Ref.get(view), output),
|
|
268
|
+
`${output.columns()}x${output.rows()}`
|
|
269
|
+
)
|
|
270
|
+
if (text.length > 0) {
|
|
271
|
+
yield* output.write(text)
|
|
272
|
+
}
|
|
246
273
|
const replaying = options.replay === true && count < current.length
|
|
247
274
|
const gap = replaying
|
|
248
275
|
? Math.min(replayGapCap, (current[count]?.at ?? 0) - (current[count - 1]?.at ?? 0)) /
|
|
@@ -271,6 +298,12 @@ const nodeListEpics = (directory: string): ReadonlyArray<string> =>
|
|
|
271
298
|
? readdirSync(directory).filter((name) => statSync(join(directory, name)).isDirectory())
|
|
272
299
|
: []
|
|
273
300
|
|
|
301
|
+
export const nodeTraceSources: TraceSources = {
|
|
302
|
+
files: nodePlainFileStore,
|
|
303
|
+
listTraces: nodeListTraces,
|
|
304
|
+
listEpics: nodeListEpics
|
|
305
|
+
}
|
|
306
|
+
|
|
274
307
|
export const nodeWatchDependencies = (
|
|
275
308
|
environment: Readonly<Record<string, string | undefined>> = process.env
|
|
276
309
|
): WatchDependencies => {
|