@quatcode/agent 0.2.9 → 0.2.11
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/args.d.ts +2 -0
- package/dist/args.d.ts.map +1 -1
- package/dist/args.js +14 -2
- package/dist/args.js.map +1 -1
- package/dist/cli.js +6 -2
- package/dist/cli.js.map +1 -1
- package/dist/gate.d.ts +4 -1
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +47 -20
- package/dist/gate.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +2 -0
- package/dist/render.js.map +1 -1
- package/dist/store.d.ts +19 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +42 -2
- package/dist/store.js.map +1 -1
- package/dist/tui.d.ts +1 -0
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +97 -9
- package/dist/tui.js.map +1 -1
- package/dist/turn.d.ts +7 -0
- package/dist/turn.d.ts.map +1 -1
- package/dist/turn.js +28 -2
- package/dist/turn.js.map +1 -1
- package/package.json +3 -3
package/dist/tui.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { readFile, stat } from "node:fs/promises";
|
|
4
|
-
import { Session, ToolRegistry, addUsage, emptyUsage, estimateCost, runAgent, } from "@quatcode/core";
|
|
4
|
+
import { Session, ToolRegistry, addUsage, compactMessages, contextWindowFor, emptyUsage, estimateCost, runAgent, } from "@quatcode/core";
|
|
5
5
|
import { analyzeCommand, createProjectTools, McpManager } from "@quatcode/tools";
|
|
6
6
|
import { buildProjectContext, createBrainTools, scanProject } from "@quatcode/brain";
|
|
7
7
|
import { Keyboard } from "./keyboard.js";
|
|
@@ -14,7 +14,7 @@ import { renderMarkdown, wrapSegments } from "./markdown.js";
|
|
|
14
14
|
import { ADD_PROVIDER, allProviders, buildProvider, defaultModelFor, effortsForModel, hasKey, keyEnvFor, probeProviderModels, providerEnvKey, providerHasModel, providerMeta, providerSlug, registerCustomProvider, removeCustomProvider, resolveProviderForModel, } from "./models.js";
|
|
15
15
|
import { ASCII_MODE, activeThemeId, bg, bold, buildColorRemap, clipAnsi, fg, getThemes, glyphs, mix, padEndTo, palette, pulse, remapColors, setTheme, shimmer, stringWidth, stripAnsi, tone, toolGlyph, } from "./theme.js";
|
|
16
16
|
import { listSessions, listSessionsForDir, loadConfig, makeSessionId, saveConfig, saveSession, deleteSession } from "./store.js";
|
|
17
|
-
import { configFile, conversationKey, deleteConversation, displayTitle, latestPerConversation, messagesForStorage, sameDir, sortConversations, updateConversation } from "./store.js";
|
|
17
|
+
import { appendAudit, auditInput, configFile, conversationKey, deleteConversation, displayTitle, latestPerConversation, messagesForStorage, sameDir, sortConversations, updateConversation } from "./store.js";
|
|
18
18
|
import { MCP_USAGE, isMcpUrl, mcpInstructionsSection, parseMcpAdd } from "./mcp.js";
|
|
19
19
|
import { AUTO, PERSONA_SETTINGS, personaSection, personaSummary, } from "./persona.js";
|
|
20
20
|
import { copyToClipboard, readClipboardImage, readClipboardText, } from "./clipboard.js";
|
|
@@ -35,6 +35,7 @@ const COMMAND_INFO = [
|
|
|
35
35
|
{ name: "/timeline", desc: "jump to a sent message" },
|
|
36
36
|
{ name: "/undo", desc: "take back the last message" },
|
|
37
37
|
{ name: "/verbose", desc: "toggle full tool output" },
|
|
38
|
+
{ name: "/compact", desc: "summarize older history to free context" },
|
|
38
39
|
{ name: "/settings", desc: "tune the agent's persona: character, tone, verbosity…" },
|
|
39
40
|
{ name: "/skills", args: "[add|remove|list]", desc: "manage agent skills" },
|
|
40
41
|
{ name: "/mcp", args: "[list|add|remove]", desc: "manage MCP servers" },
|
|
@@ -110,6 +111,8 @@ class TuiApp {
|
|
|
110
111
|
permissionInner = 0;
|
|
111
112
|
permissionBorder = palette.steel;
|
|
112
113
|
lastVisibleStart = 0;
|
|
114
|
+
lastContextTokens = 0;
|
|
115
|
+
lastCachedTokens = 0;
|
|
113
116
|
lastInputRender = null;
|
|
114
117
|
lastInputOffset = 1;
|
|
115
118
|
lastHeaderRows = 2;
|
|
@@ -1982,21 +1985,50 @@ class TuiApp {
|
|
|
1982
1985
|
this.append([
|
|
1983
1986
|
` ${tone.amber(glyphs.diamondOpen)} ${tone.mist(`// ${req.tool} blocked in PLAN mode (Tab switches to BUILD)`)}`,
|
|
1984
1987
|
]);
|
|
1988
|
+
this.audit(req, "denied", "plan");
|
|
1985
1989
|
return { allowed: false };
|
|
1986
1990
|
}
|
|
1987
1991
|
if (this.agentMode === "plan" && READ_ONLY_TOOLS.has(req.tool))
|
|
1988
1992
|
return { allowed: true };
|
|
1989
|
-
|
|
1993
|
+
const analysis = req.tool === "run_command" ? this.commandAnalysis(req) : null;
|
|
1994
|
+
const dangerous = analysis?.dangerous === true;
|
|
1995
|
+
if (this.opts.allowAll) {
|
|
1996
|
+
this.audit(req, "allowed", "allow-all", analysis);
|
|
1990
1997
|
return { allowed: true };
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1998
|
+
}
|
|
1999
|
+
// "always" never covers dangerous commands — those are confirmed each time.
|
|
2000
|
+
if (this.gateMode === "always" && !dangerous) {
|
|
2001
|
+
this.audit(req, "allowed", "always", analysis);
|
|
1994
2002
|
return { allowed: true };
|
|
1995
2003
|
}
|
|
1996
|
-
|
|
2004
|
+
const choice = await this.permissionCard(req);
|
|
2005
|
+
const granted = choice === "allow" || choice === "always";
|
|
2006
|
+
if (choice === "always" && !dangerous)
|
|
2007
|
+
this.gateMode = "always";
|
|
2008
|
+
this.audit(req, granted ? "allowed" : "denied", choice, analysis);
|
|
2009
|
+
return { allowed: granted };
|
|
1997
2010
|
},
|
|
1998
2011
|
};
|
|
1999
2012
|
}
|
|
2013
|
+
commandAnalysis(req) {
|
|
2014
|
+
const command = req.input && typeof req.input === "object" && "command" in req.input
|
|
2015
|
+
? String(req.input.command)
|
|
2016
|
+
: "";
|
|
2017
|
+
return command ? analyzeCommand(command) : null;
|
|
2018
|
+
}
|
|
2019
|
+
audit(req, decision, via, analysis) {
|
|
2020
|
+
void appendAudit({
|
|
2021
|
+
ts: new Date().toISOString(),
|
|
2022
|
+
dir: this.dir,
|
|
2023
|
+
tool: req.tool,
|
|
2024
|
+
decision,
|
|
2025
|
+
via,
|
|
2026
|
+
danger: analysis
|
|
2027
|
+
? { dangerous: analysis.dangerous, sensitive: analysis.sensitive, reasons: analysis.reasons }
|
|
2028
|
+
: undefined,
|
|
2029
|
+
input: auditInput(req.input),
|
|
2030
|
+
}).catch(() => undefined);
|
|
2031
|
+
}
|
|
2000
2032
|
async promptForKey(keyEnv) {
|
|
2001
2033
|
this.append([` ${tone.mist(`// ${keyEnv} IS NOT SET`)}`]);
|
|
2002
2034
|
const result = await this.readLine({
|
|
@@ -2055,7 +2087,7 @@ class TuiApp {
|
|
|
2055
2087
|
this.undoAfterAbort = false;
|
|
2056
2088
|
this.mode = "running";
|
|
2057
2089
|
this.scrollOffset = 0;
|
|
2058
|
-
const controller = new TurnController({ model: this.model, maxSteps: this.opts.maxSteps }, {
|
|
2090
|
+
const controller = new TurnController({ model: this.model, maxSteps: this.opts.maxSteps, contextWindow: this.contextLimit() }, {
|
|
2059
2091
|
width: () => this.contentWidth() - 2,
|
|
2060
2092
|
append: (lines) => this.append(lines),
|
|
2061
2093
|
beginLive: () => this.beginLive(),
|
|
@@ -2079,6 +2111,7 @@ class TuiApp {
|
|
|
2079
2111
|
stream: true,
|
|
2080
2112
|
signal: this.abort.signal,
|
|
2081
2113
|
images,
|
|
2114
|
+
contextLimit: this.contextLimit(),
|
|
2082
2115
|
onEvent: controller.handle,
|
|
2083
2116
|
});
|
|
2084
2117
|
}
|
|
@@ -2117,6 +2150,8 @@ class TuiApp {
|
|
|
2117
2150
|
}
|
|
2118
2151
|
this.totalUsage = addUsage(this.totalUsage, result.usage);
|
|
2119
2152
|
this.turns += 1;
|
|
2153
|
+
this.lastContextTokens = controller.contextTokens;
|
|
2154
|
+
this.lastCachedTokens = controller.cachedTokens;
|
|
2120
2155
|
const status = this.abortRequested
|
|
2121
2156
|
? "interrupted"
|
|
2122
2157
|
: result.status === "complete"
|
|
@@ -2219,6 +2254,9 @@ class TuiApp {
|
|
|
2219
2254
|
case "settings":
|
|
2220
2255
|
await this.openSettings();
|
|
2221
2256
|
return false;
|
|
2257
|
+
case "compact":
|
|
2258
|
+
await this.compactCommand();
|
|
2259
|
+
return false;
|
|
2222
2260
|
case "theme":
|
|
2223
2261
|
await this.changeTheme(arg);
|
|
2224
2262
|
return false;
|
|
@@ -2239,6 +2277,50 @@ class TuiApp {
|
|
|
2239
2277
|
return false;
|
|
2240
2278
|
}
|
|
2241
2279
|
}
|
|
2280
|
+
contextLimit() {
|
|
2281
|
+
const flag = this.opts.contextLimit;
|
|
2282
|
+
if (typeof flag === "number" && flag > 0)
|
|
2283
|
+
return flag;
|
|
2284
|
+
const env = Number(process.env.QUATCODE_CONTEXT_LIMIT);
|
|
2285
|
+
if (Number.isFinite(env) && env > 0)
|
|
2286
|
+
return env;
|
|
2287
|
+
const configured = this.config.contextLimit;
|
|
2288
|
+
if (typeof configured === "number" && configured > 0)
|
|
2289
|
+
return configured;
|
|
2290
|
+
return contextWindowFor(this.model);
|
|
2291
|
+
}
|
|
2292
|
+
async compactCommand() {
|
|
2293
|
+
const provider = this.ensureProvider();
|
|
2294
|
+
if (!provider) {
|
|
2295
|
+
this.append([
|
|
2296
|
+
` ${tone.red(glyphs.cross)} ${tone.mist("// COMPACT needs a configured provider")}`,
|
|
2297
|
+
]);
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
const before = this.session.getMessages().length;
|
|
2301
|
+
this.setStatus({ label: "compacting context" });
|
|
2302
|
+
try {
|
|
2303
|
+
const result = await compactMessages(this.session.getMessages(), {
|
|
2304
|
+
provider,
|
|
2305
|
+
model: this.model,
|
|
2306
|
+
});
|
|
2307
|
+
if (!result) {
|
|
2308
|
+
this.append([` ${tone.mist("// nothing to compact yet")}`]);
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
this.session.replaceMessages(result.messages);
|
|
2312
|
+
this.append([
|
|
2313
|
+
` ${tone.accent(glyphs.diamond)} ${tone.mist(`// CONTEXT COMPACTED · ${before} → ${result.messages.length} messages · ~${fmtTokens(result.tokensBefore)} → ~${fmtTokens(result.tokensAfter)} tokens`)}`,
|
|
2314
|
+
"",
|
|
2315
|
+
]);
|
|
2316
|
+
}
|
|
2317
|
+
catch (err) {
|
|
2318
|
+
this.append([` ${tone.red(`// compact failed: ${errorMessage(err)}`)}`]);
|
|
2319
|
+
}
|
|
2320
|
+
finally {
|
|
2321
|
+
this.setStatus(null);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2242
2324
|
async openSettings() {
|
|
2243
2325
|
const before = JSON.stringify(this.persona);
|
|
2244
2326
|
for (;;) {
|
|
@@ -2967,11 +3049,17 @@ class TuiApp {
|
|
|
2967
3049
|
}
|
|
2968
3050
|
printUsage() {
|
|
2969
3051
|
const cost = estimateCost(this.totalUsage, this.model, this.providerId);
|
|
3052
|
+
const window = this.contextLimit();
|
|
3053
|
+
const context = this.lastContextTokens > 0
|
|
3054
|
+
? `${fmtTokens(this.lastContextTokens)} / ${fmtTokens(window)} (${Math.round((this.lastContextTokens / window) * 100)}%)`
|
|
3055
|
+
: "—";
|
|
2970
3056
|
const rows = [
|
|
2971
3057
|
["turns", String(this.turns)],
|
|
3058
|
+
["context", context],
|
|
3059
|
+
["cached", this.lastCachedTokens > 0 ? `${fmtTokens(this.lastCachedTokens)} read` : "—"],
|
|
2972
3060
|
[
|
|
2973
3061
|
"tokens",
|
|
2974
|
-
`${fmtTokens(this.totalUsage.inputTokens)}↑ ${fmtTokens(this.totalUsage.outputTokens)}
|
|
3062
|
+
`${fmtTokens(this.totalUsage.inputTokens)}↑ ${fmtTokens(this.totalUsage.outputTokens)}↓ (session)`,
|
|
2975
3063
|
],
|
|
2976
3064
|
["cost", fmtCost(cost)],
|
|
2977
3065
|
["elapsed", fmtDuration(performance.now() - this.startedAt)],
|