@capagents/forgeqe 1.0.0 → 1.0.2

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.0",
3
+ "version": "1.0.2",
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",
@@ -80,7 +80,7 @@ OVERLAY_JS = r"""(() => {
80
80
  const mark = el('canvas', 'width:26px;height:26px;border-radius:6px;flex:none;display:block;');
81
81
  paintMark(mark);
82
82
  const word = el('span', 'flex:none;font-weight:800;font-size:15px;letter-spacing:-.02em;');
83
- word.append(el('span', 'color:#f4f8ff', 'Web'), el('span', 'color:#3ec4ff', 'Pilot'));
83
+ word.append(el('span', 'color:#f4f8ff', 'Forge'), el('span', 'color:#3ec4ff', 'QE'));
84
84
  const sep = el('span', 'flex:none;width:1px;height:18px;background:rgba(255,255,255,.22);');
85
85
  textEl = el('span', 'flex:1 1 auto;min-width:0;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;' +
86
86
  'word-break:break-word;color:rgba(244,248,255,.92);');
@@ -178,7 +178,7 @@ START_JS = r"""(() => {
178
178
  img.src = __ICON__;
179
179
  img.alt = '';
180
180
  const word = el('div', 'font-size:46px;font-weight:800;letter-spacing:-.03em;line-height:1;');
181
- word.append(el('span', 'color:#f4f8ff', 'Web'), el('span', 'color:#3ec4ff', 'Pilot'));
181
+ word.append(el('span', 'color:#f4f8ff', 'Forge'), el('span', 'color:#3ec4ff', 'QE'));
182
182
  const pill = el('div', 'display:flex;align-items:center;gap:10px;padding:8px 16px;border-radius:999px;background:rgba(255,255,255,.08);' +
183
183
  'border:1px solid rgba(62,196,255,.3);color:rgba(244,248,255,.85);font-size:13px;font-weight:600;');
184
184
  const dot = el('span', 'width:8px;height:8px;border-radius:50%;background:#3ec4ff;box-shadow:0 0 10px #3ec4ff;');
@@ -7,7 +7,7 @@ import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk/v2"
7
7
 
8
8
  import { entraGateway } from "../entra.ts"
9
9
  import { missingKey, type LlmProfile } from "../llm.ts"
10
- import type { CodeEvent, Usage } from "../types.ts"
10
+ import type { CodeEvent, FileAction, PlanItem, Usage } from "../types.ts"
11
11
  import { envRefs, withoutNames } from "../childEnv.ts"
12
12
  import { addUsage, recordUsage, type Role } from "../usage.ts"
13
13
  import { linkSkills, type Skill, type SkillAgent } from "../skills/registry.ts"
@@ -82,7 +82,7 @@ function trimSlash(url: string): string {
82
82
  export function providerFor(profile: LlmProfile): { provider: Record<string, unknown>; model: ModelRef } {
83
83
  if (profile.auth === "entra") throw new Error("An auth: entra profile reaches the coding agent through entraGateway, which gives it a profile of its own.")
84
84
  const key = profile.apiKeyEnv ? env(profile.apiKeyEnv) : profile.apiKey
85
- const model = { tool_call: true, temperature: false, limit: { context: 200_000, output: 32_000 } }
85
+ const model = { tool_call: true, temperature: false, limit: { context: CONTEXT_LIMIT, output: 32_000 } }
86
86
  switch (profile.provider) {
87
87
  case "azure": {
88
88
  if (!profile.endpoint) throw new Error("The azure profile needs an endpoint for the coding agent.")
@@ -375,6 +375,27 @@ export function toolError(s: string): string {
375
375
  const QUIET_TOOLS = new Set(["StructuredOutput", "todowrite", "todoread"])
376
376
 
377
377
  const FILE_VERBS: Record<string, string> = { read: "Reading", write: "Writing", edit: "Editing", patch: "Editing", multiedit: "Editing" }
378
+ const FILE_ACTION: Record<string, FileAction> = { read: "read", write: "write", edit: "edit", patch: "edit", multiedit: "edit" }
379
+
380
+ /** The context window the agent runtime is given for a profile's model (code.model leaves it to the runtime). */
381
+ export const CONTEXT_LIMIT = 200_000
382
+
383
+ /** The file a tool call read or changed, relative to the repository, when it touched one. */
384
+ export function toolFile(repo: string, tool: string, input: Record<string, unknown>): { path: string; action: FileAction } | undefined {
385
+ const action = FILE_ACTION[tool]
386
+ const where = rel(repo, input.filePath || input.path)
387
+ return action && where ? { path: where, action } : undefined
388
+ }
389
+
390
+ const PLAN_STATUS = new Set(["pending", "in_progress", "completed", "cancelled"])
391
+
392
+ /** The agent's to-do list from a todowrite call. */
393
+ export function planItems(input: Record<string, unknown>): PlanItem[] {
394
+ const todos = Array.isArray(input.todos) ? input.todos : []
395
+ return todos
396
+ .map((t: { content?: unknown; status?: unknown }) => ({ text: String(t?.content || "").trim(), status: (PLAN_STATUS.has(String(t?.status)) ? String(t?.status) : "pending") as PlanItem["status"] }))
397
+ .filter((t) => t.text)
398
+ }
378
399
 
379
400
  /** One line for a finished tool call, e.g. `Editing tests/search.spec.ts` or `Running npx playwright test …`. */
380
401
  export function toolLine(repo: string, tool: string, input: Record<string, unknown>, title?: string): string {
@@ -647,10 +668,14 @@ export async function openSession<R = AgentReport>(opts: {
647
668
  const name = String(part.state.input?.name || "")
648
669
  if (name && !skillsUsed.includes(name)) skillsUsed.push(name)
649
670
  }
671
+ if (part.tool === "todowrite" && part.state.status === "completed") emit({ type: "code", kind: "plan", items: planItems(part.state.input || {}) })
650
672
  if (QUIET_TOOLS.has(part.tool) || opts.hideTool?.(part.tool)) break
651
- const line = toolLine(opts.repo, part.tool, part.state.input || {}, part.state.title)
673
+ const input = part.state.input || {}
674
+ const line = toolLine(opts.repo, part.tool, input, part.state.title)
652
675
  const ok = part.state.status === "completed"
653
- emit({ type: "code", kind: "tool", ok, text: ok ? line : `${line}: ${clip(toolError(String(part.state.error || "")) || "failed", 120)}` })
676
+ const file = toolFile(opts.repo, part.tool, input)
677
+ const skill = part.tool === "skill" && ok ? String(input.name || "") : ""
678
+ emit({ type: "code", kind: "tool", ok, tool: part.tool, ...(file ? { file } : {}), ...(skill ? { skill } : {}), text: ok ? line : `${line}: ${clip(toolError(String(part.state.error || "")) || "failed", 120)}` })
654
679
  } else if (part?.type === "text" && part.time?.end && assistant.has(part.messageID) && !said.has(part.id)) {
655
680
  said.add(part.id)
656
681
  const text = String(part.text || "").trim()
@@ -668,7 +693,7 @@ export async function openSession<R = AgentReport>(opts: {
668
693
  }
669
694
  Object.assign(usage, addUsage(usage, step))
670
695
  recordUsage(role, step, price)
671
- emit({ type: "code", kind: "usage", usage: { ...usage } })
696
+ emit({ type: "code", kind: "usage", usage: { ...usage }, context: step.input })
672
697
  }
673
698
  break
674
699
  }