@kenkaiiii/ggcoder 5.29.1 → 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 +5 -1
- package/dist/core/agent-session-review-coverage.test.js.map +1 -1
- package/dist/core/agent-session.d.ts +10 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +276 -130
- 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.map +1 -1
- package/dist/core/ideal-review.js +4 -0
- package/dist/core/ideal-review.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/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/web-fetch.d.ts +1 -1
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.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,6 +31,7 @@ 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";
|
|
@@ -149,6 +151,8 @@ export class AgentSession {
|
|
|
149
151
|
compactionOccurred = false;
|
|
150
152
|
lastCompactionCompacted = false;
|
|
151
153
|
compactionRetryAfter = 0;
|
|
154
|
+
/** A restored oversized checkpoint must be canonicalized before its first prompt is persisted. */
|
|
155
|
+
deferredCompactionPending = false;
|
|
152
156
|
/** Latest provider count, anchored to the assistant response it measured. */
|
|
153
157
|
providerContext = null;
|
|
154
158
|
originalRequest = "";
|
|
@@ -206,6 +210,7 @@ export class AgentSession {
|
|
|
206
210
|
/** Extra workspace roots added with `/add-dir` (resolved, de-duplicated). */
|
|
207
211
|
additionalRoots = [];
|
|
208
212
|
sessionId = "";
|
|
213
|
+
checkpointGeneration = 0;
|
|
209
214
|
/** Stable identity shared by compaction and approved-plan checkpoint files. */
|
|
210
215
|
conversationId = "";
|
|
211
216
|
/** Original user-authored prompt, retained when internal messages replace history. */
|
|
@@ -753,12 +758,16 @@ export class AgentSession {
|
|
|
753
758
|
/**
|
|
754
759
|
* Process user input. Handles slash commands or runs agent loop.
|
|
755
760
|
*/
|
|
756
|
-
async prompt(content
|
|
761
|
+
async prompt(content, provenance = {
|
|
762
|
+
source: "human",
|
|
763
|
+
kind: "prompt",
|
|
764
|
+
visibility: "transcript",
|
|
765
|
+
}) {
|
|
766
|
+
await this.adoptDeferredCheckpointBeforePrompt();
|
|
757
767
|
const slash = await this.resolveSlashInput(content);
|
|
758
768
|
if (slash?.kind === "template") {
|
|
759
|
-
//
|
|
760
|
-
|
|
761
|
-
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 };
|
|
762
771
|
this.messages.push(userMessage);
|
|
763
772
|
await this.persistMessage(userMessage);
|
|
764
773
|
this.lastPersistedIndex = this.messages.length;
|
|
@@ -774,7 +783,7 @@ export class AgentSession {
|
|
|
774
783
|
return;
|
|
775
784
|
}
|
|
776
785
|
// Push user message
|
|
777
|
-
const userMessage = { role: "user", content };
|
|
786
|
+
const userMessage = { role: "user", content, provenance };
|
|
778
787
|
this.messages.push(userMessage);
|
|
779
788
|
await this.persistMessage(userMessage);
|
|
780
789
|
this.lastPersistedIndex = this.messages.length;
|
|
@@ -788,10 +797,15 @@ export class AgentSession {
|
|
|
788
797
|
* attachments are always a direct conversational turn.
|
|
789
798
|
*/
|
|
790
799
|
async promptWithAttachments(text, attachments) {
|
|
800
|
+
await this.adoptDeferredCheckpointBeforePrompt();
|
|
791
801
|
const parts = this.buildAttachmentParts(text, attachments);
|
|
792
802
|
if (parts.length === 0)
|
|
793
803
|
return;
|
|
794
|
-
const userMessage = {
|
|
804
|
+
const userMessage = {
|
|
805
|
+
role: "user",
|
|
806
|
+
content: parts,
|
|
807
|
+
provenance: { source: "human", kind: "prompt", visibility: "transcript" },
|
|
808
|
+
};
|
|
795
809
|
this.messages.push(userMessage);
|
|
796
810
|
await this.persistMessage(userMessage);
|
|
797
811
|
this.lastPersistedIndex = this.messages.length;
|
|
@@ -993,6 +1007,7 @@ export class AgentSession {
|
|
|
993
1007
|
? {
|
|
994
1008
|
role: "user",
|
|
995
1009
|
content: buildNotificationSteeringText(notified.map((entry) => entry.text)),
|
|
1010
|
+
provenance: { source: "runtime", kind: "notification", visibility: "hidden" },
|
|
996
1011
|
}
|
|
997
1012
|
: null;
|
|
998
1013
|
if (notificationMessage) {
|
|
@@ -1016,8 +1031,13 @@ export class AgentSession {
|
|
|
1016
1031
|
// (not merged): each persists as its own user message, so a resumed
|
|
1017
1032
|
// session shows the same number of bubbles the live run did.
|
|
1018
1033
|
const steeringMessages = queued.map((m) => {
|
|
1034
|
+
const provenance = {
|
|
1035
|
+
source: "human",
|
|
1036
|
+
kind: "steering",
|
|
1037
|
+
visibility: "transcript",
|
|
1038
|
+
};
|
|
1019
1039
|
if (m.attachments.length === 0) {
|
|
1020
|
-
return { role: "user", content: wrapSteeringText(m.text) };
|
|
1040
|
+
return { role: "user", content: wrapSteeringText(m.text), provenance };
|
|
1021
1041
|
}
|
|
1022
1042
|
// Queued attachments ride the same native-block path as a non-queued
|
|
1023
1043
|
// attachment prompt, prefixed with the steering framing.
|
|
@@ -1025,7 +1045,7 @@ export class AgentSession {
|
|
|
1025
1045
|
{ type: "text", text: STEERING_PREFIX },
|
|
1026
1046
|
...this.buildAttachmentParts(m.text, m.attachments),
|
|
1027
1047
|
];
|
|
1028
|
-
return { role: "user", content: parts };
|
|
1048
|
+
return { role: "user", content: parts, provenance };
|
|
1029
1049
|
});
|
|
1030
1050
|
return notificationMessage ? [...steeringMessages, notificationMessage] : steeringMessages;
|
|
1031
1051
|
}
|
|
@@ -1207,7 +1227,11 @@ export class AgentSession {
|
|
|
1207
1227
|
: []),
|
|
1208
1228
|
"Do not describe those files as compiler-clean without other evidence.",
|
|
1209
1229
|
];
|
|
1210
|
-
return {
|
|
1230
|
+
return {
|
|
1231
|
+
role: "user",
|
|
1232
|
+
provenance: message.provenance,
|
|
1233
|
+
content: `${String(message.content)}\n\n${notes.join(" ")}`,
|
|
1234
|
+
};
|
|
1211
1235
|
}
|
|
1212
1236
|
/** Auto-compact if needed, run agent loop with auth retry, and persist messages. */
|
|
1213
1237
|
async runLoop() {
|
|
@@ -1247,7 +1271,14 @@ export class AgentSession {
|
|
|
1247
1271
|
provider: this.provider,
|
|
1248
1272
|
accountId: creds.accountId,
|
|
1249
1273
|
});
|
|
1250
|
-
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
|
+
});
|
|
1251
1282
|
let activeTokens;
|
|
1252
1283
|
if (this.providerContext) {
|
|
1253
1284
|
const anchorIndex = this.messages.lastIndexOf(this.providerContext.anchor);
|
|
@@ -1261,9 +1292,17 @@ export class AgentSession {
|
|
|
1261
1292
|
this.providerContext = null;
|
|
1262
1293
|
}
|
|
1263
1294
|
}
|
|
1264
|
-
|
|
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)) {
|
|
1265
1304
|
try {
|
|
1266
|
-
await this.compact(creds);
|
|
1305
|
+
await this.compact(creds, "automatic");
|
|
1267
1306
|
if (this.lastCompactionCompacted) {
|
|
1268
1307
|
// Re-grounding hook keys off this — the context was just summarized.
|
|
1269
1308
|
this.compactionOccurred = true;
|
|
@@ -1373,12 +1412,27 @@ export class AgentSession {
|
|
|
1373
1412
|
provider: this.provider,
|
|
1374
1413
|
accountId,
|
|
1375
1414
|
});
|
|
1376
|
-
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
|
+
});
|
|
1377
1423
|
const activeTokens = calculateActiveContextTokens(messages, {
|
|
1378
1424
|
usage,
|
|
1379
1425
|
pendingMessages,
|
|
1380
1426
|
});
|
|
1381
|
-
|
|
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))
|
|
1382
1436
|
return messages;
|
|
1383
1437
|
}
|
|
1384
1438
|
// compact() operates on this.messages, while an earlier transform may
|
|
@@ -1391,7 +1445,7 @@ export class AgentSession {
|
|
|
1391
1445
|
accountId,
|
|
1392
1446
|
projectId,
|
|
1393
1447
|
baseUrl: effectiveBaseUrl,
|
|
1394
|
-
});
|
|
1448
|
+
}, force ? "forced" : "automatic");
|
|
1395
1449
|
}
|
|
1396
1450
|
catch (error) {
|
|
1397
1451
|
this.messages = messages;
|
|
@@ -1640,9 +1694,66 @@ export class AgentSession {
|
|
|
1640
1694
|
this.messages.push({
|
|
1641
1695
|
role: "user",
|
|
1642
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" },
|
|
1643
1698
|
});
|
|
1644
1699
|
}
|
|
1645
|
-
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") {
|
|
1646
1757
|
this.lastCompactionCompacted = false;
|
|
1647
1758
|
const creds = existingCredentials ??
|
|
1648
1759
|
(await this.authStorage.resolveCredentials(this.provider, {
|
|
@@ -1652,8 +1763,17 @@ export class AgentSession {
|
|
|
1652
1763
|
provider: this.provider,
|
|
1653
1764
|
accountId: creds.accountId,
|
|
1654
1765
|
});
|
|
1655
|
-
|
|
1656
|
-
|
|
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, {
|
|
1657
1777
|
provider: this.provider,
|
|
1658
1778
|
model: this.model,
|
|
1659
1779
|
apiKey: creds.accessToken,
|
|
@@ -1661,67 +1781,106 @@ export class AgentSession {
|
|
|
1661
1781
|
projectId: creds.projectId,
|
|
1662
1782
|
baseUrl: this.baseUrl ?? creds.baseUrl,
|
|
1663
1783
|
contextWindow,
|
|
1784
|
+
targetTokens: policy.targetTokens,
|
|
1664
1785
|
signal: this.opts.signal,
|
|
1786
|
+
approvedPlanPath: this.approvedPlanPath,
|
|
1665
1787
|
});
|
|
1666
|
-
|
|
1667
|
-
this.
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
// Compaction rewrote the message list, so every transcript anchor recorded
|
|
1678
|
-
// against the old indices must move with it — otherwise the markers are
|
|
1679
|
-
// re-persisted pointing at positions that no longer mean anything and
|
|
1680
|
-
// replay far below where they happened (or past the end).
|
|
1681
|
-
this.remapMarkerAnchors(result.result.anchorRemap);
|
|
1682
|
-
// Transient sessions (Ken chat/autopilot, subagent spawns) must NEVER touch
|
|
1683
|
-
// the session store: without this guard, the first auto-compaction called
|
|
1684
|
-
// sessionManager.create() and assigned a real sessionPath, silently turning
|
|
1685
|
-
// the "in-memory only" session into a persisted one — every later turn (and
|
|
1686
|
-
// every further compaction) then leaked a Ken transcript file into the
|
|
1687
|
-
// project's session list. Compact in memory only and keep sessionPath empty.
|
|
1688
|
-
if (this.opts.transient) {
|
|
1689
|
-
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
|
+
}
|
|
1690
1799
|
}
|
|
1691
1800
|
else {
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
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
|
+
}
|
|
1719
1878
|
});
|
|
1720
1879
|
}
|
|
1721
1880
|
this.eventBus.emit("compaction_end", {
|
|
1722
|
-
compacted:
|
|
1723
|
-
originalCount
|
|
1724
|
-
newCount:
|
|
1881
|
+
compacted: this.lastCompactionCompacted,
|
|
1882
|
+
originalCount,
|
|
1883
|
+
newCount: finalCount,
|
|
1725
1884
|
});
|
|
1726
1885
|
}
|
|
1727
1886
|
async newSession(preserveConversation = false) {
|
|
@@ -1729,6 +1888,7 @@ export class AgentSession {
|
|
|
1729
1888
|
// explicit new sessions reset the conversation identity.
|
|
1730
1889
|
if (!preserveConversation) {
|
|
1731
1890
|
this.conversationId = "";
|
|
1891
|
+
this.checkpointGeneration = 0;
|
|
1732
1892
|
this.sessionPreview = "";
|
|
1733
1893
|
}
|
|
1734
1894
|
// A fresh session drops any in-flight plan state so its prompt is clean.
|
|
@@ -1756,6 +1916,7 @@ export class AgentSession {
|
|
|
1756
1916
|
if (this.opts.transient) {
|
|
1757
1917
|
this.sessionId = "";
|
|
1758
1918
|
this.conversationId = "";
|
|
1919
|
+
this.checkpointGeneration = 0;
|
|
1759
1920
|
this.sessionPreview = "";
|
|
1760
1921
|
this.setSessionPath("");
|
|
1761
1922
|
this.lastPersistedIndex = this.messages.length;
|
|
@@ -2400,19 +2561,28 @@ export class AgentSession {
|
|
|
2400
2561
|
this.sessionManager.registerActivePath(nextPath);
|
|
2401
2562
|
}
|
|
2402
2563
|
async createNewSession() {
|
|
2564
|
+
const continuingConversation = Boolean(this.conversationId && this.sessionId);
|
|
2403
2565
|
const session = await this.sessionManager.create(this.cwd, this.provider, this.model, {
|
|
2404
2566
|
conversationId: this.conversationId || undefined,
|
|
2567
|
+
generation: continuingConversation ? this.checkpointGeneration + 1 : 0,
|
|
2568
|
+
parentSessionId: continuingConversation ? this.sessionId : undefined,
|
|
2405
2569
|
preview: this.sessionPreview || undefined,
|
|
2406
2570
|
});
|
|
2407
2571
|
this.sessionId = session.id;
|
|
2572
|
+
this.checkpointGeneration = session.header.generation ?? 0;
|
|
2408
2573
|
this.conversationId = session.header.conversationId ?? session.id;
|
|
2409
2574
|
this.setSessionPath(session.path);
|
|
2410
2575
|
this.lastPersistedIndex = this.messages.length;
|
|
2411
2576
|
}
|
|
2412
2577
|
async loadExistingSession(sessionPath) {
|
|
2413
|
-
|
|
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);
|
|
2414
2583
|
// Use the leaf from the header to walk the correct branch
|
|
2415
2584
|
const loadedMessages = this.sessionManager.getMessages(loaded.entries, loaded.header.leafId);
|
|
2585
|
+
this.checkpointGeneration = loaded.header.generation ?? 0;
|
|
2416
2586
|
this.conversationId = loaded.header.conversationId ?? loaded.header.id;
|
|
2417
2587
|
const legacyLabel = [...loaded.entries]
|
|
2418
2588
|
.reverse()
|
|
@@ -2461,67 +2631,41 @@ export class AgentSession {
|
|
|
2461
2631
|
provider: this.provider,
|
|
2462
2632
|
accountId: creds.accountId,
|
|
2463
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
|
+
});
|
|
2464
2653
|
const needsLoadCompaction = this.settingsManager.get("autoCompact") &&
|
|
2465
|
-
shouldCompact(this.messages, contextWindow,
|
|
2654
|
+
shouldCompact(this.messages, contextWindow, loadPolicy.threshold);
|
|
2466
2655
|
if (needsLoadCompaction && this.opts.deferLoadCompaction) {
|
|
2467
|
-
//
|
|
2468
|
-
//
|
|
2469
|
-
|
|
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;
|
|
2470
2659
|
log("INFO", "session", "Restored session exceeds context — deferring compaction to first prompt");
|
|
2471
2660
|
}
|
|
2472
2661
|
else if (needsLoadCompaction) {
|
|
2473
2662
|
await this.subAgentManager?.hydrate(loaded.header.id);
|
|
2474
2663
|
log("INFO", "session", `Restored session exceeds context — auto-compacting`);
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
accountId: creds.accountId,
|
|
2480
|
-
projectId: creds.projectId,
|
|
2481
|
-
baseUrl: this.baseUrl ?? creds.baseUrl,
|
|
2482
|
-
contextWindow,
|
|
2483
|
-
signal: this.opts.signal,
|
|
2484
|
-
});
|
|
2485
|
-
this.messages = compacted.messages;
|
|
2486
|
-
// Same anchor rebase as compact(): the restored markers were recorded
|
|
2487
|
-
// against the pre-compaction transcript.
|
|
2488
|
-
this.remapMarkerAnchors(compacted.result.anchorRemap);
|
|
2489
|
-
log("INFO", "session", `Auto-compaction complete`, {
|
|
2490
|
-
before: String(compacted.result.originalCount),
|
|
2491
|
-
after: String(compacted.result.newCount),
|
|
2492
|
-
});
|
|
2493
|
-
// Compaction rewrote history, so the on-disk file no longer reflects
|
|
2494
|
-
// what's in memory — fork a fresh session file for the compacted state
|
|
2495
|
-
// (mirrors compact()'s own persistence) so `ggcoder continue` picks up
|
|
2496
|
-
// the summary instead of the full original transcript.
|
|
2497
|
-
const session = await this.sessionManager.create(this.cwd, this.provider, this.model, {
|
|
2498
|
-
conversationId: this.conversationId || undefined,
|
|
2499
|
-
preview: this.sessionPreview || undefined,
|
|
2500
|
-
});
|
|
2501
|
-
this.sessionId = session.id;
|
|
2502
|
-
this.conversationId = session.header.conversationId ?? session.id;
|
|
2503
|
-
this.setSessionPath(session.path);
|
|
2504
|
-
await this.subAgentManager?.rebindParentSession(this.sessionId);
|
|
2505
|
-
this.currentLeafId = null;
|
|
2506
|
-
// Re-persist (compacted) messages — skip system, it's rebuilt on load
|
|
2507
|
-
for (const msg of this.messages) {
|
|
2508
|
-
if (msg.role === "system")
|
|
2509
|
-
continue;
|
|
2510
|
-
await this.persistMessage(msg);
|
|
2664
|
+
await this.compact(creds, "automatic");
|
|
2665
|
+
if (this.lastCompactionCompacted) {
|
|
2666
|
+
await this.recordInterruptedRuns(interruptedRuns);
|
|
2667
|
+
return;
|
|
2511
2668
|
}
|
|
2512
|
-
this.lastPersistedIndex = this.messages.length;
|
|
2513
|
-
// Carry restored evidence into the continuation file.
|
|
2514
|
-
await this.rePersistTurnMetrics();
|
|
2515
|
-
await this.rePersistKenTurns();
|
|
2516
|
-
await this.rePersistAutopilotMarkers();
|
|
2517
|
-
await this.rePersistAppMarkers();
|
|
2518
|
-
// Record this load-time auto-compaction's counts for the resumed notice.
|
|
2519
|
-
await this.persistAppMarker("compaction", {
|
|
2520
|
-
originalCount: compacted.result.originalCount,
|
|
2521
|
-
newCount: compacted.result.newCount,
|
|
2522
|
-
});
|
|
2523
|
-
await this.recordInterruptedRuns(interruptedRuns);
|
|
2524
|
-
return;
|
|
2525
2669
|
}
|
|
2526
2670
|
// Plain resume (no compaction needed): keep using the original session
|
|
2527
2671
|
// file/id and append future turns to it in place. Forking a new file here
|
|
@@ -2560,8 +2704,10 @@ export class AgentSession {
|
|
|
2560
2704
|
return this.messages;
|
|
2561
2705
|
}
|
|
2562
2706
|
async persistMessage(message) {
|
|
2563
|
-
if (!this.sessionPreview &&
|
|
2564
|
-
|
|
2707
|
+
if (!this.sessionPreview &&
|
|
2708
|
+
message.role === "user" &&
|
|
2709
|
+
(message.provenance?.source === "human" || !message.provenance)) {
|
|
2710
|
+
this.sessionPreview = getUserSessionPrompt(message.content, message.provenance) ?? "";
|
|
2565
2711
|
}
|
|
2566
2712
|
// Transient sessions (subagent spawns) have no session file — skip.
|
|
2567
2713
|
if (!this.sessionPath)
|
|
@@ -2581,7 +2727,7 @@ export class AgentSession {
|
|
|
2581
2727
|
createSlashCommandContext() {
|
|
2582
2728
|
return {
|
|
2583
2729
|
switchModel: (provider, model) => this.switchModel(provider, model),
|
|
2584
|
-
compact: () => this.compact(),
|
|
2730
|
+
compact: () => this.compact(undefined, "manual"),
|
|
2585
2731
|
newSession: () => this.newSession(),
|
|
2586
2732
|
listSessions: async () => {
|
|
2587
2733
|
const sessions = await this.sessionManager.list(this.cwd);
|