@kenkaiiii/ggcoder 5.26.1 → 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.
- package/dist/app-sidecar.js +71 -8
- package/dist/app-sidecar.js.map +1 -1
- package/dist/core/agent-session-queue.test.js +65 -0
- package/dist/core/agent-session-queue.test.js.map +1 -1
- package/dist/core/agent-session.d.ts +11 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +34 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/event-bus.d.ts +7 -0
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/run-claim.d.ts +37 -0
- package/dist/core/run-claim.d.ts.map +1 -0
- package/dist/core/run-claim.js +46 -0
- package/dist/core/run-claim.js.map +1 -0
- package/dist/core/run-claim.test.d.ts +2 -0
- package/dist/core/run-claim.test.d.ts.map +1 -0
- package/dist/core/run-claim.test.js +107 -0
- package/dist/core/run-claim.test.js.map +1 -0
- package/package.json +4 -4
package/dist/app-sidecar.js
CHANGED
|
@@ -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", {
|
|
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", {
|
|
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
|
|
@@ -2965,7 +2980,12 @@ async function createSession(deps, opts) {
|
|
|
2965
2980
|
return;
|
|
2966
2981
|
}
|
|
2967
2982
|
if (method === "POST" && url === "/prompt") {
|
|
2968
|
-
|
|
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) => {
|
|
2969
2989
|
if (raw === null)
|
|
2970
2990
|
return;
|
|
2971
2991
|
let text;
|
|
@@ -2985,14 +3005,17 @@ async function createSession(deps, opts) {
|
|
|
2985
3005
|
json(res, 400, { error: "empty prompt" });
|
|
2986
3006
|
return;
|
|
2987
3007
|
}
|
|
2988
|
-
if (runLifecycle.running &&
|
|
3008
|
+
if (runLifecycle.running &&
|
|
3009
|
+
runLifecycle.isCancellationRequested(runLifecycle.generation)) {
|
|
2989
3010
|
json(res, 409, {
|
|
2990
3011
|
error: runLifecycle.state === "cancelling" ? "run_cancelling" : "cancel_failed",
|
|
2991
3012
|
runState: runLifecycle.state,
|
|
2992
3013
|
});
|
|
2993
3014
|
return;
|
|
2994
3015
|
}
|
|
2995
|
-
|
|
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) {
|
|
2996
3019
|
// Queue prompts as mid-run steering (mirrors the CLI). Also queue while
|
|
2997
3020
|
// an autopilot cycle is active but between injected runs (build idle,
|
|
2998
3021
|
// Ken reviewing) so the message never starts a run that collides with
|
|
@@ -3001,10 +3024,13 @@ async function createSession(deps, opts) {
|
|
|
3001
3024
|
// path as a non-queued attachment prompt when it drains.
|
|
3002
3025
|
const prepared = attachments.length > 0 ? await prepareAttachments(cwd, attachments) : [];
|
|
3003
3026
|
const count = session.queueMessage(text, prepared);
|
|
3004
|
-
broadcast("queued", { count });
|
|
3027
|
+
broadcast("queued", { count, messages: session.listQueuedMessages() });
|
|
3005
3028
|
json(res, 202, { queued: true, count });
|
|
3006
3029
|
return;
|
|
3007
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();
|
|
3008
3034
|
json(res, 202, { accepted: true });
|
|
3009
3035
|
// Webview display hint for this prompt's user bubble (kenSent shimmer
|
|
3010
3036
|
// label / enhancer highlight segments). Anchored +1 so it attaches to
|
|
@@ -3030,7 +3056,8 @@ async function createSession(deps, opts) {
|
|
|
3030
3056
|
// how many assistant messages the run actually adds. Computed even when
|
|
3031
3057
|
// autopilot is currently off — the toggle can flip ON mid-run, and the
|
|
3032
3058
|
// gate reads the post-run value.
|
|
3033
|
-
const workflowCommand = attachments.length === 0 &&
|
|
3059
|
+
const workflowCommand = attachments.length === 0 &&
|
|
3060
|
+
isWorkflowCommandText(text, await loadWorkflowCommandSpecs());
|
|
3034
3061
|
const assistantsBefore = countAssistantMessages(session.getMessages());
|
|
3035
3062
|
const messagesBefore = session.getMessages().length;
|
|
3036
3063
|
await runAgent(text, async () => {
|
|
@@ -3082,6 +3109,10 @@ async function createSession(deps, opts) {
|
|
|
3082
3109
|
// A prompt sent while Ken was reviewing (build idle) queued but had no
|
|
3083
3110
|
// run to steer into — run it now as a fresh turn so it never strands.
|
|
3084
3111
|
await runStrandedQueue();
|
|
3112
|
+
})
|
|
3113
|
+
.finally(() => {
|
|
3114
|
+
if (claimedStart)
|
|
3115
|
+
runClaim.release();
|
|
3085
3116
|
});
|
|
3086
3117
|
return;
|
|
3087
3118
|
}
|
|
@@ -3485,6 +3516,38 @@ async function createSession(deps, opts) {
|
|
|
3485
3516
|
});
|
|
3486
3517
|
return;
|
|
3487
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
|
+
}
|
|
3488
3551
|
if (method === "POST" && url === "/kill") {
|
|
3489
3552
|
void readBody(req, res).then(async (raw) => {
|
|
3490
3553
|
if (raw === null)
|
|
@@ -3542,7 +3605,7 @@ async function createSession(deps, opts) {
|
|
|
3542
3605
|
const generation = runLifecycle.generation;
|
|
3543
3606
|
if (!pendingCancelDrain || pendingCancelDrain.generation !== generation) {
|
|
3544
3607
|
pendingCancelDrain = { generation, text: session.drainQueue() };
|
|
3545
|
-
broadcast("queued", { count: 0 });
|
|
3608
|
+
broadcast("queued", { count: 0, messages: [] });
|
|
3546
3609
|
}
|
|
3547
3610
|
const result = await runLifecycle.cancel(CANCEL_TIMEOUT_MS);
|
|
3548
3611
|
const drained = pendingCancelDrain.text;
|