@capagents/forgeqe 1.0.3 → 1.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capagents/forgeqe",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "ForgeQE: a browser agent CLI that runs goals and test cases in Chrome and turns passing runs into tests in your repo",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/tui/App.tsx CHANGED
@@ -83,6 +83,9 @@ export const SPLIT_MIN = 120
83
83
  export function App({ options }: { options: TuiOptions }) {
84
84
  const renderer = useRenderer()
85
85
  const { width, height } = useTerminalDimensions()
86
+ /** The terminal's width when a request ends (callbacks outlive the render they were made in). */
87
+ const widthRef = useRef(width)
88
+ widthRef.current = width
86
89
  const scrollRef = useRef<ScrollBoxRenderable>(null)
87
90
  const activityScroll = useRef<ScrollBoxRenderable>(null)
88
91
  const submitRef = useRef<(value: string) => void>(() => {})
@@ -775,7 +778,8 @@ export function App({ options }: { options: TuiOptions }) {
775
778
  } finally {
776
779
  pushProduced()
777
780
  // What this request used and cost in total: ForgeQE's agent, browser runs, Scribe, and the reviewer.
778
- const spent = requestLine(meterStart)
781
+ // The split view's session and tokens panels show this; only the one-pane view needs it in the chat.
782
+ const spent = widthRef.current < SPLIT_MIN ? requestLine(meterStart) : ""
779
783
  if (spent) push("sys", spent, "chat")
780
784
  finish(finalStatus)
781
785
  }
@@ -1071,17 +1075,11 @@ export function App({ options }: { options: TuiOptions }) {
1071
1075
  }
1072
1076
  const llm = cfgNow?.llm
1073
1077
  const sessionInfo: SessionInfo = {
1074
- profile,
1075
- model: model || (cfgNow ? modelLabel(cfgNow) : ""),
1076
- auth: !llm ? "–" : llm.auth === "entra" ? "Entra ID, no key" : llm.apiKeyEnv ? `API key in ${llm.apiKeyEnv}` : llm.provider === "mock" ? "offline demo" : llm.provider,
1078
+ model: cfgNow ? `${llm?.provider === "mock" ? "offline demo" : llm?.deployment || llm?.model || "none"}${profile ? ` (${profile})` : ""}` : "…",
1077
1079
  cwd: shortCwd(),
1078
- tests: testsRef.current.on ? "written to this folder" : requests.current ? `not written (${testsRef.current.why || "off"})` : "set on the first request",
1079
- browser: `${headed ? "visible" : "headless"} · up to ${maxSteps} steps${options.url ? ` · ${hostOf(options.url)}` : ""}`,
1080
1080
  ...(cfgNow?.domain ? { domain: `${cfgNow.domain.industry}${cfgNow.domain.regulations.length ? ` (${cfgNow.domain.regulations.join(", ")})` : ""}` } : {}),
1081
- ...(llm?.priceWarning && llm.price ? { price: `${llm.price.input}/${llm.price.output} ${llm.price.currency} per M in/out from llms.json, not the list price` } : {}),
1082
1081
  started: sessionStarted.current,
1083
1082
  requests: requests.current,
1084
- total: spent.roles.length ? { usage: spent.total, cost: spent.cost } : undefined,
1085
1083
  }
1086
1084
  const contextPanel: ContextInfo = {
1087
1085
  tokens: insp.context,
@@ -1119,7 +1117,7 @@ export function App({ options }: { options: TuiOptions }) {
1119
1117
  <box style={{ flexDirection: "row", height: bottom, flexShrink: 0 }}>
1120
1118
  <FilesPanel s={insp} width={third} height={bottom} />
1121
1119
  <ContextPanel c={contextPanel} width={third} height={bottom} />
1122
- <TokensPanel roles={spent.roles} total={{ usage: spent.total, cost: spent.cost }} live={running ? tokens : 0} width={paneWidth - 2 * third} height={bottom} />
1120
+ <TokensPanel total={spent.total} width={paneWidth - 2 * third} height={bottom} />
1123
1121
  </box>
1124
1122
  </box>
1125
1123
  )
@@ -2,7 +2,6 @@ import type { ScrollBoxRenderable } from "@opentui/core"
2
2
  import type { ReactNode, RefObject } from "react"
3
3
 
4
4
  import type { Usage } from "../types.ts"
5
- import { formatCost, type Cost, type Role } from "../usage.ts"
6
5
  import { Spinner } from "./components.tsx"
7
6
  import { filesByUse, type FileUse, type Inspector, type Progress } from "./session.ts"
8
7
  import { clip, tailCut } from "./text.ts"
@@ -22,13 +21,6 @@ export const SECTION = {
22
21
  tokens: theme.ok,
23
22
  }
24
23
 
25
- const ROLE_COLOR: Record<Role, string> = {
26
- "ForgeQE's agent": theme.accent,
27
- "browser agent": theme.thought,
28
- Scribe: theme.ok,
29
- reviewer: theme.warn,
30
- judge: theme.memory,
31
- }
32
24
 
33
25
  /** Rows a panel's border takes: top and bottom. */
34
26
  export const PANEL_CHROME = 2
@@ -57,47 +49,36 @@ export function Panel(props: { title: string; color: string; width?: number; hei
57
49
  }
58
50
 
59
51
  export type SessionInfo = {
60
- profile: string
52
+ /** "gpt-5.4 (azure-gpt54)": the model, and the llms.json profile it comes from. */
61
53
  model: string
62
- auth: string
63
54
  cwd: string
64
- tests: string
65
- browser: string
66
55
  domain?: string
67
- /** The profile's price is set in llms.json and differs from the model's list price. */
68
- price?: string
69
56
  started?: number
70
57
  requests: number
71
- total?: { usage: Usage; cost?: Cost }
72
58
  }
73
59
 
74
60
  /** Rows SessionPanel takes, border included. */
75
61
  export function sessionRows(info: SessionInfo): number {
76
- return 6 + (info.domain ? 1 : 0) + (info.price ? 1 : 0) + PANEL_CHROME
62
+ return 3 + (info.domain ? 1 : 0) + PANEL_CHROME
77
63
  }
78
64
 
79
- /** Top of the left pane: what this session runs with, and what it has used. */
65
+ /** Top of the left pane: what the session works with, nothing more; tokens are in their own panel. */
80
66
  export function SessionPanel({ info, width }: { info: SessionInfo; width: number }) {
81
- const room = Math.max(10, width - 14)
67
+ const room = Math.max(10, width - 15)
82
68
  const row = (label: string, value: string, color: string = theme.text) => (
83
69
  <text key={label}>
84
- <span fg={theme.muted}>{label.padEnd(9)}</span>
70
+ <span fg={theme.muted}>{label.padEnd(10)}</span>
85
71
  <span fg={color}>{clip(value, room)}</span>
86
72
  </text>
87
73
  )
88
74
  const started = info.started ? new Date(info.started) : null
89
75
  const since = started ? ` · since ${String(started.getHours()).padStart(2, "0")}:${String(started.getMinutes()).padStart(2, "0")}` : ""
90
- const used = info.total ? `${formatTokens(info.total.usage.input + info.total.usage.output)} tokens${info.total.cost ? ` · ${formatCost(info.total.cost)}` : ""}` : "nothing yet"
91
76
  return (
92
77
  <Panel title={`Session${since}`} color={SECTION.session} width={width}>
93
- {row("Model", `${info.profile || "none"} · ${info.model || "…"}`)}
94
- {row("Sign-in", info.auth, info.auth.startsWith("Entra") ? theme.ok : theme.text)}
78
+ {row("Model", info.model)}
95
79
  {row("Folder", tailCut(info.cwd, room))}
96
- {row("Tests", info.tests, info.tests.startsWith("written") ? theme.ok : theme.muted)}
97
- {row("Browser", info.browser)}
98
80
  {info.domain ? row("Domain", info.domain, theme.memory) : null}
99
- {info.price ? row("Price", info.price, theme.warn) : null}
100
- {row("Used", `${info.requests} request${info.requests === 1 ? "" : "s"} · ${used}`, theme.muted)}
81
+ {row("Requests", String(info.requests), theme.muted)}
101
82
  </Panel>
102
83
  )
103
84
  }
@@ -309,40 +290,21 @@ export function ContextPanel({ c, width, height }: { c: ContextInfo; width: numb
309
290
  )
310
291
  }
311
292
 
312
- export function TokensPanel(props: {
313
- roles: { role: Role; usage: Usage; cost?: Cost }[]
314
- total: { usage: Usage; cost?: Cost }
315
- /** Tokens of the request in progress, counted as they stream; the roles above get them when each run ends. */
316
- live?: number
317
- width: number
318
- height: number
319
- }) {
320
- const { roles, total, width } = props
321
- // The label takes what the token count (7) and, when priced, the cost (two spaces and up to 7) leave.
322
- const priced = roles.some((r) => r.cost) || Boolean(total.cost)
323
- const labelWidth = Math.max(6, width - 4 - 7 - (priced ? 9 : 0))
324
- const line = (label: string, labelColor: string, u: Usage, c: Cost | undefined, valueColor: string = theme.text) => (
293
+ /** What the session used, in tokens: the total and how it splits. Costs are left to reports and `forgeqe models`. */
294
+ export function TokensPanel(props: { total: Usage; width: number; height: number }) {
295
+ const { total, width } = props
296
+ const line = (label: string, n: number, color: string = theme.text) => (
325
297
  <text key={label}>
326
- <span fg={labelColor}>{clip(label, labelWidth).padEnd(labelWidth)}</span>
327
- <span fg={valueColor}>{formatTokens(u.input + u.output).padStart(7)}</span>
328
- {c ? <span fg={theme.muted}>{` ${formatCost(c)}`}</span> : null}
298
+ <span fg={theme.muted}>{label.padEnd(10)}</span>
299
+ <span fg={color}>{formatTokens(n).padStart(Math.max(7, Math.min(10, width - 14)))}</span>
329
300
  </text>
330
301
  )
331
302
  return (
332
303
  <Panel title="Tokens" color={SECTION.tokens} width={width} height={props.height}>
333
- {props.live ? (
334
- <text>
335
- <span fg={theme.warn}>{"This request".padEnd(labelWidth)}</span>
336
- <span fg={theme.warn}>{formatTokens(props.live).padStart(7)}</span>
337
- </text>
338
- ) : null}
339
- {roles.length ? roles.map((r) => line(r.role, ROLE_COLOR[r.role] || theme.text, r.usage, r.cost)) : props.live ? null : <text fg={theme.faint}>{"None used yet"}</text>}
340
- {roles.length > 1 ? line("Total", theme.text, total.usage, total.cost, theme.ok) : null}
341
- {roles.length ? (
342
- <text fg={theme.faint}>
343
- {clip(`in ${formatTokens(total.usage.input)} · out ${formatTokens(total.usage.output)}${total.usage.cached ? ` · ${formatTokens(total.usage.cached)} cached` : ""}`, Math.max(10, width - 4))}
344
- </text>
345
- ) : null}
304
+ {line("Total", total.input + total.output, theme.ok)}
305
+ {line("Input", total.input)}
306
+ {line("Output", total.output)}
307
+ {line("Cached", total.cached || 0, theme.muted)}
346
308
  </Panel>
347
309
  )
348
310
  }