@kenkaiiii/ggcoder 5.67.1 → 5.68.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 (60) hide show
  1. package/dist/app-sidecar.js +121 -3
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/core/agent-session.d.ts +49 -3
  4. package/dist/core/agent-session.js +297 -21
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/ask-user.d.ts +90 -8
  7. package/dist/core/ask-user.js +124 -13
  8. package/dist/core/ask-user.js.map +1 -1
  9. package/dist/core/compaction/plan-step-policy.d.ts +46 -0
  10. package/dist/core/compaction/plan-step-policy.js +57 -0
  11. package/dist/core/compaction/plan-step-policy.js.map +1 -0
  12. package/dist/core/destructive-git-guard.d.ts +63 -0
  13. package/dist/core/destructive-git-guard.js +831 -0
  14. package/dist/core/destructive-git-guard.js.map +1 -0
  15. package/dist/core/event-bus.d.ts +3 -0
  16. package/dist/core/event-bus.js +5 -0
  17. package/dist/core/event-bus.js.map +1 -1
  18. package/dist/core/keep-awake.d.ts +88 -0
  19. package/dist/core/keep-awake.js +251 -0
  20. package/dist/core/keep-awake.js.map +1 -0
  21. package/dist/core/package-threats.d.ts +18 -0
  22. package/dist/core/package-threats.js +168 -0
  23. package/dist/core/package-threats.js.map +1 -0
  24. package/dist/core/session-manager.d.ts +13 -1
  25. package/dist/core/session-manager.js +38 -18
  26. package/dist/core/session-manager.js.map +1 -1
  27. package/dist/core/session-summary-index.d.ts +37 -0
  28. package/dist/core/session-summary-index.js +172 -0
  29. package/dist/core/session-summary-index.js.map +1 -0
  30. package/dist/core/settings-manager.d.ts +2 -0
  31. package/dist/core/settings-manager.js +8 -0
  32. package/dist/core/settings-manager.js.map +1 -1
  33. package/dist/core/shell-threats-popular-packages.d.ts +11 -0
  34. package/dist/core/shell-threats-popular-packages.js +675 -0
  35. package/dist/core/shell-threats-popular-packages.js.map +1 -0
  36. package/dist/core/shell-threats.d.ts +8 -0
  37. package/dist/core/shell-threats.js +186 -0
  38. package/dist/core/shell-threats.js.map +1 -0
  39. package/dist/core/stream-rules.d.ts +30 -0
  40. package/dist/core/stream-rules.js +151 -0
  41. package/dist/core/stream-rules.js.map +1 -0
  42. package/dist/motion-agent/motion-check-tool.js +60 -29
  43. package/dist/motion-agent/motion-check-tool.js.map +1 -1
  44. package/dist/motion-agent/motion-review.d.ts +5 -2
  45. package/dist/motion-agent/motion-review.js +7 -4
  46. package/dist/motion-agent/motion-review.js.map +1 -1
  47. package/dist/tools/ask-user.js +3 -3
  48. package/dist/tools/ask-user.js.map +1 -1
  49. package/dist/tools/bash.js +37 -0
  50. package/dist/tools/bash.js.map +1 -1
  51. package/dist/tools/index.js +1 -1
  52. package/dist/tools/index.js.map +1 -1
  53. package/dist/tools/task-send.d.ts +1 -1
  54. package/dist/tools/task-send.js +15 -1
  55. package/dist/tools/task-send.js.map +1 -1
  56. package/dist/ui/components/ActivityIndicator.js +1 -0
  57. package/dist/ui/components/ActivityIndicator.js.map +1 -1
  58. package/dist/ui/hooks/useAgentLoop.d.ts +1 -1
  59. package/dist/ui/hooks/useAgentLoop.js.map +1 -1
  60. package/package.json +4 -4
@@ -68,6 +68,7 @@ import { listSidecarSessions } from "./app-sidecar-sessions.js";
68
68
  import { loadTasksSync, saveTasksSync, pruneDoneTasksSync, getNextPendingTask, markTaskInProgress, } from "./core/tasks-store.js";
69
69
  import { initLogger, log } from "./core/logger.js";
70
70
  import { installTerminationHandlers } from "./core/shutdown.js";
71
+ import { KeepAwake } from "./core/keep-awake.js";
71
72
  import { RADIO_STATIONS, getCurrentStation, getRadioVolume, playRadio, setRadioVolume, stopRadio, } from "./core/radio.js";
72
73
  import { enrichProcessPath } from "./core/shell-path.js";
73
74
  import { downscaleForPreview, shrinkToFit, validateVisionImage } from "./utils/image.js";
@@ -75,13 +76,16 @@ import { startServeMode } from "./modes/serve-mode.js";
75
76
  import { installSteroids, probeSteroids } from "./core/steroids.js";
76
77
  import { loadTelegramConfig, saveTelegramConfig, verifyBotToken } from "./core/telegram-config.js";
77
78
  import { loadServers, addServer, removeServer, getServer, parseMcpAddCommand, MCPClientManager, McpOAuthStore, createElicitationBridge, } from "./core/mcp/index.js";
78
- import { createAskUserBridge } from "./core/ask-user.js";
79
+ import { askSoftDeadlineMs, createAskUserBridge, deliverLateAnswer, } from "./core/ask-user.js";
79
80
  import { createAskUserTool } from "./tools/ask-user.js";
80
81
  import { buildSnapshot, levelForXp, rankForLevel } from "./core/progress/ranks.js";
81
82
  import { loadProgress, peekProgress, updateProgress } from "./core/progress/store.js";
82
83
  import { awardPrompt, awardCommits } from "./core/progress/engine.js";
83
84
  import { detectNewCommits, repoKey } from "./core/progress/git-xp.js";
84
85
  import { rebuildFromSessions } from "./core/progress/rebuild.js";
86
+ /** App-wide idle-sleep guard: every window's runs share one OS assertion. The
87
+ * `keepAwake` setting is applied at daemon start and live via /keep-awake. */
88
+ const keepAwake = new KeepAwake();
85
89
  const AUTOMATION_PROVENANCE = {
86
90
  source: "runtime",
87
91
  kind: "automation",
@@ -568,6 +572,8 @@ async function main() {
568
572
  // ~/.gg/debug.log (initLogger truncates on each start).
569
573
  const sidecarLog = path.join(paths.agentDir, "gg-app-sidecar.log");
570
574
  initLogger(sidecarLog);
575
+ // Apply the saved keepAwake setting before any run can acquire the guard.
576
+ keepAwake.setEnabled((await new SettingsManager(paths.settingsFile).load()).keepAwake);
571
577
  // The desktop sidecar previously omitted the stream diagnostic hook used by
572
578
  // the CLI, leaving device-specific provider stalls impossible to distinguish from
573
579
  // event-loop starvation or a broken streaming network path. Keep routine
@@ -906,6 +912,40 @@ async function main() {
906
912
  daemonJson(res, 200, { ok: true });
907
913
  return;
908
914
  }
915
+ // Keep-awake is app-wide (one OS assertion for every window), so its
916
+ // setting lives at the daemon level and applies live to in-flight runs.
917
+ if (method === "GET" && url === "/keep-awake") {
918
+ daemonJson(res, 200, { enabled: keepAwake.isEnabled });
919
+ return;
920
+ }
921
+ if (method === "POST" && url === "/keep-awake") {
922
+ void daemonReadBody(req, res).then(async (raw) => {
923
+ if (raw === null)
924
+ return;
925
+ let enabled;
926
+ try {
927
+ enabled = JSON.parse(raw).enabled;
928
+ }
929
+ catch {
930
+ enabled = undefined;
931
+ }
932
+ if (typeof enabled !== "boolean") {
933
+ daemonJson(res, 400, { error: "enabled must be a boolean" });
934
+ return;
935
+ }
936
+ keepAwake.setEnabled(enabled);
937
+ try {
938
+ const sm = new SettingsManager(paths.settingsFile);
939
+ await sm.load();
940
+ await sm.set("keepAwake", enabled);
941
+ }
942
+ catch (err) {
943
+ log("WARN", "app-sidecar", "failed to persist keepAwake", { err: String(err) });
944
+ }
945
+ daemonJson(res, 200, { enabled });
946
+ });
947
+ return;
948
+ }
909
949
  // Progress is daemon-level so the Home screen can paint before a project
910
950
  // session exists; per-session callers still work through the same endpoint.
911
951
  if (method === "GET" && url === "/progress") {
@@ -959,6 +999,8 @@ async function main() {
959
999
  // Radio playback is app-wide (one stream across all windows), so it stops
960
1000
  // at the daemon level, not per session.
961
1001
  stopRadio();
1002
+ // Drop the idle-sleep assertion first: session teardown may hang.
1003
+ keepAwake.dispose();
962
1004
  // Close the ~/.gg progress fs.watch handle (baseline #8 leak fix).
963
1005
  progress.dispose();
964
1006
  await Promise.all([...sessions.values()].map((c) => c.dispose().catch(() => { })));
@@ -1313,9 +1355,33 @@ async function createSession(deps, opts) {
1313
1355
  // MCP bridge above: broadcast over SSE, resolved when the webview POSTs
1314
1356
  // /ask/:id. Registered ONLY here — a TUI/headless/subagent run has nobody to
1315
1357
  // answer, so the tool is absent there rather than hanging on a dead channel.
1358
+ //
1359
+ // Soft deadline ("async ask"): past it the tool returns "no answer yet —
1360
+ // proceed on your best guess" and the question STAYS OPEN. A later answer
1361
+ // rides the ordinary user queue: steering into a live run, or the next turn
1362
+ // when idle. The deadline is short when nobody is watching (autopilot, task
1363
+ // run-all, a scheduled prompt).
1364
+ let scheduledRunActive = false;
1316
1365
  const asks = createAskUserBridge({
1317
1366
  broadcast: (prompt) => broadcast("ask_user", prompt),
1318
- onTimeout: (prompt) => log("WARN", "app-sidecar", "ask_user timed out", { id: prompt.id }),
1367
+ timeoutMs: () => askSoftDeadlineMs(autopilot || autopilotActive || taskRunAll || scheduledRunActive),
1368
+ onTimeout: (prompt) => {
1369
+ log("WARN", "app-sidecar", "ask_user deadline passed; agent proceeding", { id: prompt.id });
1370
+ broadcast("ask_user_deferred", { id: prompt.id });
1371
+ },
1372
+ onClosed: (ids) => broadcast("ask_user_closed", { ids }),
1373
+ onLateAnswer: (late) => {
1374
+ log("INFO", "app-sidecar", "ask_user late answer queued", { id: late.prompt.id });
1375
+ deliverLateAnswer(late, {
1376
+ queueMessage: (text) => session.queueMessage(text),
1377
+ isBusy: () => running || runClaim.active || autopilotActive,
1378
+ onQueued: () => broadcast("queued", {
1379
+ count: session.getQueuedCount(),
1380
+ messages: session.listQueuedMessages(),
1381
+ }),
1382
+ startIdleRun: () => void runStrandedQueue(),
1383
+ });
1384
+ },
1319
1385
  });
1320
1386
  const askUserTool = createAskUserTool(asks.park);
1321
1387
  // The session file path to resume (passed by the daemon's POST /session);
@@ -1860,11 +1926,14 @@ async function createSession(deps, opts) {
1860
1926
  if (!d.silent)
1861
1927
  broadcast("retry", { reason: d.reason, attempt: d.attempt, delayMs: d.delayMs });
1862
1928
  });
1929
+ session.eventBus.on("stream_rule_triggered", (d) => broadcast("stream_rule_triggered", { rules: d.rules, source: d.source, toolName: d.toolName }));
1863
1930
  session.eventBus.on("max_turns", (d) => broadcast("max_turns", d));
1864
1931
  // The agent consumed queued steering at a turn boundary. Re-broadcast as the
1865
1932
  // usual `queued` depth update so the webview drops the pending affordance the
1866
1933
  // moment the message lands in the loop, not at run_end.
1867
1934
  session.eventBus.on("queue_drained", (d) => broadcast("queued", { count: d.count, messages: session.listQueuedMessages() }));
1935
+ // A fresh/loaded session must not receive late answers to the old one's questions.
1936
+ session.eventBus.on("session_start", () => asks.closeDeferred());
1868
1937
  session.eventBus.on("tool_call_start", (d) => {
1869
1938
  toolCallNames.set(d.toolCallId, d.name);
1870
1939
  broadcast("tool_call_start", d);
@@ -1924,6 +1993,26 @@ async function createSession(deps, opts) {
1924
1993
  session.eventBus.on("subagent_state", (d) => broadcast("subagent_state", d));
1925
1994
  session.eventBus.on("compaction_start", (d) => broadcast("compaction_start", d));
1926
1995
  session.eventBus.on("compaction_end", (d) => broadcast("compaction_end", d));
1996
+ // Keep the computer awake while this window's agent works: owned runs and
1997
+ // autopilot cycles (RunLifecycle state), Ken replies, and background
1998
+ // sub-agents that outlive their parent run. Released on settle (completed,
1999
+ // failed or cancelled) and on session dispose.
2000
+ let releaseRunAwake = null;
2001
+ let releaseSubagentAwake = null;
2002
+ const activeSubagents = new Set();
2003
+ session.eventBus.on("subagent_state", (d) => {
2004
+ if (d.state === "starting" || d.state === "running")
2005
+ activeSubagents.add(d.agent_id);
2006
+ else
2007
+ activeSubagents.delete(d.agent_id);
2008
+ if (activeSubagents.size > 0) {
2009
+ releaseSubagentAwake ??= keepAwake.acquire("subagent");
2010
+ }
2011
+ else {
2012
+ releaseSubagentAwake?.();
2013
+ releaseSubagentAwake = null;
2014
+ }
2015
+ });
1927
2016
  let running = false;
1928
2017
  // Closes the window between `/prompt` deciding to start a run and `runAgent`
1929
2018
  // flipping `running` — that stretch awaits, so Node yields inside it. See
@@ -1931,6 +2020,13 @@ async function createSession(deps, opts) {
1931
2020
  const runClaim = new RunClaim();
1932
2021
  const runLifecycle = new RunLifecycle((runState) => {
1933
2022
  running = runState !== "idle";
2023
+ if (running) {
2024
+ releaseRunAwake ??= keepAwake.acquire("run");
2025
+ }
2026
+ else {
2027
+ releaseRunAwake?.();
2028
+ releaseRunAwake = null;
2029
+ }
1934
2030
  if (runState === "cancelling")
1935
2031
  broadcast("run_cancelling", { runState });
1936
2032
  },
@@ -3598,6 +3694,8 @@ async function createSession(deps, opts) {
3598
3694
  // Claim the run NOW, synchronously. Everything below this line may
3599
3695
  // yield, and `running` does not flip until runAgent begins.
3600
3696
  claimedStart = runClaim.claim();
3697
+ // A scheduled prompt has nobody watching: short ask deadline.
3698
+ scheduledRunActive = meta?.scheduled === true;
3601
3699
  json(res, 202, { accepted: true });
3602
3700
  // Gate inputs captured around the run: whether this turn is a workflow
3603
3701
  // slash command (attachment prompts skip slash expansion entirely), and
@@ -3696,8 +3794,10 @@ async function createSession(deps, opts) {
3696
3794
  await runStrandedQueue();
3697
3795
  })
3698
3796
  .finally(() => {
3699
- if (claimedStart)
3797
+ if (claimedStart) {
3798
+ scheduledRunActive = false;
3700
3799
  runClaim.release();
3800
+ }
3701
3801
  });
3702
3802
  return;
3703
3803
  }
@@ -3733,6 +3833,7 @@ async function createSession(deps, opts) {
3733
3833
  }
3734
3834
  json(res, 202, { accepted: true });
3735
3835
  kenRunning = true;
3836
+ const releaseKenAwake = keepAwake.acquire("ken");
3736
3837
  broadcast("ken_run_start", { text });
3737
3838
  try {
3738
3839
  const ken = await ensureKenSession();
@@ -3749,6 +3850,7 @@ async function createSession(deps, opts) {
3749
3850
  broadcastError("ken_error", "ken run failed", err);
3750
3851
  }
3751
3852
  finally {
3853
+ releaseKenAwake();
3752
3854
  kenRunning = false;
3753
3855
  broadcast("ken_run_end", {});
3754
3856
  const pending = pendingKenModel;
@@ -4226,6 +4328,18 @@ async function createSession(deps, opts) {
4226
4328
  json(res, 200, payload);
4227
4329
  return;
4228
4330
  }
4331
+ if (method === "POST" && url === "/prewarm") {
4332
+ // Best-effort Anthropic cache prewarm on the composer's first keystroke.
4333
+ // Fire-and-forget: AgentSession.prewarm() applies its own gating (provider,
4334
+ // setting, history size, TTL, in-flight) and swallows request errors.
4335
+ if (!running) {
4336
+ void session.prewarm().catch((err) => {
4337
+ log("WARN", "app-sidecar", "prewarm failed", { err: String(err) });
4338
+ });
4339
+ }
4340
+ json(res, 202, { accepted: !running });
4341
+ return;
4342
+ }
4229
4343
  if (method === "POST" && url === "/cancel") {
4230
4344
  void (async () => {
4231
4345
  // Even between task runs, cancellation stops the sweep. Active provider
@@ -5082,6 +5196,10 @@ async function createSession(deps, opts) {
5082
5196
  json(res, 404, { error: "not found" });
5083
5197
  }
5084
5198
  async function dispose() {
5199
+ releaseRunAwake?.();
5200
+ releaseRunAwake = null;
5201
+ releaseSubagentAwake?.();
5202
+ releaseSubagentAwake = null;
5085
5203
  elicitations.cancelAll();
5086
5204
  asks.cancelAll();
5087
5205
  tasksPollStopped = true;