@kenkaiiii/ggcoder 5.74.2 → 5.74.3
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 -4
- package/dist/app-sidecar.js.map +1 -1
- package/dist/core/agent-session.d.ts +6 -0
- package/dist/core/agent-session.js +29 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autopilot-cycle.d.ts +6 -2
- package/dist/core/autopilot-cycle.js +1 -1
- package/dist/core/autopilot-cycle.js.map +1 -1
- package/dist/core/plan-handoff.d.ts +30 -0
- package/dist/core/plan-handoff.js +10 -0
- package/dist/core/plan-handoff.js.map +1 -0
- package/dist/core/plan-submission-gate.d.ts +28 -0
- package/dist/core/plan-submission-gate.js +27 -0
- package/dist/core/plan-submission-gate.js.map +1 -0
- package/package.json +4 -4
package/dist/app-sidecar.js
CHANGED
|
@@ -27,6 +27,7 @@ import { setStreamDiagnostic } from "@kenkaiiii/gg-agent";
|
|
|
27
27
|
import { AgentSession } from "./core/agent-session.js";
|
|
28
28
|
import { RunLifecycle } from "./core/run-lifecycle.js";
|
|
29
29
|
import { RunClaim } from "./core/run-claim.js";
|
|
30
|
+
import { shouldOfferPendingPlan } from "./core/plan-handoff.js";
|
|
30
31
|
import { CHAT_AGENT_IDS, chatAgentSessionsDir, createChatAgent, parseChatAgentId, switchChatAgent, } from "./chat-agents/index.js";
|
|
31
32
|
import { createMotionAgentSession } from "./motion-agent/motion-agent.js";
|
|
32
33
|
import { buildJiwaTools, JiwaStore } from "./chat-agents/jiwa.js";
|
|
@@ -1435,6 +1436,8 @@ async function createSession(deps, opts) {
|
|
|
1435
1436
|
session = new AgentSession({
|
|
1436
1437
|
...baseSessionOptions,
|
|
1437
1438
|
additionalTools: [askUserTool],
|
|
1439
|
+
// A run must not end mid-plan with nothing submitted: nobody could review it.
|
|
1440
|
+
planSubmissionGate: true,
|
|
1438
1441
|
// Plan mode belongs only to the coding agent.
|
|
1439
1442
|
onEnterPlan: async (reason) => {
|
|
1440
1443
|
deactivateApprovedPlan();
|
|
@@ -2101,6 +2104,47 @@ async function createSession(deps, opts) {
|
|
|
2101
2104
|
pendingPlanContent = "";
|
|
2102
2105
|
planGeneration++;
|
|
2103
2106
|
}
|
|
2107
|
+
// Plan feedback (Ken's or the user's) is revised back in read-only plan
|
|
2108
|
+
// mode. exit_plan leaves plan mode, so without this a "revise the plan" run
|
|
2109
|
+
// had full write access and could start implementing before any approval.
|
|
2110
|
+
async function reenterPlanModeForRevision() {
|
|
2111
|
+
if (session.getPlanMode())
|
|
2112
|
+
return;
|
|
2113
|
+
deactivateApprovedPlan();
|
|
2114
|
+
await session.setPlanMode(true);
|
|
2115
|
+
broadcast("plan_mode", { active: true });
|
|
2116
|
+
log("INFO", "app-sidecar", "plan mode re-entered for revision");
|
|
2117
|
+
}
|
|
2118
|
+
// Plan generation last handed to the human review box (-1: none yet).
|
|
2119
|
+
let offeredPlanGeneration = -1;
|
|
2120
|
+
// The plan the human review box should show right now, or null. Only once
|
|
2121
|
+
// everything has settled — see shouldOfferPendingPlan.
|
|
2122
|
+
function pendingPlanForHuman(offeredGeneration) {
|
|
2123
|
+
const offer = shouldOfferPendingPlan({
|
|
2124
|
+
planPath: pendingPlanPath,
|
|
2125
|
+
generation: planGeneration,
|
|
2126
|
+
offeredGeneration,
|
|
2127
|
+
running,
|
|
2128
|
+
starting: runClaim.active,
|
|
2129
|
+
autopilotActive,
|
|
2130
|
+
queued: session.getQueuedCount(),
|
|
2131
|
+
});
|
|
2132
|
+
return offer && pendingPlanPath !== null
|
|
2133
|
+
? { planPath: pendingPlanPath, content: pendingPlanContent }
|
|
2134
|
+
: null;
|
|
2135
|
+
}
|
|
2136
|
+
// Open the Accept / Feedback / Reject box once nothing else will act on the
|
|
2137
|
+
// submitted plan. Called at every settle point (turn end, queue drain, task
|
|
2138
|
+
// run end). Without it, a plan Ken didn't approve — handed back, review
|
|
2139
|
+
// failed, capped, cancelled, or skipped — sat pending with no way to act.
|
|
2140
|
+
function offerPendingPlan() {
|
|
2141
|
+
const plan = pendingPlanForHuman(offeredPlanGeneration);
|
|
2142
|
+
if (!plan)
|
|
2143
|
+
return;
|
|
2144
|
+
offeredPlanGeneration = planGeneration;
|
|
2145
|
+
log("INFO", "app-sidecar", "plan handed to user for review", { planPath: plan.planPath });
|
|
2146
|
+
broadcast("plan_review", plan);
|
|
2147
|
+
}
|
|
2104
2148
|
// Workflow (prompt-template) commands: built-in + the project's custom
|
|
2105
2149
|
// `.gg/commands/*.md`. Used to gate autopilot off command turns and to label
|
|
2106
2150
|
// expanded templates in Ken's digests. Loaded fresh so a newly added custom
|
|
@@ -2523,7 +2567,12 @@ async function createSession(deps, opts) {
|
|
|
2523
2567
|
// control flow lives in driveAutopilotCycle (core/autopilot-cycle.ts) so
|
|
2524
2568
|
// every exit path is unit-tested; this only wires the real dependencies.
|
|
2525
2569
|
async function runAutopilotCycle(originalRequest) {
|
|
2526
|
-
if (!autopilot || autopilotCancelled
|
|
2570
|
+
if (!autopilot || autopilotCancelled)
|
|
2571
|
+
return;
|
|
2572
|
+
// Unverified work skips a work review silently. A pending plan still enters
|
|
2573
|
+
// the cycle so driveAutopilotCycle says WHY Ken stepped aside
|
|
2574
|
+
// (autopilot_human) before the plan is handed to the user.
|
|
2575
|
+
if (pendingPlanPath === null && session.getVerificationProblem())
|
|
2527
2576
|
return;
|
|
2528
2577
|
const generation = runLifecycle.begin(abortOwnedWork).generation;
|
|
2529
2578
|
pendingCancelDrain = null;
|
|
@@ -2611,7 +2660,11 @@ async function createSession(deps, opts) {
|
|
|
2611
2660
|
},
|
|
2612
2661
|
// Autopilot-injected run: GG Coder receives the framed prompt (no human
|
|
2613
2662
|
// is watching this turn) while run_start keeps the clean label.
|
|
2614
|
-
runPrompt: (body) => runAgent(body, () =>
|
|
2663
|
+
runPrompt: (body, opts) => runAgent(body, async () => {
|
|
2664
|
+
if (opts?.planRevision)
|
|
2665
|
+
await reenterPlanModeForRevision();
|
|
2666
|
+
await session.prompt(frameAutopilotInjection(body), AUTOMATION_PROVENANCE);
|
|
2667
|
+
}),
|
|
2615
2668
|
emit: (event) => {
|
|
2616
2669
|
// Persist the terminal verdict marker so a resumed session renders the
|
|
2617
2670
|
// same Ken bubble the live run showed instead of dropping it or
|
|
@@ -2677,8 +2730,10 @@ async function createSession(deps, opts) {
|
|
|
2677
2730
|
if (!next.text.trim() && next.attachments.length === 0)
|
|
2678
2731
|
continue;
|
|
2679
2732
|
// A queued message draining as a fresh turn supersedes any pending
|
|
2680
|
-
// plan, exactly like a direct POST /prompt
|
|
2733
|
+
// plan and clears a stale Stop, exactly like a direct POST /prompt
|
|
2734
|
+
// turn — otherwise an earlier Stop silently disables Ken for this turn.
|
|
2681
2735
|
clearPendingPlan();
|
|
2736
|
+
autopilotCancelled = false;
|
|
2682
2737
|
const workflowCommand = next.attachments.length === 0 &&
|
|
2683
2738
|
isWorkflowCommandText(next.text, await loadWorkflowCommandSpecs());
|
|
2684
2739
|
const assistantsBefore = countAssistantMessages(session.getMessages());
|
|
@@ -2722,6 +2777,7 @@ async function createSession(deps, opts) {
|
|
|
2722
2777
|
}
|
|
2723
2778
|
finally {
|
|
2724
2779
|
drainingStrandedQueue = false;
|
|
2780
|
+
offerPendingPlan();
|
|
2725
2781
|
}
|
|
2726
2782
|
}
|
|
2727
2783
|
// ── Task runner (project task list → sessions) ──────────────
|
|
@@ -2768,6 +2824,9 @@ async function createSession(deps, opts) {
|
|
|
2768
2824
|
}
|
|
2769
2825
|
taskRunAll = false;
|
|
2770
2826
|
broadcast("tasks_run_done", {});
|
|
2827
|
+
// Task runs never start an autopilot cycle; a plan a task submitted goes
|
|
2828
|
+
// straight to the user.
|
|
2829
|
+
offerPendingPlan();
|
|
2771
2830
|
}
|
|
2772
2831
|
// ── Provider auth (login) bridge ───────────────────────────
|
|
2773
2832
|
// OAuth login functions are interactive (open a URL, sometimes prompt for a
|
|
@@ -3003,6 +3062,9 @@ async function createSession(deps, opts) {
|
|
|
3003
3062
|
chatAgent,
|
|
3004
3063
|
running,
|
|
3005
3064
|
reviewPending: autopilotActive,
|
|
3065
|
+
// A reconnecting window (reload/reopen) re-opens the review box for
|
|
3066
|
+
// a plan still waiting on the user, regardless of earlier offers.
|
|
3067
|
+
pendingPlan: pendingPlanForHuman(-1),
|
|
3006
3068
|
runState: runLifecycle.state,
|
|
3007
3069
|
thinkingLevel: session.getThinkingLevel() ?? null,
|
|
3008
3070
|
supportedThinkingLevels: getSupportedThinkingLevels(st.provider, st.model),
|
|
@@ -3762,6 +3824,9 @@ async function createSession(deps, opts) {
|
|
|
3762
3824
|
const assistantsBefore = countAssistantMessages(session.getMessages());
|
|
3763
3825
|
const messagesBefore = session.getMessages().length;
|
|
3764
3826
|
await runAgent(text, async () => {
|
|
3827
|
+
// The review box's Feedback: revise read-only, like Ken's feedback.
|
|
3828
|
+
if (meta?.planRevision === true)
|
|
3829
|
+
await reenterPlanModeForRevision();
|
|
3765
3830
|
if (attachments.length > 0) {
|
|
3766
3831
|
// Persist each attachment under .gg/uploads so files are inspectable
|
|
3767
3832
|
// by the agent's tools, then prompt with the media as native blocks.
|
|
@@ -3816,6 +3881,8 @@ async function createSession(deps, opts) {
|
|
|
3816
3881
|
if (claimedStart) {
|
|
3817
3882
|
scheduledRunActive = false;
|
|
3818
3883
|
runClaim.release();
|
|
3884
|
+
// The claim held the plan back until the turn fully settled.
|
|
3885
|
+
offerPendingPlan();
|
|
3819
3886
|
}
|
|
3820
3887
|
});
|
|
3821
3888
|
return;
|
|
@@ -4469,7 +4536,7 @@ async function createSession(deps, opts) {
|
|
|
4469
4536
|
json(res, 400, { error: "invalid JSON body" });
|
|
4470
4537
|
return;
|
|
4471
4538
|
}
|
|
4472
|
-
if (running) {
|
|
4539
|
+
if (running || runClaim.active) {
|
|
4473
4540
|
json(res, 409, { error: "cannot accept a plan while the agent is running" });
|
|
4474
4541
|
return;
|
|
4475
4542
|
}
|