@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 +1 -1
- package/py/forgeqe_engine/brand.py +2 -2
- package/src/code/runtime.ts +30 -5
- package/src/report/template.html +1 -1
- package/src/tui/App.tsx +244 -41
- package/src/tui/components.tsx +12 -8
- package/src/tui/inspector.tsx +262 -0
- package/src/tui/session.ts +115 -0
- package/src/tui/text.ts +5 -0
- package/src/tui/transcript.tsx +2 -0
- package/src/types.ts +12 -2
package/package.json
CHANGED
|
@@ -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', '
|
|
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', '
|
|
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;');
|
package/src/code/runtime.ts
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
-
|
|
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
|
}
|