@kenkaiiii/ggcoder 5.11.0 → 5.13.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 (83) hide show
  1. package/README.md +25 -12
  2. package/dist/app-sidecar.js +69 -6
  3. package/dist/app-sidecar.js.map +1 -1
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +60 -11
  6. package/dist/cli.js.map +1 -1
  7. package/dist/core/agent-session-subagent-policy.test.d.ts +2 -0
  8. package/dist/core/agent-session-subagent-policy.test.d.ts.map +1 -0
  9. package/dist/core/agent-session-subagent-policy.test.js +36 -0
  10. package/dist/core/agent-session-subagent-policy.test.js.map +1 -0
  11. package/dist/core/agent-session.d.ts +8 -1
  12. package/dist/core/agent-session.d.ts.map +1 -1
  13. package/dist/core/agent-session.js +26 -16
  14. package/dist/core/agent-session.js.map +1 -1
  15. package/dist/core/auth-providers.js +5 -5
  16. package/dist/core/auth-providers.js.map +1 -1
  17. package/dist/core/event-bus.d.ts +2 -0
  18. package/dist/core/event-bus.d.ts.map +1 -1
  19. package/dist/core/event-bus.js.map +1 -1
  20. package/dist/core/mcp/store.d.ts +2 -2
  21. package/dist/core/radio.d.ts +3 -0
  22. package/dist/core/radio.d.ts.map +1 -1
  23. package/dist/core/radio.js +54 -8
  24. package/dist/core/radio.js.map +1 -1
  25. package/dist/core/radio.test.d.ts +2 -0
  26. package/dist/core/radio.test.d.ts.map +1 -0
  27. package/dist/core/radio.test.js +45 -0
  28. package/dist/core/radio.test.js.map +1 -0
  29. package/dist/core/subagent-manager.d.ts +73 -0
  30. package/dist/core/subagent-manager.d.ts.map +1 -0
  31. package/dist/core/subagent-manager.js +408 -0
  32. package/dist/core/subagent-manager.js.map +1 -0
  33. package/dist/core/subagent-manager.test.d.ts +2 -0
  34. package/dist/core/subagent-manager.test.d.ts.map +1 -0
  35. package/dist/core/subagent-manager.test.js +86 -0
  36. package/dist/core/subagent-manager.test.js.map +1 -0
  37. package/dist/core/subagent-policy.d.ts +4 -0
  38. package/dist/core/subagent-policy.d.ts.map +1 -0
  39. package/dist/core/subagent-policy.js +19 -0
  40. package/dist/core/subagent-policy.js.map +1 -0
  41. package/dist/modes/subagent-worker-mode.d.ts +14 -0
  42. package/dist/modes/subagent-worker-mode.d.ts.map +1 -0
  43. package/dist/modes/subagent-worker-mode.js +185 -0
  44. package/dist/modes/subagent-worker-mode.js.map +1 -0
  45. package/dist/tools/index.d.ts +10 -2
  46. package/dist/tools/index.d.ts.map +1 -1
  47. package/dist/tools/index.js +17 -1
  48. package/dist/tools/index.js.map +1 -1
  49. package/dist/tools/subagent-control.d.ts +6 -0
  50. package/dist/tools/subagent-control.d.ts.map +1 -0
  51. package/dist/tools/subagent-control.js +95 -0
  52. package/dist/tools/subagent-control.js.map +1 -0
  53. package/dist/tools/subagent-control.test.d.ts +2 -0
  54. package/dist/tools/subagent-control.test.d.ts.map +1 -0
  55. package/dist/tools/subagent-control.test.js +40 -0
  56. package/dist/tools/subagent-control.test.js.map +1 -0
  57. package/dist/tools/subagent-shared.d.ts +24 -0
  58. package/dist/tools/subagent-shared.d.ts.map +1 -0
  59. package/dist/tools/subagent-shared.js +53 -0
  60. package/dist/tools/subagent-shared.js.map +1 -0
  61. package/dist/tools/subagent.d.ts.map +1 -1
  62. package/dist/tools/subagent.js +21 -64
  63. package/dist/tools/subagent.js.map +1 -1
  64. package/dist/tools/subagent.test.js +16 -0
  65. package/dist/tools/subagent.test.js.map +1 -1
  66. package/dist/ui/App.d.ts +2 -0
  67. package/dist/ui/App.d.ts.map +1 -1
  68. package/dist/ui/App.js +62 -2
  69. package/dist/ui/App.js.map +1 -1
  70. package/dist/ui/hooks/useModeState.d.ts +4 -2
  71. package/dist/ui/hooks/useModeState.d.ts.map +1 -1
  72. package/dist/ui/hooks/useModeState.js +14 -3
  73. package/dist/ui/hooks/useModeState.js.map +1 -1
  74. package/dist/ui/login.d.ts.map +1 -1
  75. package/dist/ui/login.js +13 -5
  76. package/dist/ui/login.js.map +1 -1
  77. package/dist/ui/render.d.ts +14 -0
  78. package/dist/ui/render.d.ts.map +1 -1
  79. package/dist/ui/render.js +2 -0
  80. package/dist/ui/render.js.map +1 -1
  81. package/dist/ui/render.test.js +17 -0
  82. package/dist/ui/render.test.js.map +1 -1
  83. package/package.json +4 -4
package/README.md CHANGED
@@ -150,23 +150,36 @@ Plus built-in workflows that ship with the binary:
150
150
 
151
151
  GG Coder comes with a focused set of tools. Each one is small, well-described, and earns its place in the prompt.
152
152
 
153
- | Tool | What it does |
154
- |---|---|
155
- | `bash` | Run shell commands |
156
- | `read` | Read file contents |
157
- | `write` | Write files |
158
- | `edit` | Surgical string replacements |
159
- | `grep` | Search file contents (regex) |
160
- | `find` | Find files by glob pattern |
161
- | `ls` | List directory contents |
162
- | `web_fetch` | Fetch URL content |
163
- | `screenshot` | Open a URL / dev server in a headless browser and capture a PNG so the agent can see the rendered page |
164
- | `subagent` | Spawn parallel sub-agents |
153
+ | Tool | What it does |
154
+ | --------------------------------- | ------------------------------------------------------------------------------------------------------ |
155
+ | `bash` | Run shell commands |
156
+ | `read` | Read file contents |
157
+ | `write` | Write files |
158
+ | `edit` | Surgical string replacements |
159
+ | `grep` | Search file contents (regex) |
160
+ | `find` | Find files by glob pattern |
161
+ | `ls` | List directory contents |
162
+ | `web_fetch` | Fetch URL content |
163
+ | `screenshot` | Open a URL / dev server in a headless browser and capture a PNG so the agent can see the rendered page |
164
+ | `subagent` | Run one blocking, isolated child task (backward-compatible) |
165
+ | `spawn_agent` / `wait_agent` | Launch persistent child turns concurrently and collect results |
166
+ | `send_message` / `followup_task` | Steer a running child or reuse an idle child's context |
167
+ | `list_agents` / `interrupt_agent` | Inspect or interrupt persistent children |
165
168
 
166
169
  The `screenshot` tool needs the optional `playwright` dependency plus a one-time `npx playwright install chromium`. Without it the tool returns an install hint instead of failing the turn. Captured images render inline in graphics-capable terminals (kitty, Ghostty, WezTerm, iTerm2); other terminals show a text line.
167
170
 
168
171
  Plus the [Grep MCP](https://grep.dev) for searching across 1M+ public GitHub repos. Add your own MCPs in settings if you need more — but start lean.
169
172
 
173
+ ### Async subagent lifecycle
174
+
175
+ `subagent` remains blocking. The async suite launches persistent NDJSON worker processes, so a parent can start up to four active child turns, keep working, steer them, and wait for any or all results. Up to eight idle workers remain available for follow-up; bounded snapshots retain the latest 20 agents.
176
+
177
+ Only GPT-5.6 Sol/Terra at **Ultra** delegates proactively. Lower Sol/Terra levels use async agents only when the user or project/skill instructions request delegation; other models receive no proactive policy.
178
+
179
+ Children share the parent working directory, not isolated worktrees. Parallel writes must target disjoint files or subsystems. Async fan-out is one level deep, child output is bounded, idle workers reap after 10 minutes, and workers are not resumable after a CLI/app restart.
180
+
181
+ Parent cancellation interrupts active children. Session disposal shuts down every worker process alongside background commands, LSP servers, and MCP connections.
182
+
170
183
  ---
171
184
 
172
185
  ## 🪄 Custom commands
@@ -20,6 +20,7 @@ import { randomUUID } from "node:crypto";
20
20
  import { parseArgs } from "node:util";
21
21
  import { formatError } from "@kenkaiiii/gg-ai";
22
22
  import { runJsonMode } from "./modes/json-mode.js";
23
+ import { runSubagentWorkerMode } from "./modes/subagent-worker-mode.js";
23
24
  import { AgentSession } from "./core/agent-session.js";
24
25
  import { buildKenSystemPrompt, buildKenAutopilotSystemPrompt } from "./core/ken-prompt.js";
25
26
  import { buildKenDigest, buildKenAutopilotContext, buildKenAutopilotPlanContext, } from "./core/ken-context.js";
@@ -46,7 +47,7 @@ import { loadCustomCommands } from "./core/custom-commands.js";
46
47
  import { discoverProjects, listRecentSessions } from "./core/project-discovery.js";
47
48
  import { loadTasksSync, saveTasksSync, pruneDoneTasksSync, getNextPendingTask, markTaskInProgress, } from "./core/tasks-store.js";
48
49
  import { initLogger, log } from "./core/logger.js";
49
- import { RADIO_STATIONS, getCurrentStation, playRadio, stopRadio } from "./core/radio.js";
50
+ import { RADIO_STATIONS, getCurrentStation, getRadioVolume, playRadio, setRadioVolume, stopRadio, } from "./core/radio.js";
50
51
  import { enrichProcessPath } from "./core/shell-path.js";
51
52
  import { downscaleForPreview, validateVisionImage } from "./utils/image.js";
52
53
  import { startServeMode } from "./modes/serve-mode.js";
@@ -457,6 +458,11 @@ function daemonJson(res, status, body) {
457
458
  res.end(JSON.stringify(body));
458
459
  }
459
460
  async function main() {
461
+ // Hidden persistent-worker dispatch must win before strict JSON/server parsing.
462
+ if (process.argv.includes("--subagent-worker")) {
463
+ await runSubagentWorkerMode();
464
+ return;
465
+ }
460
466
  // Sub-agent JSON-mode dispatch must win before any sidecar/server setup.
461
467
  if (await runJsonModeIfRequested())
462
468
  return;
@@ -681,16 +687,40 @@ async function main() {
681
687
  process.stdout.write(`GG_APP_LISTENING ${addr.port}\n`);
682
688
  log("INFO", "app-sidecar", "daemon listening", { port: String(addr.port), host });
683
689
  });
684
- const shutdown = async () => {
690
+ const shellPid = process.ppid;
691
+ let shuttingDown = false;
692
+ async function shutdown() {
693
+ if (shuttingDown)
694
+ return;
695
+ shuttingDown = true;
696
+ clearInterval(parentWatch);
685
697
  // Radio playback is app-wide (one stream across all windows), so it stops
686
698
  // at the daemon level, not per session.
687
699
  stopRadio();
688
700
  await Promise.all([...sessions.values()].map((c) => c.dispose().catch(() => { })));
689
701
  server.close();
690
702
  process.exit(0);
691
- };
703
+ }
692
704
  process.on("SIGINT", () => void shutdown());
693
705
  process.on("SIGTERM", () => void shutdown());
706
+ process.once("exit", stopRadio);
707
+ // Tauri can disappear without delivering a signal (force-quit, dev runner
708
+ // teardown, crash). Detect reparenting or a dead shell so the daemon and its
709
+ // radio player do not survive as audible orphans.
710
+ const parentWatch = setInterval(() => {
711
+ let parentAlive = process.ppid === shellPid;
712
+ if (parentAlive && shellPid > 1) {
713
+ try {
714
+ process.kill(shellPid, 0);
715
+ }
716
+ catch (error) {
717
+ parentAlive = error.code === "EPERM";
718
+ }
719
+ }
720
+ if (!parentAlive)
721
+ void shutdown();
722
+ }, 1_000);
723
+ parentWatch.unref?.();
694
724
  }
695
725
  /** Ken's read-only tool allow-list. Excludes every mutating tool (write/edit/
696
726
  * bash/tasks/subagent/generate_image/enter_plan/exit_plan/task_*) so the mentor
@@ -1039,6 +1069,7 @@ async function createSession(deps, opts) {
1039
1069
  });
1040
1070
  session.eventBus.on("model_change", (d) => broadcast("model_change", d));
1041
1071
  session.eventBus.on("hook", (d) => broadcast("hook", d));
1072
+ session.eventBus.on("subagent_state", (d) => broadcast("subagent_state", d));
1042
1073
  session.eventBus.on("compaction_start", (d) => broadcast("compaction_start", d));
1043
1074
  session.eventBus.on("compaction_end", (d) => broadcast("compaction_end", d));
1044
1075
  let running = false;
@@ -2137,12 +2168,17 @@ async function createSession(deps, opts) {
2137
2168
  }
2138
2169
  // Assistant tool_call blocks: detect sub-agent delegations.
2139
2170
  if (msg.role === "assistant" && typeof msg.content !== "string") {
2140
- const subagentCalls = msg.content.filter((c) => c.type === "tool_call" && c.name === "subagent");
2171
+ const subagentCalls = msg.content.filter((c) => c.type === "tool_call" && (c.name === "subagent" || c.name === "spawn_agent"));
2141
2172
  if (subagentCalls.length > 0) {
2142
2173
  const agents = subagentCalls.map((c) => {
2143
2174
  const result = toolResultMap.get(c.id);
2144
2175
  return {
2145
- agentName: typeof c.args?.agent === "string" ? c.args.agent : undefined,
2176
+ agentName: c.name === "spawn_agent" && typeof c.args?.task_name === "string"
2177
+ ? c.args.task_name
2178
+ : typeof c.args?.agent === "string"
2179
+ ? c.args.agent
2180
+ : undefined,
2181
+ // Async workers are intentionally non-resumable; restored rows are historical.
2146
2182
  status: result?.isError ? "error" : "done",
2147
2183
  toolUseCount: 0,
2148
2184
  };
@@ -2427,7 +2463,34 @@ async function createSession(deps, opts) {
2427
2463
  // duplicate audio across windows (the original per-window goal), now for
2428
2464
  // free. (To restore per-window radio, key playback by sessionId.)
2429
2465
  if (method === "GET" && url === "/radio") {
2430
- json(res, 200, { stations: RADIO_STATIONS, current: getCurrentStation() });
2466
+ json(res, 200, {
2467
+ stations: RADIO_STATIONS,
2468
+ current: getCurrentStation(),
2469
+ volume: getRadioVolume(),
2470
+ });
2471
+ return;
2472
+ }
2473
+ if (method === "POST" && url === "/radio/volume") {
2474
+ void readBody(req).then((raw) => {
2475
+ let volume;
2476
+ try {
2477
+ volume = Number(JSON.parse(raw).volume);
2478
+ }
2479
+ catch {
2480
+ json(res, 400, { error: "invalid JSON body" });
2481
+ return;
2482
+ }
2483
+ if (!Number.isFinite(volume)) {
2484
+ json(res, 400, { error: "volume must be a number" });
2485
+ return;
2486
+ }
2487
+ const result = setRadioVolume(volume);
2488
+ if (!result.ok) {
2489
+ json(res, 400, { error: result.error ?? "Radio volume failed to update." });
2490
+ return;
2491
+ }
2492
+ json(res, 200, { current: getCurrentStation(), volume: getRadioVolume() });
2493
+ });
2431
2494
  return;
2432
2495
  }
2433
2496
  if (method === "POST" && url === "/radio") {