@kenkaiiii/ggcoder 5.17.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 +175 -45
- package/dist/app-sidecar.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +9 -2
- package/dist/cli.js.map +1 -1
- 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 +8 -8
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +52 -44
- 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 +1 -0
- package/dist/core/compaction/compactor.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/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 +8 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +7 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-manager.test.js +17 -0
- 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 +2 -4
- package/dist/core/subagent-manager.d.ts.map +1 -1
- package/dist/core/subagent-manager.js +4 -0
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/core/subagent-manager.test.js +6 -0
- package/dist/core/subagent-manager.test.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/ui/App.d.ts +0 -2
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +3 -66
- package/dist/ui/App.js.map +1 -1
- 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/useSessionPersistence.d.ts.map +1 -1
- package/dist/ui/hooks/useSessionPersistence.js +3 -0
- 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 +0 -2
- package/dist/ui/render.d.ts.map +1 -1
- package/dist/ui/render.js +0 -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
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ Switch mid-conversation with `/model`. Not locked to anyone.
|
|
|
71
71
|
|---|---|---|
|
|
72
72
|
| **Anthropic** | Claude Opus 4.8, Sonnet 5, Haiku 4.5 | OAuth |
|
|
73
73
|
| **OpenAI** | GPT-5.5, GPT-5.5 Pro, GPT-5.4, GPT-5.3 Codex | OAuth |
|
|
74
|
-
| **Moonshot** | Kimi K2.7 | API key |
|
|
74
|
+
| **Moonshot** | Kimi K3, Kimi K2.7 Code | OAuth or API key |
|
|
75
75
|
| **Z.AI (GLM)** | GLM-5.1, GLM-4.7, GLM-4.7 Flash | API key |
|
|
76
76
|
| **MiniMax** | MiniMax M3 (image + video) | API key |
|
|
77
77
|
| **Xiaomi (MiMo)** | MiMo-V2.5-Pro, MiMo-V2.5-Pro-UltraSpeed, MiMo-V2.5 (image + video) | API key |
|
|
@@ -80,7 +80,7 @@ Switch mid-conversation with `/model`. Not locked to anyone.
|
|
|
80
80
|
|
|
81
81
|
The same conversation, the same tools, the same project context — only the model changes. Use a strong reasoning model when you need it, swap to a fast cheap one for grunt work, never restart your session.
|
|
82
82
|
|
|
83
|
-
**Attachments.** Drag, paste, or type a path to attach images and video in the chat input. Video is sent natively to models that support it (Gemini 3.x, Kimi K2.7, MiniMax M3, MiMo-V2.5); for other models the video is saved to a temp file and the model is told to inspect it with ffmpeg or its own tools.
|
|
83
|
+
**Attachments.** Drag, paste, or type a path to attach images and video in the chat input. Video is sent natively to models that support it (Gemini 3.x, Kimi K3/K2.7 Code, MiniMax M3, MiMo-V2.5); for other models the video is saved to a temp file and the model is told to inspect it with ffmpeg or its own tools.
|
|
84
84
|
|
|
85
85
|
---
|
|
86
86
|
|
package/dist/app-sidecar.js
CHANGED
|
@@ -44,7 +44,8 @@ import { ensureAppDirs, loadSavedSettings } from "./config.js";
|
|
|
44
44
|
import { SettingsManager } from "./core/settings-manager.js";
|
|
45
45
|
import { getModel, getMaxThinkingLevel, getContextWindow, MODELS } from "./core/model-registry.js";
|
|
46
46
|
import { resolveStartOrFallback } from "./core/resolve-start.js";
|
|
47
|
-
import { getGitBranch, isGitRepo } from "./utils/git.js";
|
|
47
|
+
import { getGitBranch, getGitDirtyFileCount, isGitRepo } from "./utils/git.js";
|
|
48
|
+
import { extractPlanSteps } from "./utils/plan-steps.js";
|
|
48
49
|
import { getNextThinkingLevel, getSupportedThinkingLevels, isThinkingLevelSupported, } from "./core/thinking-level.js";
|
|
49
50
|
import { PROMPT_COMMANDS } from "./core/prompt-commands.js";
|
|
50
51
|
import { loadCustomCommands } from "./core/custom-commands.js";
|
|
@@ -1035,7 +1036,9 @@ async function createSession(deps, opts) {
|
|
|
1035
1036
|
...baseSessionOptions,
|
|
1036
1037
|
// Plan mode belongs only to the coding agent.
|
|
1037
1038
|
onEnterPlan: async (reason) => {
|
|
1039
|
+
deactivateApprovedPlan();
|
|
1038
1040
|
await session.setPlanMode(true);
|
|
1041
|
+
broadcast("plan_progress", { total: 0, completed: [] });
|
|
1039
1042
|
broadcast("plan_enter", { reason: reason ?? "" });
|
|
1040
1043
|
void session.persistAppMarker("plan", { reason: reason ?? "" }).catch(() => { });
|
|
1041
1044
|
},
|
|
@@ -1065,11 +1068,17 @@ async function createSession(deps, opts) {
|
|
|
1065
1068
|
}
|
|
1066
1069
|
}
|
|
1067
1070
|
log("INFO", "app-sidecar", "session ready", { provider, model, mode, chatAgent, cwd });
|
|
1068
|
-
//
|
|
1069
|
-
//
|
|
1071
|
+
// Workspace extras (context window, git status, background tasks). Git state
|
|
1072
|
+
// is resolved once at startup and refreshed after every run; the context
|
|
1070
1073
|
// window follows the active model.
|
|
1071
|
-
|
|
1072
|
-
|
|
1074
|
+
const [initialGitBranch, initialGitIsRepo, initialDirtyFileCount] = await Promise.all([
|
|
1075
|
+
getGitBranch(cwd).catch(() => null),
|
|
1076
|
+
isGitRepo(cwd).catch(() => false),
|
|
1077
|
+
getGitDirtyFileCount(cwd).catch(() => 0),
|
|
1078
|
+
]);
|
|
1079
|
+
let gitBranch = initialGitBranch;
|
|
1080
|
+
let gitIsRepo = initialGitIsRepo;
|
|
1081
|
+
let gitDirtyFileCount = initialDirtyFileCount;
|
|
1073
1082
|
function currentContextWindow() {
|
|
1074
1083
|
const st = session.getState();
|
|
1075
1084
|
return getContextWindow(st.model, { provider: st.provider, accountId: st.accountId });
|
|
@@ -1081,6 +1090,7 @@ async function createSession(deps, opts) {
|
|
|
1081
1090
|
contextWindow: currentContextWindow(),
|
|
1082
1091
|
gitBranch,
|
|
1083
1092
|
isGitRepo: gitIsRepo,
|
|
1093
|
+
gitDirtyFileCount,
|
|
1084
1094
|
tasks: session.listBackgroundProcesses(),
|
|
1085
1095
|
};
|
|
1086
1096
|
}
|
|
@@ -1090,8 +1100,93 @@ async function createSession(deps, opts) {
|
|
|
1090
1100
|
// failures (e.g. repeated invalid-argument errors) instead of leaving the
|
|
1091
1101
|
// fatal-abort path with no forensic trail.
|
|
1092
1102
|
const toolCallNames = new Map();
|
|
1103
|
+
// Approved-plan progress belongs beside the plan file, not in the webview.
|
|
1104
|
+
// The implementation can rewrite/expand that file mid-run, so a step count
|
|
1105
|
+
// frozen at approval time becomes dishonest (the exact stale-total bug the
|
|
1106
|
+
// CLI already fixed). Re-read the live file after tools/markers, retain the
|
|
1107
|
+
// last valid step section during transient edits, and send one authoritative
|
|
1108
|
+
// snapshot to the app.
|
|
1109
|
+
let approvedPlanPath = null;
|
|
1110
|
+
let approvedPlanTotal = 0;
|
|
1111
|
+
let approvedPlanMarkers = new Set();
|
|
1112
|
+
let approvedPlanGeneration = 0;
|
|
1113
|
+
let planMarkerTail = "";
|
|
1114
|
+
let planProgressSync = Promise.resolve(false);
|
|
1115
|
+
function planProgressPayload() {
|
|
1116
|
+
const completed = [...approvedPlanMarkers]
|
|
1117
|
+
.filter((step) => step >= 1 && step <= approvedPlanTotal)
|
|
1118
|
+
.sort((a, b) => a - b);
|
|
1119
|
+
return { total: approvedPlanTotal, completed };
|
|
1120
|
+
}
|
|
1121
|
+
async function syncApprovedPlanProgress(generation) {
|
|
1122
|
+
const planPath = approvedPlanPath;
|
|
1123
|
+
if (planPath === null || generation !== approvedPlanGeneration)
|
|
1124
|
+
return false;
|
|
1125
|
+
const content = await fs.readFile(planPath, "utf-8").catch(() => null);
|
|
1126
|
+
if (approvedPlanPath !== planPath || generation !== approvedPlanGeneration)
|
|
1127
|
+
return false;
|
|
1128
|
+
if (content !== null) {
|
|
1129
|
+
const freshTotal = extractPlanSteps(content).length;
|
|
1130
|
+
// During an in-place rewrite the step section can briefly disappear.
|
|
1131
|
+
// Keep the last real total instead of flashing 0 or declaring completion.
|
|
1132
|
+
if (freshTotal > 0 || approvedPlanTotal === 0)
|
|
1133
|
+
approvedPlanTotal = freshTotal;
|
|
1134
|
+
}
|
|
1135
|
+
broadcast("plan_progress", planProgressPayload());
|
|
1136
|
+
return (approvedPlanTotal > 0 &&
|
|
1137
|
+
Array.from({ length: approvedPlanTotal }, (_, index) => index + 1).every((step) => approvedPlanMarkers.has(step)));
|
|
1138
|
+
}
|
|
1139
|
+
function queueApprovedPlanProgressSync() {
|
|
1140
|
+
const generation = approvedPlanGeneration;
|
|
1141
|
+
planProgressSync = planProgressSync
|
|
1142
|
+
.catch(() => false)
|
|
1143
|
+
.then(() => syncApprovedPlanProgress(generation))
|
|
1144
|
+
.catch((error) => {
|
|
1145
|
+
log("WARN", "app-sidecar", "plan progress refresh failed", {
|
|
1146
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1147
|
+
});
|
|
1148
|
+
return false;
|
|
1149
|
+
});
|
|
1150
|
+
return planProgressSync;
|
|
1151
|
+
}
|
|
1152
|
+
async function activateApprovedPlan(planPath) {
|
|
1153
|
+
deactivateApprovedPlan();
|
|
1154
|
+
await session.setApprovedPlan(planPath);
|
|
1155
|
+
if (!planPath)
|
|
1156
|
+
return 0;
|
|
1157
|
+
approvedPlanPath = planPath;
|
|
1158
|
+
await queueApprovedPlanProgressSync();
|
|
1159
|
+
return approvedPlanTotal;
|
|
1160
|
+
}
|
|
1161
|
+
function deactivateApprovedPlan() {
|
|
1162
|
+
approvedPlanGeneration++;
|
|
1163
|
+
approvedPlanPath = null;
|
|
1164
|
+
approvedPlanTotal = 0;
|
|
1165
|
+
approvedPlanMarkers = new Set();
|
|
1166
|
+
planMarkerTail = "";
|
|
1167
|
+
planProgressSync = Promise.resolve(false);
|
|
1168
|
+
}
|
|
1169
|
+
function recordApprovedPlanMarkers(text) {
|
|
1170
|
+
if (approvedPlanPath === null || !text)
|
|
1171
|
+
return;
|
|
1172
|
+
const candidate = planMarkerTail + text;
|
|
1173
|
+
let changed = false;
|
|
1174
|
+
for (const match of candidate.matchAll(/\[DONE:(\d+)\]/gi)) {
|
|
1175
|
+
const step = Number.parseInt(match[1], 10);
|
|
1176
|
+
if (step >= 1 && !approvedPlanMarkers.has(step)) {
|
|
1177
|
+
approvedPlanMarkers.add(step);
|
|
1178
|
+
changed = true;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
planMarkerTail = candidate.slice(-32);
|
|
1182
|
+
if (changed)
|
|
1183
|
+
void queueApprovedPlanProgressSync();
|
|
1184
|
+
}
|
|
1093
1185
|
// Forward every relevant bus event to the webview.
|
|
1094
|
-
session.eventBus.on("text_delta", (d) =>
|
|
1186
|
+
session.eventBus.on("text_delta", (d) => {
|
|
1187
|
+
broadcast("text_delta", d);
|
|
1188
|
+
recordApprovedPlanMarkers(d.text);
|
|
1189
|
+
});
|
|
1095
1190
|
session.eventBus.on("thinking_delta", (d) => broadcast("thinking_delta", d));
|
|
1096
1191
|
session.eventBus.on("tool_call_start", (d) => {
|
|
1097
1192
|
toolCallNames.set(d.toolCallId, d.name);
|
|
@@ -1108,6 +1203,11 @@ async function createSession(deps, opts) {
|
|
|
1108
1203
|
...(d.isError ? { result: d.result.slice(0, 500) } : {}),
|
|
1109
1204
|
});
|
|
1110
1205
|
broadcast("tool_call_end", d);
|
|
1206
|
+
// Any tool can mutate the approved plan (including bash), so refresh after
|
|
1207
|
+
// every completed call while tracking is active. The file is tiny and this
|
|
1208
|
+
// keeps the displayed total aligned before the next completion marker.
|
|
1209
|
+
if (approvedPlanPath !== null)
|
|
1210
|
+
void queueApprovedPlanProgressSync();
|
|
1111
1211
|
});
|
|
1112
1212
|
// Native server tools (e.g. Anthropic web_search) do NOT end the turn — text
|
|
1113
1213
|
// streams before and after them in the SAME turn. The webview must reset its
|
|
@@ -1132,7 +1232,6 @@ async function createSession(deps, opts) {
|
|
|
1132
1232
|
});
|
|
1133
1233
|
const cancelledRunEndGenerations = new Set();
|
|
1134
1234
|
let pendingCancelDrain = null;
|
|
1135
|
-
let titleGenerated = false;
|
|
1136
1235
|
// Bumped by /cancel — a run whose cancel generation changed mid-flight was
|
|
1137
1236
|
// canceled and earns no XP.
|
|
1138
1237
|
let cancelGeneration = 0;
|
|
@@ -1347,20 +1446,6 @@ async function createSession(deps, opts) {
|
|
|
1347
1446
|
});
|
|
1348
1447
|
return ken;
|
|
1349
1448
|
}
|
|
1350
|
-
// Resumed session: if it already has a conversation, generate its title now so
|
|
1351
|
-
// the title bar shows it immediately on load (not just after the next prompt).
|
|
1352
|
-
{
|
|
1353
|
-
const hasHistory = session
|
|
1354
|
-
.getMessages()
|
|
1355
|
-
.some((m) => m.role === "user" || m.role === "assistant");
|
|
1356
|
-
if (hasHistory) {
|
|
1357
|
-
titleGenerated = true;
|
|
1358
|
-
void session.generateTitle().then((title) => {
|
|
1359
|
-
if (title)
|
|
1360
|
-
broadcast("session_title", { title });
|
|
1361
|
-
});
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
1449
|
function abortOwnedWork() {
|
|
1365
1450
|
cancelGeneration++;
|
|
1366
1451
|
abort.abort();
|
|
@@ -1423,10 +1508,34 @@ async function createSession(deps, opts) {
|
|
|
1423
1508
|
// Fire-and-forget — XP must never delay or break run teardown.
|
|
1424
1509
|
void progress.awardRun(cwd, runStartedAt, opts.id);
|
|
1425
1510
|
}
|
|
1426
|
-
// A run may have switched branches
|
|
1427
|
-
// background tasks
|
|
1428
|
-
gitBranch = await
|
|
1429
|
-
|
|
1511
|
+
// A run may have switched branches, changed files, or spawned/finished
|
|
1512
|
+
// background tasks. Refresh the workspace extras once it settles.
|
|
1513
|
+
[gitBranch, gitIsRepo, gitDirtyFileCount] = await Promise.all([
|
|
1514
|
+
getGitBranch(cwd).catch(() => gitBranch),
|
|
1515
|
+
isGitRepo(cwd).catch(() => gitIsRepo),
|
|
1516
|
+
getGitDirtyFileCount(cwd).catch(() => gitDirtyFileCount),
|
|
1517
|
+
]);
|
|
1518
|
+
// Serialize behind any marker/tool-triggered refresh so the terminal
|
|
1519
|
+
// progress snapshot uses the live plan file. Once every canonical step
|
|
1520
|
+
// is complete, remove the approved plan from future system prompts and
|
|
1521
|
+
// clear the widget before run_end paints the idle activity bar.
|
|
1522
|
+
if (runSucceeded &&
|
|
1523
|
+
!cancelled &&
|
|
1524
|
+
approvedPlanPath !== null &&
|
|
1525
|
+
(await queueApprovedPlanProgressSync())) {
|
|
1526
|
+
try {
|
|
1527
|
+
await session.setApprovedPlan(undefined);
|
|
1528
|
+
deactivateApprovedPlan();
|
|
1529
|
+
broadcast("plan_progress", { total: 0, completed: [] });
|
|
1530
|
+
}
|
|
1531
|
+
catch (error) {
|
|
1532
|
+
// Keep tracking when prompt cleanup fails; hiding the widget here
|
|
1533
|
+
// would claim completion while the approved-plan contract remained.
|
|
1534
|
+
log("WARN", "app-sidecar", "completed plan cleanup failed", {
|
|
1535
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1430
1539
|
if (ownsGeneration)
|
|
1431
1540
|
finishOwnedGeneration(generation, false);
|
|
1432
1541
|
// A cancelled injected run is still owned by the surrounding autopilot
|
|
@@ -1445,13 +1554,6 @@ async function createSession(deps, opts) {
|
|
|
1445
1554
|
broadcast("tasks_list", { tasks: pruneDoneTasksSync(cwd) });
|
|
1446
1555
|
broadcast("queued", { count: session.getQueuedCount() });
|
|
1447
1556
|
broadcast("extras", footerExtras());
|
|
1448
|
-
if (!titleGenerated) {
|
|
1449
|
-
titleGenerated = true;
|
|
1450
|
-
void session.generateTitle().then((title) => {
|
|
1451
|
-
if (title)
|
|
1452
|
-
broadcast("session_title", { title });
|
|
1453
|
-
});
|
|
1454
|
-
}
|
|
1455
1557
|
}
|
|
1456
1558
|
}
|
|
1457
1559
|
// ── Autopilot orchestration ─────────────────────────────────
|
|
@@ -1582,22 +1684,22 @@ async function createSession(deps, opts) {
|
|
|
1582
1684
|
if (pendingPlanPath === null || planGeneration !== planGenAtReview)
|
|
1583
1685
|
return false;
|
|
1584
1686
|
const planPath = pendingPlanPath;
|
|
1687
|
+
let planTotal;
|
|
1585
1688
|
try {
|
|
1586
|
-
await session.newSession();
|
|
1689
|
+
await session.newSession(true);
|
|
1587
1690
|
injectedAutopilotPrompts = [];
|
|
1588
|
-
|
|
1589
|
-
await session.setApprovedPlan(planPath);
|
|
1691
|
+
planTotal = await activateApprovedPlan(planPath);
|
|
1590
1692
|
}
|
|
1591
1693
|
catch (err) {
|
|
1592
1694
|
broadcastError("autopilot_error", "autopilot plan accept failed", err);
|
|
1593
1695
|
return false;
|
|
1594
1696
|
}
|
|
1595
1697
|
clearPendingPlan();
|
|
1596
|
-
//
|
|
1597
|
-
//
|
|
1598
|
-
// session_reset clears it — accepted must land first.
|
|
1698
|
+
// Keep the approval marker ahead of the reset, then seed the reset
|
|
1699
|
+
// with the sidecar's canonical count from the actual plan file.
|
|
1599
1700
|
broadcast("autopilot_plan_accepted", {});
|
|
1600
|
-
broadcast("session_reset", {});
|
|
1701
|
+
broadcast("session_reset", { planTotal });
|
|
1702
|
+
broadcast("plan_progress", planProgressPayload());
|
|
1601
1703
|
// Persisted into the NEW session so a resume shows the marker.
|
|
1602
1704
|
void session.persistAutopilotMarker("plan_approved");
|
|
1603
1705
|
return true;
|
|
@@ -1755,9 +1857,9 @@ async function createSession(deps, opts) {
|
|
|
1755
1857
|
return false;
|
|
1756
1858
|
// Fresh session per task so one task's context never bleeds into the next.
|
|
1757
1859
|
await session.newSession();
|
|
1860
|
+
deactivateApprovedPlan();
|
|
1758
1861
|
injectedAutopilotPrompts = [];
|
|
1759
1862
|
clearPendingPlan();
|
|
1760
|
-
titleGenerated = false;
|
|
1761
1863
|
broadcast("session_reset", {});
|
|
1762
1864
|
markTaskInProgress(cwd, task.id);
|
|
1763
1865
|
broadcast("tasks_list", { tasks: loadTasksSync(cwd) });
|
|
@@ -1838,6 +1940,30 @@ async function createSession(deps, opts) {
|
|
|
1838
1940
|
tasksPoll.unref?.();
|
|
1839
1941
|
};
|
|
1840
1942
|
scheduleTasksPoll(1500);
|
|
1943
|
+
// Files can change outside the agent (editor saves, terminal commits), so keep
|
|
1944
|
+
// the dirty count current while idle. Branch/repo state already refreshes after
|
|
1945
|
+
// agent runs; polling only the count avoids spawning three git processes per tick.
|
|
1946
|
+
let gitPoll;
|
|
1947
|
+
let gitPollStopped = false;
|
|
1948
|
+
const scheduleGitPoll = (delay) => {
|
|
1949
|
+
if (gitPollStopped)
|
|
1950
|
+
return;
|
|
1951
|
+
gitPoll = setTimeout(() => {
|
|
1952
|
+
void getGitDirtyFileCount(cwd)
|
|
1953
|
+
.catch(() => gitDirtyFileCount)
|
|
1954
|
+
.then((nextDirtyFileCount) => {
|
|
1955
|
+
if (gitPollStopped)
|
|
1956
|
+
return;
|
|
1957
|
+
if (nextDirtyFileCount !== gitDirtyFileCount) {
|
|
1958
|
+
gitDirtyFileCount = nextDirtyFileCount;
|
|
1959
|
+
broadcast("extras", footerExtras());
|
|
1960
|
+
}
|
|
1961
|
+
scheduleGitPoll(5000);
|
|
1962
|
+
});
|
|
1963
|
+
}, delay);
|
|
1964
|
+
gitPoll.unref?.();
|
|
1965
|
+
};
|
|
1966
|
+
scheduleGitPoll(5000);
|
|
1841
1967
|
function readBody(req) {
|
|
1842
1968
|
return new Promise((resolve, reject) => {
|
|
1843
1969
|
const chunks = [];
|
|
@@ -2984,6 +3110,7 @@ async function createSession(deps, opts) {
|
|
|
2984
3110
|
if (mode === "chat") {
|
|
2985
3111
|
await session.persistAppMarker("agent_handoff", { chatAgent });
|
|
2986
3112
|
}
|
|
3113
|
+
deactivateApprovedPlan();
|
|
2987
3114
|
injectedAutopilotPrompts = [];
|
|
2988
3115
|
clearPendingPlan();
|
|
2989
3116
|
broadcast("session_reset", {});
|
|
@@ -3036,12 +3163,12 @@ async function createSession(deps, opts) {
|
|
|
3036
3163
|
broadcast("autopilot_ignored", {});
|
|
3037
3164
|
}
|
|
3038
3165
|
try {
|
|
3039
|
-
await session.newSession();
|
|
3166
|
+
await session.newSession(true);
|
|
3040
3167
|
injectedAutopilotPrompts = [];
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
broadcast("
|
|
3044
|
-
json(res, 200, { ok: true });
|
|
3168
|
+
const planTotal = await activateApprovedPlan(planPath);
|
|
3169
|
+
broadcast("session_reset", { planTotal });
|
|
3170
|
+
broadcast("plan_progress", planProgressPayload());
|
|
3171
|
+
json(res, 200, { ok: true, planTotal });
|
|
3045
3172
|
}
|
|
3046
3173
|
catch (err) {
|
|
3047
3174
|
json(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -3469,6 +3596,9 @@ async function createSession(deps, opts) {
|
|
|
3469
3596
|
tasksPollStopped = true;
|
|
3470
3597
|
if (tasksPoll)
|
|
3471
3598
|
clearTimeout(tasksPoll);
|
|
3599
|
+
gitPollStopped = true;
|
|
3600
|
+
if (gitPoll)
|
|
3601
|
+
clearTimeout(gitPoll);
|
|
3472
3602
|
// Stop the Telegram serve loop + dispose its per-chat sessions.
|
|
3473
3603
|
if (serveController)
|
|
3474
3604
|
await serveController.stop().catch(() => { });
|