@kenkaiiii/ggcoder 5.16.0 → 5.17.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 (99) hide show
  1. package/dist/app-sidecar.js +129 -44
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +15 -0
  5. package/dist/cli.js.map +1 -1
  6. package/dist/core/agent-session-review-coverage.test.d.ts +2 -0
  7. package/dist/core/agent-session-review-coverage.test.d.ts.map +1 -0
  8. package/dist/core/agent-session-review-coverage.test.js +57 -0
  9. package/dist/core/agent-session-review-coverage.test.js.map +1 -0
  10. package/dist/core/agent-session.d.ts +11 -4
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +133 -22
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/compaction/compactor.test.js +1 -1
  15. package/dist/core/compaction/compactor.test.js.map +1 -1
  16. package/dist/core/event-bus.d.ts +2 -0
  17. package/dist/core/event-bus.d.ts.map +1 -1
  18. package/dist/core/event-bus.js.map +1 -1
  19. package/dist/core/ideal-review.d.ts +27 -0
  20. package/dist/core/ideal-review.d.ts.map +1 -1
  21. package/dist/core/ideal-review.js +64 -0
  22. package/dist/core/ideal-review.js.map +1 -1
  23. package/dist/core/ideal-review.test.js +34 -1
  24. package/dist/core/ideal-review.test.js.map +1 -1
  25. package/dist/core/lsp/client.d.ts +3 -0
  26. package/dist/core/lsp/client.d.ts.map +1 -1
  27. package/dist/core/lsp/client.js +20 -0
  28. package/dist/core/lsp/client.js.map +1 -1
  29. package/dist/core/lsp/manager.d.ts +32 -9
  30. package/dist/core/lsp/manager.d.ts.map +1 -1
  31. package/dist/core/lsp/manager.js +89 -37
  32. package/dist/core/lsp/manager.js.map +1 -1
  33. package/dist/core/lsp/manager.test.js +56 -4
  34. package/dist/core/lsp/manager.test.js.map +1 -1
  35. package/dist/core/run-lifecycle.d.ts +40 -0
  36. package/dist/core/run-lifecycle.d.ts.map +1 -0
  37. package/dist/core/run-lifecycle.js +95 -0
  38. package/dist/core/run-lifecycle.js.map +1 -0
  39. package/dist/core/run-lifecycle.test.d.ts +2 -0
  40. package/dist/core/run-lifecycle.test.d.ts.map +1 -0
  41. package/dist/core/run-lifecycle.test.js +64 -0
  42. package/dist/core/run-lifecycle.test.js.map +1 -0
  43. package/dist/core/session-manager.d.ts +25 -1
  44. package/dist/core/session-manager.d.ts.map +1 -1
  45. package/dist/core/session-manager.js +66 -1
  46. package/dist/core/session-manager.js.map +1 -1
  47. package/dist/core/session-manager.test.js +80 -2
  48. package/dist/core/session-manager.test.js.map +1 -1
  49. package/dist/core/subagent-manager.d.ts +40 -3
  50. package/dist/core/subagent-manager.d.ts.map +1 -1
  51. package/dist/core/subagent-manager.js +342 -30
  52. package/dist/core/subagent-manager.js.map +1 -1
  53. package/dist/core/subagent-manager.test.js +189 -3
  54. package/dist/core/subagent-manager.test.js.map +1 -1
  55. package/dist/core/subagent-store.d.ts +20 -0
  56. package/dist/core/subagent-store.d.ts.map +1 -0
  57. package/dist/core/subagent-store.js +177 -0
  58. package/dist/core/subagent-store.js.map +1 -0
  59. package/dist/core/subagent-store.test.d.ts +2 -0
  60. package/dist/core/subagent-store.test.d.ts.map +1 -0
  61. package/dist/core/subagent-store.test.js +94 -0
  62. package/dist/core/subagent-store.test.js.map +1 -0
  63. package/dist/modes/agent-home-mode.d.ts.map +1 -1
  64. package/dist/modes/agent-home-mode.js +10 -3
  65. package/dist/modes/agent-home-mode.js.map +1 -1
  66. package/dist/modes/serve-mode.d.ts.map +1 -1
  67. package/dist/modes/serve-mode.js +10 -3
  68. package/dist/modes/serve-mode.js.map +1 -1
  69. package/dist/modes/subagent-worker-mode.d.ts +2 -0
  70. package/dist/modes/subagent-worker-mode.d.ts.map +1 -1
  71. package/dist/modes/subagent-worker-mode.js +13 -3
  72. package/dist/modes/subagent-worker-mode.js.map +1 -1
  73. package/dist/tools/tasks.d.ts +1 -1
  74. package/dist/tools/web-fetch.d.ts +1 -1
  75. package/dist/ui/App.d.ts +7 -1
  76. package/dist/ui/App.d.ts.map +1 -1
  77. package/dist/ui/App.js +63 -17
  78. package/dist/ui/App.js.map +1 -1
  79. package/dist/ui/components/Footer.d.ts +1 -0
  80. package/dist/ui/components/Footer.d.ts.map +1 -1
  81. package/dist/ui/components/Footer.js +3 -3
  82. package/dist/ui/components/Footer.js.map +1 -1
  83. package/dist/ui/components/Footer.test.d.ts +2 -0
  84. package/dist/ui/components/Footer.test.d.ts.map +1 -0
  85. package/dist/ui/components/Footer.test.js +16 -0
  86. package/dist/ui/components/Footer.test.js.map +1 -0
  87. package/dist/ui/hooks/useAgentLoop.d.ts +8 -3
  88. package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
  89. package/dist/ui/hooks/useAgentLoop.js +64 -8
  90. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  91. package/dist/ui/hooks/useSessionPersistence.d.ts +4 -2
  92. package/dist/ui/hooks/useSessionPersistence.d.ts.map +1 -1
  93. package/dist/ui/hooks/useSessionPersistence.js +7 -1
  94. package/dist/ui/hooks/useSessionPersistence.js.map +1 -1
  95. package/dist/ui/render.d.ts +7 -0
  96. package/dist/ui/render.d.ts.map +1 -1
  97. package/dist/ui/render.js +4 -0
  98. package/dist/ui/render.js.map +1 -1
  99. package/package.json +4 -4
@@ -18,10 +18,11 @@ import os from "node:os";
18
18
  import path from "node:path";
19
19
  import { randomUUID } from "node:crypto";
20
20
  import { parseArgs } from "node:util";
21
- import { formatError } from "@kenkaiiii/gg-ai";
21
+ import { environmentSecrets, formatError, redactValue, } 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 { RunLifecycle } from "./core/run-lifecycle.js";
25
26
  import { CHAT_AGENT_IDS, chatAgentSessionsDir, createChatAgent, parseChatAgentId, sessionsDirForChatAgent, switchChatAgent, } from "./chat-agents/index.js";
26
27
  import { buildJiwaTools, JiwaStore } from "./chat-agents/jiwa.js";
27
28
  import { buildMemoryTools, MemoryStore } from "./chat-agents/memory.js";
@@ -929,7 +930,8 @@ async function createSession(deps, opts) {
929
930
  const clients = new Set();
930
931
  let clientSeq = 0;
931
932
  function broadcast(type, data) {
932
- const frame = `data: ${JSON.stringify({ type, data })}\n\n`;
933
+ const safePayload = redactValue({ type, data }, { secrets: environmentSecrets(process.env) });
934
+ const frame = `data: ${JSON.stringify(safePayload)}\n\n`;
933
935
  for (const c of clients)
934
936
  c.res.write(frame);
935
937
  }
@@ -1123,6 +1125,13 @@ async function createSession(deps, opts) {
1123
1125
  session.eventBus.on("compaction_start", (d) => broadcast("compaction_start", d));
1124
1126
  session.eventBus.on("compaction_end", (d) => broadcast("compaction_end", d));
1125
1127
  let running = false;
1128
+ const runLifecycle = new RunLifecycle((runState) => {
1129
+ running = runState !== "idle";
1130
+ if (runState === "cancelling")
1131
+ broadcast("run_cancelling", { runState });
1132
+ });
1133
+ const cancelledRunEndGenerations = new Set();
1134
+ let pendingCancelDrain = null;
1126
1135
  let titleGenerated = false;
1127
1136
  // Bumped by /cancel — a run whose cancel generation changed mid-flight was
1128
1137
  // canceled and earns no XP.
@@ -1145,6 +1154,7 @@ async function createSession(deps, opts) {
1145
1154
  let autopilotCancelled = false;
1146
1155
  // Hard cap on review→prompt→review rounds per user turn (loop safety).
1147
1156
  const MAX_AUTOPILOT_ROUNDS = 3;
1157
+ const CANCEL_TIMEOUT_MS = 5_000;
1148
1158
  // Prompt bodies Autopilot Ken injected into the BUILD session this
1149
1159
  // conversation. Passed into every Ken digest so injected prompts render as
1150
1160
  // "Ken autopilot (injected)" instead of `**User:**` — otherwise multi-round
@@ -1351,29 +1361,63 @@ async function createSession(deps, opts) {
1351
1361
  });
1352
1362
  }
1353
1363
  }
1354
- // Core run lifecycle shared by /prompt and the task runner: flips `running`,
1355
- // brackets the run with run_start/run_end, refreshes the footer extras, and
1356
- // generates the session title once. `label` is the text shown live with the
1357
- // run_start frame.
1364
+ function abortOwnedWork() {
1365
+ cancelGeneration++;
1366
+ abort.abort();
1367
+ // Stop a run-all sweep and every async child through AgentSession's signal.
1368
+ taskRunAll = false;
1369
+ autopilotCancelled = true;
1370
+ kenAutoAbort.abort();
1371
+ }
1372
+ function installFreshRunControllers() {
1373
+ abort = new AbortController();
1374
+ session.setSignal(abort.signal);
1375
+ kenAutoAbort = new AbortController();
1376
+ kenAutoSession?.setSignal(kenAutoAbort.signal);
1377
+ }
1378
+ function finishOwnedGeneration(generation, emitCancelledFallback) {
1379
+ const cancelled = runLifecycle.isCancellationRequested(generation);
1380
+ const settlement = runLifecycle.settle(generation);
1381
+ if (!settlement.settled)
1382
+ return cancelled;
1383
+ // A replacement signal is safe only after the provider-backed owner settled.
1384
+ installFreshRunControllers();
1385
+ if (cancelled && emitCancelledFallback && !cancelledRunEndGenerations.has(generation)) {
1386
+ cancelledRunEndGenerations.add(generation);
1387
+ broadcast("run_end", { cancelled: true, runState: runLifecycle.state });
1388
+ }
1389
+ return cancelled;
1390
+ }
1391
+ // Core provider-run bracket. Standalone runs own a lifecycle generation;
1392
+ // injected autopilot runs share the cycle's outer generation.
1358
1393
  async function runAgent(label, run) {
1359
- running = true;
1394
+ const ownsGeneration = !runLifecycle.running;
1395
+ const generation = ownsGeneration
1396
+ ? runLifecycle.begin(abortOwnedWork).generation
1397
+ : runLifecycle.generation;
1398
+ if (ownsGeneration)
1399
+ pendingCancelDrain = null;
1360
1400
  // Progress (Ranks): completed, non-canceled runs with ≥1 assistant turn earn
1361
1401
  // XP — prompt + any commits authored during the run window.
1362
1402
  const runStartedAt = Date.now();
1363
1403
  const cancelGenAtStart = cancelGeneration;
1364
1404
  const assistantsBeforeRun = countAssistantMessages(session.getMessages());
1365
1405
  let runSucceeded = false;
1366
- broadcast("run_start", { text: label });
1406
+ broadcast("run_start", { text: label, runState: runLifecycle.state });
1367
1407
  try {
1368
- await run();
1408
+ if (!runLifecycle.isCancellationRequested(generation))
1409
+ await run();
1369
1410
  runSucceeded = true;
1370
1411
  }
1371
1412
  catch (err) {
1372
- broadcastError("error", "run failed", err);
1413
+ if (!runLifecycle.isCancellationRequested(generation)) {
1414
+ broadcastError("error", "run failed", err);
1415
+ }
1373
1416
  }
1374
1417
  finally {
1375
- running = false;
1418
+ const cancelled = runLifecycle.isCancellationRequested(generation);
1376
1419
  if (runSucceeded &&
1420
+ !cancelled &&
1377
1421
  cancelGeneration === cancelGenAtStart &&
1378
1422
  countAssistantMessages(session.getMessages()) > assistantsBeforeRun) {
1379
1423
  // Fire-and-forget — XP must never delay or break run teardown.
@@ -1383,21 +1427,24 @@ async function createSession(deps, opts) {
1383
1427
  // background tasks — refresh the footer extras once it settles.
1384
1428
  gitBranch = await getGitBranch(cwd).catch(() => gitBranch);
1385
1429
  gitIsRepo = await isGitRepo(cwd).catch(() => gitIsRepo);
1386
- broadcast("run_end", {});
1430
+ if (ownsGeneration)
1431
+ finishOwnedGeneration(generation, false);
1432
+ // A cancelled injected run is still owned by the surrounding autopilot
1433
+ // cycle; its outer finalizer emits the one terminal cancelled run_end.
1434
+ if (!(cancelled && !ownsGeneration)) {
1435
+ if (cancelled)
1436
+ cancelledRunEndGenerations.add(generation);
1437
+ broadcast("run_end", {
1438
+ ...(cancelled ? { cancelled: true } : {}),
1439
+ runState: runLifecycle.state,
1440
+ });
1441
+ }
1387
1442
  // Autopilot's review loop is driven explicitly from POST /prompt (see
1388
- // runAutopilotCycle), NOT from this shared finally — that keeps the
1389
- // injected GG Coder runs this cycle triggers from recursively re-entering
1390
- // the loop through the same bracket.
1391
- // The agent may have marked project tasks done during the run — prune the
1392
- // completed ones so they drop out of the Tasks modal automatically (users
1393
- // never have to delete finished tasks by hand).
1443
+ // runAutopilotCycle), NOT from this shared finally — that keeps injected
1444
+ // runs from recursively entering the same review loop.
1394
1445
  broadcast("tasks_list", { tasks: pruneDoneTasksSync(cwd) });
1395
- // Queue drains into the run as steering, so it's empty by run_end —
1396
- // sync the webview indicator.
1397
1446
  broadcast("queued", { count: session.getQueuedCount() });
1398
1447
  broadcast("extras", footerExtras());
1399
- // Generate a session title once, after the first run, for the title bar
1400
- // (best-effort, async — don't block the response).
1401
1448
  if (!titleGenerated) {
1402
1449
  titleGenerated = true;
1403
1450
  void session.generateTitle().then((title) => {
@@ -1430,7 +1477,8 @@ async function createSession(deps, opts) {
1430
1477
  return parseAutopilotVerdict(lastAssistantText(ken.getMessages()));
1431
1478
  }
1432
1479
  catch (err) {
1433
- broadcastError("autopilot_error", "autopilot review failed", err);
1480
+ if (!autopilotCancelled)
1481
+ broadcastError("autopilot_error", "autopilot review failed", err);
1434
1482
  return null;
1435
1483
  }
1436
1484
  finally {
@@ -1505,6 +1553,8 @@ async function createSession(deps, opts) {
1505
1553
  async function runAutopilotCycle(originalRequest) {
1506
1554
  if (!autopilot || autopilotCancelled)
1507
1555
  return;
1556
+ const generation = runLifecycle.begin(abortOwnedWork).generation;
1557
+ pendingCancelDrain = null;
1508
1558
  autopilotActive = true;
1509
1559
  // Generation captured by the last plan review; acceptPlan re-checks it so
1510
1560
  // a user Accept/Reject landing mid-review always wins.
@@ -1619,6 +1669,8 @@ async function createSession(deps, opts) {
1619
1669
  }
1620
1670
  finally {
1621
1671
  autopilotActive = false;
1672
+ finishOwnedGeneration(generation, true);
1673
+ queueMicrotask(() => void runStrandedQueue());
1622
1674
  }
1623
1675
  }
1624
1676
  // ── Stranded-queue drain ───────────────────────────────
@@ -1795,7 +1847,7 @@ async function createSession(deps, opts) {
1795
1847
  });
1796
1848
  }
1797
1849
  function json(res, status, body) {
1798
- const payload = JSON.stringify(body);
1850
+ const payload = JSON.stringify(redactValue(body, { secrets: environmentSecrets(process.env) }));
1799
1851
  res.writeHead(status, {
1800
1852
  "content-type": "application/json",
1801
1853
  "access-control-allow-origin": "*",
@@ -1811,6 +1863,7 @@ async function createSession(deps, opts) {
1811
1863
  mode,
1812
1864
  chatAgent,
1813
1865
  running,
1866
+ runState: runLifecycle.state,
1814
1867
  ready: true,
1815
1868
  thinkingLevel: session.getThinkingLevel() ?? null,
1816
1869
  supportedThinkingLevels: getSupportedThinkingLevels(st.provider, st.model),
@@ -1883,6 +1936,7 @@ async function createSession(deps, opts) {
1883
1936
  mode,
1884
1937
  chatAgent,
1885
1938
  running,
1939
+ runState: runLifecycle.state,
1886
1940
  thinkingLevel: session.getThinkingLevel() ?? null,
1887
1941
  supportedThinkingLevels: getSupportedThinkingLevels(st.provider, st.model),
1888
1942
  supportsVideo: getModel(st.model)?.supportsVideo ?? false,
@@ -2378,6 +2432,13 @@ async function createSession(deps, opts) {
2378
2432
  json(res, 400, { error: "empty prompt" });
2379
2433
  return;
2380
2434
  }
2435
+ if (runLifecycle.running && runLifecycle.isCancellationRequested(runLifecycle.generation)) {
2436
+ json(res, 409, {
2437
+ error: runLifecycle.state === "cancelling" ? "run_cancelling" : "cancel_failed",
2438
+ runState: runLifecycle.state,
2439
+ });
2440
+ return;
2441
+ }
2381
2442
  if (running || autopilotActive) {
2382
2443
  // Queue prompts as mid-run steering (mirrors the CLI). Also queue while
2383
2444
  // an autopilot cycle is active but between injected runs (build idle,
@@ -2866,26 +2927,50 @@ async function createSession(deps, opts) {
2866
2927
  return;
2867
2928
  }
2868
2929
  if (method === "POST" && url === "/cancel") {
2869
- cancelGeneration++;
2870
- abort.abort();
2871
- abort = new AbortController();
2872
- session.setSignal(abort.signal);
2873
- running = false;
2874
- // Stop a run-all sweep so the next pending task isn't auto-started.
2875
- taskRunAll = false;
2876
- // Stop any in-flight autopilot cycle: flag it so the loop bails between
2877
- // steps, and abort a review that's mid-prompt on the kenAuto session.
2878
- autopilotCancelled = true;
2879
- kenAutoAbort.abort();
2880
- kenAutoAbort = new AbortController();
2881
- kenAutoSession?.setSignal(kenAutoAbort.signal);
2882
- autopilotReviewing = false;
2883
- // Drop any queued steering and return it so the webview can restore it to
2884
- // the composer.
2885
- const drained = session.drainQueue();
2886
- broadcast("run_end", { cancelled: true });
2887
- broadcast("queued", { count: 0 });
2888
- json(res, 200, { cancelled: true, drained });
2930
+ void (async () => {
2931
+ // Even between task runs, cancellation stops the sweep. Active provider
2932
+ // ownership invokes the full abort hook exactly once through lifecycle.
2933
+ taskRunAll = false;
2934
+ autopilotCancelled = true;
2935
+ if (!runLifecycle.running) {
2936
+ kenAutoAbort.abort();
2937
+ kenAutoAbort = new AbortController();
2938
+ kenAutoSession?.setSignal(kenAutoAbort.signal);
2939
+ }
2940
+ const generation = runLifecycle.generation;
2941
+ if (!pendingCancelDrain || pendingCancelDrain.generation !== generation) {
2942
+ pendingCancelDrain = { generation, text: session.drainQueue() };
2943
+ broadcast("queued", { count: 0 });
2944
+ }
2945
+ const result = await runLifecycle.cancel(CANCEL_TIMEOUT_MS);
2946
+ const drained = pendingCancelDrain.text;
2947
+ if (result.status === "failed") {
2948
+ broadcast("cancel_failed", {
2949
+ error: "cancel_failed",
2950
+ reason: result.reason,
2951
+ runState: runLifecycle.state,
2952
+ });
2953
+ json(res, 504, {
2954
+ error: "cancel_failed",
2955
+ reason: result.reason,
2956
+ runState: runLifecycle.state,
2957
+ drained,
2958
+ });
2959
+ return;
2960
+ }
2961
+ json(res, 200, {
2962
+ cancelled: result.status === "cancelled",
2963
+ runState: runLifecycle.state,
2964
+ drained,
2965
+ });
2966
+ })().catch((error) => {
2967
+ broadcast("cancel_failed", { error: "cancel_failed", runState: runLifecycle.state });
2968
+ json(res, 500, {
2969
+ error: "cancel_failed",
2970
+ message: error instanceof Error ? error.message : String(error),
2971
+ runState: runLifecycle.state,
2972
+ });
2973
+ });
2889
2974
  return;
2890
2975
  }
2891
2976
  if (method === "POST" && url === "/new-session") {