@kenkaiiii/ggcoder 5.22.3 → 5.22.4

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: evidence-led-ui
3
- description: Use for web/mobile UI changes or evaluation: pages, components, dashboards, design systems, responsiveness, accessibility, or visual polish, even without explicit design request. Exclude database/API schemas, CLI output, copy-only work, standalone image/SVG generation, and description-only tasks.
3
+ description: Use for broad or design-sensitive web/mobile UI work: net-new pages/screens, redesigns, design-system work, visual polish, responsive or accessibility overhauls, and UI quality review/critique. Do NOT use for small UI edits that follow existing patterns (bug fixes, minor styling tweaks, wiring, copy changes, small component additions matching neighbors); just match the surrounding code for those. Also exclude database/API schemas, CLI output, standalone image/SVG generation, and description-only tasks.
4
4
  license: See LICENSES.md
5
5
  compatibility: Full review requires filesystem inspection and rendered screenshots; web research and browser/device tooling are optional and unavailable checks must be reported honestly.
6
6
  ---
@@ -21,6 +21,7 @@ import { parseArgs } from "node:util";
21
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
+ import { setStreamDiagnostic } from "@kenkaiiii/gg-agent";
24
25
  import { AgentSession } from "./core/agent-session.js";
25
26
  import { RunLifecycle } from "./core/run-lifecycle.js";
26
27
  import { CHAT_AGENT_IDS, chatAgentSessionsDir, createChatAgent, parseChatAgentId, switchChatAgent, } from "./chat-agents/index.js";
@@ -47,6 +48,7 @@ import { SettingsManager } from "./core/settings-manager.js";
47
48
  import { getModel, getDefaultThinkingLevel, getContextWindow, MODELS, } from "./core/model-registry.js";
48
49
  import { resolveStartOrFallback } from "./core/resolve-start.js";
49
50
  import { getGitBranch, getGitDirtyFileCount, isGitRepo } from "./utils/git.js";
51
+ import { getGitHubOpenCounts, getGitHubRepoSlug } from "./utils/github.js";
50
52
  import { extractPlanSteps } from "./utils/plan-steps.js";
51
53
  import { getNextThinkingLevel, getSupportedThinkingLevels, isThinkingLevelSupported, } from "./core/thinking-level.js";
52
54
  import { PROMPT_COMMANDS } from "./core/prompt-commands.js";
@@ -518,6 +520,31 @@ async function main() {
518
520
  // ~/.gg/debug.log (initLogger truncates on each start).
519
521
  const sidecarLog = path.join(paths.agentDir, "gg-app-sidecar.log");
520
522
  initLogger(sidecarLog);
523
+ // The desktop sidecar previously omitted the stream diagnostic hook used by
524
+ // the CLI, leaving device-specific provider stalls impossible to distinguish from
525
+ // event-loop starvation or a broken streaming network path. Keep routine
526
+ // phases lightweight; timeout phases include non-sensitive runtime context.
527
+ setStreamDiagnostic((phase, data) => {
528
+ const includeRuntime = phase === "idle_timeout_fired" ||
529
+ phase === "hard_timeout_fired" ||
530
+ phase === "stall_exhausted";
531
+ log("INFO", "stream", phase, {
532
+ ...(data ?? {}),
533
+ ...(includeRuntime
534
+ ? {
535
+ platform: process.platform,
536
+ arch: process.arch,
537
+ osRelease: os.release(),
538
+ node: process.version,
539
+ logicalCpus: os.cpus().length,
540
+ totalMemoryMb: Math.round(os.totalmem() / 1024 / 1024),
541
+ freeMemoryMb: Math.round(os.freemem() / 1024 / 1024),
542
+ rssMb: Math.round(process.memoryUsage().rss / 1024 / 1024),
543
+ processUptimeSec: Math.round(process.uptime()),
544
+ }
545
+ : {}),
546
+ });
547
+ });
521
548
  // Global last-resort guards, installed as early as the logger allows so they
522
549
  // cover the WHOLE lifecycle — including startup/initialize, the phase the
523
550
  // "sidecar did not start in time" bug lives in. The sidecar is a long-lived
@@ -1192,14 +1219,20 @@ async function createSession(deps, opts) {
1192
1219
  // Workspace extras (context window, git status, background tasks). Git state
1193
1220
  // is resolved once at startup and refreshed after every run; the context
1194
1221
  // window follows the active model.
1195
- const [initialGitBranch, initialGitIsRepo, initialDirtyFileCount] = await Promise.all([
1222
+ const [initialGitBranch, initialGitIsRepo, initialDirtyFileCount, initialGitHubSlug] = await Promise.all([
1196
1223
  getGitBranch(cwd).catch(() => null),
1197
1224
  isGitRepo(cwd).catch(() => false),
1198
1225
  getGitDirtyFileCount(cwd).catch(() => 0),
1226
+ getGitHubRepoSlug(cwd).catch(() => null),
1199
1227
  ]);
1200
1228
  let gitBranch = initialGitBranch;
1201
1229
  let gitIsRepo = initialGitIsRepo;
1202
1230
  let gitDirtyFileCount = initialDirtyFileCount;
1231
+ // Open issue/PR counts for the origin repo's GitHub slug, via the `gh` CLI's
1232
+ // auth. null = unknown (gh missing/unauthed, non-GitHub origin) → chips hidden.
1233
+ const gitHubSlug = initialGitHubSlug;
1234
+ let gitHubIssues = null;
1235
+ let gitHubPRs = null;
1203
1236
  function currentContextWindow() {
1204
1237
  const st = session.getState();
1205
1238
  return getContextWindow(st.model, { provider: st.provider, accountId: st.accountId });
@@ -1212,9 +1245,26 @@ async function createSession(deps, opts) {
1212
1245
  gitBranch,
1213
1246
  isGitRepo: gitIsRepo,
1214
1247
  gitDirtyFileCount,
1248
+ gitHubIssues,
1249
+ gitHubPRs,
1250
+ gitHubRepoUrl: gitHubSlug ? `https://github.com/${gitHubSlug}` : null,
1215
1251
  tasks: session.listBackgroundProcesses(),
1216
1252
  };
1217
1253
  }
1254
+ // Refresh the GitHub counts and broadcast only on change. Transient failures
1255
+ // keep the last-known numbers so the chips don't flicker off on a timeout.
1256
+ async function refreshGitHubCounts() {
1257
+ if (!gitHubSlug)
1258
+ return;
1259
+ const counts = await getGitHubOpenCounts(gitHubSlug);
1260
+ if (!counts)
1261
+ return;
1262
+ if (counts.issues !== gitHubIssues || counts.prs !== gitHubPRs) {
1263
+ gitHubIssues = counts.issues;
1264
+ gitHubPRs = counts.prs;
1265
+ broadcast("extras", footerExtras());
1266
+ }
1267
+ }
1218
1268
  // tool_call_end carries no tool name (only the id), so remember each call's
1219
1269
  // name from tool_call_start to log a useful line on completion. Mirrors the
1220
1270
  // CLI's logging so the app sidecar's ~/.gg/gg-app-sidecar.log records tool
@@ -1650,6 +1700,9 @@ async function createSession(deps, opts) {
1650
1700
  isGitRepo(cwd).catch(() => gitIsRepo),
1651
1701
  getGitDirtyFileCount(cwd).catch(() => gitDirtyFileCount),
1652
1702
  ]);
1703
+ // A run may have opened/closed issues or PRs — refresh fire-and-forget so
1704
+ // teardown isn't delayed by the network. Broadcasts itself on change.
1705
+ void refreshGitHubCounts();
1653
1706
  // Serialize behind any marker/tool-triggered refresh so the terminal
1654
1707
  // progress snapshot uses the live plan file. Once every canonical step
1655
1708
  // is complete, remove the approved plan from future system prompts and
@@ -2102,6 +2155,20 @@ async function createSession(deps, opts) {
2102
2155
  gitPoll.unref?.();
2103
2156
  };
2104
2157
  scheduleGitPoll(5000);
2158
+ // GitHub issue/PR counts change outside the app (web UI, teammates), so poll
2159
+ // on a slow cadence. Network-bound, so keep it well under the search API's
2160
+ // rate budget (2 calls per tick). No-op when the origin isn't a GitHub repo.
2161
+ let gitHubPoll;
2162
+ let gitHubPollStopped = false;
2163
+ const scheduleGitHubPoll = (delay) => {
2164
+ if (gitHubPollStopped)
2165
+ return;
2166
+ gitHubPoll = setTimeout(() => {
2167
+ void refreshGitHubCounts().finally(() => scheduleGitHubPoll(60_000));
2168
+ }, delay);
2169
+ gitHubPoll.unref?.();
2170
+ };
2171
+ scheduleGitHubPoll(2000);
2105
2172
  function readBody(req, res) {
2106
2173
  return readCappedBody(req, res);
2107
2174
  }
@@ -3795,6 +3862,9 @@ async function createSession(deps, opts) {
3795
3862
  gitPollStopped = true;
3796
3863
  if (gitPoll)
3797
3864
  clearTimeout(gitPoll);
3865
+ gitHubPollStopped = true;
3866
+ if (gitHubPoll)
3867
+ clearTimeout(gitHubPoll);
3798
3868
  // Stop the Telegram serve loop + dispose its per-chat sessions.
3799
3869
  if (serveController)
3800
3870
  await serveController.stop().catch(() => { });