@gajae-code/coding-agent 0.10.0 → 0.10.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/CHANGELOG.md +33 -0
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/policy.d.ts +3 -0
- package/dist/types/coordinator-mcp/server.d.ts +17 -5
- package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
- package/dist/types/extensibility/extensions/types.d.ts +3 -1
- package/dist/types/modes/components/gajae-pet-widget.d.ts +1 -0
- package/dist/types/modes/components/pet-capability.d.ts +37 -0
- package/dist/types/modes/components/pet-selector.d.ts +1 -1
- package/dist/types/modes/components/settings-selector.d.ts +9 -0
- package/dist/types/modes/components/status-line/types.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +0 -10
- package/dist/types/modes/interactive-mode.d.ts +2 -1
- package/dist/types/modes/print-mode.d.ts +3 -4
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +2 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +17 -1
- package/dist/types/modes/types.d.ts +11 -1
- package/dist/types/modes/utils/context-usage.d.ts +4 -19
- package/dist/types/session/agent-session.d.ts +14 -1
- package/dist/types/session/context-estimation.d.ts +31 -0
- package/package.json +7 -7
- package/src/cli.ts +1 -0
- package/src/config/model-profiles.ts +19 -19
- package/src/config/settings.ts +96 -45
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/policy.ts +30 -0
- package/src/coordinator-mcp/server.ts +294 -66
- package/src/coordinator-mcp/session-reaper.ts +129 -0
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +3 -1
- package/src/extensibility/slash-commands.ts +3 -3
- package/src/gjc-runtime/tmux-owner-isolation.ts +4 -1
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/main.ts +3 -5
- package/src/modes/acp/acp-agent.ts +2 -2
- package/src/modes/components/custom-editor.ts +1 -0
- package/src/modes/components/footer.ts +3 -3
- package/src/modes/components/gajae-pet-widget.ts +169 -20
- package/src/modes/components/pet-capability.ts +115 -0
- package/src/modes/components/pet-selector.ts +4 -6
- package/src/modes/components/settings-selector.ts +28 -2
- package/src/modes/components/status-line/segments.ts +13 -6
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +4 -171
- package/src/modes/controllers/extension-ui-controller.ts +10 -9
- package/src/modes/controllers/input-controller.ts +43 -13
- package/src/modes/controllers/selector-controller.ts +7 -18
- package/src/modes/interactive-mode.ts +65 -4
- package/src/modes/print-mode.ts +228 -92
- package/src/modes/rpc/rpc-client.ts +58 -1
- package/src/modes/rpc/rpc-mode.ts +286 -85
- package/src/modes/rpc/rpc-types.ts +15 -1
- package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
- package/src/modes/shared/agent-wire/command-validation.ts +10 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/types.ts +11 -1
- package/src/modes/utils/context-usage.ts +56 -92
- package/src/session/agent-session.ts +337 -90
- package/src/session/context-estimation.ts +91 -0
- package/src/session/session-manager.ts +9 -2
- package/src/slash-commands/builtin-registry.ts +31 -19
- package/src/slash-commands/helpers/context-report.ts +26 -4
|
@@ -3,16 +3,20 @@ import type { CompactionSettings } from "@gajae-code/agent-core/compaction";
|
|
|
3
3
|
import {
|
|
4
4
|
effectiveReserveTokens,
|
|
5
5
|
estimateMessageTokensHeuristic,
|
|
6
|
-
estimateTextTokensHeuristic,
|
|
7
6
|
resolveThresholdTokens,
|
|
8
7
|
} from "@gajae-code/agent-core/compaction";
|
|
9
8
|
import type { Model } from "@gajae-code/ai";
|
|
10
9
|
import { formatNumber } from "@gajae-code/utils";
|
|
11
|
-
import type { Skill } from "../../extensibility/skills";
|
|
12
10
|
import type { AgentSession } from "../../session/agent-session";
|
|
13
|
-
import
|
|
11
|
+
import { computeNonMessageBreakdown } from "../../session/context-estimation";
|
|
14
12
|
import type { theme as Theme } from "../theme/theme";
|
|
15
13
|
|
|
14
|
+
export {
|
|
15
|
+
computeNonMessageTokens,
|
|
16
|
+
estimateSkillsTokens,
|
|
17
|
+
estimateToolSchemaTokens,
|
|
18
|
+
} from "../../session/context-estimation";
|
|
19
|
+
|
|
16
20
|
const GRID_COLS = 20;
|
|
17
21
|
const GRID_ROWS = 10;
|
|
18
22
|
const GRID_CELLS = GRID_COLS * GRID_ROWS;
|
|
@@ -38,90 +42,13 @@ export interface ContextBreakdown {
|
|
|
38
42
|
contextWindow: number;
|
|
39
43
|
categories: CategoryInfo[];
|
|
40
44
|
lastUserTurnTokens: number;
|
|
41
|
-
|
|
45
|
+
estimatedCategoryTotal: number;
|
|
46
|
+
usedTokens: number | null;
|
|
47
|
+
source: "provider_anchor" | "heuristic" | "unknown";
|
|
42
48
|
autoCompactBufferTokens: number;
|
|
43
49
|
freeTokens: number;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
export function estimateSkillsTokens(skills: readonly Skill[]): number {
|
|
47
|
-
const fragments: string[] = [];
|
|
48
|
-
for (const skill of skills) {
|
|
49
|
-
// "- name: description\n" wire framing tokenizes ~identically to the
|
|
50
|
-
// concatenated form, so encode each piece separately and sum.
|
|
51
|
-
fragments.push(skill.name, skill.description);
|
|
52
|
-
}
|
|
53
|
-
return estimateTextTokensHeuristic(fragments);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function estimateToolSchemaTokens(
|
|
57
|
-
tools: ReadonlyArray<Pick<Tool, "name" | "description" | "parameters">>,
|
|
58
|
-
): number {
|
|
59
|
-
const fragments: string[] = [];
|
|
60
|
-
for (const tool of tools) {
|
|
61
|
-
fragments.push(tool.name, tool.description);
|
|
62
|
-
try {
|
|
63
|
-
fragments.push(JSON.stringify(tool.parameters ?? {}));
|
|
64
|
-
} catch {
|
|
65
|
-
// Schema may contain functions or cycles; ignore.
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return estimateTextTokensHeuristic(fragments);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Compute just the NON-MESSAGE token total: system prompt (with its skills
|
|
73
|
-
* section subtracted, since skills are tokenized separately) + system context
|
|
74
|
-
* (the rest of the system-prompt array) + tools + skills.
|
|
75
|
-
*
|
|
76
|
-
* Exposed so callers like `StatusLineComponent` can cache the non-message
|
|
77
|
-
* total separately from the message total. Non-message inputs (skills,
|
|
78
|
-
* tools, system prompt) change rarely; the message list grows on every
|
|
79
|
-
* streaming turn. Splitting the two lets the caller refresh each on its own
|
|
80
|
-
* cadence — non-message recomputed only when the inputs identity changes,
|
|
81
|
-
* messages walked incrementally as new entries append.
|
|
82
|
-
*/
|
|
83
|
-
export function computeNonMessageTokens(session: AgentSession): number {
|
|
84
|
-
const parts = computeNonMessageBreakdown(session);
|
|
85
|
-
return (
|
|
86
|
-
parts.systemPromptTokens + parts.systemContextTokens + parts.rulesTokens + parts.toolsTokens + parts.skillsTokens
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Shared helper for the four non-message token totals. Single source of truth
|
|
92
|
-
* for both `computeNonMessageTokens` (status-line incremental cache) and
|
|
93
|
-
* `computeContextBreakdown` (/context panel). The split avoids drift between
|
|
94
|
-
* the two surfaces — they MUST report the same numbers.
|
|
95
|
-
*/
|
|
96
|
-
function computeNonMessageBreakdown(session: AgentSession): {
|
|
97
|
-
rulesTokens: number;
|
|
98
|
-
skillsTokens: number;
|
|
99
|
-
toolsTokens: number;
|
|
100
|
-
systemContextTokens: number;
|
|
101
|
-
systemPromptTokens: number;
|
|
102
|
-
} {
|
|
103
|
-
const skillsTokens = estimateSkillsTokens(session.skills ?? []);
|
|
104
|
-
const toolsTokens = estimateToolSchemaTokens(session.agent?.state?.tools ?? []);
|
|
105
|
-
const systemPromptParts = session.systemPrompt ?? [];
|
|
106
|
-
const rulesTokens = estimateRulesTokens(systemPromptParts);
|
|
107
|
-
const systemContextTokens = estimateTextTokensHeuristic(systemPromptParts.slice(1));
|
|
108
|
-
const systemPromptTokens = Math.max(
|
|
109
|
-
0,
|
|
110
|
-
estimateTextTokensHeuristic(systemPromptParts[0] ?? "") - skillsTokens - rulesTokens,
|
|
111
|
-
);
|
|
112
|
-
return { rulesTokens, skillsTokens, toolsTokens, systemContextTokens, systemPromptTokens };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function estimateRulesTokens(systemPromptParts: readonly string[]): number {
|
|
116
|
-
const fragments: string[] = [];
|
|
117
|
-
for (const part of systemPromptParts) {
|
|
118
|
-
for (const match of part.matchAll(/<rules>[\s\S]*?<\/rules>/g)) {
|
|
119
|
-
fragments.push(match[0]);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return fragments.length === 0 ? 0 : estimateTextTokensHeuristic(fragments);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
52
|
function splitLastUserTurn(messages: readonly AgentMessage[]): {
|
|
126
53
|
regularMessagesTokens: number;
|
|
127
54
|
lastUserTurnTokens: number;
|
|
@@ -198,7 +125,11 @@ export function computeContextBreakdown(
|
|
|
198
125
|
},
|
|
199
126
|
];
|
|
200
127
|
|
|
201
|
-
const
|
|
128
|
+
const estimatedCategoryTotal = categories.reduce((sum, c) => sum + c.tokens, 0);
|
|
129
|
+
const contextUsage = session.getContextUsage?.();
|
|
130
|
+
const source = contextUsage?.source ?? "heuristic";
|
|
131
|
+
const usedTokens = source === "unknown" ? null : (contextUsage?.tokens ?? estimatedCategoryTotal);
|
|
132
|
+
const tokensForFreeSpace = usedTokens ?? estimatedCategoryTotal;
|
|
202
133
|
|
|
203
134
|
let autoCompactBufferTokens = 0;
|
|
204
135
|
if (contextWindow > 0) {
|
|
@@ -214,16 +145,18 @@ export function computeContextBreakdown(
|
|
|
214
145
|
autoCompactBufferTokens = effectiveReserveTokens(contextWindow, compactionSettings);
|
|
215
146
|
}
|
|
216
147
|
}
|
|
217
|
-
autoCompactBufferTokens = Math.min(autoCompactBufferTokens, Math.max(0, contextWindow -
|
|
148
|
+
autoCompactBufferTokens = Math.min(autoCompactBufferTokens, Math.max(0, contextWindow - tokensForFreeSpace));
|
|
218
149
|
|
|
219
|
-
const freeTokens = Math.max(0, contextWindow -
|
|
150
|
+
const freeTokens = Math.max(0, contextWindow - tokensForFreeSpace - autoCompactBufferTokens);
|
|
220
151
|
|
|
221
152
|
return {
|
|
222
153
|
model,
|
|
223
154
|
contextWindow,
|
|
224
155
|
categories,
|
|
225
156
|
lastUserTurnTokens,
|
|
157
|
+
estimatedCategoryTotal,
|
|
226
158
|
usedTokens,
|
|
159
|
+
source,
|
|
227
160
|
autoCompactBufferTokens,
|
|
228
161
|
freeTokens,
|
|
229
162
|
};
|
|
@@ -310,18 +243,48 @@ function percentString(part: number, whole: number, fractionDigits = 1): string
|
|
|
310
243
|
|
|
311
244
|
function buildLegendLines(breakdown: ContextBreakdown, theme: typeof Theme): string[] {
|
|
312
245
|
const lines: string[] = [];
|
|
313
|
-
const {
|
|
246
|
+
const {
|
|
247
|
+
model,
|
|
248
|
+
contextWindow,
|
|
249
|
+
categories,
|
|
250
|
+
estimatedCategoryTotal,
|
|
251
|
+
usedTokens,
|
|
252
|
+
source,
|
|
253
|
+
autoCompactBufferTokens,
|
|
254
|
+
freeTokens,
|
|
255
|
+
} = breakdown;
|
|
314
256
|
|
|
315
257
|
const modelName = model?.name ?? model?.id ?? "no model";
|
|
316
258
|
const modelId = model?.id ?? "unknown";
|
|
317
259
|
const windowLabel = formatNumber(contextWindow).toLowerCase();
|
|
260
|
+
const totalSourceLabel =
|
|
261
|
+
source === "provider_anchor"
|
|
262
|
+
? "provider-reported"
|
|
263
|
+
: source === "unknown"
|
|
264
|
+
? "estimated; exact count unknown until next response"
|
|
265
|
+
: "estimated";
|
|
318
266
|
|
|
319
267
|
lines.push(theme.bold(`${modelName}`) + theme.fg("dim", ` (${windowLabel} context)`));
|
|
320
268
|
lines.push(theme.fg("muted", `${modelId}[${windowLabel}]`));
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
theme.
|
|
324
|
-
|
|
269
|
+
if (usedTokens === null) {
|
|
270
|
+
lines.push(
|
|
271
|
+
`${theme.bold("unknown")}${theme.fg("dim", `/${windowLabel} tokens`)}` +
|
|
272
|
+
theme.fg("muted", " (exact count unknown until next response)"),
|
|
273
|
+
);
|
|
274
|
+
} else {
|
|
275
|
+
lines.push(
|
|
276
|
+
`${theme.bold(formatNumber(usedTokens))}${theme.fg("dim", `/${windowLabel} tokens`)}` +
|
|
277
|
+
theme.fg("muted", ` (${percentString(usedTokens, contextWindow)}) (${totalSourceLabel})`),
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
if (source !== "unknown" && usedTokens !== null && estimatedCategoryTotal !== usedTokens) {
|
|
281
|
+
lines.push(
|
|
282
|
+
theme.fg(
|
|
283
|
+
"muted",
|
|
284
|
+
`Estimated category total: ${formatNumber(estimatedCategoryTotal)} tokens (composition below is estimated)`,
|
|
285
|
+
),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
325
288
|
lines.push("");
|
|
326
289
|
lines.push(theme.fg("muted", "Estimated usage by category"));
|
|
327
290
|
|
|
@@ -334,8 +297,9 @@ function buildLegendLines(breakdown: ContextBreakdown, theme: typeof Theme): str
|
|
|
334
297
|
}
|
|
335
298
|
|
|
336
299
|
const freeDot = theme.fg("dim", CELL_FREE);
|
|
300
|
+
const freeLabel = usedTokens === null ? "Free space (estimated)" : "Free space";
|
|
337
301
|
lines.push(
|
|
338
|
-
`${freeDot}
|
|
302
|
+
`${freeDot} ${freeLabel}: ${theme.bold(formatNumber(freeTokens))} ${theme.fg("dim", `(${percentString(freeTokens, contextWindow)})`)}`,
|
|
339
303
|
);
|
|
340
304
|
|
|
341
305
|
if (autoCompactBufferTokens > 0) {
|