@kenkaiiii/ggcoder 5.26.0 → 5.26.2

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 (57) hide show
  1. package/dist/app-sidecar.js +71 -52
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +0 -3
  5. package/dist/cli.js.map +1 -1
  6. package/dist/config.d.ts +0 -2
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +0 -3
  9. package/dist/config.js.map +1 -1
  10. package/dist/config.test.js +0 -12
  11. package/dist/config.test.js.map +1 -1
  12. package/dist/core/agent-session-queue.test.js +65 -0
  13. package/dist/core/agent-session-queue.test.js.map +1 -1
  14. package/dist/core/agent-session.d.ts +11 -13
  15. package/dist/core/agent-session.d.ts.map +1 -1
  16. package/dist/core/agent-session.js +34 -64
  17. package/dist/core/agent-session.js.map +1 -1
  18. package/dist/core/event-bus.d.ts +7 -0
  19. package/dist/core/event-bus.d.ts.map +1 -1
  20. package/dist/core/event-bus.js.map +1 -1
  21. package/dist/core/run-claim.d.ts +37 -0
  22. package/dist/core/run-claim.d.ts.map +1 -0
  23. package/dist/core/run-claim.js +46 -0
  24. package/dist/core/run-claim.js.map +1 -0
  25. package/dist/core/run-claim.test.d.ts +2 -0
  26. package/dist/core/run-claim.test.d.ts.map +1 -0
  27. package/dist/core/run-claim.test.js +107 -0
  28. package/dist/core/run-claim.test.js.map +1 -0
  29. package/dist/core/settings-manager.d.ts +0 -1
  30. package/dist/core/settings-manager.d.ts.map +1 -1
  31. package/dist/core/settings-manager.js +0 -7
  32. package/dist/core/settings-manager.js.map +1 -1
  33. package/dist/ui/App.d.ts +0 -2
  34. package/dist/ui/App.d.ts.map +1 -1
  35. package/dist/ui/App.js +1 -35
  36. package/dist/ui/App.js.map +1 -1
  37. package/dist/ui/render.d.ts +0 -2
  38. package/dist/ui/render.d.ts.map +1 -1
  39. package/dist/ui/render.js +0 -1
  40. package/dist/ui/render.js.map +1 -1
  41. package/package.json +4 -4
  42. package/dist/core/memory/compaction-notes.d.ts +0 -31
  43. package/dist/core/memory/compaction-notes.d.ts.map +0 -1
  44. package/dist/core/memory/compaction-notes.js +0 -162
  45. package/dist/core/memory/compaction-notes.js.map +0 -1
  46. package/dist/core/memory/compaction-notes.test.d.ts +0 -2
  47. package/dist/core/memory/compaction-notes.test.d.ts.map +0 -1
  48. package/dist/core/memory/compaction-notes.test.js +0 -110
  49. package/dist/core/memory/compaction-notes.test.js.map +0 -1
  50. package/dist/core/memory/journal.d.ts +0 -55
  51. package/dist/core/memory/journal.d.ts.map +0 -1
  52. package/dist/core/memory/journal.js +0 -212
  53. package/dist/core/memory/journal.js.map +0 -1
  54. package/dist/core/memory/journal.test.d.ts +0 -2
  55. package/dist/core/memory/journal.test.d.ts.map +0 -1
  56. package/dist/core/memory/journal.test.js +0 -184
  57. package/dist/core/memory/journal.test.js.map +0 -1
@@ -24,6 +24,7 @@ import { runSubagentWorkerMode } from "./modes/subagent-worker-mode.js";
24
24
  import { setStreamDiagnostic } from "@kenkaiiii/gg-agent";
25
25
  import { AgentSession } from "./core/agent-session.js";
26
26
  import { RunLifecycle } from "./core/run-lifecycle.js";
27
+ import { RunClaim } from "./core/run-claim.js";
27
28
  import { CHAT_AGENT_IDS, chatAgentSessionsDir, createChatAgent, parseChatAgentId, switchChatAgent, } from "./chat-agents/index.js";
28
29
  import { buildJiwaTools, JiwaStore } from "./chat-agents/jiwa.js";
29
30
  import { buildMemoryTools, MemoryStore } from "./chat-agents/memory.js";
@@ -1532,6 +1533,10 @@ async function createSession(deps, opts) {
1532
1533
  recordApprovedPlanMarkers(d.text);
1533
1534
  });
1534
1535
  session.eventBus.on("thinking_delta", (d) => broadcast("thinking_delta", d));
1536
+ // The agent consumed queued steering at a turn boundary. Re-broadcast as the
1537
+ // usual `queued` depth update so the webview drops the pending affordance the
1538
+ // moment the message lands in the loop, not at run_end.
1539
+ session.eventBus.on("queue_drained", (d) => broadcast("queued", { count: d.count, messages: session.listQueuedMessages() }));
1535
1540
  session.eventBus.on("tool_call_start", (d) => {
1536
1541
  toolCallNames.set(d.toolCallId, d.name);
1537
1542
  broadcast("tool_call_start", d);
@@ -1577,6 +1582,10 @@ async function createSession(deps, opts) {
1577
1582
  session.eventBus.on("compaction_start", (d) => broadcast("compaction_start", d));
1578
1583
  session.eventBus.on("compaction_end", (d) => broadcast("compaction_end", d));
1579
1584
  let running = false;
1585
+ // Closes the window between `/prompt` deciding to start a run and `runAgent`
1586
+ // flipping `running` — that stretch awaits, so Node yields inside it. See
1587
+ // RunClaim.
1588
+ const runClaim = new RunClaim();
1580
1589
  const runLifecycle = new RunLifecycle((runState) => {
1581
1590
  running = runState !== "idle";
1582
1591
  if (runState === "cancelling")
@@ -1913,7 +1922,10 @@ async function createSession(deps, opts) {
1913
1922
  // runAutopilotCycle), NOT from this shared finally — that keeps injected
1914
1923
  // runs from recursively entering the same review loop.
1915
1924
  broadcast("tasks_list", { tasks: pruneDoneTasksSync(cwd) });
1916
- broadcast("queued", { count: session.getQueuedCount() });
1925
+ broadcast("queued", {
1926
+ count: session.getQueuedCount(),
1927
+ messages: session.listQueuedMessages(),
1928
+ });
1917
1929
  broadcast("extras", footerExtras());
1918
1930
  }
1919
1931
  }
@@ -2159,7 +2171,10 @@ async function createSession(deps, opts) {
2159
2171
  const next = session.takeNextQueuedMessage();
2160
2172
  if (!next)
2161
2173
  return;
2162
- broadcast("queued", { count: session.getQueuedCount() });
2174
+ broadcast("queued", {
2175
+ count: session.getQueuedCount(),
2176
+ messages: session.listQueuedMessages(),
2177
+ });
2163
2178
  if (!next.text.trim() && next.attachments.length === 0)
2164
2179
  continue;
2165
2180
  // A queued message draining as a fresh turn supersedes any pending
@@ -2402,50 +2417,6 @@ async function createSession(deps, opts) {
2402
2417
  });
2403
2418
  return;
2404
2419
  }
2405
- // Durable per-project journal (.gg/memory.md). Distinct from /memories,
2406
- // which is the personal chat-memory store shared by the chat agents.
2407
- if (method === "GET" && url === "/project-memory") {
2408
- void (async () => {
2409
- try {
2410
- const sm = new SettingsManager(paths.settingsFile);
2411
- await sm.load();
2412
- json(res, 200, { enabled: sm.get("memoryEnabled") });
2413
- }
2414
- catch (error) {
2415
- captureSidecarError(error, "app-sidecar.project-memory.get");
2416
- json(res, 500, { error: error instanceof Error ? error.message : String(error) });
2417
- }
2418
- })();
2419
- return;
2420
- }
2421
- if (method === "POST" && url === "/project-memory") {
2422
- void readBody(req, res).then(async (raw) => {
2423
- if (raw === null)
2424
- return;
2425
- let enabled;
2426
- try {
2427
- enabled = JSON.parse(raw).enabled === true;
2428
- }
2429
- catch {
2430
- json(res, 400, { error: "invalid JSON body" });
2431
- return;
2432
- }
2433
- try {
2434
- const sm = new SettingsManager(paths.settingsFile);
2435
- await sm.load();
2436
- await sm.set("memoryEnabled", enabled);
2437
- // Applies from the next session: the journal handle is built during
2438
- // AgentSession.initialize(), so a live run keeps its current behaviour
2439
- // rather than half-applying the change mid-task.
2440
- json(res, 200, { enabled });
2441
- }
2442
- catch (error) {
2443
- captureSidecarError(error, "app-sidecar.project-memory.set");
2444
- json(res, 500, { error: error instanceof Error ? error.message : String(error) });
2445
- }
2446
- });
2447
- return;
2448
- }
2449
2420
  if (method === "GET" && url === "/jiwa") {
2450
2421
  void jiwaStore
2451
2422
  .snapshot()
@@ -3009,7 +2980,12 @@ async function createSession(deps, opts) {
3009
2980
  return;
3010
2981
  }
3011
2982
  if (method === "POST" && url === "/prompt") {
3012
- void readBody(req, res).then(async (raw) => {
2983
+ // Per-request: only the prompt that actually claimed the start may release
2984
+ // it. A bare release would let an early-returning request (bad JSON, or a
2985
+ // prompt that queued) clear a claim another request is still holding.
2986
+ let claimedStart = false;
2987
+ void readBody(req, res)
2988
+ .then(async (raw) => {
3013
2989
  if (raw === null)
3014
2990
  return;
3015
2991
  let text;
@@ -3029,14 +3005,17 @@ async function createSession(deps, opts) {
3029
3005
  json(res, 400, { error: "empty prompt" });
3030
3006
  return;
3031
3007
  }
3032
- if (runLifecycle.running && runLifecycle.isCancellationRequested(runLifecycle.generation)) {
3008
+ if (runLifecycle.running &&
3009
+ runLifecycle.isCancellationRequested(runLifecycle.generation)) {
3033
3010
  json(res, 409, {
3034
3011
  error: runLifecycle.state === "cancelling" ? "run_cancelling" : "cancel_failed",
3035
3012
  runState: runLifecycle.state,
3036
3013
  });
3037
3014
  return;
3038
3015
  }
3039
- if (running || autopilotActive) {
3016
+ // `runClaim` covers the gap before `runAgent` flips `running`: a
3017
+ // prompt arriving in that window must queue, not start a second run.
3018
+ if (running || runClaim.active || autopilotActive) {
3040
3019
  // Queue prompts as mid-run steering (mirrors the CLI). Also queue while
3041
3020
  // an autopilot cycle is active but between injected runs (build idle,
3042
3021
  // Ken reviewing) so the message never starts a run that collides with
@@ -3045,10 +3024,13 @@ async function createSession(deps, opts) {
3045
3024
  // path as a non-queued attachment prompt when it drains.
3046
3025
  const prepared = attachments.length > 0 ? await prepareAttachments(cwd, attachments) : [];
3047
3026
  const count = session.queueMessage(text, prepared);
3048
- broadcast("queued", { count });
3027
+ broadcast("queued", { count, messages: session.listQueuedMessages() });
3049
3028
  json(res, 202, { queued: true, count });
3050
3029
  return;
3051
3030
  }
3031
+ // Claim the run NOW, synchronously. Everything below this line may
3032
+ // yield, and `running` does not flip until runAgent begins.
3033
+ claimedStart = runClaim.claim();
3052
3034
  json(res, 202, { accepted: true });
3053
3035
  // Webview display hint for this prompt's user bubble (kenSent shimmer
3054
3036
  // label / enhancer highlight segments). Anchored +1 so it attaches to
@@ -3074,7 +3056,8 @@ async function createSession(deps, opts) {
3074
3056
  // how many assistant messages the run actually adds. Computed even when
3075
3057
  // autopilot is currently off — the toggle can flip ON mid-run, and the
3076
3058
  // gate reads the post-run value.
3077
- const workflowCommand = attachments.length === 0 && isWorkflowCommandText(text, await loadWorkflowCommandSpecs());
3059
+ const workflowCommand = attachments.length === 0 &&
3060
+ isWorkflowCommandText(text, await loadWorkflowCommandSpecs());
3078
3061
  const assistantsBefore = countAssistantMessages(session.getMessages());
3079
3062
  const messagesBefore = session.getMessages().length;
3080
3063
  await runAgent(text, async () => {
@@ -3126,6 +3109,10 @@ async function createSession(deps, opts) {
3126
3109
  // A prompt sent while Ken was reviewing (build idle) queued but had no
3127
3110
  // run to steer into — run it now as a fresh turn so it never strands.
3128
3111
  await runStrandedQueue();
3112
+ })
3113
+ .finally(() => {
3114
+ if (claimedStart)
3115
+ runClaim.release();
3129
3116
  });
3130
3117
  return;
3131
3118
  }
@@ -3529,6 +3516,38 @@ async function createSession(deps, opts) {
3529
3516
  });
3530
3517
  return;
3531
3518
  }
3519
+ // Pending queued steering, for the composer's cancel affordance.
3520
+ if (method === "GET" && url === "/queued") {
3521
+ json(res, 200, { queued: session.listQueuedMessages() });
3522
+ return;
3523
+ }
3524
+ // Cancel one pending queued message by id.
3525
+ if (method === "POST" && url === "/queued/cancel") {
3526
+ void readBody(req, res).then((raw) => {
3527
+ if (raw === null)
3528
+ return;
3529
+ let id;
3530
+ try {
3531
+ id = JSON.parse(raw).id ?? "";
3532
+ }
3533
+ catch {
3534
+ json(res, 400, { error: "invalid JSON body" });
3535
+ return;
3536
+ }
3537
+ if (!id.trim()) {
3538
+ json(res, 400, { error: "missing queued message id" });
3539
+ return;
3540
+ }
3541
+ // `false` means it already drained into the run between render and
3542
+ // click. That is a race, not an error, so report it as a normal result
3543
+ // and let the client reconcile from the fresh list.
3544
+ const cancelled = session.cancelQueuedMessage(id);
3545
+ const queued = session.listQueuedMessages();
3546
+ broadcast("queued", { count: queued.length, messages: queued });
3547
+ json(res, 200, { cancelled, queued });
3548
+ });
3549
+ return;
3550
+ }
3532
3551
  if (method === "POST" && url === "/kill") {
3533
3552
  void readBody(req, res).then(async (raw) => {
3534
3553
  if (raw === null)
@@ -3586,7 +3605,7 @@ async function createSession(deps, opts) {
3586
3605
  const generation = runLifecycle.generation;
3587
3606
  if (!pendingCancelDrain || pendingCancelDrain.generation !== generation) {
3588
3607
  pendingCancelDrain = { generation, text: session.drainQueue() };
3589
- broadcast("queued", { count: 0 });
3608
+ broadcast("queued", { count: 0, messages: [] });
3590
3609
  }
3591
3610
  const result = await runLifecycle.cancel(CANCEL_TIMEOUT_MS);
3592
3611
  const drained = pendingCancelDrain.text;