@kenkaiiii/ggcoder 5.13.3 → 5.15.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 (95) hide show
  1. package/dist/app-sidecar.js +158 -48
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/chat-agents/general.d.ts +7 -0
  4. package/dist/chat-agents/general.d.ts.map +1 -0
  5. package/dist/chat-agents/general.js +16 -0
  6. package/dist/chat-agents/general.js.map +1 -0
  7. package/dist/chat-agents/general.test.d.ts +2 -0
  8. package/dist/chat-agents/general.test.d.ts.map +1 -0
  9. package/dist/chat-agents/general.test.js +47 -0
  10. package/dist/chat-agents/general.test.js.map +1 -0
  11. package/dist/chat-agents/index.d.ts +11 -0
  12. package/dist/chat-agents/index.d.ts.map +1 -0
  13. package/dist/chat-agents/index.js +89 -0
  14. package/dist/chat-agents/index.js.map +1 -0
  15. package/dist/chat-agents/memory.d.ts +53 -0
  16. package/dist/chat-agents/memory.d.ts.map +1 -0
  17. package/dist/chat-agents/memory.js +362 -0
  18. package/dist/chat-agents/memory.js.map +1 -0
  19. package/dist/chat-agents/memory.test.d.ts +2 -0
  20. package/dist/chat-agents/memory.test.d.ts.map +1 -0
  21. package/dist/chat-agents/memory.test.js +155 -0
  22. package/dist/chat-agents/memory.test.js.map +1 -0
  23. package/dist/chat-agents/research.d.ts +7 -0
  24. package/dist/chat-agents/research.d.ts.map +1 -0
  25. package/dist/chat-agents/research.js +18 -0
  26. package/dist/chat-agents/research.js.map +1 -0
  27. package/dist/chat-agents/shared.d.ts +11 -0
  28. package/dist/chat-agents/shared.d.ts.map +1 -0
  29. package/dist/chat-agents/shared.js +48 -0
  30. package/dist/chat-agents/shared.js.map +1 -0
  31. package/dist/chat-agents/specialists.test.d.ts +2 -0
  32. package/dist/chat-agents/specialists.test.d.ts.map +1 -0
  33. package/dist/chat-agents/specialists.test.js +97 -0
  34. package/dist/chat-agents/specialists.test.js.map +1 -0
  35. package/dist/chat-agents/therapist.d.ts +7 -0
  36. package/dist/chat-agents/therapist.d.ts.map +1 -0
  37. package/dist/chat-agents/therapist.js +20 -0
  38. package/dist/chat-agents/therapist.js.map +1 -0
  39. package/dist/chat-agents/types.d.ts +4 -0
  40. package/dist/chat-agents/types.d.ts.map +1 -0
  41. package/dist/chat-agents/types.js +7 -0
  42. package/dist/chat-agents/types.js.map +1 -0
  43. package/dist/core/agent-session-memory-tail.test.d.ts +2 -0
  44. package/dist/core/agent-session-memory-tail.test.d.ts.map +1 -0
  45. package/dist/core/agent-session-memory-tail.test.js +122 -0
  46. package/dist/core/agent-session-memory-tail.test.js.map +1 -0
  47. package/dist/core/agent-session.d.ts +25 -0
  48. package/dist/core/agent-session.d.ts.map +1 -1
  49. package/dist/core/agent-session.js +128 -65
  50. package/dist/core/agent-session.js.map +1 -1
  51. package/dist/core/agents.d.ts +1 -1
  52. package/dist/core/agents.d.ts.map +1 -1
  53. package/dist/core/agents.js +4 -2
  54. package/dist/core/agents.js.map +1 -1
  55. package/dist/core/progress/rebuild.d.ts +3 -3
  56. package/dist/core/progress/rebuild.d.ts.map +1 -1
  57. package/dist/core/progress/rebuild.js +33 -27
  58. package/dist/core/progress/rebuild.js.map +1 -1
  59. package/dist/core/progress/rebuild.test.js +15 -0
  60. package/dist/core/progress/rebuild.test.js.map +1 -1
  61. package/dist/core/project-discovery.d.ts +1 -1
  62. package/dist/core/project-discovery.d.ts.map +1 -1
  63. package/dist/core/project-discovery.js +1 -2
  64. package/dist/core/project-discovery.js.map +1 -1
  65. package/dist/core/project-discovery.test.js +14 -1
  66. package/dist/core/project-discovery.test.js.map +1 -1
  67. package/dist/core/session-manager.d.ts +1 -1
  68. package/dist/core/session-manager.d.ts.map +1 -1
  69. package/dist/core/session-manager.js +2 -1
  70. package/dist/core/session-manager.js.map +1 -1
  71. package/dist/core/session-manager.test.js +18 -0
  72. package/dist/core/session-manager.test.js.map +1 -1
  73. package/dist/core/subagent-manager.d.ts.map +1 -1
  74. package/dist/core/subagent-manager.js +4 -0
  75. package/dist/core/subagent-manager.js.map +1 -1
  76. package/dist/core/subagent-manager.test.js +5 -1
  77. package/dist/core/subagent-manager.test.js.map +1 -1
  78. package/dist/tools/generate-image.d.ts.map +1 -1
  79. package/dist/tools/generate-image.js +10 -3
  80. package/dist/tools/generate-image.js.map +1 -1
  81. package/dist/tools/generate-image.test.js +17 -0
  82. package/dist/tools/generate-image.test.js.map +1 -1
  83. package/dist/tools/web-fetch.d.ts +1 -0
  84. package/dist/tools/web-fetch.d.ts.map +1 -1
  85. package/dist/tools/web-fetch.js +112 -35
  86. package/dist/tools/web-fetch.js.map +1 -1
  87. package/dist/tools/web-fetch.test.js +146 -1
  88. package/dist/tools/web-fetch.test.js.map +1 -1
  89. package/dist/tools/web-search.d.ts +10 -0
  90. package/dist/tools/web-search.d.ts.map +1 -1
  91. package/dist/tools/web-search.js +236 -28
  92. package/dist/tools/web-search.js.map +1 -1
  93. package/dist/tools/web-search.test.js +77 -6
  94. package/dist/tools/web-search.test.js.map +1 -1
  95. package/package.json +4 -4
@@ -22,6 +22,8 @@ import { formatError } from "@kenkaiiii/gg-ai";
22
22
  import { runJsonMode } from "./modes/json-mode.js";
23
23
  import { runSubagentWorkerMode } from "./modes/subagent-worker-mode.js";
24
24
  import { AgentSession } from "./core/agent-session.js";
25
+ import { CHAT_AGENT_IDS, chatAgentSessionsDir, createChatAgent, parseChatAgentId, sessionsDirForChatAgent, switchChatAgent, } from "./chat-agents/index.js";
26
+ import { buildMemoryTools, MemoryStore } from "./chat-agents/memory.js";
25
27
  import { buildKenSystemPrompt, buildKenAutopilotSystemPrompt } from "./core/ken-prompt.js";
26
28
  import { buildKenDigest, buildKenAutopilotContext, buildKenAutopilotPlanContext, } from "./core/ken-context.js";
27
29
  import { parseAutopilotVerdict } from "./core/autopilot-verdict.js";
@@ -508,6 +510,13 @@ async function main() {
508
510
  // request to its window's session via the `x-gg-session` header (and the
509
511
  // `?session=` query for the SSE /events stream).
510
512
  const sessions = new Map();
513
+ const memoryStore = new MemoryStore({
514
+ onChange: ({ memories }) => {
515
+ for (const ctx of sessions.values()) {
516
+ ctx.broadcast("memory_change", { count: memories.length });
517
+ }
518
+ },
519
+ });
511
520
  // XP/rank progress — loaded once per daemon; awards fan out to every window.
512
521
  // Each frame is tagged `origin: true` only for the session that earned the
513
522
  // XP, so that window alone plays sounds/chips while the rest just re-render.
@@ -606,7 +615,7 @@ async function main() {
606
615
  return;
607
616
  }
608
617
  // ── Daemon-level routes (session lifecycle) ──────────────────────────
609
- // Create a session for a window: { cwd, sessionPath? } → { sessionId }.
618
+ // Create a session for a window: { mode?, cwd, sessionPath? } → { sessionId }.
610
619
  if (method === "POST" && url === "/session") {
611
620
  void daemonReadBody(req).then(async (raw) => {
612
621
  let body = {};
@@ -616,15 +625,22 @@ async function main() {
616
625
  catch {
617
626
  /* empty/invalid body → defaults below */
618
627
  }
628
+ const mode = body.mode === "chat" ? "chat" : "code";
629
+ const chatAgent = parseChatAgentId(body.chatAgent);
619
630
  const sessionCwd = typeof body.cwd === "string" && body.cwd
620
631
  ? body.cwd
621
632
  : (process.env.GG_APP_CWD ?? process.cwd());
622
633
  const sessionPath = typeof body.sessionPath === "string" && body.sessionPath ? body.sessionPath : undefined;
623
634
  const id = randomUUID();
624
635
  try {
625
- const ctx = await createSession({ auth, paths, progress }, { id, cwd: sessionCwd, sessionPath });
636
+ const ctx = await createSession({ auth, paths, progress, memoryStore }, { id, mode, chatAgent, cwd: sessionCwd, sessionPath });
626
637
  sessions.set(id, ctx);
627
- log("INFO", "app-sidecar", "session created", { id, cwd: sessionCwd });
638
+ log("INFO", "app-sidecar", "session created", {
639
+ id,
640
+ mode,
641
+ chatAgent,
642
+ cwd: sessionCwd,
643
+ });
628
644
  daemonJson(res, 200, { sessionId: id });
629
645
  }
630
646
  catch (err) {
@@ -775,8 +791,14 @@ function buildKenContext(buildSession, cwd, gitBranch, question, workflowCommand
775
791
  });
776
792
  }
777
793
  async function createProgressManager(agentDir, broadcastAll) {
778
- // Boot: recovery chain main → backup → one-time session-history rebuild → empty.
779
- let file = await loadProgress({ rebuild: () => rebuildFromSessions() });
794
+ // Boot: recovery chain main → backup → coding + chat session rebuild → empty.
795
+ const coderSessionsDir = path.join(agentDir, "sessions");
796
+ let file = await loadProgress({
797
+ rebuild: () => rebuildFromSessions([
798
+ coderSessionsDir,
799
+ ...CHAT_AGENT_IDS.map((agentId) => chatAgentSessionsDir(coderSessionsDir, agentId)),
800
+ ]),
801
+ });
780
802
  // Don't re-celebrate an old levelUp event on boot.
781
803
  let lastSeenNonce = file.lastEvent?.nonce ?? null;
782
804
  log("INFO", "app-sidecar", "progress loaded", {
@@ -864,8 +886,10 @@ async function createProgressManager(agentDir, broadcastAll) {
864
886
  * logger, PATH, shared auth file, and radio live at the daemon level.
865
887
  */
866
888
  async function createSession(deps, opts) {
867
- const { auth, progress } = deps;
889
+ const { auth, progress, memoryStore } = deps;
868
890
  const paths = deps.paths;
891
+ const mode = opts.mode;
892
+ let chatAgent = opts.chatAgent;
869
893
  const cwd = opts.cwd;
870
894
  // Base host for parsing request-URL query params (value is irrelevant to
871
895
  // parsing); the daemon owns the real listen host.
@@ -968,51 +992,69 @@ async function createSession(deps, opts) {
968
992
  // empty/unset starts a fresh session.
969
993
  const resumeSessionPath = opts.sessionPath;
970
994
  let abort = new AbortController();
971
- const session = new AgentSession({
995
+ const baseSessionOptions = {
972
996
  provider,
973
997
  model,
974
998
  cwd,
975
999
  thinkingLevel,
976
1000
  sessionId: resumeSessionPath,
977
1001
  signal: abort.signal,
978
- // The shell gates window readiness on the GG_APP_LISTENING handshake, which
979
- // can't fire until initialize() resolves. Connect MCP in the background so a
980
- // slow or hanging stdio server (e.g. a first-run `npx -y @playwright/mcp`
981
- // download) can't delay the sidecar past the webview's startup timeout
982
- // ("sidecar did not start in time"). Tools attach when the servers come up.
1002
+ // Keep MCP startup off the readiness path in both modes.
983
1003
  backgroundMcpConnect: true,
984
- // Plan mode: the agent's enter_plan/exit_plan tools drive these. We flip
985
- // session plan state (rebuilds the system prompt + enforces read-only
986
- // tools) and surface the transition to the webview.
987
- onEnterPlan: async (reason) => {
988
- await session.setPlanMode(true);
989
- broadcast("plan_enter", { reason: reason ?? "" });
990
- // Persist the plan-mode banner so a resumed session still shows it.
991
- void session.persistAppMarker("plan", { reason: reason ?? "" }).catch(() => { });
992
- },
993
- onExitPlan: async (planPath) => {
994
- await session.setPlanMode(false);
995
- // Surface the plan's path + markdown so the webview can show the review
996
- // modal (Accept / Feedback / Reject). Best-effort content read.
997
- let content;
998
- try {
999
- content = await fs.readFile(planPath, "utf-8");
1000
- }
1001
- catch {
1002
- content = "";
1003
- }
1004
- // Record the submitted plan so the autopilot gate can route this turn
1005
- // into a PLAN review instead of a stale work review (plan mode is
1006
- // already false here, so the gate's planMode check alone never catches
1007
- // a submission). setPendingPlan bumps planGeneration, which invalidates
1008
- // any in-flight Ken plan review racing a user action.
1009
- setPendingPlan(planPath, content);
1010
- broadcast("plan_exit", { planPath, content });
1011
- return "Plan submitted for user review. Wait for the user to approve, reject, or dismiss it before implementing.";
1012
- },
1013
- });
1004
+ };
1005
+ let session;
1006
+ if (mode === "chat") {
1007
+ session = createChatAgent(chatAgent, {
1008
+ ...baseSessionOptions,
1009
+ sessionsDir: paths.sessionsDir,
1010
+ additionalTools: buildMemoryTools(memoryStore),
1011
+ getSystemPromptTail: () => memoryStore.renderForPrompt(),
1012
+ onAgentChange: async (nextAgent) => {
1013
+ chatAgent = nextAgent;
1014
+ broadcast("chat_agent_change", { chatAgent: nextAgent });
1015
+ await session.persistAppMarker("agent_handoff", { chatAgent: nextAgent }).catch((error) => {
1016
+ log("WARN", "app-sidecar", "agent handoff marker persist failed", {
1017
+ message: error instanceof Error ? error.message : String(error),
1018
+ });
1019
+ });
1020
+ },
1021
+ });
1022
+ }
1023
+ else {
1024
+ session = new AgentSession({
1025
+ ...baseSessionOptions,
1026
+ // Plan mode belongs only to the coding agent.
1027
+ onEnterPlan: async (reason) => {
1028
+ await session.setPlanMode(true);
1029
+ broadcast("plan_enter", { reason: reason ?? "" });
1030
+ void session.persistAppMarker("plan", { reason: reason ?? "" }).catch(() => { });
1031
+ },
1032
+ onExitPlan: async (planPath) => {
1033
+ await session.setPlanMode(false);
1034
+ let content;
1035
+ try {
1036
+ content = await fs.readFile(planPath, "utf-8");
1037
+ }
1038
+ catch {
1039
+ content = "";
1040
+ }
1041
+ setPendingPlan(planPath, content);
1042
+ broadcast("plan_exit", { planPath, content });
1043
+ return "Plan submitted for user review. Wait for the user to approve, reject, or dismiss it before implementing.";
1044
+ },
1045
+ });
1046
+ }
1014
1047
  await session.initialize();
1015
- log("INFO", "app-sidecar", "session ready", { provider, model, cwd });
1048
+ if (mode === "chat") {
1049
+ const restoredAgent = [...session.getAppMarkers()]
1050
+ .reverse()
1051
+ .find((marker) => marker.kind === "agent_handoff")?.data.chatAgent;
1052
+ if (typeof restoredAgent === "string") {
1053
+ chatAgent = parseChatAgentId(restoredAgent);
1054
+ await switchChatAgent(session, chatAgent, false);
1055
+ }
1056
+ }
1057
+ log("INFO", "app-sidecar", "session ready", { provider, model, mode, chatAgent, cwd });
1016
1058
  // Footer extras (context window, git branch, background tasks). The git
1017
1059
  // branch is resolved once at startup and refreshed lazily; the context
1018
1060
  // window follows the active model.
@@ -1081,7 +1123,7 @@ async function createSession(deps, opts) {
1081
1123
  // gg-app.json on boot; flipped via POST /autopilot. When on, POST /prompt runs
1082
1124
  // runAutopilotCycle after the user's turn settles — Ken auto-reviews the work
1083
1125
  // and drives the review→prompt→review loop.
1084
- let autopilot = await loadAutopilot(cwd);
1126
+ let autopilot = mode === "code" && (await loadAutopilot(cwd));
1085
1127
  // True while an autopilot review is in flight (used to defer kenAuto model
1086
1128
  // switches, like kenRunning does for chat Ken, and to drive the spinner).
1087
1129
  let autopilotReviewing = false;
@@ -1758,6 +1800,8 @@ async function createSession(deps, opts) {
1758
1800
  const st = session.getState();
1759
1801
  json(res, 200, {
1760
1802
  ...st,
1803
+ mode,
1804
+ chatAgent,
1761
1805
  running,
1762
1806
  ready: true,
1763
1807
  thinkingLevel: session.getThinkingLevel() ?? null,
@@ -1773,6 +1817,26 @@ async function createSession(deps, opts) {
1773
1817
  json(res, 200, progress.snapshot());
1774
1818
  return;
1775
1819
  }
1820
+ if (method === "GET" && url === "/memories") {
1821
+ void memoryStore
1822
+ .snapshot()
1823
+ .then((snapshot) => json(res, 200, snapshot))
1824
+ .catch((error) => json(res, 500, { error: error instanceof Error ? error.message : String(error) }));
1825
+ return;
1826
+ }
1827
+ if (method === "DELETE" && url.startsWith("/memories/")) {
1828
+ const id = decodeURIComponent(url.slice("/memories/".length));
1829
+ if (!id) {
1830
+ json(res, 400, { error: "memory id is required" });
1831
+ return;
1832
+ }
1833
+ void memoryStore
1834
+ .forget(id)
1835
+ .then(() => memoryStore.snapshot())
1836
+ .then((snapshot) => json(res, 200, snapshot))
1837
+ .catch((error) => json(res, 500, { error: error instanceof Error ? error.message : String(error) }));
1838
+ return;
1839
+ }
1776
1840
  if (method === "GET" && (url === "/events" || url.startsWith("/events?"))) {
1777
1841
  res.writeHead(200, {
1778
1842
  "content-type": "text/event-stream",
@@ -1788,6 +1852,8 @@ async function createSession(deps, opts) {
1788
1852
  type: "ready",
1789
1853
  data: {
1790
1854
  ...st,
1855
+ mode,
1856
+ chatAgent,
1791
1857
  running,
1792
1858
  thinkingLevel: session.getThinkingLevel() ?? null,
1793
1859
  supportedThinkingLevels: getSupportedThinkingLevels(st.provider, st.model),
@@ -1903,7 +1969,33 @@ async function createSession(deps, opts) {
1903
1969
  json(res, 400, { error: "missing cwd query param" });
1904
1970
  return;
1905
1971
  }
1906
- void listRecentSessions(target, 5)
1972
+ const requestedAgent = new URL(url, `http://${host}`).searchParams.get("chatAgent");
1973
+ if (requestedAgent === "all") {
1974
+ void Promise.all(CHAT_AGENT_IDS.map(async (agentId) => {
1975
+ const agentSessions = await listRecentSessions(target, 5, chatAgentSessionsDir(paths.sessionsDir, agentId));
1976
+ return agentSessions.map((item) => ({ ...item, chatAgent: agentId }));
1977
+ }))
1978
+ .then(async (groups) => {
1979
+ const dated = await Promise.all(groups.flat().map(async (item) => ({
1980
+ item,
1981
+ mtime: await fs
1982
+ .stat(item.path)
1983
+ .then((stat) => stat.mtimeMs)
1984
+ .catch(() => 0),
1985
+ })));
1986
+ const recent = dated
1987
+ .sort((left, right) => right.mtime - left.mtime)
1988
+ .slice(0, 5)
1989
+ .map(({ item }) => item);
1990
+ json(res, 200, { sessions: recent });
1991
+ })
1992
+ .catch(() => json(res, 200, { sessions: [] }));
1993
+ return;
1994
+ }
1995
+ const sessionsDir = mode === "chat" || requestedAgent
1996
+ ? sessionsDirForChatAgent(paths.sessionsDir, requestedAgent ?? chatAgent)
1997
+ : paths.sessionsDir;
1998
+ void listRecentSessions(target, 5, sessionsDir)
1907
1999
  .then((sessions) => json(res, 200, { sessions }))
1908
2000
  .catch(() => json(res, 200, { sessions: [] }));
1909
2001
  return;
@@ -2210,9 +2302,12 @@ async function createSession(deps, opts) {
2210
2302
  return;
2211
2303
  }
2212
2304
  if (method === "GET" && url === "/commands") {
2305
+ if (mode === "chat") {
2306
+ json(res, 200, { commands: [] });
2307
+ return;
2308
+ }
2213
2309
  // Workflow commands with agent functionality: built-in prompt templates +
2214
- // the user's own `.gg/commands/*.md`. UI commands (model/quit/etc.) are
2215
- // handled webview-side and intentionally excluded.
2310
+ // the user's own `.gg/commands/*.md`.
2216
2311
  void (async () => {
2217
2312
  const builtins = PROMPT_COMMANDS.map((c) => ({
2218
2313
  name: c.name,
@@ -2350,6 +2445,10 @@ async function createSession(deps, opts) {
2350
2445
  // webview keeps the bubbles separate. The context digest is assembled fresh
2351
2446
  // from the BUILD session's transcript each turn (one-way mirror).
2352
2447
  if (method === "POST" && url === "/ken/prompt") {
2448
+ if (mode === "chat") {
2449
+ json(res, 404, { error: "Ken is not available in GG Chat." });
2450
+ return;
2451
+ }
2353
2452
  void readBody(req).then(async (raw) => {
2354
2453
  let text;
2355
2454
  try {
@@ -2405,6 +2504,10 @@ async function createSession(deps, opts) {
2405
2504
  return;
2406
2505
  }
2407
2506
  if (method === "POST" && url === "/autopilot") {
2507
+ if (mode === "chat") {
2508
+ json(res, 404, { error: "Autopilot is not available in GG Chat." });
2509
+ return;
2510
+ }
2408
2511
  void readBody(req).then(async (raw) => {
2409
2512
  let enabled;
2410
2513
  try {
@@ -2761,7 +2864,10 @@ async function createSession(deps, opts) {
2761
2864
  }
2762
2865
  void session
2763
2866
  .newSession()
2764
- .then(() => {
2867
+ .then(async () => {
2868
+ if (mode === "chat") {
2869
+ await session.persistAppMarker("agent_handoff", { chatAgent });
2870
+ }
2765
2871
  injectedAutopilotPrompts = [];
2766
2872
  clearPendingPlan();
2767
2873
  broadcast("session_reset", {});
@@ -3260,6 +3366,10 @@ async function createSession(deps, opts) {
3260
3366
  }
3261
3367
  return {
3262
3368
  id: opts.id,
3369
+ mode,
3370
+ get chatAgent() {
3371
+ return chatAgent;
3372
+ },
3263
3373
  cwd,
3264
3374
  sessionPath: opts.sessionPath,
3265
3375
  session,