@kenkaiiii/ggcoder 5.22.0 → 5.22.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 +115 -40
- package/dist/app-sidecar.js.map +1 -1
- package/dist/core/subagent-manager.d.ts.map +1 -1
- package/dist/core/subagent-manager.js +11 -2
- package/dist/core/subagent-manager.js.map +1 -1
- package/dist/utils/http-body.d.ts +19 -0
- package/dist/utils/http-body.d.ts.map +1 -0
- package/dist/utils/http-body.js +58 -0
- package/dist/utils/http-body.js.map +1 -0
- package/dist/utils/http-body.test.d.ts +2 -0
- package/dist/utils/http-body.test.d.ts.map +1 -0
- package/dist/utils/http-body.test.js +68 -0
- package/dist/utils/http-body.test.js.map +1 -0
- package/package.json +4 -4
package/dist/app-sidecar.js
CHANGED
|
@@ -35,6 +35,7 @@ import { validateKenModelPref, effectiveKenModel } from "./core/ken-model.js";
|
|
|
35
35
|
import { normalizeAutopilotMarkersForHistory, normalizeAppMarkersForHistory, normalizeKenTurnsForHistory, restoreUserRow, restoreAssistantTexts, autopilotMarkerCopySeed, } from "./core/session-history.js";
|
|
36
36
|
import { AuthStorage } from "./core/auth-storage.js";
|
|
37
37
|
import { cleanupToolOutputs } from "./tools/overflow.js";
|
|
38
|
+
import { readCappedBody } from "./utils/http-body.js";
|
|
38
39
|
import { fetchSubscriptionUsage, MOONSHOT_OAUTH_KEY, SubscriptionUsageError, XIAOMI_CREDITS_KEY, } from "@kenkaiiii/gg-core";
|
|
39
40
|
import { loginAnthropic } from "./core/oauth/anthropic.js";
|
|
40
41
|
import { loginOpenAI } from "./core/oauth/openai.js";
|
|
@@ -240,6 +241,9 @@ async function prepareAttachments(cwd, attachments) {
|
|
|
240
241
|
return out;
|
|
241
242
|
}
|
|
242
243
|
const FILE_SEARCH_LIMIT = 20;
|
|
244
|
+
// Upper bound on files walked per search (baseline #8 memory cap). Far above the
|
|
245
|
+
// 20-result output limit, so relevance/recency ranking is unaffected in practice.
|
|
246
|
+
const FILE_SEARCH_SCAN_CAP = 50_000;
|
|
243
247
|
/** Score a candidate path against a lowercased query. Higher is better; a
|
|
244
248
|
* negative score means "no match". Basename hits beat path hits; prefix beats
|
|
245
249
|
* substring; shorter paths break ties. */
|
|
@@ -292,8 +296,13 @@ async function searchProjectFiles(cwd, rawQuery) {
|
|
|
292
296
|
}
|
|
293
297
|
const ig = ignore.default().add(gitignore);
|
|
294
298
|
// `stats: true` gives mtime without a second stat pass, so the empty-query
|
|
295
|
-
// "recent files" path is a single walk.
|
|
296
|
-
|
|
299
|
+
// "recent files" path is a single walk. Stream + hard scan cap (baseline #8):
|
|
300
|
+
// collecting the full result array retained ~0.9 MB per 1k files (18.5 MB at
|
|
301
|
+
// 20k), unbounded. Bail after FILE_SEARCH_SCAN_CAP entries so a giant repo
|
|
302
|
+
// can't balloon RSS; the newest/most-relevant matches still surface because
|
|
303
|
+
// the cap is far above the 20-result output limit.
|
|
304
|
+
const entries = [];
|
|
305
|
+
const scanStream = fg.default.stream("**/*", {
|
|
297
306
|
cwd,
|
|
298
307
|
dot: false,
|
|
299
308
|
onlyFiles: true,
|
|
@@ -302,6 +311,17 @@ async function searchProjectFiles(cwd, rawQuery) {
|
|
|
302
311
|
followSymbolicLinks: false,
|
|
303
312
|
stats: true,
|
|
304
313
|
});
|
|
314
|
+
for await (const entry of scanStream) {
|
|
315
|
+
entries.push(entry);
|
|
316
|
+
if (entries.length >= FILE_SEARCH_SCAN_CAP) {
|
|
317
|
+
scanStream.destroy();
|
|
318
|
+
log("DEBUG", "app-sidecar", "file search scan cap hit", {
|
|
319
|
+
cap: String(FILE_SEARCH_SCAN_CAP),
|
|
320
|
+
cwd,
|
|
321
|
+
});
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
305
325
|
const files = entries.filter((e) => !ig.ignores(e.path));
|
|
306
326
|
if (!query) {
|
|
307
327
|
return files
|
|
@@ -469,13 +489,8 @@ async function runJsonModeIfRequested() {
|
|
|
469
489
|
// ── Daemon-level HTTP helpers (shared by the session-management routes) ─────
|
|
470
490
|
// The per-session route table has its own local copies; these serve the
|
|
471
491
|
// daemon's own POST /session / DELETE /session routes.
|
|
472
|
-
function daemonReadBody(req) {
|
|
473
|
-
return
|
|
474
|
-
const chunks = [];
|
|
475
|
-
req.on("data", (c) => chunks.push(c));
|
|
476
|
-
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
477
|
-
req.on("error", reject);
|
|
478
|
-
});
|
|
492
|
+
function daemonReadBody(req, res) {
|
|
493
|
+
return readCappedBody(req, res);
|
|
479
494
|
}
|
|
480
495
|
function daemonJson(res, status, body) {
|
|
481
496
|
res.writeHead(status, {
|
|
@@ -679,7 +694,9 @@ async function main() {
|
|
|
679
694
|
// ── Daemon-level routes (session lifecycle) ──────────────────────────
|
|
680
695
|
// Create a session for a window: { mode?, cwd, sessionPath? } → { sessionId }.
|
|
681
696
|
if (method === "POST" && url === "/session") {
|
|
682
|
-
void daemonReadBody(req).then(async (raw) => {
|
|
697
|
+
void daemonReadBody(req, res).then(async (raw) => {
|
|
698
|
+
if (raw === null)
|
|
699
|
+
return;
|
|
683
700
|
let body = {};
|
|
684
701
|
try {
|
|
685
702
|
body = raw ? JSON.parse(raw) : {};
|
|
@@ -777,6 +794,8 @@ async function main() {
|
|
|
777
794
|
// Radio playback is app-wide (one stream across all windows), so it stops
|
|
778
795
|
// at the daemon level, not per session.
|
|
779
796
|
stopRadio();
|
|
797
|
+
// Close the ~/.gg progress fs.watch handle (baseline #8 leak fix).
|
|
798
|
+
progress.dispose();
|
|
780
799
|
await Promise.all([...sessions.values()].map((c) => c.dispose().catch(() => { })));
|
|
781
800
|
server.close();
|
|
782
801
|
process.exit(0);
|
|
@@ -914,6 +933,7 @@ async function createProgressManager(agentDir, broadcastAll) {
|
|
|
914
933
|
// Watch ~/.gg (dir watch survives the atomic tmp+rename) for progress.json
|
|
915
934
|
// writes from other daemon processes; debounce, reload read-only, dedupe by nonce.
|
|
916
935
|
let watchDebounce = null;
|
|
936
|
+
let progressWatcher = null;
|
|
917
937
|
try {
|
|
918
938
|
const watcher = fsWatch(agentDir, (_event, filename) => {
|
|
919
939
|
if (filename !== "progress.json")
|
|
@@ -935,6 +955,7 @@ async function createProgressManager(agentDir, broadcastAll) {
|
|
|
935
955
|
}, 150);
|
|
936
956
|
});
|
|
937
957
|
watcher.unref();
|
|
958
|
+
progressWatcher = watcher;
|
|
938
959
|
}
|
|
939
960
|
catch (err) {
|
|
940
961
|
captureSidecarError(err, "app-sidecar.progress.watch");
|
|
@@ -942,7 +963,22 @@ async function createProgressManager(agentDir, broadcastAll) {
|
|
|
942
963
|
message: err instanceof Error ? err.message : String(err),
|
|
943
964
|
});
|
|
944
965
|
}
|
|
945
|
-
|
|
966
|
+
// Dispose closes the fs.watch handle (baseline #8: it was previously never
|
|
967
|
+
// closed — a per-daemon leak) and clears any pending debounce timer.
|
|
968
|
+
function dispose() {
|
|
969
|
+
if (watchDebounce) {
|
|
970
|
+
clearTimeout(watchDebounce);
|
|
971
|
+
watchDebounce = null;
|
|
972
|
+
}
|
|
973
|
+
try {
|
|
974
|
+
progressWatcher?.close();
|
|
975
|
+
}
|
|
976
|
+
catch {
|
|
977
|
+
// Already closed / never opened — nothing to do.
|
|
978
|
+
}
|
|
979
|
+
progressWatcher = null;
|
|
980
|
+
}
|
|
981
|
+
return { snapshot, awardRun, dispose };
|
|
946
982
|
}
|
|
947
983
|
/**
|
|
948
984
|
* Build one in-process agent session: its AgentSession, SSE client set, event
|
|
@@ -2057,13 +2093,8 @@ async function createSession(deps, opts) {
|
|
|
2057
2093
|
gitPoll.unref?.();
|
|
2058
2094
|
};
|
|
2059
2095
|
scheduleGitPoll(5000);
|
|
2060
|
-
function readBody(req) {
|
|
2061
|
-
return
|
|
2062
|
-
const chunks = [];
|
|
2063
|
-
req.on("data", (c) => chunks.push(c));
|
|
2064
|
-
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
2065
|
-
req.on("error", reject);
|
|
2066
|
-
});
|
|
2096
|
+
function readBody(req, res) {
|
|
2097
|
+
return readCappedBody(req, res);
|
|
2067
2098
|
}
|
|
2068
2099
|
function json(res, status, body) {
|
|
2069
2100
|
const payload = JSON.stringify(redactValue(body, { secrets: environmentSecrets(process.env) }));
|
|
@@ -2204,7 +2235,9 @@ async function createSession(deps, opts) {
|
|
|
2204
2235
|
return;
|
|
2205
2236
|
}
|
|
2206
2237
|
if (method === "POST" && url === "/settings") {
|
|
2207
|
-
void readBody(req).then(async (raw) => {
|
|
2238
|
+
void readBody(req, res).then(async (raw) => {
|
|
2239
|
+
if (raw === null)
|
|
2240
|
+
return;
|
|
2208
2241
|
let projectsRoot;
|
|
2209
2242
|
try {
|
|
2210
2243
|
projectsRoot = JSON.parse(raw).projectsRoot ?? "";
|
|
@@ -2227,7 +2260,9 @@ async function createSession(deps, opts) {
|
|
|
2227
2260
|
return;
|
|
2228
2261
|
}
|
|
2229
2262
|
if (method === "POST" && url === "/create-project") {
|
|
2230
|
-
void readBody(req).then(async (raw) => {
|
|
2263
|
+
void readBody(req, res).then(async (raw) => {
|
|
2264
|
+
if (raw === null)
|
|
2265
|
+
return;
|
|
2231
2266
|
let name;
|
|
2232
2267
|
try {
|
|
2233
2268
|
name = JSON.parse(raw).name ?? "";
|
|
@@ -2640,7 +2675,9 @@ async function createSession(deps, opts) {
|
|
|
2640
2675
|
return;
|
|
2641
2676
|
}
|
|
2642
2677
|
if (method === "POST" && url === "/prompt") {
|
|
2643
|
-
void readBody(req).then(async (raw) => {
|
|
2678
|
+
void readBody(req, res).then(async (raw) => {
|
|
2679
|
+
if (raw === null)
|
|
2680
|
+
return;
|
|
2644
2681
|
let text;
|
|
2645
2682
|
let attachments;
|
|
2646
2683
|
let meta;
|
|
@@ -2767,7 +2804,9 @@ async function createSession(deps, opts) {
|
|
|
2767
2804
|
json(res, 404, { error: "Ken is not available in GG Chat." });
|
|
2768
2805
|
return;
|
|
2769
2806
|
}
|
|
2770
|
-
void readBody(req).then(async (raw) => {
|
|
2807
|
+
void readBody(req, res).then(async (raw) => {
|
|
2808
|
+
if (raw === null)
|
|
2809
|
+
return;
|
|
2771
2810
|
let text;
|
|
2772
2811
|
try {
|
|
2773
2812
|
text = JSON.parse(raw).text ?? "";
|
|
@@ -2826,7 +2865,9 @@ async function createSession(deps, opts) {
|
|
|
2826
2865
|
json(res, 404, { error: "Autopilot is not available in GG Chat." });
|
|
2827
2866
|
return;
|
|
2828
2867
|
}
|
|
2829
|
-
void readBody(req).then(async (raw) => {
|
|
2868
|
+
void readBody(req, res).then(async (raw) => {
|
|
2869
|
+
if (raw === null)
|
|
2870
|
+
return;
|
|
2830
2871
|
let enabled;
|
|
2831
2872
|
try {
|
|
2832
2873
|
enabled = Boolean(JSON.parse(raw).enabled);
|
|
@@ -2847,7 +2888,9 @@ async function createSession(deps, opts) {
|
|
|
2847
2888
|
return;
|
|
2848
2889
|
}
|
|
2849
2890
|
if (method === "POST" && url === "/enhance") {
|
|
2850
|
-
void readBody(req).then(async (raw) => {
|
|
2891
|
+
void readBody(req, res).then(async (raw) => {
|
|
2892
|
+
if (raw === null)
|
|
2893
|
+
return;
|
|
2851
2894
|
let text;
|
|
2852
2895
|
try {
|
|
2853
2896
|
text = JSON.parse(raw).text ?? "";
|
|
@@ -2896,7 +2939,9 @@ async function createSession(deps, opts) {
|
|
|
2896
2939
|
return;
|
|
2897
2940
|
}
|
|
2898
2941
|
if (method === "POST" && url === "/radio/volume") {
|
|
2899
|
-
void readBody(req).then((raw) => {
|
|
2942
|
+
void readBody(req, res).then((raw) => {
|
|
2943
|
+
if (raw === null)
|
|
2944
|
+
return;
|
|
2900
2945
|
let volume;
|
|
2901
2946
|
try {
|
|
2902
2947
|
volume = Number(JSON.parse(raw).volume);
|
|
@@ -2919,7 +2964,9 @@ async function createSession(deps, opts) {
|
|
|
2919
2964
|
return;
|
|
2920
2965
|
}
|
|
2921
2966
|
if (method === "POST" && url === "/radio") {
|
|
2922
|
-
void readBody(req).then((raw) => {
|
|
2967
|
+
void readBody(req, res).then((raw) => {
|
|
2968
|
+
if (raw === null)
|
|
2969
|
+
return;
|
|
2923
2970
|
let station;
|
|
2924
2971
|
try {
|
|
2925
2972
|
station = JSON.parse(raw).station ?? "";
|
|
@@ -2943,7 +2990,9 @@ async function createSession(deps, opts) {
|
|
|
2943
2990
|
return;
|
|
2944
2991
|
}
|
|
2945
2992
|
if (method === "POST" && url === "/tasks/run") {
|
|
2946
|
-
void readBody(req).then((raw) => {
|
|
2993
|
+
void readBody(req, res).then((raw) => {
|
|
2994
|
+
if (raw === null)
|
|
2995
|
+
return;
|
|
2947
2996
|
let id;
|
|
2948
2997
|
let all;
|
|
2949
2998
|
try {
|
|
@@ -2965,7 +3014,9 @@ async function createSession(deps, opts) {
|
|
|
2965
3014
|
return;
|
|
2966
3015
|
}
|
|
2967
3016
|
if (method === "POST" && url === "/tasks/delete") {
|
|
2968
|
-
void readBody(req).then((raw) => {
|
|
3017
|
+
void readBody(req, res).then((raw) => {
|
|
3018
|
+
if (raw === null)
|
|
3019
|
+
return;
|
|
2969
3020
|
let id;
|
|
2970
3021
|
try {
|
|
2971
3022
|
id = JSON.parse(raw).id ?? "";
|
|
@@ -3003,7 +3054,9 @@ async function createSession(deps, opts) {
|
|
|
3003
3054
|
return;
|
|
3004
3055
|
}
|
|
3005
3056
|
if (method === "POST" && url === "/model") {
|
|
3006
|
-
void readBody(req).then(async (raw) => {
|
|
3057
|
+
void readBody(req, res).then(async (raw) => {
|
|
3058
|
+
if (raw === null)
|
|
3059
|
+
return;
|
|
3007
3060
|
let modelId;
|
|
3008
3061
|
try {
|
|
3009
3062
|
modelId = JSON.parse(raw).model ?? "";
|
|
@@ -3072,7 +3125,9 @@ async function createSession(deps, opts) {
|
|
|
3072
3125
|
// to BOTH Ken sessions (chat + autopilot reviewer); a switch landing while
|
|
3073
3126
|
// either is mid-run defers via the pending-model mechanics.
|
|
3074
3127
|
if (method === "POST" && url === "/ken/model") {
|
|
3075
|
-
void readBody(req).then(async (raw) => {
|
|
3128
|
+
void readBody(req, res).then(async (raw) => {
|
|
3129
|
+
if (raw === null)
|
|
3130
|
+
return;
|
|
3076
3131
|
let modelId;
|
|
3077
3132
|
try {
|
|
3078
3133
|
const parsed = JSON.parse(raw).model;
|
|
@@ -3116,7 +3171,9 @@ async function createSession(deps, opts) {
|
|
|
3116
3171
|
return;
|
|
3117
3172
|
}
|
|
3118
3173
|
if (method === "POST" && url === "/kill") {
|
|
3119
|
-
void readBody(req).then(async (raw) => {
|
|
3174
|
+
void readBody(req, res).then(async (raw) => {
|
|
3175
|
+
if (raw === null)
|
|
3176
|
+
return;
|
|
3120
3177
|
let id;
|
|
3121
3178
|
try {
|
|
3122
3179
|
id = JSON.parse(raw).id ?? "";
|
|
@@ -3237,7 +3294,9 @@ async function createSession(deps, opts) {
|
|
|
3237
3294
|
// 3. session_reset tells the webview to clear its transcript; it then runs
|
|
3238
3295
|
// the "implement it now" prompt in the clean session.
|
|
3239
3296
|
if (method === "POST" && url === "/plan/accept") {
|
|
3240
|
-
void readBody(req).then(async (raw) => {
|
|
3297
|
+
void readBody(req, res).then(async (raw) => {
|
|
3298
|
+
if (raw === null)
|
|
3299
|
+
return;
|
|
3241
3300
|
let planPath;
|
|
3242
3301
|
try {
|
|
3243
3302
|
planPath = JSON.parse(raw).planPath || undefined;
|
|
@@ -3289,7 +3348,9 @@ async function createSession(deps, opts) {
|
|
|
3289
3348
|
return;
|
|
3290
3349
|
}
|
|
3291
3350
|
if (method === "POST" && url === "/auth/apikey") {
|
|
3292
|
-
void readBody(req).then(async (raw) => {
|
|
3351
|
+
void readBody(req, res).then(async (raw) => {
|
|
3352
|
+
if (raw === null)
|
|
3353
|
+
return;
|
|
3293
3354
|
let provider = "";
|
|
3294
3355
|
let key;
|
|
3295
3356
|
let variant;
|
|
@@ -3332,7 +3393,9 @@ async function createSession(deps, opts) {
|
|
|
3332
3393
|
return;
|
|
3333
3394
|
}
|
|
3334
3395
|
if (method === "POST" && url === "/auth/oauth/start") {
|
|
3335
|
-
void readBody(req).then((raw) => {
|
|
3396
|
+
void readBody(req, res).then((raw) => {
|
|
3397
|
+
if (raw === null)
|
|
3398
|
+
return;
|
|
3336
3399
|
let provider = "";
|
|
3337
3400
|
try {
|
|
3338
3401
|
provider = JSON.parse(raw).provider ?? "";
|
|
@@ -3389,7 +3452,9 @@ async function createSession(deps, opts) {
|
|
|
3389
3452
|
return;
|
|
3390
3453
|
}
|
|
3391
3454
|
if (method === "POST" && url === "/auth/oauth/code") {
|
|
3392
|
-
void readBody(req).then((raw) => {
|
|
3455
|
+
void readBody(req, res).then((raw) => {
|
|
3456
|
+
if (raw === null)
|
|
3457
|
+
return;
|
|
3393
3458
|
let code;
|
|
3394
3459
|
try {
|
|
3395
3460
|
code = JSON.parse(raw).code ?? "";
|
|
@@ -3409,7 +3474,9 @@ async function createSession(deps, opts) {
|
|
|
3409
3474
|
return;
|
|
3410
3475
|
}
|
|
3411
3476
|
if (method === "POST" && url === "/auth/logout") {
|
|
3412
|
-
void readBody(req).then(async (raw) => {
|
|
3477
|
+
void readBody(req, res).then(async (raw) => {
|
|
3478
|
+
if (raw === null)
|
|
3479
|
+
return;
|
|
3413
3480
|
let provider;
|
|
3414
3481
|
try {
|
|
3415
3482
|
provider = JSON.parse(raw).provider ?? "";
|
|
@@ -3448,7 +3515,9 @@ async function createSession(deps, opts) {
|
|
|
3448
3515
|
return;
|
|
3449
3516
|
}
|
|
3450
3517
|
if (method === "POST" && url === "/telegram") {
|
|
3451
|
-
void readBody(req).then(async (raw) => {
|
|
3518
|
+
void readBody(req, res).then(async (raw) => {
|
|
3519
|
+
if (raw === null)
|
|
3520
|
+
return;
|
|
3452
3521
|
let botTokenInput;
|
|
3453
3522
|
let userIdInput;
|
|
3454
3523
|
try {
|
|
@@ -3552,7 +3621,9 @@ async function createSession(deps, opts) {
|
|
|
3552
3621
|
return;
|
|
3553
3622
|
}
|
|
3554
3623
|
if (method === "POST" && url === "/mcp/add") {
|
|
3555
|
-
void readBody(req).then(async (raw) => {
|
|
3624
|
+
void readBody(req, res).then(async (raw) => {
|
|
3625
|
+
if (raw === null)
|
|
3626
|
+
return;
|
|
3556
3627
|
let line;
|
|
3557
3628
|
let scopeValue;
|
|
3558
3629
|
let bodyCwd;
|
|
@@ -3609,7 +3680,9 @@ async function createSession(deps, opts) {
|
|
|
3609
3680
|
return;
|
|
3610
3681
|
}
|
|
3611
3682
|
if (method === "POST" && url === "/mcp/remove") {
|
|
3612
|
-
void readBody(req).then(async (raw) => {
|
|
3683
|
+
void readBody(req, res).then(async (raw) => {
|
|
3684
|
+
if (raw === null)
|
|
3685
|
+
return;
|
|
3613
3686
|
let name;
|
|
3614
3687
|
let scopeValue;
|
|
3615
3688
|
let bodyCwd;
|
|
@@ -3646,7 +3719,9 @@ async function createSession(deps, opts) {
|
|
|
3646
3719
|
// `mcp_auth_error`. Responds 202 immediately and runs the flow in the
|
|
3647
3720
|
// background (the browser round-trip can take a while).
|
|
3648
3721
|
if (method === "POST" && url === "/mcp/login") {
|
|
3649
|
-
void readBody(req).then(async (raw) => {
|
|
3722
|
+
void readBody(req, res).then(async (raw) => {
|
|
3723
|
+
if (raw === null)
|
|
3724
|
+
return;
|
|
3650
3725
|
let name;
|
|
3651
3726
|
let scopeValue;
|
|
3652
3727
|
let bodyCwd;
|