@kenkaiiii/ggcoder 4.3.193 → 4.3.195

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 (58) hide show
  1. package/dist/cli.js +17 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/core/agent-session.d.ts +24 -0
  4. package/dist/core/agent-session.d.ts.map +1 -1
  5. package/dist/core/agent-session.js +101 -1
  6. package/dist/core/agent-session.js.map +1 -1
  7. package/dist/core/prompt-commands.js +3 -3
  8. package/dist/core/prompt-commands.test.js +4 -0
  9. package/dist/core/prompt-commands.test.js.map +1 -1
  10. package/dist/core/repomap-context.d.ts +11 -0
  11. package/dist/core/repomap-context.d.ts.map +1 -0
  12. package/dist/core/repomap-context.js +68 -0
  13. package/dist/core/repomap-context.js.map +1 -0
  14. package/dist/core/repomap-context.test.d.ts +2 -0
  15. package/dist/core/repomap-context.test.d.ts.map +1 -0
  16. package/dist/core/repomap-context.test.js +47 -0
  17. package/dist/core/repomap-context.test.js.map +1 -0
  18. package/dist/core/repomap.d.ts +74 -0
  19. package/dist/core/repomap.d.ts.map +1 -0
  20. package/dist/core/repomap.js +897 -0
  21. package/dist/core/repomap.js.map +1 -0
  22. package/dist/core/repomap.test.d.ts +2 -0
  23. package/dist/core/repomap.test.d.ts.map +1 -0
  24. package/dist/core/repomap.test.js +444 -0
  25. package/dist/core/repomap.test.js.map +1 -0
  26. package/dist/core/slash-commands.d.ts +2 -0
  27. package/dist/core/slash-commands.d.ts.map +1 -1
  28. package/dist/core/slash-commands.js +15 -0
  29. package/dist/core/slash-commands.js.map +1 -1
  30. package/dist/tools/edit.d.ts +1 -1
  31. package/dist/tools/edit.d.ts.map +1 -1
  32. package/dist/tools/edit.js +2 -1
  33. package/dist/tools/edit.js.map +1 -1
  34. package/dist/tools/edit.test.js +20 -0
  35. package/dist/tools/edit.test.js.map +1 -1
  36. package/dist/tools/index.d.ts +4 -0
  37. package/dist/tools/index.d.ts.map +1 -1
  38. package/dist/tools/index.js +3 -3
  39. package/dist/tools/index.js.map +1 -1
  40. package/dist/tools/read.d.ts +1 -1
  41. package/dist/tools/read.d.ts.map +1 -1
  42. package/dist/tools/read.js +2 -1
  43. package/dist/tools/read.js.map +1 -1
  44. package/dist/tools/write.d.ts +1 -1
  45. package/dist/tools/write.d.ts.map +1 -1
  46. package/dist/tools/write.js +2 -1
  47. package/dist/tools/write.js.map +1 -1
  48. package/dist/tools/write.test.js +19 -0
  49. package/dist/tools/write.test.js.map +1 -1
  50. package/dist/ui/App.d.ts +6 -0
  51. package/dist/ui/App.d.ts.map +1 -1
  52. package/dist/ui/App.js +129 -29
  53. package/dist/ui/App.js.map +1 -1
  54. package/dist/ui/render.d.ts +6 -0
  55. package/dist/ui/render.d.ts.map +1 -1
  56. package/dist/ui/render.js +2 -0
  57. package/dist/ui/render.js.map +1 -1
  58. package/package.json +7 -5
package/dist/ui/App.js CHANGED
@@ -52,6 +52,8 @@ import { loadCustomCommands } from "../core/custom-commands.js";
52
52
  import { buildSystemPrompt } from "../system-prompt.js";
53
53
  import { detectLanguages, LANGUAGE_DISPLAY_NAMES, } from "../core/language-detector.js";
54
54
  import { detectVerifyCommands } from "../core/verify-commands.js";
55
+ import { FOCUSED_REPO_MAP_MAX_CHARS, FIRST_TURN_REPO_MAP_MAX_CHARS, buildRepoMap, createRepoMapCache, } from "../core/repomap.js";
56
+ import { getLatestUserText, injectRepoMapContextMessages, stripRepoMapContextMessages, } from "../core/repomap-context.js";
55
57
  import { extractPlanSteps, findCompletedMarkers, markStepsCompleted, segmentDisplayText, stripDoneMarkers, } from "../utils/plan-steps.js";
56
58
  import { getMCPServers } from "../core/mcp/index.js";
57
59
  import { trimFlushedItems, flushOnTurnText, flushOnTurnEnd, flushOverflow, } from "./live-item-flush.js";
@@ -337,6 +339,12 @@ export function App(props) {
337
339
  const currentToolsRef = useRef(props.tools);
338
340
  const [thinkingEnabled, setThinkingEnabled] = useState(!!props.thinking);
339
341
  const messagesRef = useRef(props.sessionStore?.messages ?? props.messages);
342
+ const repoMapInjectionEnabledRef = useRef(true);
343
+ const repoMapDirtyRef = useRef(true);
344
+ const repoMapMarkdownRef = useRef("");
345
+ const repoMapSnapshotRef = useRef(undefined);
346
+ const repoMapChangedCountRef = useRef(0);
347
+ const repoMapCacheRef = useRef(createRepoMapCache());
340
348
  const [planAutoExpand, setPlanAutoExpand] = useState(props.sessionStore?.planAutoExpand ?? false);
341
349
  const approvedPlanPathRef = useRef(props.sessionStore?.approvedPlanPath);
342
350
  const planStepsRef = useRef(props.sessionStore?.planSteps ?? []);
@@ -740,59 +748,92 @@ export function App(props) {
740
748
  return messages; // Return unchanged on failure
741
749
  }
742
750
  }, [currentModel, currentProvider, activeApiKey]);
751
+ const getRepoMapSignalCount = useCallback(() => {
752
+ return ((props.repoMapChangedFilesRef?.current.size ?? 0) +
753
+ (props.repoMapReadFilesRef?.current.size ?? 0));
754
+ }, [props.repoMapChangedFilesRef, props.repoMapReadFilesRef]);
755
+ const getRepoMapBudget = useCallback(() => {
756
+ const userTurns = messagesRef.current.filter((message) => message.role === "user").length;
757
+ const readCount = props.repoMapReadFilesRef?.current.size ?? 0;
758
+ if (userTurns <= 1 && readCount === 0)
759
+ return FIRST_TURN_REPO_MAP_MAX_CHARS;
760
+ if (readCount > 0)
761
+ return FOCUSED_REPO_MAP_MAX_CHARS;
762
+ return FOCUSED_REPO_MAP_MAX_CHARS + 1000;
763
+ }, [props.repoMapReadFilesRef]);
764
+ const refreshRepoMap = useCallback(async (latestUserPrompt) => {
765
+ const rendered = await buildRepoMap({
766
+ cwd: cwdRef.current,
767
+ maxChars: getRepoMapBudget(),
768
+ changedFiles: [...(props.repoMapChangedFilesRef?.current ?? new Set())],
769
+ readFiles: [...(props.repoMapReadFilesRef?.current ?? new Set())],
770
+ focusTerms: latestUserPrompt ? [latestUserPrompt] : [],
771
+ cache: repoMapCacheRef.current,
772
+ });
773
+ repoMapMarkdownRef.current = rendered.markdown;
774
+ repoMapSnapshotRef.current = rendered.snapshot;
775
+ repoMapChangedCountRef.current = getRepoMapSignalCount();
776
+ repoMapDirtyRef.current = false;
777
+ return rendered.markdown;
778
+ }, [
779
+ getRepoMapBudget,
780
+ getRepoMapSignalCount,
781
+ props.repoMapChangedFilesRef,
782
+ props.repoMapReadFilesRef,
783
+ ]);
784
+ const stripRepoMapMessages = useCallback((messages) => {
785
+ return stripRepoMapContextMessages(messages);
786
+ }, []);
787
+ const injectRepoMapContext = useCallback(async (messages) => {
788
+ if (!repoMapInjectionEnabledRef.current)
789
+ return stripRepoMapMessages(messages);
790
+ const stripped = stripRepoMapMessages(messages);
791
+ const latestUserPrompt = getLatestUserText(stripped);
792
+ const signalCount = getRepoMapSignalCount();
793
+ if (signalCount !== repoMapChangedCountRef.current)
794
+ repoMapDirtyRef.current = true;
795
+ if (repoMapDirtyRef.current || !repoMapMarkdownRef.current) {
796
+ await refreshRepoMap(latestUserPrompt);
797
+ }
798
+ if (!repoMapMarkdownRef.current)
799
+ return stripped;
800
+ return injectRepoMapContextMessages(stripped, repoMapMarkdownRef.current);
801
+ }, [props.repoMapChangedFilesRef, props.repoMapReadFilesRef, refreshRepoMap, stripRepoMapMessages]);
743
802
  /**
744
803
  * transformContext callback for the agent loop.
745
804
  * Called before each LLM call and on context overflow.
746
- * Checks if auto-compaction is needed and runs it.
747
- *
748
- * Uses actual API-reported token counts (from previous turn_end) when
749
- * available, falling back to the character-based estimate. A 30-second
750
- * cooldown prevents repeated compaction — matching the pattern used by
751
- * Mysti, openclaw, and other real-world agent frameworks.
805
+ * Compacts persistent chat only, then injects the dynamic repo map transiently.
752
806
  */
753
807
  const transformContext = useCallback(async (messages, options) => {
808
+ const stripped = stripRepoMapMessages(messages);
754
809
  const settings = settingsRef.current;
755
810
  const autoCompact = settings?.get("autoCompact") ?? true;
756
811
  const threshold = settings?.get("compactThreshold") ?? 0.8;
757
812
  // Force-compact on context overflow regardless of settings
758
813
  if (options?.force) {
759
- const result = await compactConversation(messages);
814
+ const result = await compactConversation(stripped);
760
815
  lastCompactionTimeRef.current = Date.now();
761
- return result;
816
+ return injectRepoMapContext(result);
762
817
  }
763
818
  if (!autoCompact)
764
- return messages;
819
+ return injectRepoMapContext(stripped);
765
820
  // Time-based cooldown: skip if compaction ran within the last 30 seconds
766
821
  if (Date.now() - lastCompactionTimeRef.current < 30_000) {
767
822
  log("INFO", "compaction", `Skipping compaction — cooldown active`);
768
- return messages;
823
+ return injectRepoMapContext(stripped);
769
824
  }
770
825
  const contextWindow = getContextWindow(currentModel);
771
- // Reserve = max output budget + ~5K headroom for system prompt + tool
772
- // schemas. Otherwise the API rejects requests where the prompt fits the
773
- // window but leaves no room for the response (e.g. Codex Mini at 200K
774
- // ctx / 100K out — pre-turn estimate may say 160K but a 100K reasoning
775
- // response then overflows).
776
826
  const modelInfo = getModel(currentModel);
777
827
  const reserveTokens = (modelInfo?.maxOutputTokens ?? 0) + 5_000;
778
- // Prefer actual API-reported tokens over char-based estimate, but only
779
- // when the token count was recorded AFTER the most recent compaction.
780
- // A count from before compaction is stale — it reflects the old context
781
- // size and would trigger compaction again immediately for no reason.
782
828
  const tokensFresh = lastActualTokensTimestampRef.current > lastCompactionTimeRef.current;
783
829
  const actualTokens = lastActualTokensRef.current > 0 && tokensFresh ? lastActualTokensRef.current : undefined;
784
- if (shouldCompact(messages, contextWindow, threshold, actualTokens, reserveTokens)) {
785
- const before = messages.length;
786
- const result = await compactConversation(messages);
830
+ if (shouldCompact(stripped, contextWindow, threshold, actualTokens, reserveTokens)) {
831
+ const result = await compactConversation(stripped);
787
832
  lastCompactionTimeRef.current = Date.now();
788
- // If compaction was a no-op (e.g. too few middle messages to summarize),
789
- // return the original reference so the agent loop doesn't replace messages.
790
- if (result.length === before)
791
- return messages;
792
- return result;
833
+ return injectRepoMapContext(result);
793
834
  }
794
- return messages;
795
- }, [currentModel, compactConversation]);
835
+ return injectRepoMapContext(stripped);
836
+ }, [currentModel, compactConversation, injectRepoMapContext, stripRepoMapMessages]);
796
837
  // ── Background task bar state (external store) ──────────
797
838
  const { bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, } = useTaskBarStore();
798
839
  useTaskBarPolling(props.processManager);
@@ -828,6 +869,7 @@ export function App(props) {
828
869
  transformContext,
829
870
  }, {
830
871
  onComplete: useCallback(() => {
872
+ messagesRef.current = stripRepoMapMessages(messagesRef.current);
831
873
  persistNewMessages();
832
874
  // Auto-clear plan progress and approved plan when all steps are completed
833
875
  const steps = planStepsRef.current;
@@ -880,6 +922,7 @@ export function App(props) {
880
922
  }
881
923
  }, [
882
924
  persistNewMessages,
925
+ stripRepoMapMessages,
883
926
  planMode,
884
927
  props.cwd,
885
928
  props.skills,
@@ -1706,6 +1749,47 @@ export function App(props) {
1706
1749
  setLiveItems([{ kind: "plan_event", event: "dismissed", id: getId() }]);
1707
1750
  return;
1708
1751
  }
1752
+ // Handle /map — show, refresh, or toggle dynamic repo map injection
1753
+ if (trimmed === "/map" ||
1754
+ trimmed === "/map refresh" ||
1755
+ trimmed === "/map on" ||
1756
+ trimmed === "/map off") {
1757
+ const action = trimmed.slice("/map".length).trim();
1758
+ if (action === "on") {
1759
+ repoMapInjectionEnabledRef.current = true;
1760
+ repoMapDirtyRef.current = true;
1761
+ setLiveItems((prev) => [
1762
+ ...prev,
1763
+ { kind: "info", text: "Dynamic repo map injection is on.", id: getId() },
1764
+ ]);
1765
+ return;
1766
+ }
1767
+ if (action === "off") {
1768
+ repoMapInjectionEnabledRef.current = false;
1769
+ messagesRef.current = stripRepoMapMessages(messagesRef.current);
1770
+ setLiveItems((prev) => [
1771
+ ...prev,
1772
+ {
1773
+ kind: "info",
1774
+ text: "Dynamic repo map injection is off for this session.",
1775
+ id: getId(),
1776
+ },
1777
+ ]);
1778
+ return;
1779
+ }
1780
+ if (action === "refresh")
1781
+ repoMapDirtyRef.current = true;
1782
+ const markdown = await refreshRepoMap(getLatestUserText(messagesRef.current));
1783
+ setLiveItems((prev) => [
1784
+ ...prev,
1785
+ {
1786
+ kind: "info",
1787
+ text: formatRepoMapCommandOutput(repoMapInjectionEnabledRef.current, markdown, action === "refresh"),
1788
+ id: getId(),
1789
+ },
1790
+ ]);
1791
+ return;
1792
+ }
1709
1793
  // Handle /plans — open plan pane
1710
1794
  if (trimmed === "/plans") {
1711
1795
  if (props.resetUI && props.sessionStore && !agentLoop.isRunning) {
@@ -1895,6 +1979,8 @@ export function App(props) {
1895
1979
  compactConversation,
1896
1980
  rebuildSystemPrompt,
1897
1981
  replaceSystemPrompt,
1982
+ refreshRepoMap,
1983
+ stripRepoMapMessages,
1898
1984
  ]);
1899
1985
  const handleDoubleExit = useDoublePress(setExitPending, () => process.exit(0));
1900
1986
  const handleAbort = useCallback(() => {
@@ -2280,6 +2366,13 @@ export function App(props) {
2280
2366
  log("WARN", "pixel", `chdir failed: ${err.message}`);
2281
2367
  }
2282
2368
  cwdRef.current = prep.projectPath;
2369
+ repoMapDirtyRef.current = true;
2370
+ repoMapMarkdownRef.current = "";
2371
+ repoMapSnapshotRef.current = undefined;
2372
+ repoMapChangedCountRef.current = 0;
2373
+ repoMapCacheRef.current = createRepoMapCache();
2374
+ props.repoMapChangedFilesRef?.current.clear();
2375
+ props.repoMapReadFilesRef?.current.clear();
2283
2376
  setDisplayedCwd(prep.projectPath);
2284
2377
  let toolsForPixelFix = currentToolsRef.current;
2285
2378
  if (props.rebuildToolsForCwd) {
@@ -2618,4 +2711,11 @@ export function App(props) {
2618
2711
  ]);
2619
2712
  }, cwd: props.cwd, commands: allCommands, eyesCount: eyesCount }), overlay === "model" ? (_jsx(ModelSelector, { onSelect: handleModelSelect, onCancel: () => setOverlay(null), loggedInProviders: props.loggedInProviders ?? [currentProvider], currentModel: currentModel, currentProvider: currentProvider })) : overlay === "theme" ? (_jsx(ThemeSelector, { onSelect: handleThemeSelect, onCancel: () => setOverlay(null), currentTheme: theme.name })) : (_jsx(Footer, { model: currentModel, tokensIn: agentLoop.contextUsed, cwd: displayedCwd, gitBranch: gitBranch, thinkingLevel: thinkingEnabled ? getMaxThinkingLevel(currentModel) : undefined, planMode: planMode, exitPending: exitPending })), (bgTasks.length > 0 || (eyesCount !== undefined && eyesCount > 0) || updatePending) && (_jsxs(Box, { children: [bgTasks.length > 0 && (_jsx(BackgroundTasksBar, { tasks: bgTasks, focused: taskBarFocused, expanded: taskBarExpanded, selectedIndex: selectedTaskIndex, onExpand: handleTaskBarExpand, onCollapse: handleTaskBarCollapse, onKill: handleTaskKill, onExit: handleTaskBarExit, onNavigate: handleTaskNavigate })), eyesCount !== undefined && eyesCount > 0 && (_jsx(Box, { paddingLeft: bgTasks.length > 0 ? 2 : 1, paddingRight: 1, children: _jsx(Text, { color: theme.accent, bold: true, children: `${eyesCount} eyes signal${eyesCount === 1 ? "" : "s"} · Run /eyes-improve to enhance GG Coder` }) })), updatePending && (_jsx(Box, { paddingLeft: bgTasks.length > 0 || (eyesCount !== undefined && eyesCount > 0) ? 2 : 1, paddingRight: 1, children: _jsx(Text, { color: theme.success, bold: true, children: "\u2728 Update ready \u00B7 restart to apply" }) }))] }))] }))] }));
2620
2713
  }
2714
+ function formatRepoMapCommandOutput(enabled, markdown, refreshed) {
2715
+ const status = enabled ? "on" : "off";
2716
+ const prefix = refreshed
2717
+ ? `Dynamic repo map refreshed · injection: ${status}`
2718
+ : `Dynamic repo map · injection: ${status}`;
2719
+ return `${prefix}\n\n${markdown}`;
2720
+ }
2621
2721
  //# sourceMappingURL=App.js.map