@kenkaiiii/ggcoder 5.29.0 → 5.30.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/dist/app-sidecar-auth-broadcast.test.js +14 -3
- package/dist/app-sidecar-auth-broadcast.test.js.map +1 -1
- package/dist/app-sidecar.js +27 -20
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli/command-routing.d.ts +1 -1
- package/dist/cli/command-routing.d.ts.map +1 -1
- package/dist/cli/command-routing.js +1 -0
- package/dist/cli/command-routing.js.map +1 -1
- package/dist/cli/command-routing.test.js +1 -0
- package/dist/cli/command-routing.test.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +167 -47
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -1
- package/dist/config.test.js +10 -1
- package/dist/config.test.js.map +1 -1
- package/dist/core/agent-session-compaction.test.js +240 -2
- package/dist/core/agent-session-compaction.test.js.map +1 -1
- package/dist/core/agent-session-crash-durability.test.js +9 -2
- package/dist/core/agent-session-crash-durability.test.js.map +1 -1
- package/dist/core/agent-session-process-gate.test.js +5 -1
- package/dist/core/agent-session-process-gate.test.js.map +1 -1
- package/dist/core/agent-session-review-coverage.test.js +80 -23
- package/dist/core/agent-session-review-coverage.test.js.map +1 -1
- package/dist/core/agent-session.d.ts +12 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +294 -134
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/active-context.test.js +21 -0
- package/dist/core/compaction/active-context.test.js.map +1 -1
- package/dist/core/compaction/compactor.d.ts +24 -6
- package/dist/core/compaction/compactor.d.ts.map +1 -1
- package/dist/core/compaction/compactor.js +246 -98
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/compaction/compactor.test.js +107 -8
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/compaction/policy.d.ts +17 -0
- package/dist/core/compaction/policy.d.ts.map +1 -0
- package/dist/core/compaction/policy.js +22 -0
- package/dist/core/compaction/policy.js.map +1 -0
- package/dist/core/compaction/policy.test.d.ts +2 -0
- package/dist/core/compaction/policy.test.d.ts.map +1 -0
- package/dist/core/compaction/policy.test.js +35 -0
- package/dist/core/compaction/policy.test.js.map +1 -0
- package/dist/core/foreign-session-import.d.ts.map +1 -1
- package/dist/core/foreign-session-import.js +5 -1
- package/dist/core/foreign-session-import.js.map +1 -1
- package/dist/core/ideal-review.d.ts +20 -1
- package/dist/core/ideal-review.d.ts.map +1 -1
- package/dist/core/ideal-review.js +37 -2
- package/dist/core/ideal-review.js.map +1 -1
- package/dist/core/ideal-review.test.js +99 -3
- package/dist/core/ideal-review.test.js.map +1 -1
- package/dist/core/loop-breaker.d.ts.map +1 -1
- package/dist/core/loop-breaker.js +1 -0
- package/dist/core/loop-breaker.js.map +1 -1
- package/dist/core/process-gate.d.ts.map +1 -1
- package/dist/core/process-gate.js +9 -1
- package/dist/core/process-gate.js.map +1 -1
- package/dist/core/process-manager-notifications.test.js +4 -2
- package/dist/core/process-manager-notifications.test.js.map +1 -1
- package/dist/core/regrounding.d.ts.map +1 -1
- package/dist/core/regrounding.js +1 -0
- package/dist/core/regrounding.js.map +1 -1
- package/dist/core/session-compaction.d.ts +5 -0
- package/dist/core/session-compaction.d.ts.map +1 -1
- package/dist/core/session-compaction.js +10 -1
- package/dist/core/session-compaction.js.map +1 -1
- package/dist/core/session-compaction.test.js +16 -1
- package/dist/core/session-compaction.test.js.map +1 -1
- package/dist/core/session-export.d.ts.map +1 -1
- package/dist/core/session-export.js +11 -8
- package/dist/core/session-export.js.map +1 -1
- package/dist/core/session-history.d.ts +7 -2
- package/dist/core/session-history.d.ts.map +1 -1
- package/dist/core/session-history.js +52 -9
- package/dist/core/session-history.js.map +1 -1
- package/dist/core/session-history.test.js +26 -1
- package/dist/core/session-history.test.js.map +1 -1
- package/dist/core/session-manager.d.ts +106 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +316 -15
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-manager.test.js +109 -1
- package/dist/core/session-manager.test.js.map +1 -1
- package/dist/core/session-notification-visibility.test.js +34 -0
- package/dist/core/session-notification-visibility.test.js.map +1 -1
- package/dist/core/session-preview.d.ts +2 -2
- package/dist/core/session-preview.d.ts.map +1 -1
- package/dist/core/session-preview.js +8 -5
- package/dist/core/session-preview.js.map +1 -1
- package/dist/core/session-restore-display.test.js +25 -0
- package/dist/core/session-restore-display.test.js.map +1 -1
- package/dist/core/session-storage.d.ts.map +1 -1
- package/dist/core/session-storage.js +39 -3
- package/dist/core/session-storage.js.map +1 -1
- package/dist/core/subagent-manager.d.ts.map +1 -1
- package/dist/core/subagent-manager.js +9 -1
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/subagent-manager.test.js +4 -3
- package/dist/core/subagent-manager.test.js.map +1 -1
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +17 -3
- package/dist/interactive.js.map +1 -1
- package/dist/modes/acp-mode.d.ts +82 -0
- package/dist/modes/acp-mode.d.ts.map +1 -0
- package/dist/modes/acp-mode.js +802 -0
- package/dist/modes/acp-mode.js.map +1 -0
- package/dist/modes/acp-mode.test.d.ts +2 -0
- package/dist/modes/acp-mode.test.d.ts.map +1 -0
- package/dist/modes/acp-mode.test.js +611 -0
- package/dist/modes/acp-mode.test.js.map +1 -0
- package/dist/modes/index.d.ts +1 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -0
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/json-mode.d.ts +28 -0
- package/dist/modes/json-mode.d.ts.map +1 -1
- package/dist/modes/json-mode.js +32 -0
- package/dist/modes/json-mode.js.map +1 -1
- package/dist/modes/json-mode.test.d.ts +2 -0
- package/dist/modes/json-mode.test.d.ts.map +1 -0
- package/dist/modes/json-mode.test.js +84 -0
- package/dist/modes/json-mode.test.js.map +1 -0
- package/dist/session.d.ts +24 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +32 -0
- package/dist/session.js.map +1 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +27 -6
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/subagent.test.js +53 -3
- package/dist/tools/subagent.test.js.map +1 -1
- package/dist/tools/web-fetch.d.ts +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +18 -4
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/package.json +4 -4
|
@@ -11,8 +11,9 @@ import { getClaudeCliUserAgent } from "./claude-code-version.js";
|
|
|
11
11
|
import { kimiCodingHeaders, isKimiCodingEndpoint } from "./oauth/kimi.js";
|
|
12
12
|
import { SessionManager, KEN_TURN_CUSTOM_KIND, AUTOPILOT_MARKER_CUSTOM_KIND, APP_MARKER_CUSTOM_KIND, } from "./session-manager.js";
|
|
13
13
|
import { ExtensionLoader } from "./extensions/loader.js";
|
|
14
|
-
import { shouldCompact, compact } from "./compaction/compactor.js";
|
|
14
|
+
import { shouldCompact, compact, } from "./compaction/compactor.js";
|
|
15
15
|
import { remapAnchorForCompaction, stripRecordedPosition } from "./session-history.js";
|
|
16
|
+
import { sourceFingerprint as computeSourceFingerprint } from "./session-compaction.js";
|
|
16
17
|
import { getAuthStorageKeys, getContextWindow, getModel, getToolResultCharLimit, MODELS, } from "./model-registry.js";
|
|
17
18
|
import { discoverSkills } from "./skills.js";
|
|
18
19
|
import { ensureAppDirs } from "../config.js";
|
|
@@ -30,11 +31,12 @@ import { createToolSearchTool } from "../tools/tool-search.js";
|
|
|
30
31
|
import { log } from "./logger.js";
|
|
31
32
|
import { setEstimatorModel, calibrateEstimatorFromUsage } from "./compaction/token-estimator.js";
|
|
32
33
|
import { calculateActiveContextTokens } from "./compaction/active-context.js";
|
|
34
|
+
import { resolveCompactionPolicy } from "./compaction/policy.js";
|
|
33
35
|
import { pruneStaleToolResults } from "./compaction/tool-result-pruner.js";
|
|
34
36
|
import { discoverAgents } from "./agents.js";
|
|
35
37
|
import { enhancePrompt } from "../utils/prompt-enhancer.js";
|
|
36
38
|
import { detectProjectStack } from "./language-detector.js";
|
|
37
|
-
import { evaluateIdealReview, buildIdealReviewMessage, buildReviewCoverageMessage, withReviewCoverageRequirements, detectTestDrift, ReviewCoverageTracker, } from "./ideal-review.js";
|
|
39
|
+
import { evaluateIdealReview, buildIdealReviewMessage, buildReviewCoverageEscalationMessage, buildReviewCoverageMessage, MAX_REVIEW_COVERAGE_INJECTIONS, withReviewCoverageRequirements, detectTestDrift, ReviewCoverageTracker, } from "./ideal-review.js";
|
|
38
40
|
import { evaluateLoopBreak, buildLoopBreakMessage, CycleDetector, ToolCallProgressTracker, detectTextRepetition, } from "./loop-breaker.js";
|
|
39
41
|
import { buildRegroundingMessage } from "./regrounding.js";
|
|
40
42
|
import { wrapSteeringText, buildNotificationSteeringText, STEERING_PREFIX } from "./steering.js";
|
|
@@ -137,6 +139,8 @@ export class AgentSession {
|
|
|
137
139
|
/** Runtime-only suppression while Ken owns verification in autopilot mode. */
|
|
138
140
|
idealReviewSuppressed = false;
|
|
139
141
|
reviewCoverage;
|
|
142
|
+
/** Coverage follow-ups spent this run, capped by MAX_REVIEW_COVERAGE_INJECTIONS. */
|
|
143
|
+
reviewCoverageInjected = 0;
|
|
140
144
|
/** 0 = none; 1 = first nudge sent; 2 = final stop-and-report injected. */
|
|
141
145
|
loopBreakInjected = 0;
|
|
142
146
|
regroundingInjected = false;
|
|
@@ -147,6 +151,8 @@ export class AgentSession {
|
|
|
147
151
|
compactionOccurred = false;
|
|
148
152
|
lastCompactionCompacted = false;
|
|
149
153
|
compactionRetryAfter = 0;
|
|
154
|
+
/** A restored oversized checkpoint must be canonicalized before its first prompt is persisted. */
|
|
155
|
+
deferredCompactionPending = false;
|
|
150
156
|
/** Latest provider count, anchored to the assistant response it measured. */
|
|
151
157
|
providerContext = null;
|
|
152
158
|
originalRequest = "";
|
|
@@ -204,6 +210,7 @@ export class AgentSession {
|
|
|
204
210
|
/** Extra workspace roots added with `/add-dir` (resolved, de-duplicated). */
|
|
205
211
|
additionalRoots = [];
|
|
206
212
|
sessionId = "";
|
|
213
|
+
checkpointGeneration = 0;
|
|
207
214
|
/** Stable identity shared by compaction and approved-plan checkpoint files. */
|
|
208
215
|
conversationId = "";
|
|
209
216
|
/** Original user-authored prompt, retained when internal messages replace history. */
|
|
@@ -751,12 +758,16 @@ export class AgentSession {
|
|
|
751
758
|
/**
|
|
752
759
|
* Process user input. Handles slash commands or runs agent loop.
|
|
753
760
|
*/
|
|
754
|
-
async prompt(content
|
|
761
|
+
async prompt(content, provenance = {
|
|
762
|
+
source: "human",
|
|
763
|
+
kind: "prompt",
|
|
764
|
+
visibility: "transcript",
|
|
765
|
+
}) {
|
|
766
|
+
await this.adoptDeferredCheckpointBeforePrompt();
|
|
755
767
|
const slash = await this.resolveSlashInput(content);
|
|
756
768
|
if (slash?.kind === "template") {
|
|
757
|
-
//
|
|
758
|
-
|
|
759
|
-
const userMessage = { role: "user", content: slash.fullPrompt };
|
|
769
|
+
// Prompt templates remain human-originated: the user invoked the command.
|
|
770
|
+
const userMessage = { role: "user", content: slash.fullPrompt, provenance };
|
|
760
771
|
this.messages.push(userMessage);
|
|
761
772
|
await this.persistMessage(userMessage);
|
|
762
773
|
this.lastPersistedIndex = this.messages.length;
|
|
@@ -772,7 +783,7 @@ export class AgentSession {
|
|
|
772
783
|
return;
|
|
773
784
|
}
|
|
774
785
|
// Push user message
|
|
775
|
-
const userMessage = { role: "user", content };
|
|
786
|
+
const userMessage = { role: "user", content, provenance };
|
|
776
787
|
this.messages.push(userMessage);
|
|
777
788
|
await this.persistMessage(userMessage);
|
|
778
789
|
this.lastPersistedIndex = this.messages.length;
|
|
@@ -786,10 +797,15 @@ export class AgentSession {
|
|
|
786
797
|
* attachments are always a direct conversational turn.
|
|
787
798
|
*/
|
|
788
799
|
async promptWithAttachments(text, attachments) {
|
|
800
|
+
await this.adoptDeferredCheckpointBeforePrompt();
|
|
789
801
|
const parts = this.buildAttachmentParts(text, attachments);
|
|
790
802
|
if (parts.length === 0)
|
|
791
803
|
return;
|
|
792
|
-
const userMessage = {
|
|
804
|
+
const userMessage = {
|
|
805
|
+
role: "user",
|
|
806
|
+
content: parts,
|
|
807
|
+
provenance: { source: "human", kind: "prompt", visibility: "transcript" },
|
|
808
|
+
};
|
|
793
809
|
this.messages.push(userMessage);
|
|
794
810
|
await this.persistMessage(userMessage);
|
|
795
811
|
this.lastPersistedIndex = this.messages.length;
|
|
@@ -878,6 +894,7 @@ export class AgentSession {
|
|
|
878
894
|
this.hookFileEditCounts.clear();
|
|
879
895
|
this.hookToolCalls.clear();
|
|
880
896
|
this.reviewCoverage.reset();
|
|
897
|
+
this.reviewCoverageInjected = 0;
|
|
881
898
|
this.idealReviewPhase = "idle";
|
|
882
899
|
this.loopBreakInjected = 0;
|
|
883
900
|
this.regroundingInjected = false;
|
|
@@ -990,6 +1007,7 @@ export class AgentSession {
|
|
|
990
1007
|
? {
|
|
991
1008
|
role: "user",
|
|
992
1009
|
content: buildNotificationSteeringText(notified.map((entry) => entry.text)),
|
|
1010
|
+
provenance: { source: "runtime", kind: "notification", visibility: "hidden" },
|
|
993
1011
|
}
|
|
994
1012
|
: null;
|
|
995
1013
|
if (notificationMessage) {
|
|
@@ -1013,8 +1031,13 @@ export class AgentSession {
|
|
|
1013
1031
|
// (not merged): each persists as its own user message, so a resumed
|
|
1014
1032
|
// session shows the same number of bubbles the live run did.
|
|
1015
1033
|
const steeringMessages = queued.map((m) => {
|
|
1034
|
+
const provenance = {
|
|
1035
|
+
source: "human",
|
|
1036
|
+
kind: "steering",
|
|
1037
|
+
visibility: "transcript",
|
|
1038
|
+
};
|
|
1016
1039
|
if (m.attachments.length === 0) {
|
|
1017
|
-
return { role: "user", content: wrapSteeringText(m.text) };
|
|
1040
|
+
return { role: "user", content: wrapSteeringText(m.text), provenance };
|
|
1018
1041
|
}
|
|
1019
1042
|
// Queued attachments ride the same native-block path as a non-queued
|
|
1020
1043
|
// attachment prompt, prefixed with the steering framing.
|
|
@@ -1022,7 +1045,7 @@ export class AgentSession {
|
|
|
1022
1045
|
{ type: "text", text: STEERING_PREFIX },
|
|
1023
1046
|
...this.buildAttachmentParts(m.text, m.attachments),
|
|
1024
1047
|
];
|
|
1025
|
-
return { role: "user", content: parts };
|
|
1048
|
+
return { role: "user", content: parts, provenance };
|
|
1026
1049
|
});
|
|
1027
1050
|
return notificationMessage ? [...steeringMessages, notificationMessage] : steeringMessages;
|
|
1028
1051
|
}
|
|
@@ -1133,9 +1156,20 @@ export class AgentSession {
|
|
|
1133
1156
|
lspMissing: lspEvidence.missing,
|
|
1134
1157
|
});
|
|
1135
1158
|
if (coverage.missing.length > 0) {
|
|
1136
|
-
|
|
1137
|
-
this.
|
|
1138
|
-
|
|
1159
|
+
if (this.reviewCoverageInjected < MAX_REVIEW_COVERAGE_INJECTIONS) {
|
|
1160
|
+
this.reviewCoverageInjected += 1;
|
|
1161
|
+
return [
|
|
1162
|
+
this.withReviewLspEvidence(buildReviewCoverageMessage(coverage.missing), lspEvidence),
|
|
1163
|
+
];
|
|
1164
|
+
}
|
|
1165
|
+
// Budget spent: close the gate so the run cannot spin on a file that
|
|
1166
|
+
// never becomes readable, and require the gap be reported to the user.
|
|
1167
|
+
this.idealReviewPhase = "complete";
|
|
1168
|
+
log("INFO", "ideal", "Ideal review coverage escalated after retry budget", {
|
|
1169
|
+
injected: String(this.reviewCoverageInjected),
|
|
1170
|
+
missing: coverage.missing,
|
|
1171
|
+
});
|
|
1172
|
+
return [buildReviewCoverageEscalationMessage(coverage.missing)];
|
|
1139
1173
|
}
|
|
1140
1174
|
this.idealReviewPhase = "complete";
|
|
1141
1175
|
return null;
|
|
@@ -1193,7 +1227,11 @@ export class AgentSession {
|
|
|
1193
1227
|
: []),
|
|
1194
1228
|
"Do not describe those files as compiler-clean without other evidence.",
|
|
1195
1229
|
];
|
|
1196
|
-
return {
|
|
1230
|
+
return {
|
|
1231
|
+
role: "user",
|
|
1232
|
+
provenance: message.provenance,
|
|
1233
|
+
content: `${String(message.content)}\n\n${notes.join(" ")}`,
|
|
1234
|
+
};
|
|
1197
1235
|
}
|
|
1198
1236
|
/** Auto-compact if needed, run agent loop with auth retry, and persist messages. */
|
|
1199
1237
|
async runLoop() {
|
|
@@ -1233,7 +1271,14 @@ export class AgentSession {
|
|
|
1233
1271
|
provider: this.provider,
|
|
1234
1272
|
accountId: creds.accountId,
|
|
1235
1273
|
});
|
|
1236
|
-
const
|
|
1274
|
+
const policy = resolveCompactionPolicy({
|
|
1275
|
+
provider: this.provider,
|
|
1276
|
+
model: this.model,
|
|
1277
|
+
contextWindow,
|
|
1278
|
+
threshold: this.settingsManager.get("compactThreshold"),
|
|
1279
|
+
accountId: creds.accountId,
|
|
1280
|
+
approvedPlanPath: this.approvedPlanPath,
|
|
1281
|
+
});
|
|
1237
1282
|
let activeTokens;
|
|
1238
1283
|
if (this.providerContext) {
|
|
1239
1284
|
const anchorIndex = this.messages.lastIndexOf(this.providerContext.anchor);
|
|
@@ -1247,9 +1292,17 @@ export class AgentSession {
|
|
|
1247
1292
|
this.providerContext = null;
|
|
1248
1293
|
}
|
|
1249
1294
|
}
|
|
1250
|
-
|
|
1295
|
+
log("INFO", "compaction", "Pre-run compaction decision", {
|
|
1296
|
+
provider: this.provider,
|
|
1297
|
+
model: this.model,
|
|
1298
|
+
transport: this.provider === "openai" && creds.accountId ? "codex_oauth" : "public_api",
|
|
1299
|
+
contextWindow: String(contextWindow),
|
|
1300
|
+
activeTokens: activeTokens === undefined ? "estimated" : String(activeTokens),
|
|
1301
|
+
triggerLimit: String(policy.targetTokens),
|
|
1302
|
+
});
|
|
1303
|
+
if (shouldCompact(this.messages, contextWindow, policy.threshold, activeTokens)) {
|
|
1251
1304
|
try {
|
|
1252
|
-
await this.compact(creds);
|
|
1305
|
+
await this.compact(creds, "automatic");
|
|
1253
1306
|
if (this.lastCompactionCompacted) {
|
|
1254
1307
|
// Re-grounding hook keys off this — the context was just summarized.
|
|
1255
1308
|
this.compactionOccurred = true;
|
|
@@ -1359,12 +1412,27 @@ export class AgentSession {
|
|
|
1359
1412
|
provider: this.provider,
|
|
1360
1413
|
accountId,
|
|
1361
1414
|
});
|
|
1362
|
-
const
|
|
1415
|
+
const policy = resolveCompactionPolicy({
|
|
1416
|
+
provider: this.provider,
|
|
1417
|
+
model: this.model,
|
|
1418
|
+
contextWindow,
|
|
1419
|
+
threshold: this.settingsManager.get("compactThreshold"),
|
|
1420
|
+
accountId,
|
|
1421
|
+
approvedPlanPath: this.approvedPlanPath,
|
|
1422
|
+
});
|
|
1363
1423
|
const activeTokens = calculateActiveContextTokens(messages, {
|
|
1364
1424
|
usage,
|
|
1365
1425
|
pendingMessages,
|
|
1366
1426
|
});
|
|
1367
|
-
|
|
1427
|
+
log("INFO", "compaction", "In-flight compaction decision", {
|
|
1428
|
+
provider: this.provider,
|
|
1429
|
+
model: this.model,
|
|
1430
|
+
transport: this.provider === "openai" && accountId ? "codex_oauth" : "public_api",
|
|
1431
|
+
contextWindow: String(contextWindow),
|
|
1432
|
+
activeTokens: String(activeTokens),
|
|
1433
|
+
triggerLimit: String(policy.targetTokens),
|
|
1434
|
+
});
|
|
1435
|
+
if (!shouldCompact(messages, contextWindow, policy.threshold, activeTokens))
|
|
1368
1436
|
return messages;
|
|
1369
1437
|
}
|
|
1370
1438
|
// compact() operates on this.messages, while an earlier transform may
|
|
@@ -1377,7 +1445,7 @@ export class AgentSession {
|
|
|
1377
1445
|
accountId,
|
|
1378
1446
|
projectId,
|
|
1379
1447
|
baseUrl: effectiveBaseUrl,
|
|
1380
|
-
});
|
|
1448
|
+
}, force ? "forced" : "automatic");
|
|
1381
1449
|
}
|
|
1382
1450
|
catch (error) {
|
|
1383
1451
|
this.messages = messages;
|
|
@@ -1626,9 +1694,66 @@ export class AgentSession {
|
|
|
1626
1694
|
this.messages.push({
|
|
1627
1695
|
role: "user",
|
|
1628
1696
|
content: `[Model switched from ${from} to ${to}. Everything above was produced under the previous model; continue the current task from here.]`,
|
|
1697
|
+
provenance: { source: "runtime", kind: "model_switch", visibility: "hidden" },
|
|
1629
1698
|
});
|
|
1630
1699
|
}
|
|
1631
|
-
async
|
|
1700
|
+
async adoptCompactionCheckpoint(loaded) {
|
|
1701
|
+
const systemMessage = this.messages[0];
|
|
1702
|
+
const loadedMessages = this.sessionManager.getMessages(loaded.entries, loaded.header.leafId);
|
|
1703
|
+
this.messages = [systemMessage, ...loadedMessages];
|
|
1704
|
+
this.sessionId = loaded.header.id;
|
|
1705
|
+
this.conversationId = loaded.header.conversationId ?? loaded.header.id;
|
|
1706
|
+
this.checkpointGeneration = loaded.header.generation ?? 0;
|
|
1707
|
+
this.currentLeafId = loaded.header.leafId;
|
|
1708
|
+
this.setSessionPath(loaded.path);
|
|
1709
|
+
this.kenTurns = this.sessionManager.getKenTurns(loaded.entries, loaded.header.leafId);
|
|
1710
|
+
this.autopilotMarkers = this.sessionManager.getAutopilotMarkers(loaded.entries, loaded.header.leafId);
|
|
1711
|
+
this.appMarkers = this.sessionManager.getAppMarkers(loaded.entries, loaded.header.leafId);
|
|
1712
|
+
this.turnMetrics = this.sessionManager.getTurnMetrics(loaded.entries);
|
|
1713
|
+
this.lastPersistedIndex = this.messages.length;
|
|
1714
|
+
this.providerContext = null;
|
|
1715
|
+
await this.subAgentManager?.rebindParentSession(this.sessionId);
|
|
1716
|
+
}
|
|
1717
|
+
/** Canonicalize a deferred restore before a new prompt can fork stale history. */
|
|
1718
|
+
async adoptDeferredCheckpointBeforePrompt() {
|
|
1719
|
+
if (!this.deferredCompactionPending || !this.conversationId)
|
|
1720
|
+
return;
|
|
1721
|
+
this.deferredCompactionPending = false;
|
|
1722
|
+
const canonicalPath = await this.sessionManager.resolveCanonicalSession(this.conversationId, this.cwd);
|
|
1723
|
+
if (!canonicalPath || canonicalPath === this.sessionPath)
|
|
1724
|
+
return;
|
|
1725
|
+
await this.adoptCompactionCheckpoint(await this.sessionManager.load(canonicalPath));
|
|
1726
|
+
}
|
|
1727
|
+
async persistCompactionCheckpoint(sourceFingerprint, result) {
|
|
1728
|
+
const parentSessionId = this.sessionId || undefined;
|
|
1729
|
+
const session = await this.sessionManager.create(this.cwd, this.provider, this.model, {
|
|
1730
|
+
conversationId: this.conversationId || undefined,
|
|
1731
|
+
generation: this.checkpointGeneration + 1,
|
|
1732
|
+
parentSessionId,
|
|
1733
|
+
sourceFingerprint,
|
|
1734
|
+
preview: this.sessionPreview || undefined,
|
|
1735
|
+
});
|
|
1736
|
+
this.sessionId = session.id;
|
|
1737
|
+
this.checkpointGeneration = session.header.generation ?? 0;
|
|
1738
|
+
this.conversationId = session.header.conversationId ?? session.id;
|
|
1739
|
+
this.currentLeafId = null;
|
|
1740
|
+
this.setSessionPath(session.path);
|
|
1741
|
+
await this.subAgentManager?.rebindParentSession(this.sessionId);
|
|
1742
|
+
for (const message of this.messages) {
|
|
1743
|
+
if (message.role !== "system")
|
|
1744
|
+
await this.persistMessage(message);
|
|
1745
|
+
}
|
|
1746
|
+
this.lastPersistedIndex = this.messages.length;
|
|
1747
|
+
await this.rePersistTurnMetrics();
|
|
1748
|
+
await this.rePersistKenTurns();
|
|
1749
|
+
await this.rePersistAutopilotMarkers();
|
|
1750
|
+
await this.rePersistAppMarkers();
|
|
1751
|
+
await this.persistAppMarker("compaction", {
|
|
1752
|
+
originalCount: result.originalCount,
|
|
1753
|
+
newCount: result.newCount,
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
async compact(existingCredentials, mode = "manual") {
|
|
1632
1757
|
this.lastCompactionCompacted = false;
|
|
1633
1758
|
const creds = existingCredentials ??
|
|
1634
1759
|
(await this.authStorage.resolveCredentials(this.provider, {
|
|
@@ -1638,8 +1763,17 @@ export class AgentSession {
|
|
|
1638
1763
|
provider: this.provider,
|
|
1639
1764
|
accountId: creds.accountId,
|
|
1640
1765
|
});
|
|
1641
|
-
|
|
1642
|
-
|
|
1766
|
+
const policy = resolveCompactionPolicy({
|
|
1767
|
+
provider: this.provider,
|
|
1768
|
+
model: this.model,
|
|
1769
|
+
contextWindow,
|
|
1770
|
+
threshold: this.settingsManager.get("compactThreshold"),
|
|
1771
|
+
accountId: creds.accountId,
|
|
1772
|
+
approvedPlanPath: this.approvedPlanPath,
|
|
1773
|
+
});
|
|
1774
|
+
const originalCount = this.messages.length;
|
|
1775
|
+
this.eventBus.emit("compaction_start", { messageCount: originalCount });
|
|
1776
|
+
const runCompactor = () => compact(this.messages, {
|
|
1643
1777
|
provider: this.provider,
|
|
1644
1778
|
model: this.model,
|
|
1645
1779
|
apiKey: creds.accessToken,
|
|
@@ -1647,67 +1781,106 @@ export class AgentSession {
|
|
|
1647
1781
|
projectId: creds.projectId,
|
|
1648
1782
|
baseUrl: this.baseUrl ?? creds.baseUrl,
|
|
1649
1783
|
contextWindow,
|
|
1784
|
+
targetTokens: policy.targetTokens,
|
|
1650
1785
|
signal: this.opts.signal,
|
|
1786
|
+
approvedPlanPath: this.approvedPlanPath,
|
|
1651
1787
|
});
|
|
1652
|
-
|
|
1653
|
-
this.
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
// Compaction rewrote the message list, so every transcript anchor recorded
|
|
1664
|
-
// against the old indices must move with it — otherwise the markers are
|
|
1665
|
-
// re-persisted pointing at positions that no longer mean anything and
|
|
1666
|
-
// replay far below where they happened (or past the end).
|
|
1667
|
-
this.remapMarkerAnchors(result.result.anchorRemap);
|
|
1668
|
-
// Transient sessions (Ken chat/autopilot, subagent spawns) must NEVER touch
|
|
1669
|
-
// the session store: without this guard, the first auto-compaction called
|
|
1670
|
-
// sessionManager.create() and assigned a real sessionPath, silently turning
|
|
1671
|
-
// the "in-memory only" session into a persisted one — every later turn (and
|
|
1672
|
-
// every further compaction) then leaked a Ken transcript file into the
|
|
1673
|
-
// project's session list. Compact in memory only and keep sessionPath empty.
|
|
1674
|
-
if (this.opts.transient) {
|
|
1675
|
-
this.lastPersistedIndex = this.messages.length;
|
|
1788
|
+
let finalCount = originalCount;
|
|
1789
|
+
if (this.opts.transient || !this.conversationId) {
|
|
1790
|
+
const result = await runCompactor();
|
|
1791
|
+
finalCount = result.result.newCount;
|
|
1792
|
+
this.messages = result.messages;
|
|
1793
|
+
this.lastCompactionCompacted = result.result.compacted;
|
|
1794
|
+
if (result.result.compacted) {
|
|
1795
|
+
this.providerContext = null;
|
|
1796
|
+
this.remapMarkerAnchors(result.result.anchorRemap);
|
|
1797
|
+
this.lastPersistedIndex = this.messages.length;
|
|
1798
|
+
}
|
|
1676
1799
|
}
|
|
1677
1800
|
else {
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1801
|
+
const conversationId = this.conversationId;
|
|
1802
|
+
await this.sessionManager.withCompactionLease(conversationId, this.opts.signal, async () => {
|
|
1803
|
+
let sourceFingerprint = computeSourceFingerprint(this.messages);
|
|
1804
|
+
const canonicalPath = await this.sessionManager.resolveCanonicalSession(conversationId, this.cwd);
|
|
1805
|
+
if (canonicalPath && canonicalPath !== this.sessionPath) {
|
|
1806
|
+
const newest = await this.sessionManager.load(canonicalPath);
|
|
1807
|
+
if (newest.header.sourceFingerprint === sourceFingerprint) {
|
|
1808
|
+
await this.adoptCompactionCheckpoint(newest);
|
|
1809
|
+
this.lastCompactionCompacted = true;
|
|
1810
|
+
finalCount = this.messages.length;
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
// The canonical branch contains different progress. Rebase onto it before
|
|
1814
|
+
// compacting so a stale caller cannot supersede newer history by generation.
|
|
1815
|
+
await this.adoptCompactionCheckpoint(newest);
|
|
1816
|
+
sourceFingerprint = computeSourceFingerprint(this.messages);
|
|
1817
|
+
}
|
|
1818
|
+
const attempt = await this.sessionManager.readCompactionAttemptState(conversationId);
|
|
1819
|
+
const attemptActive = !attempt?.expiresAt || Date.parse(attempt.expiresAt) > Date.now();
|
|
1820
|
+
if (mode === "automatic" &&
|
|
1821
|
+
attempt?.fingerprint === sourceFingerprint &&
|
|
1822
|
+
attempt.policyKey === policy.policyKey &&
|
|
1823
|
+
attemptActive) {
|
|
1824
|
+
if (attempt.outcome === "success" && attempt.checkpointId) {
|
|
1825
|
+
const checkpointPath = await this.sessionManager.findById(this.cwd, attempt.checkpointId);
|
|
1826
|
+
if (checkpointPath) {
|
|
1827
|
+
const checkpoint = await this.sessionManager.load(checkpointPath);
|
|
1828
|
+
if (checkpoint.header.sourceFingerprint === sourceFingerprint) {
|
|
1829
|
+
await this.adoptCompactionCheckpoint(checkpoint);
|
|
1830
|
+
this.lastCompactionCompacted = true;
|
|
1831
|
+
finalCount = this.messages.length;
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
else if (attempt.outcome === "failed" || attempt.outcome === "noop") {
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
try {
|
|
1841
|
+
const result = await runCompactor();
|
|
1842
|
+
finalCount = result.result.newCount;
|
|
1843
|
+
this.messages = result.messages;
|
|
1844
|
+
this.lastCompactionCompacted = result.result.compacted;
|
|
1845
|
+
if (!result.result.compacted) {
|
|
1846
|
+
await this.sessionManager.writeCompactionAttemptState(conversationId, {
|
|
1847
|
+
fingerprint: sourceFingerprint,
|
|
1848
|
+
policyKey: policy.policyKey,
|
|
1849
|
+
outcome: "noop",
|
|
1850
|
+
updatedAt: new Date().toISOString(),
|
|
1851
|
+
expiresAt: new Date(Date.now() + 30_000).toISOString(),
|
|
1852
|
+
});
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
this.providerContext = null;
|
|
1856
|
+
this.remapMarkerAnchors(result.result.anchorRemap);
|
|
1857
|
+
await this.persistCompactionCheckpoint(sourceFingerprint, result.result);
|
|
1858
|
+
await this.sessionManager.writeCompactionAttemptState(conversationId, {
|
|
1859
|
+
fingerprint: sourceFingerprint,
|
|
1860
|
+
policyKey: policy.policyKey,
|
|
1861
|
+
outcome: "success",
|
|
1862
|
+
checkpointId: this.sessionId,
|
|
1863
|
+
updatedAt: new Date().toISOString(),
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
catch (error) {
|
|
1867
|
+
await this.sessionManager
|
|
1868
|
+
.writeCompactionAttemptState(conversationId, {
|
|
1869
|
+
fingerprint: sourceFingerprint,
|
|
1870
|
+
policyKey: policy.policyKey,
|
|
1871
|
+
outcome: "failed",
|
|
1872
|
+
updatedAt: new Date().toISOString(),
|
|
1873
|
+
expiresAt: new Date(Date.now() + 30_000).toISOString(),
|
|
1874
|
+
})
|
|
1875
|
+
.catch(() => { });
|
|
1876
|
+
throw error;
|
|
1877
|
+
}
|
|
1705
1878
|
});
|
|
1706
1879
|
}
|
|
1707
1880
|
this.eventBus.emit("compaction_end", {
|
|
1708
|
-
compacted:
|
|
1709
|
-
originalCount
|
|
1710
|
-
newCount:
|
|
1881
|
+
compacted: this.lastCompactionCompacted,
|
|
1882
|
+
originalCount,
|
|
1883
|
+
newCount: finalCount,
|
|
1711
1884
|
});
|
|
1712
1885
|
}
|
|
1713
1886
|
async newSession(preserveConversation = false) {
|
|
@@ -1715,6 +1888,7 @@ export class AgentSession {
|
|
|
1715
1888
|
// explicit new sessions reset the conversation identity.
|
|
1716
1889
|
if (!preserveConversation) {
|
|
1717
1890
|
this.conversationId = "";
|
|
1891
|
+
this.checkpointGeneration = 0;
|
|
1718
1892
|
this.sessionPreview = "";
|
|
1719
1893
|
}
|
|
1720
1894
|
// A fresh session drops any in-flight plan state so its prompt is clean.
|
|
@@ -1742,6 +1916,7 @@ export class AgentSession {
|
|
|
1742
1916
|
if (this.opts.transient) {
|
|
1743
1917
|
this.sessionId = "";
|
|
1744
1918
|
this.conversationId = "";
|
|
1919
|
+
this.checkpointGeneration = 0;
|
|
1745
1920
|
this.sessionPreview = "";
|
|
1746
1921
|
this.setSessionPath("");
|
|
1747
1922
|
this.lastPersistedIndex = this.messages.length;
|
|
@@ -2386,19 +2561,28 @@ export class AgentSession {
|
|
|
2386
2561
|
this.sessionManager.registerActivePath(nextPath);
|
|
2387
2562
|
}
|
|
2388
2563
|
async createNewSession() {
|
|
2564
|
+
const continuingConversation = Boolean(this.conversationId && this.sessionId);
|
|
2389
2565
|
const session = await this.sessionManager.create(this.cwd, this.provider, this.model, {
|
|
2390
2566
|
conversationId: this.conversationId || undefined,
|
|
2567
|
+
generation: continuingConversation ? this.checkpointGeneration + 1 : 0,
|
|
2568
|
+
parentSessionId: continuingConversation ? this.sessionId : undefined,
|
|
2391
2569
|
preview: this.sessionPreview || undefined,
|
|
2392
2570
|
});
|
|
2393
2571
|
this.sessionId = session.id;
|
|
2572
|
+
this.checkpointGeneration = session.header.generation ?? 0;
|
|
2394
2573
|
this.conversationId = session.header.conversationId ?? session.id;
|
|
2395
2574
|
this.setSessionPath(session.path);
|
|
2396
2575
|
this.lastPersistedIndex = this.messages.length;
|
|
2397
2576
|
}
|
|
2398
2577
|
async loadExistingSession(sessionPath) {
|
|
2399
|
-
|
|
2578
|
+
// A stale physical checkpoint is only an address, not the conversation tip.
|
|
2579
|
+
// Resolve every resume—not just over-threshold/deferred compaction resumes—
|
|
2580
|
+
// before reading history so the next prompt cannot continue an old branch.
|
|
2581
|
+
const canonicalPath = (await this.sessionManager.resolveCanonicalSession(sessionPath, this.cwd)) ?? sessionPath;
|
|
2582
|
+
const loaded = await this.sessionManager.load(canonicalPath);
|
|
2400
2583
|
// Use the leaf from the header to walk the correct branch
|
|
2401
2584
|
const loadedMessages = this.sessionManager.getMessages(loaded.entries, loaded.header.leafId);
|
|
2585
|
+
this.checkpointGeneration = loaded.header.generation ?? 0;
|
|
2402
2586
|
this.conversationId = loaded.header.conversationId ?? loaded.header.id;
|
|
2403
2587
|
const legacyLabel = [...loaded.entries]
|
|
2404
2588
|
.reverse()
|
|
@@ -2447,67 +2631,41 @@ export class AgentSession {
|
|
|
2447
2631
|
provider: this.provider,
|
|
2448
2632
|
accountId: creds.accountId,
|
|
2449
2633
|
});
|
|
2634
|
+
this.sessionId = loaded.header.id;
|
|
2635
|
+
this.setSessionPath(loaded.path);
|
|
2636
|
+
this.lastPersistedIndex = this.messages.length;
|
|
2637
|
+
const loadPolicy = resolveCompactionPolicy({
|
|
2638
|
+
provider: this.provider,
|
|
2639
|
+
model: this.model,
|
|
2640
|
+
contextWindow,
|
|
2641
|
+
threshold: this.settingsManager.get("compactThreshold"),
|
|
2642
|
+
accountId: creds.accountId,
|
|
2643
|
+
approvedPlanPath: this.approvedPlanPath,
|
|
2644
|
+
});
|
|
2645
|
+
log("INFO", "compaction", "Restore compaction decision", {
|
|
2646
|
+
provider: this.provider,
|
|
2647
|
+
model: this.model,
|
|
2648
|
+
transport: this.provider === "openai" && creds.accountId ? "codex_oauth" : "public_api",
|
|
2649
|
+
contextWindow: String(contextWindow),
|
|
2650
|
+
activeTokens: "estimated",
|
|
2651
|
+
triggerLimit: String(loadPolicy.targetTokens),
|
|
2652
|
+
});
|
|
2450
2653
|
const needsLoadCompaction = this.settingsManager.get("autoCompact") &&
|
|
2451
|
-
shouldCompact(this.messages, contextWindow,
|
|
2654
|
+
shouldCompact(this.messages, contextWindow, loadPolicy.threshold);
|
|
2452
2655
|
if (needsLoadCompaction && this.opts.deferLoadCompaction) {
|
|
2453
|
-
//
|
|
2454
|
-
//
|
|
2455
|
-
|
|
2656
|
+
// Canonicalize again immediately before the first prompt is persisted:
|
|
2657
|
+
// another process may create the shared checkpoint after this load.
|
|
2658
|
+
this.deferredCompactionPending = true;
|
|
2456
2659
|
log("INFO", "session", "Restored session exceeds context — deferring compaction to first prompt");
|
|
2457
2660
|
}
|
|
2458
2661
|
else if (needsLoadCompaction) {
|
|
2459
2662
|
await this.subAgentManager?.hydrate(loaded.header.id);
|
|
2460
2663
|
log("INFO", "session", `Restored session exceeds context — auto-compacting`);
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
accountId: creds.accountId,
|
|
2466
|
-
projectId: creds.projectId,
|
|
2467
|
-
baseUrl: this.baseUrl ?? creds.baseUrl,
|
|
2468
|
-
contextWindow,
|
|
2469
|
-
signal: this.opts.signal,
|
|
2470
|
-
});
|
|
2471
|
-
this.messages = compacted.messages;
|
|
2472
|
-
// Same anchor rebase as compact(): the restored markers were recorded
|
|
2473
|
-
// against the pre-compaction transcript.
|
|
2474
|
-
this.remapMarkerAnchors(compacted.result.anchorRemap);
|
|
2475
|
-
log("INFO", "session", `Auto-compaction complete`, {
|
|
2476
|
-
before: String(compacted.result.originalCount),
|
|
2477
|
-
after: String(compacted.result.newCount),
|
|
2478
|
-
});
|
|
2479
|
-
// Compaction rewrote history, so the on-disk file no longer reflects
|
|
2480
|
-
// what's in memory — fork a fresh session file for the compacted state
|
|
2481
|
-
// (mirrors compact()'s own persistence) so `ggcoder continue` picks up
|
|
2482
|
-
// the summary instead of the full original transcript.
|
|
2483
|
-
const session = await this.sessionManager.create(this.cwd, this.provider, this.model, {
|
|
2484
|
-
conversationId: this.conversationId || undefined,
|
|
2485
|
-
preview: this.sessionPreview || undefined,
|
|
2486
|
-
});
|
|
2487
|
-
this.sessionId = session.id;
|
|
2488
|
-
this.conversationId = session.header.conversationId ?? session.id;
|
|
2489
|
-
this.setSessionPath(session.path);
|
|
2490
|
-
await this.subAgentManager?.rebindParentSession(this.sessionId);
|
|
2491
|
-
this.currentLeafId = null;
|
|
2492
|
-
// Re-persist (compacted) messages — skip system, it's rebuilt on load
|
|
2493
|
-
for (const msg of this.messages) {
|
|
2494
|
-
if (msg.role === "system")
|
|
2495
|
-
continue;
|
|
2496
|
-
await this.persistMessage(msg);
|
|
2664
|
+
await this.compact(creds, "automatic");
|
|
2665
|
+
if (this.lastCompactionCompacted) {
|
|
2666
|
+
await this.recordInterruptedRuns(interruptedRuns);
|
|
2667
|
+
return;
|
|
2497
2668
|
}
|
|
2498
|
-
this.lastPersistedIndex = this.messages.length;
|
|
2499
|
-
// Carry restored evidence into the continuation file.
|
|
2500
|
-
await this.rePersistTurnMetrics();
|
|
2501
|
-
await this.rePersistKenTurns();
|
|
2502
|
-
await this.rePersistAutopilotMarkers();
|
|
2503
|
-
await this.rePersistAppMarkers();
|
|
2504
|
-
// Record this load-time auto-compaction's counts for the resumed notice.
|
|
2505
|
-
await this.persistAppMarker("compaction", {
|
|
2506
|
-
originalCount: compacted.result.originalCount,
|
|
2507
|
-
newCount: compacted.result.newCount,
|
|
2508
|
-
});
|
|
2509
|
-
await this.recordInterruptedRuns(interruptedRuns);
|
|
2510
|
-
return;
|
|
2511
2669
|
}
|
|
2512
2670
|
// Plain resume (no compaction needed): keep using the original session
|
|
2513
2671
|
// file/id and append future turns to it in place. Forking a new file here
|
|
@@ -2546,8 +2704,10 @@ export class AgentSession {
|
|
|
2546
2704
|
return this.messages;
|
|
2547
2705
|
}
|
|
2548
2706
|
async persistMessage(message) {
|
|
2549
|
-
if (!this.sessionPreview &&
|
|
2550
|
-
|
|
2707
|
+
if (!this.sessionPreview &&
|
|
2708
|
+
message.role === "user" &&
|
|
2709
|
+
(message.provenance?.source === "human" || !message.provenance)) {
|
|
2710
|
+
this.sessionPreview = getUserSessionPrompt(message.content, message.provenance) ?? "";
|
|
2551
2711
|
}
|
|
2552
2712
|
// Transient sessions (subagent spawns) have no session file — skip.
|
|
2553
2713
|
if (!this.sessionPath)
|
|
@@ -2567,7 +2727,7 @@ export class AgentSession {
|
|
|
2567
2727
|
createSlashCommandContext() {
|
|
2568
2728
|
return {
|
|
2569
2729
|
switchModel: (provider, model) => this.switchModel(provider, model),
|
|
2570
|
-
compact: () => this.compact(),
|
|
2730
|
+
compact: () => this.compact(undefined, "manual"),
|
|
2571
2731
|
newSession: () => this.newSession(),
|
|
2572
2732
|
listSessions: async () => {
|
|
2573
2733
|
const sessions = await this.sessionManager.list(this.cwd);
|