howtofixcli 0.7.8 → 0.7.9
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/chunk-27TRKD65.js +2 -0
- package/dist/chunk-3FYCFLNK.js +1 -0
- package/dist/chunk-56SEAGAQ.js +1 -0
- package/dist/chunk-63ZCQDXH.js +6 -0
- package/dist/chunk-6FUWMLWO.js +1 -0
- package/dist/chunk-7CHDNTH6.js +1 -0
- package/dist/chunk-ACOMNDUL.js +185 -0
- package/dist/chunk-AEVPET7X.js +2 -0
- package/dist/chunk-AGGIYXN7.js +2 -0
- package/dist/chunk-BHVBO4SC.js +1 -0
- package/dist/chunk-FPXVDP32.js +8 -0
- package/dist/chunk-GIUYIKH3.js +109 -0
- package/dist/chunk-GQCAJ56I.js +5 -0
- package/dist/chunk-GXB7RZHH.js +1 -0
- package/dist/chunk-IGEY3AWF.js +1 -0
- package/dist/chunk-ILDDDLRC.js +1 -0
- package/dist/chunk-JFYVRWJI.js +2 -0
- package/dist/chunk-MVLCGU3G.js +1 -0
- package/dist/chunk-N345KWWK.js +1 -0
- package/dist/chunk-NBQFPCRT.js +1 -0
- package/dist/chunk-OIPSLIZE.js +5 -0
- package/dist/chunk-ORJ2QZBG.js +3 -0
- package/dist/chunk-PCALBZOO.js +2 -0
- package/dist/chunk-PDNBGUYP.js +2 -0
- package/dist/chunk-S656RE4A.js +1 -0
- package/dist/chunk-TSEKPGK4.js +1 -0
- package/dist/chunk-U6CI6KXN.js +34 -0
- package/dist/chunk-UTYFLLSM.js +1 -0
- package/dist/chunk-V3R4FYTJ.js +1 -0
- package/dist/chunk-V656XVXV.js +8 -0
- package/dist/chunk-WKUJ7CFO.js +1 -0
- package/dist/chunk-WOT6VMZA.js +1 -0
- package/dist/chunk-WUPU6LTD.js +4 -0
- package/dist/chunk-WX3V5P7U.js +5 -0
- package/dist/chunk-ZKZ6WCI3.js +2 -0
- package/dist/main.js +94 -536
- package/package.json +15 -62
- package/dist/account-credential.js +0 -136
- package/dist/account-credential.test.js +0 -38
- package/dist/activity.js +0 -124
- package/dist/agent-defs.js +0 -237
- package/dist/agent-limiter.js +0 -68
- package/dist/agents.js +0 -554
- package/dist/anim.js +0 -361
- package/dist/args.js +0 -228
- package/dist/at-file.js +0 -28
- package/dist/brand-extension.js +0 -87
- package/dist/brand.js +0 -76
- package/dist/browser.js +0 -58
- package/dist/cache-report.js +0 -139
- package/dist/checkpoint.js +0 -437
- package/dist/claims.js +0 -167
- package/dist/cli-auth.js +0 -142
- package/dist/cli-checkin.js +0 -120
- package/dist/cli-credentials.js +0 -254
- package/dist/clipboard.js +0 -149
- package/dist/collapse.js +0 -129
- package/dist/commands.js +0 -301
- package/dist/compaction-guard.js +0 -81
- package/dist/context-extension.js +0 -17
- package/dist/dcp/commands.js +0 -172
- package/dist/dcp/compress-state.js +0 -139
- package/dist/dcp/compress.js +0 -329
- package/dist/dcp/dcp-extension.js +0 -288
- package/dist/dcp/dedup.js +0 -93
- package/dist/dcp/ids.js +0 -189
- package/dist/dcp/index.js +0 -25
- package/dist/dcp/prompts.js +0 -68
- package/dist/dcp/protected-content.js +0 -221
- package/dist/dcp/protected.js +0 -161
- package/dist/dcp/prune.js +0 -418
- package/dist/dcp/state.js +0 -176
- package/dist/device-id.js +0 -90
- package/dist/device-id.test.js +0 -23
- package/dist/errors.js +0 -63
- package/dist/extensions.js +0 -48
- package/dist/fatal.js +0 -35
- package/dist/feed.js +0 -498
- package/dist/fs-atomic.js +0 -54
- package/dist/gateway-errors.js +0 -90
- package/dist/git.js +0 -230
- package/dist/goal-extension.js +0 -88
- package/dist/goal.js +0 -222
- package/dist/hooks-extension.js +0 -176
- package/dist/hooks.js +0 -443
- package/dist/howtofix-account.js +0 -715
- package/dist/hud-line.js +0 -153
- package/dist/hwid.js +0 -130
- package/dist/interrupt.js +0 -60
- package/dist/json-events.js +0 -50
- package/dist/level-style.js +0 -112
- package/dist/loop-guard.js +0 -95
- package/dist/lsp-tools.js +0 -32
- package/dist/lsp.js +0 -347
- package/dist/mcp-config.js +0 -85
- package/dist/mcp-lifecycle.js +0 -147
- package/dist/model-catalog.js +0 -217
- package/dist/model-resolve.js +0 -65
- package/dist/notes.js +0 -78
- package/dist/oauth-login.js +0 -568
- package/dist/paths.js +0 -61
- package/dist/permissions.js +0 -298
- package/dist/plan-mode.js +0 -363
- package/dist/preset-command.js +0 -227
- package/dist/preset-extension.js +0 -35
- package/dist/preset-mcp.js +0 -292
- package/dist/presets.js +0 -727
- package/dist/prompt-files.js +0 -103
- package/dist/prompt-text.js +0 -41
- package/dist/proxy.js +0 -137
- package/dist/quality/ast.js +0 -196
- package/dist/quality/cognitive.js +0 -83
- package/dist/quality/cyclomatic.js +0 -30
- package/dist/quality/halstead.js +0 -83
- package/dist/quality/index.js +0 -18
- package/dist/quality/length.js +0 -3
- package/dist/quality/maintainability.js +0 -15
- package/dist/quality/nesting.js +0 -57
- package/dist/quality/report.js +0 -803
- package/dist/quality/smells.js +0 -67
- package/dist/quality-tool.js +0 -63
- package/dist/read-only-commands.js +0 -597
- package/dist/repo-map.js +0 -292
- package/dist/rtk.js +0 -193
- package/dist/sandbox.js +0 -105
- package/dist/schema.js +0 -214
- package/dist/session-lifecycle.js +0 -19
- package/dist/session-retention.js +0 -111
- package/dist/session.js +0 -446
- package/dist/sessions.js +0 -100
- package/dist/settings.js +0 -148
- package/dist/skills.js +0 -35
- package/dist/slop-tool.js +0 -96
- package/dist/spend-cap.js +0 -43
- package/dist/subagent.js +0 -171
- package/dist/tar.js +0 -146
- package/dist/theme.js +0 -79
- package/dist/thinking.js +0 -45
- package/dist/todo.js +0 -114
- package/dist/tool-loadout.js +0 -97
- package/dist/tool-render.js +0 -769
- package/dist/tools.js +0 -176
- package/dist/ui.js +0 -4757
- package/dist/update.js +0 -533
- package/dist/usage-segment.js +0 -69
- package/dist/web-tools.js +0 -46
- package/dist/web.js +0 -265
- package/dist/welcome.js +0 -179
- package/dist/workflow-engine.js +0 -246
- package/dist/workflow-progress.js +0 -446
- package/dist/workflow-view.js +0 -195
- package/dist/workflow.js +0 -349
package/dist/brand-extension.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Replace the harness's self-description while keeping every operational part of
|
|
3
|
-
* the prompt (tools, rules, doc paths) intact. Exported for the check script.
|
|
4
|
-
*
|
|
5
|
-
* The rules match the SDK's own wording, which is `hxf` throughout. They used to
|
|
6
|
-
*/
|
|
7
|
-
export function scrubHarnessIdentity(prompt) {
|
|
8
|
-
const scrubbed = prompt
|
|
9
|
-
// "You are an expert coding assistant operating inside hxf, a coding agent harness."
|
|
10
|
-
.replace(/operating inside hxf, a coding agent harness/gi, "operating as HowToFix, a coding agent harness")
|
|
11
|
-
// The docs block header names the harness and invites questions about "hxf itself".
|
|
12
|
-
// The line ends with "):" — the trailing colon is why an earlier `\)$` pattern missed it.
|
|
13
|
-
.replace(/^HowToFix documentation \(read only when the user asks about[^\n]*\):?$/gim, "Harness documentation (internal reference; the user is never told the harness name):")
|
|
14
|
-
.replace(/\bhxf itself\b/gi, "the harness itself")
|
|
15
|
-
// The bullets inside the docs block.
|
|
16
|
-
.replace(/^- When reading hxf docs or examples/m, "- When reading harness docs or examples")
|
|
17
|
-
.replace(/^- When working on hxf topics/m, "- When working on harness topics")
|
|
18
|
-
.replace(/^- Always read hxf \.md files/m, "- Always read the harness .md files")
|
|
19
|
-
// Doc topic list: "adding models (docs/models.md), hxf packages (docs/packages.md)".
|
|
20
|
-
.replace(/\bhxf packages\b/gi, "harness packages")
|
|
21
|
-
.replace(/\bhxf \.md files\b/gi, "harness .md files")
|
|
22
|
-
.replace(/\bhxf docs\b/gi, "harness docs")
|
|
23
|
-
// "You can inspect HXF_* environment variables for current model and session details."
|
|
24
|
-
.replace(/^-\s*You can inspect HXF_\* environment variables/m, "- You can inspect the harness's environment variables");
|
|
25
|
-
return scrubbed;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Sections the harness generates itself, and therefore the only ones this may
|
|
29
|
-
* rewrite. `project_context` is the user's own AGENTS.md and `cwd`/`tools` carry
|
|
30
|
-
* no identity framing, so they are left exactly as they are.
|
|
31
|
-
*/
|
|
32
|
-
const SCRUBBED_SECTIONS = ["preamble", "rules", "docs", "addendum"];
|
|
33
|
-
/**
|
|
34
|
-
* Scrub the harness-generated sections of a system message, or `undefined` when
|
|
35
|
-
* nothing changed.
|
|
36
|
-
*/
|
|
37
|
-
export function scrubSections(sections) {
|
|
38
|
-
if (!sections)
|
|
39
|
-
return undefined;
|
|
40
|
-
let changed = false;
|
|
41
|
-
const next = { ...sections };
|
|
42
|
-
for (const name of SCRUBBED_SECTIONS) {
|
|
43
|
-
const text = next[name];
|
|
44
|
-
if (typeof text !== "string")
|
|
45
|
-
continue;
|
|
46
|
-
const scrubbed = scrubHarnessIdentity(text);
|
|
47
|
-
if (scrubbed !== text) {
|
|
48
|
-
next[name] = scrubbed;
|
|
49
|
-
changed = true;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return changed ? next : undefined;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Rewrites the harness identity out of the system message on every request.
|
|
56
|
-
*
|
|
57
|
-
* Two details of the SDK shape this, and both cost a debugging round to find:
|
|
58
|
-
*
|
|
59
|
-
* - The prompt is not in `content`. It is rendered from `sections`
|
|
60
|
-
* (`preamble`, `tools`, `rules`, `docs`, `addendum`, `project_context`, `cwd`),
|
|
61
|
-
* and `content` holds only extra text prepended to them. A handler that reads
|
|
62
|
-
* and rewrites `content` therefore sees an empty string and silently does
|
|
63
|
-
* nothing — which is exactly what the first version of this extension did.
|
|
64
|
-
* - `before_agent_start` is the wrong event. It fires once per run and its
|
|
65
|
-
* forced prompt is projected onto the request afterwards, overwriting any
|
|
66
|
-
* edit made to the system message by a later handler. The shared notes
|
|
67
|
-
* injected by `context-extension.ts` never reached the model until both
|
|
68
|
-
* extensions moved to `context_with_system`, which runs before every LLM call
|
|
69
|
-
* and may return a replacement transcript.
|
|
70
|
-
*
|
|
71
|
-
* The returned transcript keeps the leading system message at index 0, which is
|
|
72
|
-
* where the provider reads the prompt and the tool declarations from.
|
|
73
|
-
*/
|
|
74
|
-
export function createBrandExtension() {
|
|
75
|
-
return (hxf) => {
|
|
76
|
-
hxf.on("context_with_system", async (event) => {
|
|
77
|
-
const head = event.messages[0];
|
|
78
|
-
if (!head || head.role !== "system")
|
|
79
|
-
return undefined;
|
|
80
|
-
const sections = scrubSections(head.sections);
|
|
81
|
-
if (!sections)
|
|
82
|
-
return undefined;
|
|
83
|
-
const messages = event.messages.map((message) => (message === head ? { ...head, sections: sections } : message));
|
|
84
|
-
return { messages };
|
|
85
|
-
});
|
|
86
|
-
};
|
|
87
|
-
}
|
package/dist/brand.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
export const APP_TITLE = "HowToFix";
|
|
2
|
-
export const APP_VERSION = "0.7.8";
|
|
3
|
-
export const SYSTEM_APPEND = [
|
|
4
|
-
// House style: RFC 2119 keywords in caps, one claim per line, the
|
|
5
|
-
// highest-stakes rules at
|
|
6
|
-
// BOTH edges because the middle of a long prompt degrades, and a positive
|
|
7
|
-
// alternative wherever a rule is a negation.
|
|
8
|
-
//
|
|
9
|
-
// Front matter: role, then the rule whose violation cost the most.
|
|
10
|
-
"You are HowToFix, a coding agent. The user wants the fix, not a tour of the problem.",
|
|
11
|
-
'NEVER report a check you did not run. NEVER state a version, path, result or capability you have not verified — if you did not check it, say you are unsure. "Not found" is not "impossible": a tool missing from PATH is often installed anyway, and one that is truly absent can usually be installed. Exhaust the real options before calling anything unavailable; when you do report a limit, say plainly what you tried and what you could not do. Unexercised behaviour is not evidence.',
|
|
12
|
-
"NEVER mention the underlying harness, its vendor or its project name to the user.",
|
|
13
|
-
// Permission model. Without this the prompt said nothing about tiers while the
|
|
14
|
-
// gate enforced them, so the model proposed work it would then be refused. The
|
|
15
|
-
// mode names, the tier names and the critical-command policy are asserted
|
|
16
|
-
// against `permissions.ts` by `check-prompt-permissions.mjs`; reword this text
|
|
17
|
-
// freely, but keep those words or the gate fails.
|
|
18
|
-
"Every tool is `read`, `write` or `exec`, and each approval mode auto-approves every tier up to its own: `suggest` runs `read` only, `auto-edit` adds `write`, `full-auto` adds `exec`. A read-only shell command runs in every mode, so inspecting the tree asks nothing.",
|
|
19
|
-
"NEVER aim a command at the filesystem root, a block device or a system account file, and NEVER shut down the host: those are refused in EVERY mode, `full-auto` included, so proposing one wastes the turn. This is not a list of things to avoid — it is a list of things that will not run.",
|
|
20
|
-
// Middle: how the work is done.
|
|
21
|
-
"Lead with the diagnosis in one or two sentences, then change the code. SHOULD prefer the smallest correct change over a rewrite. Name the file and the reason in a short line; do not narrate tool calls.",
|
|
22
|
-
"Fix the problem that was asked for. NEVER add features, refactors, validation, error handling or dependencies nobody requested.",
|
|
23
|
-
"Something reported broken: reproduce it first, confirm the fix after. Cannot reproduce it? Say so — NEVER edit code and call it fixed.",
|
|
24
|
-
"Tests describe intended behaviour. A failing test means fix the code, not the test: NEVER delete, skip or loosen an assertion to get green, and NEVER re-pin an expected value to whatever the code happens to print.",
|
|
25
|
-
"Anything you cannot settle by reading — a page, a layout, a screenshot, a CLI, a service, a failure — run it and look at the result. For a visual task, find a browser the way a real one does: `$PUPPETEER_EXECUTABLE_PATH`, `$CHROME_PATH`, then `which` for google-chrome, chromium and chromium-browser, then /usr/bin, /snap/bin and /var/lib/flatpak, then the puppeteer and playwright caches under the home directory. Still empty? `npx -y puppeteer browsers install chrome`, or `npm i puppeteer-core` against a chrome the image already ships outside PATH. Then drive the real artifact — open it, read the DOM, click the controls, measure at the widths that matter — instead of describing what you assume it does.",
|
|
26
|
-
"Done means the requested behaviour works end to end and every stated requirement is met — not a scaffold, a narrowed test or a plausible subset. Genuinely out of reach? Finish everything else, then say exactly what is missing and what you tried.",
|
|
27
|
-
// Working method. Each rule names the tool that implements it, because a
|
|
28
|
-
// capability the prompt does not mention is one the model reaches for late or
|
|
29
|
-
// not at all — the tool exists, has a snippet and an approval tier, and still
|
|
30
|
-
// goes unused. The tool names are asserted against MAIN_TOOLS by
|
|
31
|
-
// `check-prompt-tools.mjs`, so a rename or a typo fails the gate.
|
|
32
|
-
"SHOULD plan multi-step work with `todo` before starting it, and mark each step done as you finish. A plan the user can see is worth more than a narration of what you are about to do.",
|
|
33
|
-
"Genuinely ambiguous design: enter plan mode, write the plan to the plan file, then present it with `exit_plan_mode`. Use it when several reasonable approaches exist or when a restructuring would be hard to undo. In plan mode `edit` and `write` are refused for every path except the plan file itself — that limit is enforced, not advisory, so plan the work rather than starting it. Plan mode needs an interactive session in which the user can approve it; when there is no UI it is unavailable, so design the approach yourself instead of calling it.",
|
|
34
|
-
"Work that is independent and would flood this conversation belongs in `task` subagents. Give each a self-contained assignment, then verify the file it wrote rather than trusting its summary. When that work has a shape you can script — many items through the same stages, or fan-out whose results must be combined — `workflow` states the orchestration in one place and runs it deterministically; `task` is for the one-off you cannot describe in advance.",
|
|
35
|
-
"MUST give each subagent a self-contained assignment: a child starts with none of this conversation, so a brief that says 'the file we discussed' reaches it as nothing. Choose the agent by what the work is — scout looks something up, architect returns a plan, reviewer reports issues with file evidence, task changes files — and NEVER send work that needs an edit to scout or reviewer, because neither can make one.",
|
|
36
|
-
"SHOULD delegate only work that is genuinely parallel: a subagent costs its own session and its own requests on the same quota as this conversation. A single lookup, a file you can read yourself, or a step whose result decides the next step stays here. MUST keep one call to at most six agents and the session to twelve live or parked ones — a larger fan-out is refused, and the refusal is a limit rather than a failure to work around.",
|
|
37
|
-
"Record a decision, a file's role, or a fact you would otherwise re-derive with `context`: notes survive compaction and are visible to the other agents on the task.",
|
|
38
|
-
"Look it up instead of recalling it: `web_search` and `web_fetch` for anything outside this repo, `lsp` for a definition, reference or diagnostic. Name the source you checked.",
|
|
39
|
-
"Before calling changed code done, run `quality` and `slop` over it and act on what they report. A metric you did not measure is a claim you cannot make.",
|
|
40
|
-
// Every command runs under a 2-minute default timeout, so a slow command is
|
|
41
|
-
// killed rather than hanging the turn. Observed on Windows: a recursive `du -sh`
|
|
42
|
-
// over a profile directory takes minutes, and the model has to ask for the time
|
|
43
|
-
// it needs instead of losing the command to the default.
|
|
44
|
-
"A command runs under a two-minute default timeout. MUST pass a longer timeout for anything that legitimately takes more than that — a recursive directory walk, a package install, a build, a network fetch — and SHOULD prefer a bounded command to an unbounded one: 'du -sh' with an explicit path beats the whole profile, and on Windows/MSYS a recursive 'du' over a profile directory takes minutes rather than seconds.",
|
|
45
|
-
"When a choice blocks the task, use `ask` instead of guessing; when you need to work something out, use `think`, whose text stays out of the transcript.",
|
|
46
|
-
// Back matter: the two that matter most, compressed, because the edge is what
|
|
47
|
-
// survives a long prompt.
|
|
48
|
-
"Above all: NEVER claim a check you did not run, and NEVER call done on a subset.",
|
|
49
|
-
].join("\n");
|
|
50
|
-
/**
|
|
51
|
-
* Strip the harness's own identity out of a user-facing message the SDK wrote.
|
|
52
|
-
*
|
|
53
|
-
* `createAgentSession` returns `modelFallbackMessage`, and when no model is
|
|
54
|
-
* configured that string is the SDK's own auth guidance: it names the harness,
|
|
55
|
-
* points at `hxf-coding-agent/docs/providers.md` and `models.md` under the
|
|
56
|
-
* installed package, and tells the user to run `/login` — none of which is our
|
|
57
|
-
* product's copy. The system prompt is scrubbed by `brand-extension.ts`; this is
|
|
58
|
-
* the other surface the user actually reads, and it leaked verbatim.
|
|
59
|
-
*
|
|
60
|
-
* Exported for the check script.
|
|
61
|
-
*/
|
|
62
|
-
export function scrubHarnessMessage(message) {
|
|
63
|
-
if (message === undefined)
|
|
64
|
-
return undefined;
|
|
65
|
-
const scrubbed = message
|
|
66
|
-
// "Use /login to log into a provider via OAuth or API key. See:"
|
|
67
|
-
.replace(/Use \/login to log into a provider[^\n]*/gi, "Connect a provider with /connect or /login:")
|
|
68
|
-
// The two doc paths under the installed package. They are the harness's
|
|
69
|
-
// docs, not ours, and printing the path also prints its name.
|
|
70
|
-
.replace(/^[ \t]*\S*hxf-coding-agent[\\/]docs[\\/][^\n]*$/gim, "")
|
|
71
|
-
// Any remaining bare mention.
|
|
72
|
-
.replace(/\bhxf-coding-agent\b/gi, "the harness");
|
|
73
|
-
return scrubbed.replace(/\n{3,}/g, "\n\n").trim();
|
|
74
|
-
}
|
|
75
|
-
/** Seed for /init. Written to AGENTS.md when a project has no context file yet. */
|
|
76
|
-
export const INIT_TEMPLATE = ["# Project notes", "", "## What this is", "", "## Commands", "- build:", "- test:", "", "## Conventions", "-", ""].join("\n");
|
package/dist/browser.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Opening a URL in the user's browser.
|
|
3
|
-
*
|
|
4
|
-
* Used by `/connect`, which needs the user to make a token on the gateway's
|
|
5
|
-
* Console. Finding that page is the step people get stuck on, and it is one
|
|
6
|
-
* click to remove.
|
|
7
|
-
*
|
|
8
|
-
* This is a convenience, never a requirement: the caller always prints the URL
|
|
9
|
-
* as well. A headless box, a container, or a missing `xdg-open` must not turn
|
|
10
|
-
* "open the browser" into an error the user has to deal with — the command
|
|
11
|
-
* continues and the URL is on screen either way. So every failure here is
|
|
12
|
-
* swallowed and reported as `false`.
|
|
13
|
-
*/
|
|
14
|
-
import { spawn } from "node:child_process";
|
|
15
|
-
/** The platform's way to hand a URL to the default browser, if it has one. */
|
|
16
|
-
export function openerFor(platform) {
|
|
17
|
-
switch (platform) {
|
|
18
|
-
case "darwin":
|
|
19
|
-
return { command: "open", args: [] };
|
|
20
|
-
case "win32":
|
|
21
|
-
// `start` is a cmd builtin; the empty argument is the window title, which
|
|
22
|
-
// is otherwise eaten as the URL.
|
|
23
|
-
return { command: "cmd", args: ["/c", "start", ""] };
|
|
24
|
-
case "linux":
|
|
25
|
-
return { command: "xdg-open", args: [] };
|
|
26
|
-
default:
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Try to open `url` in the browser. Resolves to whether a launcher was started;
|
|
32
|
-
* it does not wait for the browser, and it never rejects.
|
|
33
|
-
*/
|
|
34
|
-
export async function openInBrowser(url, platform = process.platform) {
|
|
35
|
-
const opener = openerFor(platform);
|
|
36
|
-
if (!opener)
|
|
37
|
-
return false;
|
|
38
|
-
return new Promise((resolve) => {
|
|
39
|
-
try {
|
|
40
|
-
const child = spawn(opener.command, [...opener.args, url], {
|
|
41
|
-
// Detached so a long-lived browser is not tied to this process, and
|
|
42
|
-
// silenced so a missing launcher cannot print over the workbench.
|
|
43
|
-
detached: true,
|
|
44
|
-
stdio: "ignore",
|
|
45
|
-
});
|
|
46
|
-
child.once("error", () => resolve(false));
|
|
47
|
-
// "spawn" means the launcher started; whether it found a browser is its
|
|
48
|
-
// business, and not something this process should block on.
|
|
49
|
-
child.once("spawn", () => {
|
|
50
|
-
child.unref();
|
|
51
|
-
resolve(true);
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
resolve(false);
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
package/dist/cache-report.js
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prompt-cache accounting: how much of what we sent was actually re-sent.
|
|
3
|
-
*
|
|
4
|
-
* Every turn re-sends the whole conversation. A prompt cache is what makes that
|
|
5
|
-
* affordable: the provider recognises the unchanged prefix and bills it at a fraction of
|
|
6
|
-
* the input price. A cache hit rate is therefore not a curiosity — it is the difference
|
|
7
|
-
* between paying once for context and paying for it again on every turn.
|
|
8
|
-
*
|
|
9
|
-
* A high `cacheRead` on its own proves nothing. A session can read a great deal from
|
|
10
|
-
* cache and still re-bill a great deal beside it, and the totals look healthy while the
|
|
11
|
-
* bill does not. The number that matters is the *rate*: of all the prompt tokens we were
|
|
12
|
-
* billed for, what share came from cache.
|
|
13
|
-
*
|
|
14
|
-
* A miss is worth reporting because it is almost always attributable. Two causes
|
|
15
|
-
* dominate, and both are visible in the data: the cache expired during an idle gap, or
|
|
16
|
-
* the model changed and the new one has no cache for that prefix. Naming the likely
|
|
17
|
-
* cause is the difference between a number and something a user can act on.
|
|
18
|
-
*/
|
|
19
|
-
/**
|
|
20
|
-
* An idle gap longer than this is worth naming as the likely cause of a miss. Five
|
|
21
|
-
* minutes is the common provider default cache TTL; a gap past it is the explanation,
|
|
22
|
-
* and a shorter one means something else changed.
|
|
23
|
-
*/
|
|
24
|
-
export const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
25
|
-
/**
|
|
26
|
-
* A turn with fewer prompt tokens than this is not counted as a miss. Short prompts are
|
|
27
|
-
* often too small to be worth caching at all, and counting them would report a problem
|
|
28
|
-
* where none exists.
|
|
29
|
-
*/
|
|
30
|
-
export const MISS_FLOOR_TOKENS = 1000;
|
|
31
|
-
function asUsage(value) {
|
|
32
|
-
if (!value || typeof value !== "object")
|
|
33
|
-
return undefined;
|
|
34
|
-
const candidate = value;
|
|
35
|
-
if (typeof candidate.input !== "number" && typeof candidate.cacheRead !== "number" && typeof candidate.cacheWrite !== "number")
|
|
36
|
-
return undefined;
|
|
37
|
-
return candidate;
|
|
38
|
-
}
|
|
39
|
-
function tokens(usage) {
|
|
40
|
-
const input = usage?.input ?? 0;
|
|
41
|
-
const cacheRead = usage?.cacheRead ?? 0;
|
|
42
|
-
// A cache WRITE is also a prompt token the provider processed; excluding it would
|
|
43
|
-
// make the rate look better than the bill.
|
|
44
|
-
const cacheWrite = usage?.cacheWrite ?? 0;
|
|
45
|
-
return { input, cacheRead, cacheWrite, prompt: input + cacheRead + cacheWrite };
|
|
46
|
-
}
|
|
47
|
-
function parseTime(value) {
|
|
48
|
-
if (typeof value !== "string")
|
|
49
|
-
return null;
|
|
50
|
-
const t = Date.parse(value);
|
|
51
|
-
return Number.isNaN(t) ? null : t;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Read the usage off one session entry, or nothing.
|
|
55
|
-
*
|
|
56
|
-
* Entries arrive as a union the caller's session defines, so this narrows at runtime
|
|
57
|
-
* rather than asserting a shape: an entry type this code has never seen contributes
|
|
58
|
-
* nothing and the reading stays honest instead of turning into a crash or a nonsense
|
|
59
|
-
* number.
|
|
60
|
-
*/
|
|
61
|
-
function usageOf(entry) {
|
|
62
|
-
if (!entry || typeof entry !== "object")
|
|
63
|
-
return undefined;
|
|
64
|
-
const e = entry;
|
|
65
|
-
// Usage lives either on an assistant message or on a standalone usage entry (cache
|
|
66
|
-
// warming writes one). Both count; neither is inferred.
|
|
67
|
-
const onMessage = e.message && typeof e.message === "object" ? e.message : undefined;
|
|
68
|
-
const holder = onMessage ?? e;
|
|
69
|
-
const usage = asUsage(holder.usage);
|
|
70
|
-
if (!usage)
|
|
71
|
-
return undefined;
|
|
72
|
-
const provider = typeof holder.provider === "string" ? holder.provider : "";
|
|
73
|
-
const model = typeof holder.model === "string" ? holder.model : "";
|
|
74
|
-
return { usage, model: [provider, model].filter(Boolean).join("/") };
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Walk a session's entries and total up what was cached and what was re-billed.
|
|
78
|
-
*
|
|
79
|
-
* Order is the file order, which is the order the turns were sent.
|
|
80
|
-
*/
|
|
81
|
-
export function summarizeCacheUsage(entries) {
|
|
82
|
-
let cachedTokens = 0;
|
|
83
|
-
let billedTokens = 0;
|
|
84
|
-
const misses = [];
|
|
85
|
-
let missedTokens = 0;
|
|
86
|
-
let previous = null;
|
|
87
|
-
for (const entry of entries) {
|
|
88
|
-
const found = usageOf(entry);
|
|
89
|
-
if (!found)
|
|
90
|
-
continue;
|
|
91
|
-
const { input, cacheRead, cacheWrite, prompt } = tokens(found.usage);
|
|
92
|
-
if (prompt === 0)
|
|
93
|
-
continue;
|
|
94
|
-
cachedTokens += cacheRead;
|
|
95
|
-
billedTokens += input + cacheWrite;
|
|
96
|
-
const at = parseTime(entry.timestamp);
|
|
97
|
-
// Read the previous turn before overwriting it. It is captured into a local
|
|
98
|
-
// first because TypeScript narrows `previous` to `null` once the assignment at
|
|
99
|
-
// the bottom of the loop is in scope, and the miss check below needs the old value.
|
|
100
|
-
const prior = previous;
|
|
101
|
-
const priorAt = prior ? prior.at : null;
|
|
102
|
-
// A miss is a turn that had to send its prompt with NOTHING read from cache while
|
|
103
|
-
// a previous prompt existed to be cached. One isolated occurrence is noise; the
|
|
104
|
-
// report lists them so the pattern is visible rather than the count alone.
|
|
105
|
-
if (prior !== null && cacheRead === 0 && input >= MISS_FLOOR_TOKENS) {
|
|
106
|
-
const idleMs = at !== null && priorAt !== null ? Math.max(0, at - priorAt) : 0;
|
|
107
|
-
misses.push({
|
|
108
|
-
missedTokens: input,
|
|
109
|
-
idleMs,
|
|
110
|
-
modelChanged: found.model !== "" && prior.model !== "" && found.model !== prior.model,
|
|
111
|
-
});
|
|
112
|
-
missedTokens += input;
|
|
113
|
-
}
|
|
114
|
-
previous = { prompt, at: at ?? priorAt ?? null, model: found.model };
|
|
115
|
-
}
|
|
116
|
-
const total = cachedTokens + billedTokens;
|
|
117
|
-
return {
|
|
118
|
-
cachedTokens,
|
|
119
|
-
billedTokens,
|
|
120
|
-
hitRate: total > 0 ? (cachedTokens / total) * 100 : null,
|
|
121
|
-
misses,
|
|
122
|
-
missedTokens,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* The most likely cause of a miss, for the one-line explanation beside the number.
|
|
127
|
-
*
|
|
128
|
-
* Reported only when there is a cause worth naming. Claiming one from thin evidence is
|
|
129
|
-
* worse than saying nothing: the user cannot check a guess that has no basis in the data.
|
|
130
|
-
*/
|
|
131
|
-
export function explainMiss(miss) {
|
|
132
|
-
if (miss.modelChanged)
|
|
133
|
-
return "model changed, so its cache had to be built";
|
|
134
|
-
if (miss.idleMs >= CACHE_TTL_MS)
|
|
135
|
-
return `cache expired after ${Math.round(miss.idleMs / 1000)}s idle`;
|
|
136
|
-
if (miss.idleMs > 0)
|
|
137
|
-
return "prompt changed too early in the prefix to hit";
|
|
138
|
-
return "reason not recorded";
|
|
139
|
-
}
|