@capagents/forgeqe 1.0.3 → 1.0.5
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/HOW_TO_USE.md +41 -3
- package/docs/user-guide.html +37 -4
- package/package.json +1 -1
- package/prompts/README.md +1 -0
- package/prompts/refine.md +17 -0
- package/src/capabilities/status.ts +4 -3
- package/src/refine.ts +116 -0
- package/src/tui/App.tsx +440 -31
- package/src/tui/components.tsx +29 -3
- package/src/tui/inspector.tsx +159 -89
- package/src/tui/next.ts +32 -0
- package/src/tui/palette.tsx +169 -0
- package/src/tui/refine-panel.tsx +94 -0
- package/src/tui/setup.ts +37 -0
- package/src/tui/suggest.ts +111 -0
- package/src/tui/transcript.tsx +47 -5
- package/src/usage.ts +1 -1
package/HOW_TO_USE.md
CHANGED
|
@@ -366,12 +366,45 @@ While a run is going, the bar under the prompt shows a spinner, the tokens used,
|
|
|
366
366
|
|
|
367
367
|
The text is the agent's own result. Failed runs show `✗ Task failed` with the reason, and stopped runs show `■ Run stopped`. `forgeqe run` prints the same summary at the end.
|
|
368
368
|
|
|
369
|
+
### The screen
|
|
370
|
+
|
|
371
|
+
On a terminal 120 columns wide or more, the shell splits in two once you send a request:
|
|
372
|
+
|
|
373
|
+
- **Left (40%)**: the **Session** panel (model and profile, folder, domain, requests so far), the conversation, and the prompt.
|
|
374
|
+
- **Right (60%)**: a status line with who is working, for how long, and a **■ Stop** button (a running suite adds a progress bar, `██████░░░░░░ 5/8`); then **Plan** (the agent's to-do list, then each browser step or suite case with ✓, ✗ and why, ◐ running), **Activity** (every tool call, browser step, and the agents' reasoning), **Files** (each file read ◉, written ✚, or edited ✎, newest first), **Context** (how full the model's context window is, and the skills, product knowledge, domain, and MCP servers in it), and **Tokens** (total, input, output, cached; no prices, which are in reports and `forgeqe models`).
|
|
375
|
+
|
|
376
|
+
On a narrower terminal the chat fills the screen, and Ctrl-O switches between the chat and the same panels.
|
|
377
|
+
|
|
378
|
+
**Moving between panels.** Shift-Tab (or a click) moves the keys from the prompt to Plan, Activity, then Files; the focused panel's border lights up and shows its keys. Esc, or typing, goes back to the prompt.
|
|
379
|
+
|
|
380
|
+
| Panel | Keys |
|
|
381
|
+
|-------|------|
|
|
382
|
+
| Plan | ↑/↓ select a step, Enter puts the whole step, with why it failed, in the chat |
|
|
383
|
+
| Activity | ↑/↓, PgUp/PgDn scroll; `a` all, `e` errors, `s` steps (browser steps and reasoning), `t` tools (tool calls and files); the filter buttons are clickable too |
|
|
384
|
+
| Files | ↑/↓ select, Enter (or a click) opens the file |
|
|
385
|
+
|
|
386
|
+
**Links.** Report folders, report files, and web addresses in the chat are links: Cmd-click (Ctrl-click on Windows and Linux) opens them in terminals that support links, such as VS Code, iTerm2, Windows Terminal, and recent GNOME Terminal. A path that no longer exists stays plain text.
|
|
387
|
+
|
|
388
|
+
**Setup checklist.** Until everything is set up, the start screen shows what most features rest on: **Model** (a live model profile), **Coding agent**, **Work items** (Azure DevOps, Jira, TestRail, Zephyr, qTest, or Xray), **Domain**, and **Notifications**, each ✓ or ○. Click an open item to see how to set it up; **Work items** starts `/auth`. The checks are the same ones `/features` uses, so the two always agree.
|
|
389
|
+
|
|
390
|
+
**Command palette.** Ctrl-P opens a palette of every action, command, profile, and example request; type a few letters to find one ("rr" finds *Refine my request*, "auth" finds the sign-in commands), ↑/↓ to choose, Enter to run it. Example requests are put in the prompt for you to edit, never sent straight away.
|
|
391
|
+
|
|
392
|
+
**`/` and `@` in the prompt.** Typing `/` lists the commands that match; typing `@` suggests references: recent runs (*last run*, then each by its folder), files in the repository by name or path (`@Login` finds `pages/Login.ts`), and, after `@` and a number such as `@1761`, the Azure DevOps work item with its title (read-only). Tab completes, ↑/↓ choose, Esc closes the list.
|
|
393
|
+
|
|
394
|
+
**Refine.** Ctrl-R (or *Refine my request* in the palette) turns what you typed into a precise request before it is sent. A read-only agent checks it against this setup: what ForgeQE can do here, the systems that are set up, the project's sprints and test cases, and recent runs. It shows the refined request with what it **checked** ✓, what it **assumed** ~, and what is **missing** ?. Enter uses it, `e` puts it in the prompt to edit, Esc keeps yours. Refine never runs anything, writes files, or changes a work item.
|
|
395
|
+
|
|
396
|
+
**Next steps.** When a request finishes, up to three suggestions appear above the prompt, such as *Write a test for this run*, *Why did it fail?*, *Open the report*, or *Rerun the cases that failed*. Click one or press Alt-1 to Alt-3; suggestions that start work are put in the prompt first.
|
|
397
|
+
|
|
369
398
|
| Key | Action |
|
|
370
399
|
|-----|--------|
|
|
371
400
|
| Enter | Send the message or command |
|
|
372
|
-
| Tab | Next LLM profile from `llms.json`; the conversation carries over to the new model |
|
|
373
|
-
|
|
|
374
|
-
|
|
|
401
|
+
| Tab | Next LLM profile from `llms.json`; the conversation carries over to the new model. While `/` or `@` suggestions show, completes the selected one |
|
|
402
|
+
| Shift-Tab | Move between the chat and the Plan, Activity, and Files panels |
|
|
403
|
+
| Ctrl-P | Command palette |
|
|
404
|
+
| Ctrl-R | Refine the request in the prompt |
|
|
405
|
+
| Ctrl-O | Chat or details, on a terminal narrower than 120 columns |
|
|
406
|
+
| Alt-1 to Alt-3 | Take a suggested next step |
|
|
407
|
+
| Esc | Ask to stop the run: **Stop run**, **Stop and close ForgeQE**, or **Keep running**. In a panel, back to the prompt |
|
|
375
408
|
| Ctrl-C | Ask to close ForgeQE: **Close** or **Cancel**. A second Ctrl-C closes straight away |
|
|
376
409
|
|
|
377
410
|
In the button prompt, ←/→ or Tab move between buttons, Enter confirms, Esc cancels, and a mouse click works too. `esc stop` and `ctrl+c close` in the bar under the prompt are also clickable. **Stop and close** waits until the browser has shut down and the run's files are saved, then closes ForgeQE.
|
|
@@ -1533,6 +1566,11 @@ Start with `--debug` (or set `FORGEQE_DEBUG=1`) for more: the browser engine log
|
|
|
1533
1566
|
| `/auth [system]`, `/auth logout <system>` | Sign in to a system in the browser (the token is typed unseen), forget one; `/auth` alone shows what is signed in |
|
|
1534
1567
|
| `/clear`, `/help`, `/exit` | Clear the transcript, show help, quit |
|
|
1535
1568
|
| Drag with the mouse | Copy the selected text |
|
|
1569
|
+
| Ctrl-P | Command palette: actions, commands, profiles, example requests |
|
|
1570
|
+
| Ctrl-R | Refine the request before sending it |
|
|
1571
|
+
| `/`, `@` | Suggest commands; suggest runs, files, and work items to reference |
|
|
1572
|
+
| Shift-Tab | Move between the chat and the Plan, Activity, and Files panels |
|
|
1573
|
+
| Cmd/Ctrl-click a path | Open a report, file, or web address |
|
|
1536
1574
|
|
|
1537
1575
|
---
|
|
1538
1576
|
|
package/docs/user-guide.html
CHANGED
|
@@ -543,20 +543,48 @@ Add a GitHub Actions workflow that runs tests/cases.yaml on every PR, and make i
|
|
|
543
543
|
┃ Saved .forgeqe/reports/20260928-172700/ run.json · workflow.yaml · trace.md · report.html
|
|
544
544
|
</code></pre>
|
|
545
545
|
<p>The text is the agent's own result. Failed runs show <code>✗ Task failed</code> with the reason, and stopped runs show <code>■ Run stopped</code>. <code>forgeqe run</code> prints the same summary at the end.</p>
|
|
546
|
+
</div>
|
|
547
|
+
<section class="doc sub" id="doc-how_to_use-the-screen"><h4>The screen</h4><div class="prose"><p>On a terminal 120 columns wide or more, the shell splits in two once you send a request:</p>
|
|
548
|
+
<ul>
|
|
549
|
+
<li><strong>Left (40%)</strong>: the <strong>Session</strong> panel (model and profile, folder, domain, requests so far), the conversation, and the prompt.</li>
|
|
550
|
+
<li><strong>Right (60%)</strong>: a status line with who is working, for how long, and a <strong>■ Stop</strong> button (a running suite adds a progress bar, <code>██████░░░░░░ 5/8</code>); then <strong>Plan</strong> (the agent's to-do list, then each browser step or suite case with ✓, ✗ and why, ◐ running), <strong>Activity</strong> (every tool call, browser step, and the agents' reasoning), <strong>Files</strong> (each file read ◉, written ✚, or edited ✎, newest first), <strong>Context</strong> (how full the model's context window is, and the skills, product knowledge, domain, and MCP servers in it), and <strong>Tokens</strong> (total, input, output, cached; no prices, which are in reports and <code>forgeqe models</code>).</li>
|
|
551
|
+
</ul>
|
|
552
|
+
<p>On a narrower terminal the chat fills the screen, and Ctrl-O switches between the chat and the same panels.</p>
|
|
553
|
+
<p><strong>Moving between panels.</strong> Shift-Tab (or a click) moves the keys from the prompt to Plan, Activity, then Files; the focused panel's border lights up and shows its keys. Esc, or typing, goes back to the prompt.</p>
|
|
554
|
+
<div class="tbl"><table>
|
|
555
|
+
<thead>
|
|
556
|
+
<tr><th>Panel</th><th>Keys</th></tr>
|
|
557
|
+
</thead>
|
|
558
|
+
<tbody>
|
|
559
|
+
<tr><td>Plan</td><td>↑/↓ select a step, Enter puts the whole step, with why it failed, in the chat</td></tr>
|
|
560
|
+
<tr><td>Activity</td><td>↑/↓, PgUp/PgDn scroll; <code>a</code> all, <code>e</code> errors, <code>s</code> steps (browser steps and reasoning), <code>t</code> tools (tool calls and files); the filter buttons are clickable too</td></tr>
|
|
561
|
+
<tr><td>Files</td><td>↑/↓ select, Enter (or a click) opens the file</td></tr>
|
|
562
|
+
</tbody>
|
|
563
|
+
</table></div>
|
|
564
|
+
<p><strong>Links.</strong> Report folders, report files, and web addresses in the chat are links: Cmd-click (Ctrl-click on Windows and Linux) opens them in terminals that support links, such as VS Code, iTerm2, Windows Terminal, and recent GNOME Terminal. A path that no longer exists stays plain text.</p>
|
|
565
|
+
<p><strong>Setup checklist.</strong> Until everything is set up, the start screen shows what most features rest on: <strong>Model</strong> (a live model profile), <strong>Coding agent</strong>, <strong>Work items</strong> (Azure DevOps, Jira, TestRail, Zephyr, qTest, or Xray), <strong>Domain</strong>, and <strong>Notifications</strong>, each ✓ or ○. Click an open item to see how to set it up; <strong>Work items</strong> starts <code>/auth</code>. The checks are the same ones <code>/features</code> uses, so the two always agree.</p>
|
|
566
|
+
<p><strong>Command palette.</strong> Ctrl-P opens a palette of every action, command, profile, and example request; type a few letters to find one ("rr" finds <em>Refine my request</em>, "auth" finds the sign-in commands), ↑/↓ to choose, Enter to run it. Example requests are put in the prompt for you to edit, never sent straight away.</p>
|
|
567
|
+
<p><strong><code>/</code> and <code>@</code> in the prompt.</strong> Typing <code>/</code> lists the commands that match; typing <code>@</code> suggests references: recent runs (<em>last run</em>, then each by its folder), files in the repository by name or path (<code>@Login</code> finds <code>pages/Login.ts</code>), and, after <code>@</code> and a number such as <code>@1761</code>, the Azure DevOps work item with its title (read-only). Tab completes, ↑/↓ choose, Esc closes the list.</p>
|
|
568
|
+
<p><strong>Refine.</strong> Ctrl-R (or <em>Refine my request</em> in the palette) turns what you typed into a precise request before it is sent. A read-only agent checks it against this setup: what ForgeQE can do here, the systems that are set up, the project's sprints and test cases, and recent runs. It shows the refined request with what it <strong>checked</strong> ✓, what it <strong>assumed</strong> ~, and what is <strong>missing</strong> ?. Enter uses it, <code>e</code> puts it in the prompt to edit, Esc keeps yours. Refine never runs anything, writes files, or changes a work item.</p>
|
|
569
|
+
<p><strong>Next steps.</strong> When a request finishes, up to three suggestions appear above the prompt, such as <em>Write a test for this run</em>, <em>Why did it fail?</em>, <em>Open the report</em>, or <em>Rerun the cases that failed</em>. Click one or press Alt-1 to Alt-3; suggestions that start work are put in the prompt first.</p>
|
|
546
570
|
<div class="tbl"><table>
|
|
547
571
|
<thead>
|
|
548
572
|
<tr><th>Key</th><th>Action</th></tr>
|
|
549
573
|
</thead>
|
|
550
574
|
<tbody>
|
|
551
575
|
<tr><td>Enter</td><td>Send the message or command</td></tr>
|
|
552
|
-
<tr><td>Tab</td><td>Next LLM profile from <code>llms.json</code>; the conversation carries over to the new model</td></tr>
|
|
553
|
-
<tr><td>
|
|
554
|
-
<tr><td>
|
|
576
|
+
<tr><td>Tab</td><td>Next LLM profile from <code>llms.json</code>; the conversation carries over to the new model. While <code>/</code> or <code>@</code> suggestions show, completes the selected one</td></tr>
|
|
577
|
+
<tr><td>Shift-Tab</td><td>Move between the chat and the Plan, Activity, and Files panels</td></tr>
|
|
578
|
+
<tr><td>Ctrl-P</td><td>Command palette</td></tr>
|
|
579
|
+
<tr><td>Ctrl-R</td><td>Refine the request in the prompt</td></tr>
|
|
580
|
+
<tr><td>Ctrl-O</td><td>Chat or details, on a terminal narrower than 120 columns</td></tr>
|
|
581
|
+
<tr><td>Alt-1 to Alt-3</td><td>Take a suggested next step</td></tr>
|
|
582
|
+
<tr><td>Esc</td><td>Ask to stop the run: <strong>Stop run</strong>, <strong>Stop and close ForgeQE</strong>, or <strong>Keep running</strong>. In a panel, back to the prompt</td></tr>
|
|
555
583
|
<tr><td>Ctrl-C</td><td>Ask to close ForgeQE: <strong>Close</strong> or <strong>Cancel</strong>. A second Ctrl-C closes straight away</td></tr>
|
|
556
584
|
</tbody>
|
|
557
585
|
</table></div>
|
|
558
586
|
<p>In the button prompt, ←/→ or Tab move between buttons, Enter confirms, Esc cancels, and a mouse click works too. <code>esc stop</code> and <code>ctrl+c close</code> in the bar under the prompt are also clickable. <strong>Stop and close</strong> waits until the browser has shut down and the run's files are saved, then closes ForgeQE.</p>
|
|
559
|
-
</div>
|
|
587
|
+
</div></section>
|
|
560
588
|
</section>
|
|
561
589
|
<section class="doc" id="doc-how_to_use-4-without-the-shell">
|
|
562
590
|
<h3>Without the shell</h3>
|
|
@@ -2024,6 +2052,11 @@ agents: [browser]
|
|
|
2024
2052
|
<tr><td><code>/auth [system]</code>, <code>/auth logout <system></code></td><td>Sign in to a system in the browser (the token is typed unseen), forget one; <code>/auth</code> alone shows what is signed in</td></tr>
|
|
2025
2053
|
<tr><td><code>/clear</code>, <code>/help</code>, <code>/exit</code></td><td>Clear the transcript, show help, quit</td></tr>
|
|
2026
2054
|
<tr><td>Drag with the mouse</td><td>Copy the selected text</td></tr>
|
|
2055
|
+
<tr><td>Ctrl-P</td><td>Command palette: actions, commands, profiles, example requests</td></tr>
|
|
2056
|
+
<tr><td>Ctrl-R</td><td>Refine the request before sending it</td></tr>
|
|
2057
|
+
<tr><td><code>/</code>, <code>@</code></td><td>Suggest commands; suggest runs, files, and work items to reference</td></tr>
|
|
2058
|
+
<tr><td>Shift-Tab</td><td>Move between the chat and the Plan, Activity, and Files panels</td></tr>
|
|
2059
|
+
<tr><td>Cmd/Ctrl-click a path</td><td>Open a report, file, or web address</td></tr>
|
|
2027
2060
|
</tbody>
|
|
2028
2061
|
</table></div>
|
|
2029
2062
|
<hr />
|
package/package.json
CHANGED
package/prompts/README.md
CHANGED
|
@@ -16,6 +16,7 @@ project; leave it as it is to keep getting ForgeQE's improvements when you updat
|
|
|
16
16
|
| api-scenarios.md | Plans API scenarios from test cases or plain English |
|
|
17
17
|
| explore-charters.md | Plans exploratory charters for a story |
|
|
18
18
|
| eval-judge.md | Scores the agent's test cases in `forgeqe eval domain` |
|
|
19
|
+
| refine.md | Refine (ctrl+r) in the shell: makes what you typed precise, checked against what ForgeQE knows |
|
|
19
20
|
| skill-rule.md | How agents use skills (included in most prompts) |
|
|
20
21
|
| domain-rule.md | How agents apply the product's industry risks and regulations, declared or worked out (included in every agent that analyses requirements or designs, writes, reviews, or heals tests) |
|
|
21
22
|
| browser-*.md | The browser agent: error recovery, when a goal counts as done, recording checks, clicking so runs become tests, step verdicts for test cases, picking skills |
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: refine
|
|
3
|
+
description: "Turns what the user typed into a precise request for ForgeQE's agent, checked against what ForgeQE knows."
|
|
4
|
+
used_by: "the shell's Refine (ctrl+r)"
|
|
5
|
+
---
|
|
6
|
+
You refine a request the user typed for ForgeQE, a test automation assistant, before it is sent to ForgeQE's agent. You do not carry the request out, and you do not change anything.
|
|
7
|
+
|
|
8
|
+
Make it a request the agent can act on without guessing:
|
|
9
|
+
- Keep the user's intent and scope exactly. Do not add work they did not ask for, and do not drop anything they did.
|
|
10
|
+
- Name things the way ForgeQE knows them: the system and project ("Azure DevOps project QI-Payments"), ids ("test plan 412, suite 77", "story 1761", "SHOP-12"), files and folders, and the profile. Use "This session" and the capabilities tool to know what ForgeQE can do, but describe the action in plain words ("run the test cases", "write a Playwright test"): never name ForgeQE's tools or capability ids.
|
|
11
|
+
- Resolve references from the session and the conversation ("the last run", "that story", "the payments plan") into what they point to, but only when exactly one thing fits.
|
|
12
|
+
- Check what you can with your read-only tools: that a work item, test plan, suite, or run exists, what it is called, how many cases it has. Mark each fact you state as checked or assumed.
|
|
13
|
+
- Never invent an id, a URL, a project, or a file. What you cannot resolve or check goes in missing, as a short question for the user.
|
|
14
|
+
- Keep the user's settings when they give them (browsers at once, headed, whether results go back), and add none they did not.
|
|
15
|
+
- Write the refined request as the user would say it, in one to four sentences, in their language.
|
|
16
|
+
|
|
17
|
+
Finish with the structured result: refined (the request), grounded (each fact you state, and whether you checked it), assumed (what you took to be meant, in a few words each), and missing (questions, if any).
|
|
@@ -56,7 +56,8 @@ export type CapabilityStatus = {
|
|
|
56
56
|
inputs: string[]
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
/** What `r` is missing in this setup, and how to fix it; null when it is met. */
|
|
60
|
+
export function requirementGap(r: Requirement, cfg: AppConfig, probes: Probes = MACHINE): Omit<Gap, "requirement" | "optional"> | null {
|
|
60
61
|
switch (r.kind) {
|
|
61
62
|
case "live_model":
|
|
62
63
|
return cfg.llm.provider === "none" || cfg.llm.provider === "mock"
|
|
@@ -89,7 +90,7 @@ function gap(r: Requirement, cfg: AppConfig, probes: Probes): Omit<Gap, "require
|
|
|
89
90
|
case "input":
|
|
90
91
|
return null
|
|
91
92
|
case "either": {
|
|
92
|
-
const gaps = r.of.map((alt) =>
|
|
93
|
+
const gaps = r.of.map((alt) => requirementGap(alt, cfg, probes))
|
|
93
94
|
if (gaps.some((g) => !g)) return null
|
|
94
95
|
return { missing: gaps.map((g) => g!.missing).join(", or "), fix: gaps.map((g) => g!.fix).join("; or ") }
|
|
95
96
|
}
|
|
@@ -99,7 +100,7 @@ function gap(r: Requirement, cfg: AppConfig, probes: Probes): Omit<Gap, "require
|
|
|
99
100
|
export function capabilityStatus(c: Capability, cfg: AppConfig, probes: Probes = MACHINE): CapabilityStatus {
|
|
100
101
|
const gaps: Gap[] = []
|
|
101
102
|
for (const r of c.requires) {
|
|
102
|
-
const g =
|
|
103
|
+
const g = requirementGap(r, cfg, probes)
|
|
103
104
|
if (g) gaps.push({ requirement: r, optional: Boolean("optional" in r && r.optional), ...g })
|
|
104
105
|
}
|
|
105
106
|
const state = gaps.some((g) => !g.optional) ? "needs_setup" : gaps.length ? "partial" : "ready"
|
package/src/refine.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs"
|
|
2
|
+
|
|
3
|
+
import type { AppConfig } from "./config.ts"
|
|
4
|
+
import { openSession, type OpencodeSession } from "./code/runtime.ts"
|
|
5
|
+
import { privateEnvNames } from "./childEnv.ts"
|
|
6
|
+
import { MCP_NAME, TOOL_TIMEOUT_MS, localToolServer } from "./mcp/server.ts"
|
|
7
|
+
import { TOOLS, environmentNotes, type ToolContext } from "./mcp/tools.ts"
|
|
8
|
+
import { prompt } from "./prompts.ts"
|
|
9
|
+
import type { CodeEvent } from "./types.ts"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The shell's Refine (ctrl+r): what the user typed becomes a precise request for ForgeQE's agent, checked against what
|
|
13
|
+
* ForgeQE knows (its capabilities, the configured systems and project, the domain, product knowledge, recent runs)
|
|
14
|
+
* with read-only tools. It never runs anything, and the user decides what is sent.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** The only tools the refiner may call: each reads, none changes anything or starts a run. */
|
|
18
|
+
export const REFINE_TOOLS = ["capabilities", "query_api", "find_test_cases", "list_sprints", "run_details"]
|
|
19
|
+
|
|
20
|
+
export type Refined = {
|
|
21
|
+
refined: string
|
|
22
|
+
grounded: { fact: string; checked: boolean }[]
|
|
23
|
+
assumed: string[]
|
|
24
|
+
missing: string[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const REFINE_SCHEMA = {
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
refined: { type: "string", description: "The request, precise and complete, as the user would say it" },
|
|
31
|
+
grounded: {
|
|
32
|
+
type: "array",
|
|
33
|
+
items: { type: "object", properties: { fact: { type: "string" }, checked: { type: "boolean" } }, required: ["fact", "checked"] },
|
|
34
|
+
},
|
|
35
|
+
assumed: { type: "array", items: { type: "string" } },
|
|
36
|
+
missing: { type: "array", items: { type: "string" } },
|
|
37
|
+
},
|
|
38
|
+
required: ["refined", "grounded", "assumed", "missing"],
|
|
39
|
+
} as const
|
|
40
|
+
|
|
41
|
+
const REFINE_AGENT = {
|
|
42
|
+
name: "forgeqe-refine",
|
|
43
|
+
description: "Turns what the user typed into a precise request, checked against what ForgeQE knows",
|
|
44
|
+
get prompt() {
|
|
45
|
+
return prompt("refine")
|
|
46
|
+
},
|
|
47
|
+
permission: { edit: "deny", bash: "deny", webfetch: "deny" },
|
|
48
|
+
steps: 8,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function refineRequest(text: string, recent: { user: string; reply: string }[]): string {
|
|
52
|
+
const turns = recent.slice(-4).flatMap((t) => [`User: ${t.user.slice(0, 600)}`, `ForgeQE: ${t.reply.slice(0, 600)}`])
|
|
53
|
+
return [turns.length ? `The conversation so far:\n${turns.join("\n")}\n` : "", `Refine this request:\n${text}`].filter(Boolean).join("\n")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Keeps only what the schema allows, so the shell never shows a malformed answer. */
|
|
57
|
+
export function cleanRefined(r: Partial<Refined> | null | undefined, original: string): Refined {
|
|
58
|
+
const strings = (v: unknown) => (Array.isArray(v) ? v.map(String).map((s) => s.trim()).filter(Boolean) : [])
|
|
59
|
+
return {
|
|
60
|
+
refined: String(r?.refined || "").trim() || original,
|
|
61
|
+
grounded: Array.isArray(r?.grounded) ? r!.grounded.filter((g) => g && String(g.fact || "").trim()).map((g) => ({ fact: String(g.fact).trim(), checked: g.checked === true })) : [],
|
|
62
|
+
assumed: strings(r?.assumed),
|
|
63
|
+
missing: strings(r?.missing),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function refine(opts: {
|
|
68
|
+
config: AppConfig
|
|
69
|
+
cwd: string
|
|
70
|
+
text: string
|
|
71
|
+
recent: { user: string; reply: string }[]
|
|
72
|
+
signal?: AbortSignal
|
|
73
|
+
onEvent?: (e: CodeEvent) => void
|
|
74
|
+
/** Replaces openSession in tests. */
|
|
75
|
+
open?: (o: Parameters<typeof openSession>[0]) => Promise<OpencodeSession<Refined>>
|
|
76
|
+
}): Promise<Refined> {
|
|
77
|
+
const cwd = (() => {
|
|
78
|
+
try {
|
|
79
|
+
return realpathSync(opts.cwd)
|
|
80
|
+
} catch {
|
|
81
|
+
return opts.cwd
|
|
82
|
+
}
|
|
83
|
+
})()
|
|
84
|
+
const context = (): ToolContext => ({ config: opts.config, cwd, session: "shell", writeTests: false })
|
|
85
|
+
const tools = localToolServer({ tools: TOOLS.filter((t) => REFINE_TOOLS.includes(t.name)), context, instructions: () => environmentNotes(context()), timeoutMs: TOOL_TIMEOUT_MS })
|
|
86
|
+
try {
|
|
87
|
+
const session = await (opts.open || openSession<Refined>)({
|
|
88
|
+
repo: cwd,
|
|
89
|
+
profile: opts.config.llm,
|
|
90
|
+
// The refiner works on the request, not the domain: ForgeQE's agent applies that when it handles it.
|
|
91
|
+
domain: null,
|
|
92
|
+
model: opts.config.code.model,
|
|
93
|
+
bin: opts.config.code.opencode,
|
|
94
|
+
readDirs: [cwd],
|
|
95
|
+
hide: privateEnvNames(opts.config),
|
|
96
|
+
role: "refiner",
|
|
97
|
+
signal: opts.signal,
|
|
98
|
+
onEvent: opts.onEvent,
|
|
99
|
+
agent: { ...REFINE_AGENT, prompt: `${REFINE_AGENT.prompt}\n\nThis session:\n${environmentNotes(context())}` },
|
|
100
|
+
schema: REFINE_SCHEMA as unknown as Record<string, unknown>,
|
|
101
|
+
mcp: { [MCP_NAME]: tools.entry },
|
|
102
|
+
hideTool: (tool) => tool.startsWith(`${MCP_NAME}_`),
|
|
103
|
+
title: "ForgeQE refine",
|
|
104
|
+
})
|
|
105
|
+
try {
|
|
106
|
+
let out = await session.prompt(refineRequest(opts.text, opts.recent))
|
|
107
|
+
if (!out.report && !out.error && !opts.signal?.aborted) out = await session.prompt("Give the structured result: refined, grounded, assumed, and missing.")
|
|
108
|
+
if (!out.report) throw new Error(out.error ? `Refine failed: ${out.error}` : "Refine gave no result.")
|
|
109
|
+
return cleanRefined(out.report, opts.text)
|
|
110
|
+
} finally {
|
|
111
|
+
await session.close()
|
|
112
|
+
}
|
|
113
|
+
} finally {
|
|
114
|
+
tools.stop()
|
|
115
|
+
}
|
|
116
|
+
}
|