@kenkaiiii/ggcoder 5.8.0 → 5.8.2

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 (44) hide show
  1. package/dist/app-sidecar.js +168 -34
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/core/agent-session.d.ts +32 -1
  4. package/dist/core/agent-session.d.ts.map +1 -1
  5. package/dist/core/agent-session.js +129 -19
  6. package/dist/core/agent-session.js.map +1 -1
  7. package/dist/core/mcp/deferred-catalog.d.ts +28 -0
  8. package/dist/core/mcp/deferred-catalog.d.ts.map +1 -0
  9. package/dist/core/mcp/deferred-catalog.js +72 -0
  10. package/dist/core/mcp/deferred-catalog.js.map +1 -0
  11. package/dist/core/session-history.d.ts +51 -0
  12. package/dist/core/session-history.d.ts.map +1 -0
  13. package/dist/core/session-history.js +145 -0
  14. package/dist/core/session-history.js.map +1 -0
  15. package/dist/core/session-history.test.d.ts +2 -0
  16. package/dist/core/session-history.test.d.ts.map +1 -0
  17. package/dist/core/session-history.test.js +95 -0
  18. package/dist/core/session-history.test.js.map +1 -0
  19. package/dist/core/session-manager.d.ts +16 -0
  20. package/dist/core/session-manager.d.ts.map +1 -1
  21. package/dist/core/session-manager.js +32 -0
  22. package/dist/core/session-manager.js.map +1 -1
  23. package/dist/core/session-manager.test.js +61 -1
  24. package/dist/core/session-manager.test.js.map +1 -1
  25. package/dist/core/settings-manager.d.ts +1 -0
  26. package/dist/core/settings-manager.d.ts.map +1 -1
  27. package/dist/core/settings-manager.js +4 -0
  28. package/dist/core/settings-manager.js.map +1 -1
  29. package/dist/system-prompt.d.ts.map +1 -1
  30. package/dist/system-prompt.js +13 -3
  31. package/dist/system-prompt.js.map +1 -1
  32. package/dist/system-prompt.test.js +19 -0
  33. package/dist/system-prompt.test.js.map +1 -1
  34. package/dist/tools/prompt-hints.d.ts.map +1 -1
  35. package/dist/tools/prompt-hints.js +3 -0
  36. package/dist/tools/prompt-hints.js.map +1 -1
  37. package/dist/tools/tool-search.d.ts +15 -0
  38. package/dist/tools/tool-search.d.ts.map +1 -0
  39. package/dist/tools/tool-search.js +35 -0
  40. package/dist/tools/tool-search.js.map +1 -0
  41. package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
  42. package/dist/ui/hooks/useAgentLoop.js +22 -9
  43. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  44. package/package.json +4 -4
@@ -27,6 +27,7 @@ import { parseAutopilotVerdict } from "./core/autopilot-verdict.js";
27
27
  import { isWorkflowCommandText, countAssistantMessages, shouldStartAutopilotCycle, extractTurnToolCalls, isMechanicalOnlyTurn, } from "./core/autopilot-gate.js";
28
28
  import { driveAutopilotCycle } from "./core/autopilot-cycle.js";
29
29
  import { validateKenModelPref, effectiveKenModel } from "./core/ken-model.js";
30
+ import { normalizeAutopilotMarkersForHistory, normalizeAppMarkersForHistory, normalizeKenTurnsForHistory, restoreUserRow, restoreAssistantTexts, autopilotMarkerCopySeed, } from "./core/session-history.js";
30
31
  import { AuthStorage } from "./core/auth-storage.js";
31
32
  import { MOONSHOT_OAUTH_KEY, XIAOMI_CREDITS_KEY } from "@kenkaiiii/gg-core";
32
33
  import { loginAnthropic } from "./core/oauth/anthropic.js";
@@ -811,6 +812,16 @@ async function createSession(deps, opts) {
811
812
  ...(f.statusCode != null ? { statusCode: f.statusCode } : {}),
812
813
  ...(f.resetsAt != null ? { resetsAt: f.resetsAt } : {}),
813
814
  });
815
+ // Persist the error row (display-only marker) so a resumed session shows
816
+ // the same headline/message/guidance the live run did. Best-effort.
817
+ void session
818
+ .persistAppMarker("error", {
819
+ scope: type,
820
+ headline: f.headline,
821
+ ...(f.message ? { message: f.message } : {}),
822
+ guidance: f.guidance,
823
+ })
824
+ .catch(() => { });
814
825
  }
815
826
  // The session file path to resume (passed by the daemon's POST /session);
816
827
  // empty/unset starts a fresh session.
@@ -835,6 +846,8 @@ async function createSession(deps, opts) {
835
846
  onEnterPlan: async (reason) => {
836
847
  await session.setPlanMode(true);
837
848
  broadcast("plan_enter", { reason: reason ?? "" });
849
+ // Persist the plan-mode banner so a resumed session still shows it.
850
+ void session.persistAppMarker("plan", { reason: reason ?? "" }).catch(() => { });
838
851
  },
839
852
  onExitPlan: async (planPath) => {
840
853
  await session.setPlanMode(false);
@@ -1371,14 +1384,24 @@ async function createSession(deps, opts) {
1371
1384
  },
1372
1385
  runPrompt: (body) => runAgent(body, () => session.prompt(body)),
1373
1386
  emit: (event) => {
1374
- broadcast(event.type, event.data);
1375
1387
  // Persist the terminal verdict marker so a resumed session renders the
1376
1388
  // same Ken bubble the live run showed instead of dropping it or
1377
1389
  // falling back to the raw verdict text (e.g. ALL_CLEAR).
1378
1390
  if (event.type === "autopilot_done") {
1391
+ // Broadcast the SAME copySeed the persisted marker will produce on
1392
+ // resume, so the live all-clear wording matches the resumed one
1393
+ // (computed before persist — same synchronous message count).
1394
+ const seed = autopilotMarkerCopySeed({
1395
+ version: 1,
1396
+ phase: "done",
1397
+ afterMessageCount: session.getMessages().filter((m) => m.role !== "system").length,
1398
+ });
1399
+ broadcast(event.type, { ...event.data, copySeed: seed });
1379
1400
  void session.persistAutopilotMarker("done");
1401
+ return;
1380
1402
  }
1381
- else if (event.type === "autopilot_human") {
1403
+ broadcast(event.type, event.data);
1404
+ if (event.type === "autopilot_human") {
1382
1405
  void session.persistAutopilotMarker("human", { reason: event.data.reason });
1383
1406
  }
1384
1407
  else if (event.type === "autopilot_capped") {
@@ -1481,6 +1504,8 @@ async function createSession(deps, opts) {
1481
1504
  markTaskInProgress(cwd, task.id);
1482
1505
  broadcast("tasks_list", { tasks: loadTasksSync(cwd) });
1483
1506
  broadcast("task_start", { id: task.id, title: task.title });
1507
+ // Persist the task header so a resumed task session shows what ran.
1508
+ void session.persistAppMarker("task", { title: task.title }).catch(() => { });
1484
1509
  const shortId = task.id.slice(0, 8);
1485
1510
  const completionHint = `\n\n---\nWhen you have fully completed this task, call the tasks tool to mark it done:\n` +
1486
1511
  `tasks({ action: "done", id: "${shortId}" })`;
@@ -1771,8 +1796,11 @@ async function createSession(deps, opts) {
1771
1796
  // they were recorded after, so each lands right after that message. A
1772
1797
  // turn becomes two wire rows: the `@Ken` question (user) + Ken's reply
1773
1798
  // (assistant), both flagged `ken` so the webview tints them.
1799
+ // Deduped; stale anchors are clamped to the last message (Ken turns
1800
+ // carry real conversation, so they render at the end instead of
1801
+ // vanishing).
1774
1802
  const kenByCount = new Map();
1775
- for (const turn of session.getKenTurns()) {
1803
+ for (const turn of normalizeKenTurnsForHistory(session.getKenTurns(), messages.filter((m) => m.role !== "system").length)) {
1776
1804
  const list = kenByCount.get(turn.afterMessageCount) ?? [];
1777
1805
  list.push(turn);
1778
1806
  kenByCount.set(turn.afterMessageCount, list);
@@ -1790,8 +1818,12 @@ async function createSession(deps, opts) {
1790
1818
  // Autopilot verdict markers to interleave, same anchor scheme as Ken
1791
1819
  // turns — each becomes a single assistant row the webview renders
1792
1820
  // exactly like the live `autopilot` item (never a raw verdict string).
1821
+ // Compact/continuation rewrites can carry old markers whose original
1822
+ // afterMessageCount is beyond the restored message list; dropping those
1823
+ // prevents stale all-clear bubbles from bunching at the bottom on resume.
1824
+ const restoredMessageCount = messages.filter((m) => m.role !== "system").length;
1793
1825
  const autopilotByCount = new Map();
1794
- for (const marker of session.getAutopilotMarkers()) {
1826
+ for (const marker of normalizeAutopilotMarkersForHistory(session.getAutopilotMarkers(), restoredMessageCount)) {
1795
1827
  const list = autopilotByCount.get(marker.afterMessageCount) ?? [];
1796
1828
  list.push(marker);
1797
1829
  autopilotByCount.set(marker.afterMessageCount, list);
@@ -1809,15 +1841,77 @@ async function createSession(deps, opts) {
1809
1841
  phase: marker.phase,
1810
1842
  ...(marker.reason !== undefined ? { reason: marker.reason } : {}),
1811
1843
  ...(marker.body !== undefined ? { body: marker.body } : {}),
1844
+ copySeed: marker.copySeed,
1812
1845
  },
1813
1846
  });
1814
1847
  }
1815
1848
  };
1849
+ // App transcript markers (plan banner / task header / error rows /
1850
+ // user-bubble hints), same anchor scheme. user_hint markers don't
1851
+ // become rows — they decorate the user row at their anchor instead.
1852
+ const appMarkersByCount = new Map();
1853
+ const userHintByCount = new Map();
1854
+ // Compaction-count markers pair with compacted summary rows in file
1855
+ // order (FIFO), not by anchor — the summary user message is what
1856
+ // positions the notice.
1857
+ const compactionCounts = [];
1858
+ for (const marker of normalizeAppMarkersForHistory(session.getAppMarkers(), restoredMessageCount)) {
1859
+ if (marker.kind === "user_hint") {
1860
+ userHintByCount.set(marker.afterMessageCount, marker.data);
1861
+ continue;
1862
+ }
1863
+ if (marker.kind === "compaction") {
1864
+ const d = marker.data;
1865
+ if (typeof d.originalCount === "number" && typeof d.newCount === "number") {
1866
+ compactionCounts.push({ originalCount: d.originalCount, newCount: d.newCount });
1867
+ }
1868
+ continue;
1869
+ }
1870
+ const list = appMarkersByCount.get(marker.afterMessageCount) ?? [];
1871
+ list.push(marker);
1872
+ appMarkersByCount.set(marker.afterMessageCount, list);
1873
+ }
1874
+ const flushAppMarkers = (count) => {
1875
+ const markers = appMarkersByCount.get(count);
1876
+ if (!markers)
1877
+ return;
1878
+ appMarkersByCount.delete(count);
1879
+ for (const marker of markers) {
1880
+ const d = marker.data;
1881
+ if (marker.kind === "plan") {
1882
+ history.push({
1883
+ role: "assistant",
1884
+ text: "",
1885
+ plan: { reason: typeof d.reason === "string" ? d.reason : "" },
1886
+ });
1887
+ }
1888
+ else if (marker.kind === "task") {
1889
+ history.push({
1890
+ role: "assistant",
1891
+ text: "",
1892
+ task: { title: typeof d.title === "string" ? d.title : "" },
1893
+ });
1894
+ }
1895
+ else if (marker.kind === "error" && typeof d.headline === "string") {
1896
+ history.push({
1897
+ role: "assistant",
1898
+ text: "",
1899
+ error: {
1900
+ scope: typeof d.scope === "string" ? d.scope : "error",
1901
+ headline: d.headline,
1902
+ ...(typeof d.message === "string" ? { message: d.message } : {}),
1903
+ ...(typeof d.guidance === "string" ? { guidance: d.guidance } : {}),
1904
+ },
1905
+ });
1906
+ }
1907
+ }
1908
+ };
1816
1909
  let nonSystemCount = 0;
1817
1910
  // Turns/markers recorded before any build message (anchor 0) render at
1818
1911
  // the top.
1819
1912
  flushKen(0);
1820
1913
  flushAutopilot(0);
1914
+ flushAppMarkers(0);
1821
1915
  for (const msg of messages) {
1822
1916
  if (msg.role === "system")
1823
1917
  continue;
@@ -1867,30 +1961,54 @@ async function createSession(deps, opts) {
1867
1961
  }
1868
1962
  continue;
1869
1963
  }
1870
- // User or assistant message — existing text/hook/command/compacted
1871
- // extraction, plus sub-agent group detection for assistant tool_calls.
1872
- const text = typeof msg.content === "string"
1873
- ? msg.content
1874
- : msg.content
1875
- .map((c) => c.type === "text" && "text" in c && typeof c.text === "string" ? c.text : "")
1876
- .join("");
1877
- const images = typeof msg.content === "string"
1878
- ? []
1879
- : msg.content.flatMap((c) => c.type === "image" ? [`data:${c.mediaType};base64,${c.data}`] : []);
1880
- const hook = msg.role === "user" ? detectHookKind(text) : null;
1881
- const compacted = msg.role === "user" && !hook && text.startsWith("[Previous conversation summary]");
1882
- const command = msg.role === "user" && !hook && !compacted
1883
- ? detectPromptCommand(text, commandCandidates)
1884
- : null;
1885
- if (text.trim() || images.length > 0) {
1886
- history.push({
1887
- role: msg.role,
1888
- text: command ?? text,
1889
- images,
1890
- hook,
1891
- command: command !== null,
1892
- compacted,
1893
- });
1964
+ // User or assistant message — text/hook/command/compacted extraction,
1965
+ // plus sub-agent group detection for assistant tool_calls.
1966
+ if (msg.role === "user") {
1967
+ // Rebuild the live bubble: strip the steering wrapper, drop
1968
+ // attachment/file notes the model saw but the bubble never showed.
1969
+ const restored = restoreUserRow(msg.content);
1970
+ const text = restored.text;
1971
+ const hook = detectHookKind(text);
1972
+ const compacted = !hook && text.startsWith("[Previous conversation summary]");
1973
+ const command = !hook && !compacted ? detectPromptCommand(text, commandCandidates) : null;
1974
+ if (text.trim() || restored.images.length > 0) {
1975
+ const hint = userHintByCount.get(nonSystemCount);
1976
+ history.push({
1977
+ role: "user",
1978
+ text: command ?? text,
1979
+ images: restored.images,
1980
+ hook,
1981
+ command: command !== null,
1982
+ compacted,
1983
+ // Markers accumulate across continuation files (each rewrite
1984
+ // re-persists prior ones) but only the LATEST summary row
1985
+ // survives compaction — so consume from the newest end.
1986
+ ...(compacted && compactionCounts.length > 0
1987
+ ? { compactionCounts: compactionCounts.pop() }
1988
+ : {}),
1989
+ ...(hint?.kenSent === true ? { kenSent: true } : {}),
1990
+ ...(Array.isArray(hint?.enhancements) ? { enhancements: hint.enhancements } : {}),
1991
+ });
1992
+ // Live showed the video-capability warning right after the bubble.
1993
+ if (restored.videoWarning) {
1994
+ history.push({ role: "assistant", text: "", infoKind: "video_warning" });
1995
+ }
1996
+ }
1997
+ }
1998
+ else {
1999
+ // Assistant: one wire row per persisted text block — live streaming
2000
+ // splits bubbles at server_tool_call boundaries, and the persisted
2001
+ // content keeps those blocks separate.
2002
+ for (const blockText of restoreAssistantTexts(msg.content)) {
2003
+ history.push({
2004
+ role: "assistant",
2005
+ text: blockText,
2006
+ images: [],
2007
+ hook: null,
2008
+ command: false,
2009
+ compacted: false,
2010
+ });
2011
+ }
1894
2012
  }
1895
2013
  // Assistant tool_call blocks: detect sub-agent delegations.
1896
2014
  if (msg.role === "assistant" && typeof msg.content !== "string") {
@@ -1911,19 +2029,21 @@ async function createSession(deps, opts) {
1911
2029
  });
1912
2030
  }
1913
2031
  }
1914
- // Interleave any Ken turns / autopilot markers recorded right after
1915
- // this message.
2032
+ // Interleave any Ken turns / autopilot / app markers recorded right
2033
+ // after this message.
1916
2034
  flushKen(nonSystemCount);
1917
2035
  flushAutopilot(nonSystemCount);
2036
+ flushAppMarkers(nonSystemCount);
1918
2037
  }
1919
- // Flush remaining Ken turns / autopilot markers whose anchor is at/after
1920
- // the message count (e.g. recorded before any build message, or anchors
1921
- // beyond the current count after compaction shrank the history) so none
1922
- // are dropped.
2038
+ // Flush remaining Ken turns whose anchor is at/after the message count so
2039
+ // none are dropped. Autopilot/app markers beyond the restored message
2040
+ // count were already filtered above; any remaining marker here is valid.
1923
2041
  for (const count of [...kenByCount.keys()].sort((a, b) => a - b))
1924
2042
  flushKen(count);
1925
2043
  for (const count of [...autopilotByCount.keys()].sort((a, b) => a - b))
1926
2044
  flushAutopilot(count);
2045
+ for (const count of [...appMarkersByCount.keys()].sort((a, b) => a - b))
2046
+ flushAppMarkers(count);
1927
2047
  json(res, 200, { history });
1928
2048
  })();
1929
2049
  return;
@@ -1956,10 +2076,12 @@ async function createSession(deps, opts) {
1956
2076
  void readBody(req).then(async (raw) => {
1957
2077
  let text;
1958
2078
  let attachments;
2079
+ let meta;
1959
2080
  try {
1960
2081
  const body = JSON.parse(raw);
1961
2082
  text = body.text ?? "";
1962
2083
  attachments = Array.isArray(body.attachments) ? body.attachments : [];
2084
+ meta = typeof body.meta === "object" && body.meta !== null ? body.meta : undefined;
1963
2085
  }
1964
2086
  catch {
1965
2087
  json(res, 400, { error: "invalid JSON body" });
@@ -1983,6 +2105,18 @@ async function createSession(deps, opts) {
1983
2105
  return;
1984
2106
  }
1985
2107
  json(res, 202, { accepted: true });
2108
+ // Webview display hint for this prompt's user bubble (kenSent shimmer
2109
+ // label / enhancer highlight segments). Anchored +1 so it attaches to
2110
+ // the user message the prompt below is about to push. Queued prompts
2111
+ // skip this (their position in the run is unpredictable).
2112
+ if (meta && (meta.kenSent === true || Array.isArray(meta.enhancements))) {
2113
+ void session
2114
+ .persistAppMarker("user_hint", {
2115
+ ...(meta.kenSent === true ? { kenSent: true } : {}),
2116
+ ...(Array.isArray(meta.enhancements) ? { enhancements: meta.enhancements } : {}),
2117
+ }, 1)
2118
+ .catch(() => { });
2119
+ }
1986
2120
  // Fresh user turn: clear any cancel flag left from a prior cycle so this
1987
2121
  // turn's autopilot review can run.
1988
2122
  autopilotCancelled = false;