@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.
Files changed (98) hide show
  1. package/README.md +2 -2
  2. package/dist/app-sidecar.js +175 -45
  3. package/dist/app-sidecar.js.map +1 -1
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +9 -2
  6. package/dist/cli.js.map +1 -1
  7. package/dist/core/agent-session-tool-result-policy.test.d.ts +2 -0
  8. package/dist/core/agent-session-tool-result-policy.test.d.ts.map +1 -0
  9. package/dist/core/agent-session-tool-result-policy.test.js +12 -0
  10. package/dist/core/agent-session-tool-result-policy.test.js.map +1 -0
  11. package/dist/core/agent-session.d.ts +8 -8
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +52 -44
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/auth-providers.js +1 -1
  16. package/dist/core/auth-providers.js.map +1 -1
  17. package/dist/core/compaction/compactor.test.js +1 -0
  18. package/dist/core/compaction/compactor.test.js.map +1 -1
  19. package/dist/core/project-discovery.d.ts +4 -11
  20. package/dist/core/project-discovery.d.ts.map +1 -1
  21. package/dist/core/project-discovery.js +33 -30
  22. package/dist/core/project-discovery.js.map +1 -1
  23. package/dist/core/project-discovery.test.js +148 -0
  24. package/dist/core/project-discovery.test.js.map +1 -1
  25. package/dist/core/session-compaction.d.ts +3 -0
  26. package/dist/core/session-compaction.d.ts.map +1 -1
  27. package/dist/core/session-compaction.js +14 -1
  28. package/dist/core/session-compaction.js.map +1 -1
  29. package/dist/core/session-compaction.test.js +5 -0
  30. package/dist/core/session-compaction.test.js.map +1 -1
  31. package/dist/core/session-manager.d.ts +8 -1
  32. package/dist/core/session-manager.d.ts.map +1 -1
  33. package/dist/core/session-manager.js +7 -1
  34. package/dist/core/session-manager.js.map +1 -1
  35. package/dist/core/session-manager.test.js +17 -0
  36. package/dist/core/session-manager.test.js.map +1 -1
  37. package/dist/core/session-preview.d.ts +11 -0
  38. package/dist/core/session-preview.d.ts.map +1 -0
  39. package/dist/core/session-preview.js +50 -0
  40. package/dist/core/session-preview.js.map +1 -0
  41. package/dist/core/subagent-manager.d.ts +2 -4
  42. package/dist/core/subagent-manager.d.ts.map +1 -1
  43. package/dist/core/subagent-manager.js +4 -0
  44. package/dist/core/subagent-manager.js.map +1 -1
  45. package/dist/core/subagent-manager.test.js +6 -0
  46. package/dist/core/subagent-manager.test.js.map +1 -1
  47. package/dist/tools/subagent-shared.d.ts +8 -0
  48. package/dist/tools/subagent-shared.d.ts.map +1 -1
  49. package/dist/tools/subagent-shared.js.map +1 -1
  50. package/dist/tools/subagent.d.ts +3 -8
  51. package/dist/tools/subagent.d.ts.map +1 -1
  52. package/dist/tools/subagent.js +10 -8
  53. package/dist/tools/subagent.js.map +1 -1
  54. package/dist/tools/subagent.test.js +17 -1
  55. package/dist/tools/subagent.test.js.map +1 -1
  56. package/dist/ui/App.d.ts +0 -2
  57. package/dist/ui/App.d.ts.map +1 -1
  58. package/dist/ui/App.js +3 -66
  59. package/dist/ui/App.js.map +1 -1
  60. package/dist/ui/components/SubAgentPanel.d.ts +2 -0
  61. package/dist/ui/components/SubAgentPanel.d.ts.map +1 -1
  62. package/dist/ui/components/SubAgentPanel.js +4 -2
  63. package/dist/ui/components/SubAgentPanel.js.map +1 -1
  64. package/dist/ui/hooks/useSessionPersistence.d.ts.map +1 -1
  65. package/dist/ui/hooks/useSessionPersistence.js +3 -0
  66. package/dist/ui/hooks/useSessionPersistence.js.map +1 -1
  67. package/dist/ui/hooks/useTerminalTitle.d.ts +3 -3
  68. package/dist/ui/hooks/useTerminalTitle.d.ts.map +1 -1
  69. package/dist/ui/hooks/useTerminalTitle.js +5 -9
  70. package/dist/ui/hooks/useTerminalTitle.js.map +1 -1
  71. package/dist/ui/login.d.ts.map +1 -1
  72. package/dist/ui/login.js +5 -1
  73. package/dist/ui/login.js.map +1 -1
  74. package/dist/ui/render.d.ts +0 -2
  75. package/dist/ui/render.d.ts.map +1 -1
  76. package/dist/ui/render.js +0 -4
  77. package/dist/ui/render.js.map +1 -1
  78. package/dist/ui/render.test.js +10 -1
  79. package/dist/ui/render.test.js.map +1 -1
  80. package/dist/ui/terminal-history.js +6 -2
  81. package/dist/ui/terminal-history.js.map +1 -1
  82. package/dist/ui/terminal-history.test.js +2 -2
  83. package/dist/ui/terminal-history.test.js.map +1 -1
  84. package/dist/ui/tui-history-parity.test.js +8 -1
  85. package/dist/ui/tui-history-parity.test.js.map +1 -1
  86. package/dist/utils/git.d.ts +2 -0
  87. package/dist/utils/git.d.ts.map +1 -1
  88. package/dist/utils/git.js +12 -0
  89. package/dist/utils/git.js.map +1 -1
  90. package/dist/utils/git.test.d.ts +2 -0
  91. package/dist/utils/git.test.d.ts.map +1 -0
  92. package/dist/utils/git.test.js +36 -0
  93. package/dist/utils/git.test.js.map +1 -0
  94. package/package.json +4 -4
  95. package/dist/utils/session-title.d.ts +0 -19
  96. package/dist/utils/session-title.d.ts.map +0 -1
  97. package/dist/utils/session-title.js +0 -82
  98. 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
 
@@ -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
- // Footer extras (context window, git branch, background tasks). The git
1069
- // branch is resolved once at startup and refreshed lazily; the context
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
- let gitBranch = await getGitBranch(cwd).catch(() => null);
1072
- let gitIsRepo = await isGitRepo(cwd).catch(() => false);
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) => broadcast("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 (git checkout) or spawned/finished
1427
- // background tasks refresh the footer extras once it settles.
1428
- gitBranch = await getGitBranch(cwd).catch(() => gitBranch);
1429
- gitIsRepo = await isGitRepo(cwd).catch(() => gitIsRepo);
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
- titleGenerated = false;
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
- // Ordering is load-bearing: the webview reads its still-open plan
1597
- // modal state (step count) on autopilot_plan_accepted, and
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
- titleGenerated = false;
3042
- await session.setApprovedPlan(planPath);
3043
- broadcast("session_reset", {});
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(() => { });