@kenkaiiii/ggcoder 5.16.0 → 5.18.0
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/README.md +2 -2
- package/dist/app-sidecar.js +303 -88
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +24 -2
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session-review-coverage.test.d.ts +2 -0
- package/dist/core/agent-session-review-coverage.test.d.ts.map +1 -0
- package/dist/core/agent-session-review-coverage.test.js +57 -0
- package/dist/core/agent-session-review-coverage.test.js.map +1 -0
- package/dist/core/agent-session-tool-result-policy.test.d.ts +2 -0
- package/dist/core/agent-session-tool-result-policy.test.d.ts.map +1 -0
- package/dist/core/agent-session-tool-result-policy.test.js +12 -0
- package/dist/core/agent-session-tool-result-policy.test.js.map +1 -0
- package/dist/core/agent-session.d.ts +19 -12
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +185 -66
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-providers.js +1 -1
- package/dist/core/auth-providers.js.map +1 -1
- package/dist/core/compaction/compactor.test.js +2 -1
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/event-bus.d.ts +2 -0
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/ideal-review.d.ts +27 -0
- package/dist/core/ideal-review.d.ts.map +1 -1
- package/dist/core/ideal-review.js +64 -0
- package/dist/core/ideal-review.js.map +1 -1
- package/dist/core/ideal-review.test.js +34 -1
- package/dist/core/ideal-review.test.js.map +1 -1
- package/dist/core/lsp/client.d.ts +3 -0
- package/dist/core/lsp/client.d.ts.map +1 -1
- package/dist/core/lsp/client.js +20 -0
- package/dist/core/lsp/client.js.map +1 -1
- package/dist/core/lsp/manager.d.ts +32 -9
- package/dist/core/lsp/manager.d.ts.map +1 -1
- package/dist/core/lsp/manager.js +89 -37
- package/dist/core/lsp/manager.js.map +1 -1
- package/dist/core/lsp/manager.test.js +56 -4
- package/dist/core/lsp/manager.test.js.map +1 -1
- package/dist/core/project-discovery.d.ts +4 -11
- package/dist/core/project-discovery.d.ts.map +1 -1
- package/dist/core/project-discovery.js +33 -30
- package/dist/core/project-discovery.js.map +1 -1
- package/dist/core/project-discovery.test.js +148 -0
- package/dist/core/project-discovery.test.js.map +1 -1
- package/dist/core/run-lifecycle.d.ts +40 -0
- package/dist/core/run-lifecycle.d.ts.map +1 -0
- package/dist/core/run-lifecycle.js +95 -0
- package/dist/core/run-lifecycle.js.map +1 -0
- package/dist/core/run-lifecycle.test.d.ts +2 -0
- package/dist/core/run-lifecycle.test.d.ts.map +1 -0
- package/dist/core/run-lifecycle.test.js +64 -0
- package/dist/core/run-lifecycle.test.js.map +1 -0
- package/dist/core/session-compaction.d.ts +3 -0
- package/dist/core/session-compaction.d.ts.map +1 -1
- package/dist/core/session-compaction.js +14 -1
- package/dist/core/session-compaction.js.map +1 -1
- package/dist/core/session-compaction.test.js +5 -0
- package/dist/core/session-compaction.test.js.map +1 -1
- package/dist/core/session-manager.d.ts +33 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +73 -2
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-manager.test.js +97 -2
- package/dist/core/session-manager.test.js.map +1 -1
- package/dist/core/session-preview.d.ts +11 -0
- package/dist/core/session-preview.d.ts.map +1 -0
- package/dist/core/session-preview.js +50 -0
- package/dist/core/session-preview.js.map +1 -0
- package/dist/core/subagent-manager.d.ts +42 -7
- package/dist/core/subagent-manager.d.ts.map +1 -1
- package/dist/core/subagent-manager.js +346 -30
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/subagent-manager.test.js +195 -3
- package/dist/core/subagent-manager.test.js.map +1 -1
- package/dist/core/subagent-store.d.ts +20 -0
- package/dist/core/subagent-store.d.ts.map +1 -0
- package/dist/core/subagent-store.js +177 -0
- package/dist/core/subagent-store.js.map +1 -0
- package/dist/core/subagent-store.test.d.ts +2 -0
- package/dist/core/subagent-store.test.d.ts.map +1 -0
- package/dist/core/subagent-store.test.js +94 -0
- package/dist/core/subagent-store.test.js.map +1 -0
- package/dist/modes/agent-home-mode.d.ts.map +1 -1
- package/dist/modes/agent-home-mode.js +10 -3
- package/dist/modes/agent-home-mode.js.map +1 -1
- package/dist/modes/serve-mode.d.ts.map +1 -1
- package/dist/modes/serve-mode.js +10 -3
- package/dist/modes/serve-mode.js.map +1 -1
- package/dist/modes/subagent-worker-mode.d.ts +2 -0
- package/dist/modes/subagent-worker-mode.d.ts.map +1 -1
- package/dist/modes/subagent-worker-mode.js +13 -3
- package/dist/modes/subagent-worker-mode.js.map +1 -1
- package/dist/tools/subagent-shared.d.ts +8 -0
- package/dist/tools/subagent-shared.d.ts.map +1 -1
- package/dist/tools/subagent-shared.js.map +1 -1
- package/dist/tools/subagent.d.ts +3 -8
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +10 -8
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/subagent.test.js +17 -1
- package/dist/tools/subagent.test.js.map +1 -1
- package/dist/tools/tasks.d.ts +1 -1
- package/dist/tools/web-fetch.d.ts +1 -1
- package/dist/ui/App.d.ts +7 -3
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +66 -83
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/Footer.d.ts +1 -0
- package/dist/ui/components/Footer.d.ts.map +1 -1
- package/dist/ui/components/Footer.js +3 -3
- package/dist/ui/components/Footer.js.map +1 -1
- package/dist/ui/components/Footer.test.d.ts +2 -0
- package/dist/ui/components/Footer.test.d.ts.map +1 -0
- package/dist/ui/components/Footer.test.js +16 -0
- package/dist/ui/components/Footer.test.js.map +1 -0
- package/dist/ui/components/SubAgentPanel.d.ts +2 -0
- package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
- package/dist/ui/components/SubAgentPanel.js +4 -2
- package/dist/ui/components/SubAgentPanel.js.map +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts +8 -3
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +64 -8
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/hooks/useSessionPersistence.d.ts +4 -2
- package/dist/ui/hooks/useSessionPersistence.d.ts.map +1 -1
- package/dist/ui/hooks/useSessionPersistence.js +10 -1
- package/dist/ui/hooks/useSessionPersistence.js.map +1 -1
- package/dist/ui/hooks/useTerminalTitle.d.ts +3 -3
- package/dist/ui/hooks/useTerminalTitle.d.ts.map +1 -1
- package/dist/ui/hooks/useTerminalTitle.js +5 -9
- package/dist/ui/hooks/useTerminalTitle.js.map +1 -1
- package/dist/ui/login.d.ts.map +1 -1
- package/dist/ui/login.js +5 -1
- package/dist/ui/login.js.map +1 -1
- package/dist/ui/render.d.ts +7 -2
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +4 -4
- package/dist/ui/render.js.map +1 -1
- package/dist/ui/render.test.js +10 -1
- package/dist/ui/render.test.js.map +1 -1
- package/dist/ui/terminal-history.js +6 -2
- package/dist/ui/terminal-history.js.map +1 -1
- package/dist/ui/terminal-history.test.js +2 -2
- package/dist/ui/terminal-history.test.js.map +1 -1
- package/dist/ui/tui-history-parity.test.js +8 -1
- package/dist/ui/tui-history-parity.test.js.map +1 -1
- package/dist/utils/git.d.ts +2 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +12 -0
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/git.test.d.ts +2 -0
- package/dist/utils/git.test.d.ts.map +1 -0
- package/dist/utils/git.test.js +36 -0
- package/dist/utils/git.test.js.map +1 -0
- package/package.json +4 -4
- package/dist/utils/session-title.d.ts +0 -19
- package/dist/utils/session-title.d.ts.map +0 -1
- package/dist/utils/session-title.js +0 -82
- package/dist/utils/session-title.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { agentLoop, isAbortError } from "@kenkaiiii/gg-agent";
|
|
1
|
+
import { agentLoop, isAbortError, } from "@kenkaiiii/gg-agent";
|
|
2
2
|
import { ProviderError, } from "@kenkaiiii/gg-ai";
|
|
3
3
|
import { EventBus } from "./event-bus.js";
|
|
4
4
|
import { SlashCommandRegistry, createBuiltinCommands, } from "./slash-commands.js";
|
|
@@ -11,11 +11,12 @@ import { kimiCodingHeaders, isKimiCodingEndpoint } from "./oauth/kimi.js";
|
|
|
11
11
|
import { SessionManager, KEN_TURN_CUSTOM_KIND, AUTOPILOT_MARKER_CUSTOM_KIND, APP_MARKER_CUSTOM_KIND, } from "./session-manager.js";
|
|
12
12
|
import { ExtensionLoader } from "./extensions/loader.js";
|
|
13
13
|
import { shouldCompact, compact, getCompactionReserveTokens } from "./compaction/compactor.js";
|
|
14
|
-
import { getAuthStorageKeys, getContextWindow, getModel, MODELS } from "./model-registry.js";
|
|
14
|
+
import { getAuthStorageKeys, getContextWindow, getModel, getToolResultCharLimit, MODELS, } from "./model-registry.js";
|
|
15
15
|
import { discoverSkills } from "./skills.js";
|
|
16
16
|
import { ensureAppDirs } from "../config.js";
|
|
17
17
|
import { buildSystemPrompt } from "../system-prompt.js";
|
|
18
18
|
import { createTools, createWebSearchTool, } from "../tools/index.js";
|
|
19
|
+
import { buildSubAgentCompletionFollowUp } from "./subagent-manager.js";
|
|
19
20
|
import { applyAsyncSubagentPolicy } from "./subagent-policy.js";
|
|
20
21
|
import { MCPClientManager, getAllMcpServers } from "./mcp/index.js";
|
|
21
22
|
import { DeferredToolCatalog } from "./mcp/deferred-catalog.js";
|
|
@@ -23,16 +24,22 @@ import { createToolSearchTool } from "../tools/tool-search.js";
|
|
|
23
24
|
import { log } from "./logger.js";
|
|
24
25
|
import { setEstimatorModel } from "./compaction/token-estimator.js";
|
|
25
26
|
import { discoverAgents } from "./agents.js";
|
|
26
|
-
import { generateSessionTitle } from "../utils/session-title.js";
|
|
27
27
|
import { enhancePrompt } from "../utils/prompt-enhancer.js";
|
|
28
28
|
import { detectProjectStack } from "./language-detector.js";
|
|
29
|
-
import { evaluateIdealReview, buildIdealReviewMessage, detectTestDrift, } from "./ideal-review.js";
|
|
29
|
+
import { evaluateIdealReview, buildIdealReviewMessage, buildReviewCoverageMessage, detectTestDrift, ReviewCoverageTracker, } from "./ideal-review.js";
|
|
30
30
|
import { evaluateLoopBreak, buildLoopBreakMessage, ToolCallProgressTracker, detectTextRepetition, } from "./loop-breaker.js";
|
|
31
31
|
import { buildRegroundingMessage } from "./regrounding.js";
|
|
32
32
|
import { wrapSteeringText, STEERING_PREFIX } from "./steering.js";
|
|
33
|
+
import { findUserSessionPrompt, getUserSessionPrompt } from "./session-preview.js";
|
|
33
34
|
import crypto from "node:crypto";
|
|
34
35
|
import fs from "node:fs/promises";
|
|
35
36
|
import path from "node:path";
|
|
37
|
+
// ── Tool-result policy ─────────────────────────────────────
|
|
38
|
+
/** Resolve the per-result cap passed to the agent loop for the active transport. */
|
|
39
|
+
export function resolveSessionToolResultCharLimit(model, provider, accountId) {
|
|
40
|
+
return (getToolResultCharLimit(model, { provider, accountId }) ??
|
|
41
|
+
Math.floor(getContextWindow(model, { provider, accountId }) * 3.5 * 0.3));
|
|
42
|
+
}
|
|
36
43
|
// ── Agent Session ──────────────────────────────────────────
|
|
37
44
|
export class AgentSession {
|
|
38
45
|
eventBus = new EventBus();
|
|
@@ -59,6 +66,7 @@ export class AgentSession {
|
|
|
59
66
|
// display only, persisted + reloaded so a resumed session shows the same
|
|
60
67
|
// transcript rows the live run showed.
|
|
61
68
|
appMarkers = [];
|
|
69
|
+
turnMetrics = [];
|
|
62
70
|
tools = [];
|
|
63
71
|
/** Rebuilds the read tool for a new model (video byte cap is baked in at
|
|
64
72
|
* creation). Called from switchModel so video-capable models get the
|
|
@@ -84,7 +92,8 @@ export class AgentSession {
|
|
|
84
92
|
hookProgressTracker = new ToolCallProgressTracker();
|
|
85
93
|
hookFileEditCounts = new Map();
|
|
86
94
|
hookToolCalls = new Map();
|
|
87
|
-
|
|
95
|
+
idealReviewPhase = "idle";
|
|
96
|
+
reviewCoverage;
|
|
88
97
|
loopBreakInjected = false;
|
|
89
98
|
regroundingInjected = false;
|
|
90
99
|
compactionOccurred = false;
|
|
@@ -125,6 +134,10 @@ export class AgentSession {
|
|
|
125
134
|
* model emits step-completion markers the UI's plan-progress widget reads. */
|
|
126
135
|
approvedPlanPath;
|
|
127
136
|
sessionId = "";
|
|
137
|
+
/** Stable identity shared by compaction and approved-plan checkpoint files. */
|
|
138
|
+
conversationId = "";
|
|
139
|
+
/** Original user-authored prompt, retained when internal messages replace history. */
|
|
140
|
+
sessionPreview = "";
|
|
128
141
|
/** Runtime conversation identity for provider transport headers. Transient
|
|
129
142
|
* children need one even though they intentionally have no persisted session. */
|
|
130
143
|
transportSessionId = crypto.randomUUID();
|
|
@@ -138,6 +151,7 @@ export class AgentSession {
|
|
|
138
151
|
this.provider = options.provider;
|
|
139
152
|
this.model = options.model;
|
|
140
153
|
this.cwd = options.cwd;
|
|
154
|
+
this.reviewCoverage = new ReviewCoverageTracker(this.cwd);
|
|
141
155
|
this.baseUrl = options.baseUrl;
|
|
142
156
|
this.maxTokens = this.resolveMaxTokens(options.model);
|
|
143
157
|
this.thinkingLevel = options.thinkingLevel;
|
|
@@ -203,6 +217,12 @@ export class AgentSession {
|
|
|
203
217
|
model: this.model,
|
|
204
218
|
lspDiagnostics: this.settingsManager.get("lspDiagnostics"),
|
|
205
219
|
authStorage: this.authStorage,
|
|
220
|
+
onFileRead: (filePath) => this.reviewCoverage.recordRead(filePath),
|
|
221
|
+
onFileMutated: (filePath) => {
|
|
222
|
+
const relative = path.relative(this.cwd, filePath) || path.basename(filePath);
|
|
223
|
+
this.hookFileEditCounts.set(relative, (this.hookFileEditCounts.get(relative) ?? 0) + 1);
|
|
224
|
+
this.reviewCoverage.recordChanged(filePath);
|
|
225
|
+
},
|
|
206
226
|
// Lazy — sessionId/model/provider can change after createTools() runs, so
|
|
207
227
|
// sub-agent spawns read the current parent state at execution time.
|
|
208
228
|
getProvider: () => this.provider,
|
|
@@ -273,6 +293,8 @@ export class AgentSession {
|
|
|
273
293
|
else {
|
|
274
294
|
await this.createNewSession();
|
|
275
295
|
}
|
|
296
|
+
if (this.sessionId)
|
|
297
|
+
await this.subAgentManager?.hydrate(this.sessionId);
|
|
276
298
|
// GG Coder owns its command registry. Other agents start with an isolated
|
|
277
299
|
// empty registry and can register their own commands in their own file.
|
|
278
300
|
if (this.opts.coderSlashCommands !== false) {
|
|
@@ -567,7 +589,8 @@ export class AgentSession {
|
|
|
567
589
|
this.hookProgressTracker.reset();
|
|
568
590
|
this.hookFileEditCounts.clear();
|
|
569
591
|
this.hookToolCalls.clear();
|
|
570
|
-
this.
|
|
592
|
+
this.reviewCoverage.reset();
|
|
593
|
+
this.idealReviewPhase = "idle";
|
|
571
594
|
this.loopBreakInjected = false;
|
|
572
595
|
this.regroundingInjected = false;
|
|
573
596
|
this.compactionOccurred = false;
|
|
@@ -578,7 +601,7 @@ export class AgentSession {
|
|
|
578
601
|
* the same signals the TUI's useAgentLoop collects, so the loop-break and
|
|
579
602
|
* ideal-review decisions match across the CLI and the app.
|
|
580
603
|
*/
|
|
581
|
-
trackHookEvent(event) {
|
|
604
|
+
async trackHookEvent(event) {
|
|
582
605
|
switch (event.type) {
|
|
583
606
|
case "text_delta":
|
|
584
607
|
this.hookText += event.text;
|
|
@@ -601,13 +624,6 @@ export class AgentSession {
|
|
|
601
624
|
this.hookStats.bashCalls += 1;
|
|
602
625
|
this.hookConsecutiveFailures = event.isError ? this.hookConsecutiveFailures + 1 : 0;
|
|
603
626
|
this.hookRepeatedNoProgressCalls = this.hookProgressTracker.record(name, args, event.result, event.isError);
|
|
604
|
-
if ((name === "edit" || name === "write") && args) {
|
|
605
|
-
const filePath = args.file_path;
|
|
606
|
-
if (typeof filePath === "string") {
|
|
607
|
-
const fileNext = (this.hookFileEditCounts.get(filePath) ?? 0) + 1;
|
|
608
|
-
this.hookFileEditCounts.set(filePath, fileNext);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
627
|
if (name === "edit" && !event.isError) {
|
|
612
628
|
const diff = event.details?.diff ?? event.result;
|
|
613
629
|
const added = (diff.match(/^\+[^+]/gm) ?? []).length;
|
|
@@ -618,6 +634,7 @@ export class AgentSession {
|
|
|
618
634
|
}
|
|
619
635
|
case "turn_end":
|
|
620
636
|
this.hookStats.turns = event.turn;
|
|
637
|
+
await this.persistTurnMetric(event);
|
|
621
638
|
break;
|
|
622
639
|
}
|
|
623
640
|
}
|
|
@@ -677,15 +694,35 @@ export class AgentSession {
|
|
|
677
694
|
return null;
|
|
678
695
|
}
|
|
679
696
|
/**
|
|
680
|
-
* Pre-stop
|
|
681
|
-
*
|
|
697
|
+
* Pre-stop Ideal review phase machine. Once review starts, completion is
|
|
698
|
+
* blocked until harness-owned post-injection reads cover every changed file.
|
|
682
699
|
*/
|
|
683
700
|
getHookFollowUpMessages() {
|
|
701
|
+
const childCompletionFollowUp = buildSubAgentCompletionFollowUp(this.subAgentManager);
|
|
702
|
+
if (childCompletionFollowUp)
|
|
703
|
+
return childCompletionFollowUp;
|
|
684
704
|
if (this.opts.selfCorrectionHooks === false)
|
|
685
705
|
return null;
|
|
686
|
-
if (
|
|
706
|
+
if (this.idealReviewPhase === "reviewing") {
|
|
707
|
+
const coverage = this.reviewCoverage.evidence();
|
|
708
|
+
const lspEvidence = this.reviewLspEvidence(coverage.expected);
|
|
709
|
+
log("INFO", "ideal", "Ideal review coverage check", {
|
|
710
|
+
covered: coverage.covered,
|
|
711
|
+
missing: coverage.missing,
|
|
712
|
+
lspLowConfidence: lspEvidence.lowConfidence,
|
|
713
|
+
lspMissing: lspEvidence.missing,
|
|
714
|
+
});
|
|
715
|
+
if (coverage.missing.length > 0) {
|
|
716
|
+
return [
|
|
717
|
+
this.withReviewLspEvidence(buildReviewCoverageMessage(coverage.missing), lspEvidence),
|
|
718
|
+
];
|
|
719
|
+
}
|
|
720
|
+
this.idealReviewPhase = "complete";
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
if (this.idealReviewPhase === "complete")
|
|
687
724
|
return null;
|
|
688
|
-
if (this.
|
|
725
|
+
if (!this.settingsManager.get("idealReviewEnabled"))
|
|
689
726
|
return null;
|
|
690
727
|
const decision = evaluateIdealReview(this.hookStats);
|
|
691
728
|
// Test drift fires the review even on a small change the score would skip:
|
|
@@ -693,9 +730,50 @@ export class AgentSession {
|
|
|
693
730
|
const driftedFiles = detectTestDrift(this.hookFileEditCounts.keys(), this.cwd).slice(0, 5);
|
|
694
731
|
if (!decision.shouldReview && driftedFiles.length === 0)
|
|
695
732
|
return null;
|
|
696
|
-
this.
|
|
697
|
-
this.
|
|
698
|
-
|
|
733
|
+
this.reviewCoverage.start(this.hookFileEditCounts.keys());
|
|
734
|
+
this.idealReviewPhase = "reviewing";
|
|
735
|
+
const coverage = this.reviewCoverage.evidence();
|
|
736
|
+
const lspEvidence = this.reviewLspEvidence(coverage.expected);
|
|
737
|
+
this.eventBus.emit("hook", {
|
|
738
|
+
kind: "ideal",
|
|
739
|
+
coverageExpected: coverage.expected,
|
|
740
|
+
coverageMissing: coverage.missing,
|
|
741
|
+
});
|
|
742
|
+
log("INFO", "ideal", "Injecting ideal review before final response", {
|
|
743
|
+
coverageExpected: coverage.expected,
|
|
744
|
+
coverageMissing: coverage.missing,
|
|
745
|
+
lspLowConfidence: lspEvidence.lowConfidence,
|
|
746
|
+
lspMissing: lspEvidence.missing,
|
|
747
|
+
});
|
|
748
|
+
return [
|
|
749
|
+
this.withReviewLspEvidence(buildIdealReviewMessage(decision.reasons, driftedFiles), lspEvidence),
|
|
750
|
+
];
|
|
751
|
+
}
|
|
752
|
+
reviewLspEvidence(files) {
|
|
753
|
+
const lowConfidence = [];
|
|
754
|
+
const missing = [];
|
|
755
|
+
for (const filePath of files) {
|
|
756
|
+
const outcome = this.lspManager?.getLatestOutcome(filePath);
|
|
757
|
+
if (outcome?.kind === "low_confidence")
|
|
758
|
+
lowConfidence.push(filePath);
|
|
759
|
+
else if (outcome?.kind !== "clean" && outcome?.kind !== "diagnostics")
|
|
760
|
+
missing.push(filePath);
|
|
761
|
+
}
|
|
762
|
+
return { lowConfidence, missing };
|
|
763
|
+
}
|
|
764
|
+
withReviewLspEvidence(message, evidence) {
|
|
765
|
+
if (evidence.lowConfidence.length === 0 && evidence.missing.length === 0)
|
|
766
|
+
return message;
|
|
767
|
+
const notes = [
|
|
768
|
+
...(evidence.lowConfidence.length > 0
|
|
769
|
+
? [`Diagnostics are low confidence while indexing: ${evidence.lowConfidence.join(", ")}.`]
|
|
770
|
+
: []),
|
|
771
|
+
...(evidence.missing.length > 0
|
|
772
|
+
? [`Diagnostics evidence is unavailable or missing: ${evidence.missing.join(", ")}.`]
|
|
773
|
+
: []),
|
|
774
|
+
"Do not describe those files as compiler-clean without other evidence.",
|
|
775
|
+
];
|
|
776
|
+
return { role: "user", content: `${String(message.content)}\n\n${notes.join(" ")}` };
|
|
699
777
|
}
|
|
700
778
|
/** Auto-compact if needed, run agent loop with auth retry, and persist messages. */
|
|
701
779
|
async runLoop() {
|
|
@@ -779,9 +857,9 @@ export class AgentSession {
|
|
|
779
857
|
supportsImages: modelInfo?.supportsImages,
|
|
780
858
|
supportsVideo: modelInfo?.supportsVideo,
|
|
781
859
|
userAgent,
|
|
782
|
-
//
|
|
783
|
-
//
|
|
784
|
-
maxToolResultChars:
|
|
860
|
+
// Codex caps each tool output at 10K tokens. Other transports retain the
|
|
861
|
+
// generic 30%-of-context allowance used before this provider policy.
|
|
862
|
+
maxToolResultChars: resolveSessionToolResultCharLimit(this.model, this.provider, accountId),
|
|
785
863
|
// Self-correction hooks (same as the TUI): loop-break + re-grounding are
|
|
786
864
|
// polled mid-loop; the ideal review is polled when the agent would stop.
|
|
787
865
|
getSteeringMessages: () => this.getHookSteeringMessages(),
|
|
@@ -807,7 +885,7 @@ export class AgentSession {
|
|
|
807
885
|
},
|
|
808
886
|
});
|
|
809
887
|
for await (const event of generator) {
|
|
810
|
-
this.trackHookEvent(event);
|
|
888
|
+
await this.trackHookEvent(event);
|
|
811
889
|
this.eventBus.forwardAgentEvent(event);
|
|
812
890
|
}
|
|
813
891
|
};
|
|
@@ -967,9 +1045,14 @@ export class AgentSession {
|
|
|
967
1045
|
else {
|
|
968
1046
|
// Persist compacted messages to a new session file so `ggcoder continue`
|
|
969
1047
|
// picks up the compacted state instead of the full original history.
|
|
970
|
-
const session = await this.sessionManager.create(this.cwd, this.provider, this.model
|
|
1048
|
+
const session = await this.sessionManager.create(this.cwd, this.provider, this.model, {
|
|
1049
|
+
conversationId: this.conversationId || undefined,
|
|
1050
|
+
preview: this.sessionPreview || undefined,
|
|
1051
|
+
});
|
|
971
1052
|
this.sessionId = session.id;
|
|
1053
|
+
this.conversationId = session.header.conversationId ?? session.id;
|
|
972
1054
|
this.sessionPath = session.path;
|
|
1055
|
+
await this.subAgentManager?.rebindParentSession(this.sessionId);
|
|
973
1056
|
// Write compacted messages (skip system — it's rebuilt on load)
|
|
974
1057
|
for (const msg of this.messages) {
|
|
975
1058
|
if (msg.role === "system")
|
|
@@ -977,7 +1060,8 @@ export class AgentSession {
|
|
|
977
1060
|
await this.persistMessage(msg);
|
|
978
1061
|
}
|
|
979
1062
|
this.lastPersistedIndex = this.messages.length;
|
|
980
|
-
// Carry
|
|
1063
|
+
// Carry evidence records into the new file so they survive compaction.
|
|
1064
|
+
await this.rePersistTurnMetrics();
|
|
981
1065
|
await this.rePersistKenTurns();
|
|
982
1066
|
await this.rePersistAutopilotMarkers();
|
|
983
1067
|
await this.rePersistAppMarkers();
|
|
@@ -993,7 +1077,13 @@ export class AgentSession {
|
|
|
993
1077
|
newCount: result.result.newCount,
|
|
994
1078
|
});
|
|
995
1079
|
}
|
|
996
|
-
async newSession() {
|
|
1080
|
+
async newSession(preserveConversation = false) {
|
|
1081
|
+
// Approved-plan execution is a clean checkpoint of the same conversation;
|
|
1082
|
+
// explicit new sessions reset the conversation identity.
|
|
1083
|
+
if (!preserveConversation) {
|
|
1084
|
+
this.conversationId = "";
|
|
1085
|
+
this.sessionPreview = "";
|
|
1086
|
+
}
|
|
997
1087
|
// A fresh session drops any in-flight plan state so its prompt is clean.
|
|
998
1088
|
this.planModeRef.current = false;
|
|
999
1089
|
this.approvedPlanPath = undefined;
|
|
@@ -1004,6 +1094,7 @@ export class AgentSession {
|
|
|
1004
1094
|
this.kenTurns = [];
|
|
1005
1095
|
this.autopilotMarkers = [];
|
|
1006
1096
|
this.appMarkers = [];
|
|
1097
|
+
this.turnMetrics = [];
|
|
1007
1098
|
const basePrompt = this.customSystemPrompt ??
|
|
1008
1099
|
(await buildSystemPrompt(this.cwd, this.skills, false, undefined, this.tools.map((tool) => tool.name), undefined, this.provider));
|
|
1009
1100
|
this.baseSystemPrompt = basePrompt;
|
|
@@ -1018,16 +1109,21 @@ export class AgentSession {
|
|
|
1018
1109
|
// polluted the project's session list.
|
|
1019
1110
|
if (this.opts.transient) {
|
|
1020
1111
|
this.sessionId = "";
|
|
1112
|
+
this.conversationId = "";
|
|
1113
|
+
this.sessionPreview = "";
|
|
1021
1114
|
this.sessionPath = "";
|
|
1022
1115
|
this.lastPersistedIndex = this.messages.length;
|
|
1023
1116
|
}
|
|
1024
1117
|
else {
|
|
1025
1118
|
await this.createNewSession();
|
|
1119
|
+
await this.subAgentManager?.resetParentSession(this.sessionId);
|
|
1026
1120
|
}
|
|
1027
1121
|
this.eventBus.emit("session_start", { sessionId: this.sessionId });
|
|
1028
1122
|
}
|
|
1029
1123
|
async loadSession(sessionPath) {
|
|
1030
1124
|
await this.loadExistingSession(sessionPath);
|
|
1125
|
+
if (this.sessionId)
|
|
1126
|
+
await this.subAgentManager?.hydrate(this.sessionId);
|
|
1031
1127
|
this.eventBus.emit("session_start", { sessionId: this.sessionId });
|
|
1032
1128
|
}
|
|
1033
1129
|
/**
|
|
@@ -1197,6 +1293,39 @@ export class AgentSession {
|
|
|
1197
1293
|
getMessages() {
|
|
1198
1294
|
return this.messages;
|
|
1199
1295
|
}
|
|
1296
|
+
getTurnMetrics() {
|
|
1297
|
+
return this.turnMetrics.map((metric) => ({
|
|
1298
|
+
...metric,
|
|
1299
|
+
usage: { ...metric.usage },
|
|
1300
|
+
timing: { ...metric.timing },
|
|
1301
|
+
cost: { ...metric.cost },
|
|
1302
|
+
}));
|
|
1303
|
+
}
|
|
1304
|
+
async persistTurnMetric(event) {
|
|
1305
|
+
const payload = {
|
|
1306
|
+
version: 1,
|
|
1307
|
+
turn: event.turn,
|
|
1308
|
+
provider: this.provider,
|
|
1309
|
+
model: this.model,
|
|
1310
|
+
stopReason: event.stopReason,
|
|
1311
|
+
usage: { ...event.usage },
|
|
1312
|
+
timing: { ...event.timing },
|
|
1313
|
+
cost: {
|
|
1314
|
+
status: "unavailable",
|
|
1315
|
+
reason: "No authoritative effective-dated provider pricing is available",
|
|
1316
|
+
},
|
|
1317
|
+
};
|
|
1318
|
+
this.turnMetrics.push(payload);
|
|
1319
|
+
if (this.sessionPath)
|
|
1320
|
+
await this.sessionManager.appendTurnMetric(this.sessionPath, payload);
|
|
1321
|
+
}
|
|
1322
|
+
async rePersistTurnMetrics() {
|
|
1323
|
+
if (!this.sessionPath)
|
|
1324
|
+
return;
|
|
1325
|
+
for (const metric of this.turnMetrics) {
|
|
1326
|
+
await this.sessionManager.appendTurnMetric(this.sessionPath, metric);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1200
1329
|
/** Ken Kai (mentor) turns recorded against this session, in record order. Used
|
|
1201
1330
|
* by the host to interleave Ken's advisory exchanges back into the transcript
|
|
1202
1331
|
* on resume. Never part of the LLM message history. */
|
|
@@ -1351,46 +1480,12 @@ export class AgentSession {
|
|
|
1351
1480
|
await this.sessionManager.appendEntry(this.sessionPath, entry);
|
|
1352
1481
|
}
|
|
1353
1482
|
}
|
|
1354
|
-
/**
|
|
1355
|
-
* Generate a short LLM session title from the conversation so far (first user
|
|
1356
|
-
* message + first assistant reply). Best-effort; returns null on failure or
|
|
1357
|
-
* when there's no user message yet. Uses the cheapest model for the provider.
|
|
1358
|
-
*/
|
|
1359
|
-
async generateTitle() {
|
|
1360
|
-
const extractText = (content) => typeof content === "string"
|
|
1361
|
-
? content
|
|
1362
|
-
: content
|
|
1363
|
-
.map((c) => c.type === "text" && "text" in c && typeof c.text === "string" ? c.text : "")
|
|
1364
|
-
.join(" ");
|
|
1365
|
-
const userMsg = this.messages.find((m) => m.role === "user");
|
|
1366
|
-
const assistantMsg = this.messages.find((m) => m.role === "assistant");
|
|
1367
|
-
const userText = userMsg ? extractText(userMsg.content) : "";
|
|
1368
|
-
if (!userText.trim())
|
|
1369
|
-
return null;
|
|
1370
|
-
try {
|
|
1371
|
-
const creds = await this.authStorage.resolveCredentials(this.provider, {
|
|
1372
|
-
storageKeys: this.currentAuthStorageKeys(),
|
|
1373
|
-
});
|
|
1374
|
-
const title = await generateSessionTitle({
|
|
1375
|
-
provider: this.provider,
|
|
1376
|
-
userMessage: userText,
|
|
1377
|
-
assistantPreview: assistantMsg ? extractText(assistantMsg.content).slice(0, 200) : "",
|
|
1378
|
-
apiKey: creds.accessToken,
|
|
1379
|
-
baseUrl: this.baseUrl ?? creds.baseUrl,
|
|
1380
|
-
accountId: creds.accountId,
|
|
1381
|
-
});
|
|
1382
|
-
return title || null;
|
|
1383
|
-
}
|
|
1384
|
-
catch {
|
|
1385
|
-
return null;
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
1483
|
/**
|
|
1389
1484
|
* Rewrite a draft prompt into a tighter, terminology-correct version using
|
|
1390
1485
|
* the ACTIVE provider/model. A stateless one-off LLM call (no agent loop, no
|
|
1391
1486
|
* tools, no session mutation) — safe to run even mid-run. Returns the plain
|
|
1392
1487
|
* enhanced text plus typed segments marking each corrected term. Errors throw
|
|
1393
|
-
* so the caller can surface them
|
|
1488
|
+
* so the caller can surface them.
|
|
1394
1489
|
*/
|
|
1395
1490
|
async enhancePrompt(text) {
|
|
1396
1491
|
if (!text.trim())
|
|
@@ -1488,8 +1583,12 @@ export class AgentSession {
|
|
|
1488
1583
|
}
|
|
1489
1584
|
// ── Private ────────────────────────────────────────────
|
|
1490
1585
|
async createNewSession() {
|
|
1491
|
-
const session = await this.sessionManager.create(this.cwd, this.provider, this.model
|
|
1586
|
+
const session = await this.sessionManager.create(this.cwd, this.provider, this.model, {
|
|
1587
|
+
conversationId: this.conversationId || undefined,
|
|
1588
|
+
preview: this.sessionPreview || undefined,
|
|
1589
|
+
});
|
|
1492
1590
|
this.sessionId = session.id;
|
|
1591
|
+
this.conversationId = session.header.conversationId ?? session.id;
|
|
1493
1592
|
this.sessionPath = session.path;
|
|
1494
1593
|
this.lastPersistedIndex = this.messages.length;
|
|
1495
1594
|
}
|
|
@@ -1497,6 +1596,15 @@ export class AgentSession {
|
|
|
1497
1596
|
const loaded = await this.sessionManager.load(sessionPath);
|
|
1498
1597
|
// Use the leaf from the header to walk the correct branch
|
|
1499
1598
|
const loadedMessages = this.sessionManager.getMessages(loaded.entries, loaded.header.leafId);
|
|
1599
|
+
this.conversationId = loaded.header.conversationId ?? loaded.header.id;
|
|
1600
|
+
const legacyLabel = [...loaded.entries]
|
|
1601
|
+
.reverse()
|
|
1602
|
+
.find((entry) => entry.type === "label")
|
|
1603
|
+
?.label.replace(/\s+/g, " ")
|
|
1604
|
+
.trim()
|
|
1605
|
+
.slice(0, 80);
|
|
1606
|
+
this.sessionPreview =
|
|
1607
|
+
legacyLabel || loaded.header.preview || findUserSessionPrompt(loadedMessages);
|
|
1500
1608
|
// Restore Ken's advisory turns (custom entries, not on the message branch) so
|
|
1501
1609
|
// they reappear in the transcript and survive into the continuation file.
|
|
1502
1610
|
this.kenTurns = this.sessionManager.getKenTurns(loaded.entries);
|
|
@@ -1504,6 +1612,7 @@ export class AgentSession {
|
|
|
1504
1612
|
this.autopilotMarkers = this.sessionManager.getAutopilotMarkers(loaded.entries);
|
|
1505
1613
|
// Restore app transcript markers (plan banner / task header / errors / hints).
|
|
1506
1614
|
this.appMarkers = this.sessionManager.getAppMarkers(loaded.entries);
|
|
1615
|
+
this.turnMetrics = this.sessionManager.getTurnMetrics(loaded.entries);
|
|
1507
1616
|
// Track the current leaf for subsequent entries
|
|
1508
1617
|
this.currentLeafId = loaded.header.leafId;
|
|
1509
1618
|
// Rebuild messages: keep system, add loaded
|
|
@@ -1523,6 +1632,7 @@ export class AgentSession {
|
|
|
1523
1632
|
accountId: creds.accountId,
|
|
1524
1633
|
});
|
|
1525
1634
|
if (shouldCompact(this.messages, contextWindow, 0.8, undefined, getCompactionReserveTokens(this.maxTokens))) {
|
|
1635
|
+
await this.subAgentManager?.hydrate(loaded.header.id);
|
|
1526
1636
|
log("INFO", "session", `Restored session exceeds context — auto-compacting`);
|
|
1527
1637
|
const compacted = await compact(this.messages, {
|
|
1528
1638
|
provider: this.provider,
|
|
@@ -1543,9 +1653,14 @@ export class AgentSession {
|
|
|
1543
1653
|
// what's in memory — fork a fresh session file for the compacted state
|
|
1544
1654
|
// (mirrors compact()'s own persistence) so `ggcoder continue` picks up
|
|
1545
1655
|
// the summary instead of the full original transcript.
|
|
1546
|
-
const session = await this.sessionManager.create(this.cwd, this.provider, this.model
|
|
1656
|
+
const session = await this.sessionManager.create(this.cwd, this.provider, this.model, {
|
|
1657
|
+
conversationId: this.conversationId || undefined,
|
|
1658
|
+
preview: this.sessionPreview || undefined,
|
|
1659
|
+
});
|
|
1547
1660
|
this.sessionId = session.id;
|
|
1661
|
+
this.conversationId = session.header.conversationId ?? session.id;
|
|
1548
1662
|
this.sessionPath = session.path;
|
|
1663
|
+
await this.subAgentManager?.rebindParentSession(this.sessionId);
|
|
1549
1664
|
this.currentLeafId = null;
|
|
1550
1665
|
// Re-persist (compacted) messages — skip system, it's rebuilt on load
|
|
1551
1666
|
for (const msg of this.messages) {
|
|
@@ -1554,7 +1669,8 @@ export class AgentSession {
|
|
|
1554
1669
|
await this.persistMessage(msg);
|
|
1555
1670
|
}
|
|
1556
1671
|
this.lastPersistedIndex = this.messages.length;
|
|
1557
|
-
// Carry
|
|
1672
|
+
// Carry restored evidence into the continuation file.
|
|
1673
|
+
await this.rePersistTurnMetrics();
|
|
1558
1674
|
await this.rePersistKenTurns();
|
|
1559
1675
|
await this.rePersistAutopilotMarkers();
|
|
1560
1676
|
await this.rePersistAppMarkers();
|
|
@@ -1578,6 +1694,9 @@ export class AgentSession {
|
|
|
1578
1694
|
return this.messages;
|
|
1579
1695
|
}
|
|
1580
1696
|
async persistMessage(message) {
|
|
1697
|
+
if (!this.sessionPreview && message.role === "user") {
|
|
1698
|
+
this.sessionPreview = getUserSessionPrompt(message.content) ?? "";
|
|
1699
|
+
}
|
|
1581
1700
|
// Transient sessions (subagent spawns) have no session file — skip.
|
|
1582
1701
|
if (!this.sessionPath)
|
|
1583
1702
|
return;
|