@costrict/csc 4.2.6 → 4.2.7-beta2
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/cli.js +14368 -15210
- package/dist/services/rawDump/batchWorker.js +495 -169
- package/package.json +1 -1
|
@@ -456,10 +456,10 @@ async function appendDeadLetter(entry) {
|
|
|
456
456
|
}
|
|
457
457
|
|
|
458
458
|
// src/services/rawDump/worker.ts
|
|
459
|
-
import { promises as
|
|
459
|
+
import { promises as fs9 } from "fs";
|
|
460
460
|
import { createHash as createHash2 } from "crypto";
|
|
461
461
|
import os2 from "os";
|
|
462
|
-
import
|
|
462
|
+
import path11 from "path";
|
|
463
463
|
import { fileURLToPath } from "url";
|
|
464
464
|
|
|
465
465
|
// src/costrict/provider/credentials.ts
|
|
@@ -508,8 +508,8 @@ async function saveCoStrictCredentials(credentials) {
|
|
|
508
508
|
import { createRequire } from "module";
|
|
509
509
|
function getVersion() {
|
|
510
510
|
try {
|
|
511
|
-
if (typeof MACRO !== "undefined" && "4.2.
|
|
512
|
-
return "4.2.
|
|
511
|
+
if (typeof MACRO !== "undefined" && "4.2.7-beta2")
|
|
512
|
+
return "4.2.7-beta2";
|
|
513
513
|
} catch {}
|
|
514
514
|
try {
|
|
515
515
|
const require2 = createRequire(import.meta.url);
|
|
@@ -594,8 +594,11 @@ async function refreshCoStrictToken(params) {
|
|
|
594
594
|
|
|
595
595
|
// src/services/rawDump/git.ts
|
|
596
596
|
import { execFile } from "child_process";
|
|
597
|
+
import { realpathSync } from "fs";
|
|
598
|
+
import path4 from "path";
|
|
597
599
|
import { promisify } from "util";
|
|
598
600
|
var execFileAsync = promisify(execFile);
|
|
601
|
+
var TRUNK_BRANCHES = ["origin/main", "origin/master", "main", "master"];
|
|
599
602
|
async function gitExec(args, cwd) {
|
|
600
603
|
try {
|
|
601
604
|
const { stdout } = await execFileAsync("git", args, {
|
|
@@ -610,19 +613,63 @@ async function gitExec(args, cwd) {
|
|
|
610
613
|
}
|
|
611
614
|
}
|
|
612
615
|
async function getRepoInfo(cwd) {
|
|
613
|
-
const [repoAddr, repoBranch, gitUserName, gitUserEmail] = await Promise.all([
|
|
616
|
+
const [repoAddr, repoBranch, gitUserName, gitUserEmail, gitRoot] = await Promise.all([
|
|
614
617
|
gitExec(["remote", "get-url", "origin"], cwd),
|
|
615
618
|
gitExec(["branch", "--show-current"], cwd),
|
|
616
619
|
gitExec(["config", "user.name"], cwd),
|
|
617
|
-
gitExec(["config", "user.email"], cwd)
|
|
620
|
+
gitExec(["config", "user.email"], cwd),
|
|
621
|
+
gitExec(["rev-parse", "--show-toplevel"], cwd)
|
|
618
622
|
]);
|
|
619
623
|
return {
|
|
620
624
|
repo_addr: repoAddr,
|
|
621
625
|
repo_branch: repoBranch,
|
|
622
626
|
git_user_name: gitUserName,
|
|
623
|
-
git_user_email: gitUserEmail
|
|
627
|
+
git_user_email: gitUserEmail,
|
|
628
|
+
git_root: gitRoot
|
|
624
629
|
};
|
|
625
630
|
}
|
|
631
|
+
function computeRepoRelativePath(gitRoot, workDir) {
|
|
632
|
+
if (!gitRoot)
|
|
633
|
+
return "";
|
|
634
|
+
const root = tryRealpath(gitRoot);
|
|
635
|
+
const dir = tryRealpath(workDir);
|
|
636
|
+
const rel = path4.relative(root, dir);
|
|
637
|
+
if (rel === "")
|
|
638
|
+
return ".";
|
|
639
|
+
if (rel.startsWith(".."))
|
|
640
|
+
return "";
|
|
641
|
+
return rel;
|
|
642
|
+
}
|
|
643
|
+
function tryRealpath(p) {
|
|
644
|
+
try {
|
|
645
|
+
return realpathSync(p);
|
|
646
|
+
} catch {
|
|
647
|
+
return path4.resolve(p);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
async function getBranchAncestry(cwd) {
|
|
651
|
+
const upstream = await gitExec(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], cwd);
|
|
652
|
+
if (upstream && TRUNK_BRANCHES.includes(upstream)) {
|
|
653
|
+
const forkPoint = await gitExec(["merge-base", upstream, "HEAD"], cwd);
|
|
654
|
+
return { base_branch: upstream, fork_point: forkPoint };
|
|
655
|
+
}
|
|
656
|
+
const candidates = TRUNK_BRANCHES;
|
|
657
|
+
let best = null;
|
|
658
|
+
for (const base of candidates) {
|
|
659
|
+
const forkPoint = await gitExec(["merge-base", base, "HEAD"], cwd);
|
|
660
|
+
if (!forkPoint)
|
|
661
|
+
continue;
|
|
662
|
+
const countOut = await gitExec(["rev-list", "--count", `${forkPoint}..HEAD`], cwd);
|
|
663
|
+
const distance = Number.parseInt(countOut, 10);
|
|
664
|
+
const dist = Number.isNaN(distance) ? Number.MAX_SAFE_INTEGER : distance;
|
|
665
|
+
if (!best || dist < best.distance) {
|
|
666
|
+
best = { base, forkPoint, distance: dist };
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
if (best)
|
|
670
|
+
return { base_branch: best.base, fork_point: best.forkPoint };
|
|
671
|
+
return { base_branch: "", fork_point: "" };
|
|
672
|
+
}
|
|
626
673
|
function countDiffLines(diff) {
|
|
627
674
|
let count = 0;
|
|
628
675
|
for (const line of diff.split(`
|
|
@@ -706,9 +753,69 @@ function toCommitComment(subject) {
|
|
|
706
753
|
return Array.from(subject).slice(0, 150).join("");
|
|
707
754
|
}
|
|
708
755
|
|
|
756
|
+
// src/services/rawDump/activeSessions.ts
|
|
757
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync, renameSync, realpathSync as realpathSync2 } from "fs";
|
|
758
|
+
import path5 from "path";
|
|
759
|
+
var log2 = createLogger("activeSessions");
|
|
760
|
+
var ACTIVE_SESSIONS_FILE = path5.join(getRawDumpDir(), "csc-active-sessions.json");
|
|
761
|
+
var DEFAULT_WINDOW_MS = 30 * 60 * 1000;
|
|
762
|
+
var FUTURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
763
|
+
var MAX_ACTIVE_SESSION_IDS = 64;
|
|
764
|
+
function getWindowMs() {
|
|
765
|
+
const v = process.env.CSC_ACTIVE_SESSION_WINDOW_MS;
|
|
766
|
+
if (v) {
|
|
767
|
+
const n = Number(v);
|
|
768
|
+
if (Number.isFinite(n) && n > 0)
|
|
769
|
+
return n;
|
|
770
|
+
}
|
|
771
|
+
return DEFAULT_WINDOW_MS;
|
|
772
|
+
}
|
|
773
|
+
function normalizeCwd(p) {
|
|
774
|
+
if (!p)
|
|
775
|
+
return "";
|
|
776
|
+
let resolved;
|
|
777
|
+
try {
|
|
778
|
+
resolved = realpathSync2(p);
|
|
779
|
+
} catch {
|
|
780
|
+
resolved = path5.resolve(p);
|
|
781
|
+
}
|
|
782
|
+
if (resolved.length > 1 && resolved.endsWith(path5.sep)) {
|
|
783
|
+
resolved = resolved.slice(0, -1);
|
|
784
|
+
}
|
|
785
|
+
return resolved;
|
|
786
|
+
}
|
|
787
|
+
function readMap() {
|
|
788
|
+
try {
|
|
789
|
+
const text = readFileSync2(ACTIVE_SESSIONS_FILE, "utf-8");
|
|
790
|
+
const parsed = JSON.parse(text);
|
|
791
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
792
|
+
} catch {
|
|
793
|
+
return {};
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
function getActiveSessionIds(cwd, atMs) {
|
|
797
|
+
const ref = atMs ?? Date.now();
|
|
798
|
+
const windowMs = getWindowMs();
|
|
799
|
+
const refCwd = normalizeCwd(cwd);
|
|
800
|
+
const map = readMap();
|
|
801
|
+
const hits = [];
|
|
802
|
+
for (const [sid, entry] of Object.entries(map)) {
|
|
803
|
+
const delta = ref - entry.lastSeenAt;
|
|
804
|
+
if (delta > windowMs)
|
|
805
|
+
continue;
|
|
806
|
+
if (delta < -FUTURE_TOLERANCE_MS)
|
|
807
|
+
continue;
|
|
808
|
+
if (refCwd && entry.cwd && entry.cwd !== refCwd)
|
|
809
|
+
continue;
|
|
810
|
+
hits.push({ sid, lastSeenAt: entry.lastSeenAt });
|
|
811
|
+
}
|
|
812
|
+
hits.sort((a, b) => b.lastSeenAt - a.lastSeenAt);
|
|
813
|
+
return hits.slice(0, MAX_ACTIVE_SESSION_IDS).map((h) => h.sid);
|
|
814
|
+
}
|
|
815
|
+
|
|
709
816
|
// src/services/rawDump/localStorage.ts
|
|
710
817
|
import { promises as fs4 } from "fs";
|
|
711
|
-
import
|
|
818
|
+
import path6 from "path";
|
|
712
819
|
var DEFAULT_LOCAL_DIR = getRawDumpDir();
|
|
713
820
|
var RAW_DUMP_MODE = {
|
|
714
821
|
DISABLED: 0,
|
|
@@ -757,12 +864,12 @@ async function writeLocalDump(type, body) {
|
|
|
757
864
|
endpoint = "/raw-store/task-summary";
|
|
758
865
|
} else if (type == "conversation") {
|
|
759
866
|
ymd = getDateFromTimestamp(getTimestampField("conversation", body));
|
|
760
|
-
subdir =
|
|
867
|
+
subdir = path6.join(ymd, body.task_id);
|
|
761
868
|
fname = body.request_id;
|
|
762
869
|
endpoint = "/raw-store/task-conversation";
|
|
763
870
|
} else if (type == "commit") {
|
|
764
871
|
ymd = getDateFromTimestamp(getTimestampField("commit", body));
|
|
765
|
-
subdir =
|
|
872
|
+
subdir = path6.join(normalizeProjectPath2(body.repo_addr), normalizeProjectPath2(body.repo_branch), ymd);
|
|
766
873
|
fname = body.commit_id;
|
|
767
874
|
endpoint = "/raw-store/commit";
|
|
768
875
|
} else if (type == "statistics") {
|
|
@@ -774,7 +881,7 @@ async function writeLocalDump(type, body) {
|
|
|
774
881
|
fname = `${h}-${m}-${s}`;
|
|
775
882
|
endpoint = "/raw-store/statistics";
|
|
776
883
|
} else if (type == "raw") {
|
|
777
|
-
subdir =
|
|
884
|
+
subdir = path6.join(normalizeProjectPath2(body.project), body.session_id);
|
|
778
885
|
fname = String(body.start_cursor);
|
|
779
886
|
endpoint = "/raw-store/raw-log";
|
|
780
887
|
} else {
|
|
@@ -782,9 +889,9 @@ async function writeLocalDump(type, body) {
|
|
|
782
889
|
fname = "unknown";
|
|
783
890
|
endpoint = "unknown";
|
|
784
891
|
}
|
|
785
|
-
const dumpDir =
|
|
892
|
+
const dumpDir = path6.join(dir, type, subdir);
|
|
786
893
|
const filename = `${fname}.json`;
|
|
787
|
-
const filePath =
|
|
894
|
+
const filePath = path6.join(dumpDir, filename);
|
|
788
895
|
const payload = {
|
|
789
896
|
_dumpMeta: {
|
|
790
897
|
type,
|
|
@@ -799,10 +906,69 @@ async function writeLocalDump(type, body) {
|
|
|
799
906
|
}
|
|
800
907
|
|
|
801
908
|
// src/services/rawDump/queue.ts
|
|
909
|
+
import { promises as fs6 } from "fs";
|
|
910
|
+
import path8 from "path";
|
|
911
|
+
|
|
912
|
+
// src/services/rawDump/lock.ts
|
|
802
913
|
import { promises as fs5 } from "fs";
|
|
803
|
-
import
|
|
804
|
-
var
|
|
805
|
-
|
|
914
|
+
import path7 from "path";
|
|
915
|
+
var LOCK_INFO_FILE = "lock.json";
|
|
916
|
+
async function isHolderAlive(lockDir) {
|
|
917
|
+
try {
|
|
918
|
+
const text = await fs5.readFile(path7.join(lockDir, LOCK_INFO_FILE), "utf-8");
|
|
919
|
+
const info = JSON.parse(text);
|
|
920
|
+
if (!info.pid || typeof info.pid !== "number")
|
|
921
|
+
return false;
|
|
922
|
+
try {
|
|
923
|
+
process.kill(info.pid, 0);
|
|
924
|
+
return true;
|
|
925
|
+
} catch {
|
|
926
|
+
return false;
|
|
927
|
+
}
|
|
928
|
+
} catch {
|
|
929
|
+
return false;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
async function acquireLock(lockDir, info) {
|
|
933
|
+
let retried = false;
|
|
934
|
+
while (true) {
|
|
935
|
+
try {
|
|
936
|
+
await fs5.mkdir(lockDir, { recursive: false });
|
|
937
|
+
} catch (err) {
|
|
938
|
+
if (err.code === "EEXIST") {
|
|
939
|
+
if (retried)
|
|
940
|
+
return false;
|
|
941
|
+
const alive = await isHolderAlive(lockDir);
|
|
942
|
+
if (alive)
|
|
943
|
+
return false;
|
|
944
|
+
try {
|
|
945
|
+
await fs5.rm(lockDir, { recursive: true, force: true });
|
|
946
|
+
} catch {
|
|
947
|
+
return false;
|
|
948
|
+
}
|
|
949
|
+
retried = true;
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
return false;
|
|
953
|
+
}
|
|
954
|
+
try {
|
|
955
|
+
await fs5.writeFile(path7.join(lockDir, LOCK_INFO_FILE), JSON.stringify(info), "utf-8");
|
|
956
|
+
return true;
|
|
957
|
+
} catch {
|
|
958
|
+
await releaseLock(lockDir);
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
async function releaseLock(lockDir) {
|
|
964
|
+
try {
|
|
965
|
+
await fs5.rm(lockDir, { recursive: true, force: true });
|
|
966
|
+
} catch {}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// src/services/rawDump/queue.ts
|
|
970
|
+
var QUEUE_FILE = path8.join(getRawDumpDir(), "csc-work-queue.jsonl");
|
|
971
|
+
var QUEUE_LOCK_DIR = path8.join(getRawDumpDir(), "csc-work-queue.lock.d");
|
|
806
972
|
var MAX_ATTEMPTS = 4;
|
|
807
973
|
var queue = [];
|
|
808
974
|
var queueLoaded = false;
|
|
@@ -810,7 +976,7 @@ async function loadQueue() {
|
|
|
810
976
|
if (queueLoaded)
|
|
811
977
|
return;
|
|
812
978
|
try {
|
|
813
|
-
const text = await
|
|
979
|
+
const text = await fs6.readFile(QUEUE_FILE, "utf-8");
|
|
814
980
|
queue = text.split(`
|
|
815
981
|
`).filter(Boolean).map((line) => {
|
|
816
982
|
try {
|
|
@@ -826,51 +992,37 @@ async function loadQueue() {
|
|
|
826
992
|
}
|
|
827
993
|
function clearQueue() {
|
|
828
994
|
queue = [];
|
|
829
|
-
|
|
995
|
+
fs6.writeFile(QUEUE_FILE, "", "utf-8").catch(() => {});
|
|
830
996
|
}
|
|
831
997
|
function getQueue() {
|
|
832
998
|
return [...queue];
|
|
833
999
|
}
|
|
834
1000
|
async function acquireQueueLock() {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if (!isNaN(pid) && pid !== process.pid) {
|
|
840
|
-
try {
|
|
841
|
-
process.kill(pid, 0);
|
|
842
|
-
return false;
|
|
843
|
-
} catch {}
|
|
844
|
-
}
|
|
845
|
-
} catch {}
|
|
846
|
-
await fs5.writeFile(QUEUE_LOCK_FILE, String(process.pid), "utf-8");
|
|
847
|
-
return true;
|
|
848
|
-
} catch {
|
|
849
|
-
return false;
|
|
850
|
-
}
|
|
1001
|
+
return acquireLock(QUEUE_LOCK_DIR, {
|
|
1002
|
+
pid: process.pid,
|
|
1003
|
+
startedAt: new Date().toISOString()
|
|
1004
|
+
});
|
|
851
1005
|
}
|
|
852
1006
|
async function releaseQueueLock() {
|
|
853
|
-
|
|
854
|
-
await fs5.writeFile(QUEUE_LOCK_FILE, "", "utf-8");
|
|
855
|
-
} catch {}
|
|
1007
|
+
await releaseLock(QUEUE_LOCK_DIR);
|
|
856
1008
|
}
|
|
857
1009
|
|
|
858
1010
|
// src/services/rawDump/history.ts
|
|
859
|
-
import { promises as
|
|
860
|
-
import
|
|
861
|
-
var HISTORY_FILE =
|
|
1011
|
+
import { promises as fs7 } from "fs";
|
|
1012
|
+
import path9 from "path";
|
|
1013
|
+
var HISTORY_FILE = path9.join(getCscDir(), "history.jsonl");
|
|
862
1014
|
var cache = null;
|
|
863
1015
|
async function loadHistory() {
|
|
864
1016
|
const items = [];
|
|
865
1017
|
let fileStat = null;
|
|
866
1018
|
try {
|
|
867
|
-
fileStat = await
|
|
1019
|
+
fileStat = await fs7.stat(HISTORY_FILE);
|
|
868
1020
|
} catch {
|
|
869
1021
|
cache = null;
|
|
870
1022
|
return [];
|
|
871
1023
|
}
|
|
872
1024
|
try {
|
|
873
|
-
const content = await
|
|
1025
|
+
const content = await fs7.readFile(HISTORY_FILE, "utf-8");
|
|
874
1026
|
const lines = content.split(`
|
|
875
1027
|
`);
|
|
876
1028
|
for (const line of lines) {
|
|
@@ -894,7 +1046,7 @@ async function loadHistory() {
|
|
|
894
1046
|
async function autoLoadHistory() {
|
|
895
1047
|
let fileStat = null;
|
|
896
1048
|
try {
|
|
897
|
-
fileStat = await
|
|
1049
|
+
fileStat = await fs7.stat(HISTORY_FILE);
|
|
898
1050
|
} catch {
|
|
899
1051
|
if (cache) {
|
|
900
1052
|
return null;
|
|
@@ -910,7 +1062,7 @@ async function getProjectDirs() {
|
|
|
910
1062
|
if (cache) {
|
|
911
1063
|
let fileStat = null;
|
|
912
1064
|
try {
|
|
913
|
-
fileStat = await
|
|
1065
|
+
fileStat = await fs7.stat(HISTORY_FILE);
|
|
914
1066
|
} catch {
|
|
915
1067
|
return [...new Set(cache.items.map((item) => item.project))];
|
|
916
1068
|
}
|
|
@@ -923,8 +1075,8 @@ async function getProjectDirs() {
|
|
|
923
1075
|
}
|
|
924
1076
|
|
|
925
1077
|
// src/services/rawDump/session.ts
|
|
926
|
-
import { promises as
|
|
927
|
-
import
|
|
1078
|
+
import { promises as fs8 } from "fs";
|
|
1079
|
+
import path10 from "path";
|
|
928
1080
|
|
|
929
1081
|
// src/services/rawDump/parse.ts
|
|
930
1082
|
function buildFlows(events) {
|
|
@@ -983,12 +1135,15 @@ function createConversation(convEvents) {
|
|
|
983
1135
|
duration: 0,
|
|
984
1136
|
sender: "",
|
|
985
1137
|
diffs: [],
|
|
1138
|
+
toolEvents: [],
|
|
1139
|
+
model: "",
|
|
1140
|
+
promptMode: "",
|
|
986
1141
|
preview: "",
|
|
987
1142
|
requestContent: "",
|
|
988
1143
|
responseContent: ""
|
|
989
1144
|
};
|
|
990
1145
|
}
|
|
991
|
-
function fillConversation(conv) {
|
|
1146
|
+
function fillConversation(conv, allSessionEvents) {
|
|
992
1147
|
if (conv.diffs.length > 0 || conv.startTime || conv.inputTokens > 0)
|
|
993
1148
|
return;
|
|
994
1149
|
let inputTokens = 0;
|
|
@@ -1028,12 +1183,19 @@ function fillConversation(conv) {
|
|
|
1028
1183
|
responseTexts.push(text);
|
|
1029
1184
|
}
|
|
1030
1185
|
const diffs = [];
|
|
1186
|
+
const toolEvents = [];
|
|
1187
|
+
let model = "";
|
|
1031
1188
|
conv.events.forEach((evt) => {
|
|
1032
1189
|
if (evt.type === "assistant") {
|
|
1033
|
-
diffs.push(...extractEventToolDiff(evt,
|
|
1190
|
+
diffs.push(...extractEventToolDiff(evt, allSessionEvents));
|
|
1191
|
+
toolEvents.push(...extractEventToolEvents(evt, allSessionEvents));
|
|
1192
|
+
if (!model && evt.message?.model)
|
|
1193
|
+
model = evt.message.model;
|
|
1034
1194
|
}
|
|
1035
1195
|
});
|
|
1036
1196
|
conv.diffs = diffs;
|
|
1197
|
+
conv.toolEvents = toolEvents;
|
|
1198
|
+
conv.model = model;
|
|
1037
1199
|
conv.events.forEach((evt) => {
|
|
1038
1200
|
if (evt.type === "assistant") {
|
|
1039
1201
|
const { error_code, error_reason } = extractError(evt);
|
|
@@ -1246,6 +1408,118 @@ function extractEventToolDiff(assistantEvent, allEvents) {
|
|
|
1246
1408
|
}
|
|
1247
1409
|
return diffs;
|
|
1248
1410
|
}
|
|
1411
|
+
var FILE_EDIT_TOOLS = new Set(["Edit", "MultiEdit", "Write", "NotebookEdit"]);
|
|
1412
|
+
var COMMAND_TOOLS = new Set(["Bash", "BashOutput"]);
|
|
1413
|
+
var MAX_TOOL_CONTENT = 32 * 1024;
|
|
1414
|
+
function truncateToolContent(s) {
|
|
1415
|
+
if (s === undefined)
|
|
1416
|
+
return { value: undefined, truncated: false };
|
|
1417
|
+
if (s.length <= MAX_TOOL_CONTENT)
|
|
1418
|
+
return { value: s, truncated: false };
|
|
1419
|
+
return { value: s.slice(0, MAX_TOOL_CONTENT), truncated: true };
|
|
1420
|
+
}
|
|
1421
|
+
function classifyToolKind(name) {
|
|
1422
|
+
if (name === "Edit" || name === "MultiEdit")
|
|
1423
|
+
return "edit";
|
|
1424
|
+
if (name === "Write")
|
|
1425
|
+
return "write";
|
|
1426
|
+
if (name === "NotebookEdit")
|
|
1427
|
+
return "notebook";
|
|
1428
|
+
if (COMMAND_TOOLS.has(name))
|
|
1429
|
+
return "command";
|
|
1430
|
+
if (name === "Read" || name === "NotebookRead")
|
|
1431
|
+
return "read";
|
|
1432
|
+
if (name === "Grep" || name === "Glob" || name === "WebSearch")
|
|
1433
|
+
return "search";
|
|
1434
|
+
return "other";
|
|
1435
|
+
}
|
|
1436
|
+
function inferExitCode(tur) {
|
|
1437
|
+
if (!tur)
|
|
1438
|
+
return;
|
|
1439
|
+
if (tur.interrupted === true)
|
|
1440
|
+
return 1;
|
|
1441
|
+
return 0;
|
|
1442
|
+
}
|
|
1443
|
+
function extractEventToolEvents(assistantEvent, allEvents) {
|
|
1444
|
+
const events = [];
|
|
1445
|
+
const msgUuid = assistantEvent.uuid;
|
|
1446
|
+
if (!msgUuid)
|
|
1447
|
+
return events;
|
|
1448
|
+
const content = assistantEvent.message?.content;
|
|
1449
|
+
if (!Array.isArray(content))
|
|
1450
|
+
return events;
|
|
1451
|
+
const resultById = new Map;
|
|
1452
|
+
for (const m of allEvents) {
|
|
1453
|
+
if (m.parentUuid !== msgUuid || m.type !== "user")
|
|
1454
|
+
continue;
|
|
1455
|
+
const tur = m.toolUseResult ?? m.tool_use_result;
|
|
1456
|
+
if (!tur)
|
|
1457
|
+
continue;
|
|
1458
|
+
if (Array.isArray(m.message?.content)) {
|
|
1459
|
+
for (const b of m.message.content) {
|
|
1460
|
+
if (b?.type === "tool_result" && typeof b.tool_use_id === "string") {
|
|
1461
|
+
resultById.set(b.tool_use_id, tur);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
for (const block of content) {
|
|
1467
|
+
if (block?.type !== "tool_use")
|
|
1468
|
+
continue;
|
|
1469
|
+
const toolName = block.name ?? "";
|
|
1470
|
+
if (!toolName)
|
|
1471
|
+
continue;
|
|
1472
|
+
const toolUseId = typeof block.id === "string" ? block.id : undefined;
|
|
1473
|
+
const input = block.input ?? {};
|
|
1474
|
+
const tur = toolUseId ? resultById.get(toolUseId) : undefined;
|
|
1475
|
+
const filePath = input.file_path ?? input.notebook_path ?? tur?.filePath ?? tur?.file_path ?? "";
|
|
1476
|
+
const isFileEdit = FILE_EDIT_TOOLS.has(toolName);
|
|
1477
|
+
const isCommand = COMMAND_TOOLS.has(toolName);
|
|
1478
|
+
let before;
|
|
1479
|
+
let after;
|
|
1480
|
+
if (isFileEdit) {
|
|
1481
|
+
const edits = input.edits;
|
|
1482
|
+
if (Array.isArray(edits)) {
|
|
1483
|
+
const befores = [];
|
|
1484
|
+
const afters = [];
|
|
1485
|
+
for (const e of edits) {
|
|
1486
|
+
if (typeof e?.old_string === "string")
|
|
1487
|
+
befores.push(e.old_string);
|
|
1488
|
+
if (typeof e?.new_string === "string")
|
|
1489
|
+
afters.push(e.new_string);
|
|
1490
|
+
}
|
|
1491
|
+
if (befores.length)
|
|
1492
|
+
before = befores.join(`
|
|
1493
|
+
`);
|
|
1494
|
+
if (afters.length)
|
|
1495
|
+
after = afters.join(`
|
|
1496
|
+
`);
|
|
1497
|
+
}
|
|
1498
|
+
if (before === undefined) {
|
|
1499
|
+
before = input.old_string ?? tur?.oldString ?? tur?.old_string ?? tur?.originalFile ?? tur?.original_file;
|
|
1500
|
+
}
|
|
1501
|
+
if (after === undefined) {
|
|
1502
|
+
after = input.new_string ?? input.new_source ?? input.content ?? tur?.newString ?? tur?.new_string ?? tur?.content ?? tur?.updated_file;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
const beforeCapped = truncateToolContent(before);
|
|
1506
|
+
const afterCapped = truncateToolContent(after);
|
|
1507
|
+
const touchedFiles = filePath ? [filePath] : [];
|
|
1508
|
+
events.push({
|
|
1509
|
+
name: toolName,
|
|
1510
|
+
source: isFileEdit ? "ai" : toolName,
|
|
1511
|
+
event_kind: classifyToolKind(toolName),
|
|
1512
|
+
tool_use_id: toolUseId,
|
|
1513
|
+
touched_files: touchedFiles,
|
|
1514
|
+
command: isCommand ? input.command : undefined,
|
|
1515
|
+
exit_code: isCommand ? inferExitCode(tur) : undefined,
|
|
1516
|
+
before: beforeCapped.value,
|
|
1517
|
+
after: afterCapped.value,
|
|
1518
|
+
truncated: beforeCapped.truncated || afterCapped.truncated || undefined
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
return events;
|
|
1522
|
+
}
|
|
1249
1523
|
function parseSession(cacheConversations, events) {
|
|
1250
1524
|
const sessionId = events.find((e) => e.sessionId)?.sessionId || "unknown";
|
|
1251
1525
|
const version = events.find((e) => e.version)?.version || "";
|
|
@@ -1265,6 +1539,23 @@ function parseSession(cacheConversations, events) {
|
|
|
1265
1539
|
totalCacheCreationInputTokens += evt.message.usage.cache_creation_input_tokens || 0;
|
|
1266
1540
|
}
|
|
1267
1541
|
});
|
|
1542
|
+
const idxByEvent = new Map;
|
|
1543
|
+
events.forEach((e, i) => idxByEvent.set(e, i));
|
|
1544
|
+
const permissionMarks = events.map((e, idx) => typeof e.permissionMode === "string" && e.permissionMode ? { idx, mode: e.permissionMode } : null).filter((x) => x !== null);
|
|
1545
|
+
const lastSessionMode = permissionMarks.length > 0 ? permissionMarks[permissionMarks.length - 1].mode : "";
|
|
1546
|
+
const resolvePromptMode = (conv) => {
|
|
1547
|
+
if (permissionMarks.length === 0)
|
|
1548
|
+
return "";
|
|
1549
|
+
const convIdx = idxByEvent.get(conv.userEvent) ?? -1;
|
|
1550
|
+
let mode = "";
|
|
1551
|
+
for (const p of permissionMarks) {
|
|
1552
|
+
if (p.idx <= convIdx)
|
|
1553
|
+
mode = p.mode;
|
|
1554
|
+
else
|
|
1555
|
+
break;
|
|
1556
|
+
}
|
|
1557
|
+
return mode || lastSessionMode;
|
|
1558
|
+
};
|
|
1268
1559
|
const flows = buildFlows(events);
|
|
1269
1560
|
flows.forEach((flow) => {
|
|
1270
1561
|
const convResults = splitFlowToConversations(flow);
|
|
@@ -1282,7 +1573,10 @@ function parseSession(cacheConversations, events) {
|
|
|
1282
1573
|
conversations.forEach((conv, i) => {
|
|
1283
1574
|
conv.index = i;
|
|
1284
1575
|
});
|
|
1285
|
-
conversations.forEach((conv) => fillConversation(conv));
|
|
1576
|
+
conversations.forEach((conv) => fillConversation(conv, events));
|
|
1577
|
+
conversations.forEach((conv) => {
|
|
1578
|
+
conv.promptMode = resolvePromptMode(conv);
|
|
1579
|
+
});
|
|
1286
1580
|
return {
|
|
1287
1581
|
id: sessionId,
|
|
1288
1582
|
version,
|
|
@@ -1300,10 +1594,10 @@ function parseSession(cacheConversations, events) {
|
|
|
1300
1594
|
}
|
|
1301
1595
|
|
|
1302
1596
|
// src/services/rawDump/session.ts
|
|
1303
|
-
var
|
|
1597
|
+
var log3 = createLogger("session");
|
|
1304
1598
|
async function loadSessionMessages(sessionFile) {
|
|
1305
1599
|
try {
|
|
1306
|
-
const text = await
|
|
1600
|
+
const text = await fs8.readFile(sessionFile, "utf-8");
|
|
1307
1601
|
const messages = text.split(`
|
|
1308
1602
|
`).filter(Boolean).map((line) => {
|
|
1309
1603
|
try {
|
|
@@ -1312,7 +1606,7 @@ async function loadSessionMessages(sessionFile) {
|
|
|
1312
1606
|
return null;
|
|
1313
1607
|
}
|
|
1314
1608
|
}).filter((m) => m !== null);
|
|
1315
|
-
|
|
1609
|
+
log3.debug("loaded messages from file", {
|
|
1316
1610
|
sessionFile,
|
|
1317
1611
|
count: messages.length
|
|
1318
1612
|
});
|
|
@@ -1335,10 +1629,10 @@ async function getParsedSession(taskDir, sessionId) {
|
|
|
1335
1629
|
const sessionDir = getSessionDirectory(taskDir);
|
|
1336
1630
|
const cacheKey = `${sessionDir}:${sessionId}`;
|
|
1337
1631
|
const cached = sessionMessagesCache.get(cacheKey);
|
|
1338
|
-
const sessionFile =
|
|
1632
|
+
const sessionFile = path10.join(sessionDir, `${sessionId}.jsonl`);
|
|
1339
1633
|
let stats;
|
|
1340
1634
|
try {
|
|
1341
|
-
stats = await
|
|
1635
|
+
stats = await fs8.stat(sessionFile);
|
|
1342
1636
|
} catch {
|
|
1343
1637
|
return {
|
|
1344
1638
|
sessionJsonlFileName: sessionFile,
|
|
@@ -1351,7 +1645,7 @@ async function getParsedSession(taskDir, sessionId) {
|
|
|
1351
1645
|
}
|
|
1352
1646
|
const needsReanalysis = !cached || cached.fileTimestamp !== stats.mtimeMs || cached.fileSize !== stats.size || !cached.parsedSession;
|
|
1353
1647
|
if (!needsReanalysis && cached) {
|
|
1354
|
-
|
|
1648
|
+
log3.debug("using cached parsed session", { sessionId });
|
|
1355
1649
|
return cached;
|
|
1356
1650
|
}
|
|
1357
1651
|
cleanupOldSessions();
|
|
@@ -1359,7 +1653,7 @@ async function getParsedSession(taskDir, sessionId) {
|
|
|
1359
1653
|
const messages = cached?.messages ?? await loadSessionMessages(sessionFile);
|
|
1360
1654
|
const elapsed = Date.now() - start;
|
|
1361
1655
|
if (elapsed > 100) {
|
|
1362
|
-
|
|
1656
|
+
log3.info("loadSessionMessages slow", { sessionId, elapsedMs: elapsed });
|
|
1363
1657
|
}
|
|
1364
1658
|
const cachedConvs = cached?.parsedSession?.conversations ?? [];
|
|
1365
1659
|
const parsedSession = parseSession(cachedConvs, messages);
|
|
@@ -1376,7 +1670,7 @@ async function getParsedSession(taskDir, sessionId) {
|
|
|
1376
1670
|
}
|
|
1377
1671
|
|
|
1378
1672
|
// src/services/rawDump/statistics.ts
|
|
1379
|
-
var
|
|
1673
|
+
var log4 = createLogger("worker");
|
|
1380
1674
|
async function updateStatisticsForUpload() {
|
|
1381
1675
|
const dailyStats = await statHistorySessions();
|
|
1382
1676
|
for (const [_dateKey, stats] of dailyStats) {
|
|
@@ -1390,7 +1684,7 @@ async function updateStatisticsForUpload() {
|
|
|
1390
1684
|
} else {
|
|
1391
1685
|
const startTime = new Date(_dateKey);
|
|
1392
1686
|
if (state_statistics.clean_time && startTime < new Date(state_statistics.clean_time)) {
|
|
1393
|
-
|
|
1687
|
+
log4.info("statistics skipped: too old", {
|
|
1394
1688
|
startTime: startTime.toISOString(),
|
|
1395
1689
|
clean_time: state_statistics.clean_time
|
|
1396
1690
|
});
|
|
@@ -1500,9 +1794,21 @@ async function statHistorySessions() {
|
|
|
1500
1794
|
}
|
|
1501
1795
|
|
|
1502
1796
|
// src/services/rawDump/worker.ts
|
|
1503
|
-
var
|
|
1797
|
+
var log5 = createLogger("worker");
|
|
1504
1798
|
var CSC_DIR = getCscDir();
|
|
1505
1799
|
var REQUEST_TIMEOUT_MS = 30000;
|
|
1800
|
+
var TOOL_EVENTS_PAYLOAD_BUDGET = 256 * 1024;
|
|
1801
|
+
function capToolEventsPayload(events) {
|
|
1802
|
+
let used = 0;
|
|
1803
|
+
return events.map((e) => {
|
|
1804
|
+
const size = (e.before?.length ?? 0) + (e.after?.length ?? 0);
|
|
1805
|
+
if (size > 0 && used + size > TOOL_EVENTS_PAYLOAD_BUDGET) {
|
|
1806
|
+
return { ...e, before: undefined, after: undefined, truncated: true };
|
|
1807
|
+
}
|
|
1808
|
+
used += size;
|
|
1809
|
+
return e;
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1506
1812
|
var repoInfoCache = new Map;
|
|
1507
1813
|
var REPO_CACHE_TTL_MS = 60000;
|
|
1508
1814
|
async function getCachedRepoInfo(directory) {
|
|
@@ -1515,13 +1821,13 @@ async function getCachedRepoInfo(directory) {
|
|
|
1515
1821
|
return repoInfo;
|
|
1516
1822
|
}
|
|
1517
1823
|
async function processTask(task, authData) {
|
|
1518
|
-
|
|
1824
|
+
log5.info("processing task:", { task });
|
|
1519
1825
|
await uploadRaw(task.sessionId, task.directory, authData);
|
|
1520
1826
|
const repoInfo = await getCachedRepoInfo(task.directory);
|
|
1521
1827
|
const cacheEntry = await getParsedSession(task.directory, task.sessionId);
|
|
1522
1828
|
const parsedSession = cacheEntry.parsedSession;
|
|
1523
1829
|
if (parsedSession.conversations.length === 0) {
|
|
1524
|
-
|
|
1830
|
+
log5.warn("no conversations found", { task });
|
|
1525
1831
|
}
|
|
1526
1832
|
await uploadSummary({ sessionId: task.sessionId, directory: task.directory, parsedSession }, authData);
|
|
1527
1833
|
for (const conv of parsedSession.conversations) {
|
|
@@ -1531,7 +1837,7 @@ async function processTask(task, authData) {
|
|
|
1531
1837
|
conversation: conv
|
|
1532
1838
|
}, authData, { repoInfo });
|
|
1533
1839
|
}
|
|
1534
|
-
|
|
1840
|
+
log5.info("task completed", {
|
|
1535
1841
|
sessionId: task.sessionId,
|
|
1536
1842
|
conversationCount: parsedSession.conversations.length
|
|
1537
1843
|
});
|
|
@@ -1568,12 +1874,12 @@ function getRawDumpUrl(baseUrl, endpoint, isAnonymous = false) {
|
|
|
1568
1874
|
async function uploadReport(authData, endpoint, body) {
|
|
1569
1875
|
const mode = getRawDumpMode();
|
|
1570
1876
|
if (mode === RAW_DUMP_MODE.DISABLED) {
|
|
1571
|
-
|
|
1877
|
+
log5.debug(`dump disabled, skipping ${endpoint}`);
|
|
1572
1878
|
return;
|
|
1573
1879
|
}
|
|
1574
1880
|
const type = endpoint === "/raw-store/task-conversation" ? "conversation" : endpoint === "/raw-store/task-summary" ? "summary" : endpoint === "/raw-store/commit" ? "commit" : endpoint === "/raw-store/statistics" ? "statistics" : endpoint === "/raw-store/raw-log" ? "raw" : "unknown";
|
|
1575
1881
|
if (type === "unknown") {
|
|
1576
|
-
|
|
1882
|
+
log5.warn("unknown endpoint, skipping local dump", { endpoint });
|
|
1577
1883
|
} else if (mode === RAW_DUMP_MODE.LOCAL || mode === RAW_DUMP_MODE.BOTH) {
|
|
1578
1884
|
await writeLocalDump(type, body);
|
|
1579
1885
|
}
|
|
@@ -1584,10 +1890,10 @@ async function uploadReport(authData, endpoint, body) {
|
|
|
1584
1890
|
async function writeRemoteDump(endpoint, authData, body) {
|
|
1585
1891
|
const isAnonymous = authData.isAnonymous ?? false;
|
|
1586
1892
|
const url = getRawDumpUrl(authData.baseUrl, endpoint, isAnonymous);
|
|
1587
|
-
|
|
1893
|
+
log5.debug(`POST ${endpoint}`, { url, authData, isAnonymous });
|
|
1588
1894
|
try {
|
|
1589
1895
|
await postJson(url, body, authData.headers);
|
|
1590
|
-
|
|
1896
|
+
log5.debug(`POST ${endpoint} ok`);
|
|
1591
1897
|
return;
|
|
1592
1898
|
} catch (err) {
|
|
1593
1899
|
throw err instanceof UploadError ? err : new Error(`${endpoint} failed after 3 attempts`);
|
|
@@ -1628,18 +1934,18 @@ async function postJson(url, body, headers, maxAttempts = 3) {
|
|
|
1628
1934
|
const text = await res.text().catch(() => "");
|
|
1629
1935
|
lastError = new Error(`${url} failed: ${res.status} ${text}`);
|
|
1630
1936
|
if (res.status === 429 || res.status >= 500) {
|
|
1631
|
-
|
|
1937
|
+
log5.warn(`retryable error ${res.status}, will retry`, { url, attempt, status: res.status });
|
|
1632
1938
|
continue;
|
|
1633
1939
|
}
|
|
1634
1940
|
if (res.status === 401 || res.status === 502 || res.status === 503) {
|
|
1635
|
-
|
|
1941
|
+
log5.warn(`retryable status ${res.status}, will retry`, { url, attempt });
|
|
1636
1942
|
continue;
|
|
1637
1943
|
}
|
|
1638
1944
|
throw lastError;
|
|
1639
1945
|
} catch (err) {
|
|
1640
1946
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
1641
1947
|
const isAbort = lastError.name === "AbortError";
|
|
1642
|
-
|
|
1948
|
+
log5.warn(`${isAbort ? "timeout" : "network error"}, will retry`, {
|
|
1643
1949
|
url,
|
|
1644
1950
|
attempt,
|
|
1645
1951
|
timeoutMs: REQUEST_TIMEOUT_MS,
|
|
@@ -1649,7 +1955,7 @@ async function postJson(url, body, headers, maxAttempts = 3) {
|
|
|
1649
1955
|
clearTimeout(timer);
|
|
1650
1956
|
}
|
|
1651
1957
|
}
|
|
1652
|
-
|
|
1958
|
+
log5.error("postJson failed", {
|
|
1653
1959
|
url,
|
|
1654
1960
|
headers: headersObj,
|
|
1655
1961
|
error: lastError?.message
|
|
@@ -1679,15 +1985,15 @@ function detectOs() {
|
|
|
1679
1985
|
async function auth() {
|
|
1680
1986
|
let creds = await loadCoStrictCredentials();
|
|
1681
1987
|
if (!creds?.access_token) {
|
|
1682
|
-
|
|
1988
|
+
log5.debug("auth: not authenticated");
|
|
1683
1989
|
throw new Error("Not authenticated");
|
|
1684
1990
|
}
|
|
1685
|
-
|
|
1991
|
+
log5.debug("credentials loaded", {
|
|
1686
1992
|
hasRefreshToken: !!creds.refresh_token,
|
|
1687
1993
|
baseUrl: creds.base_url
|
|
1688
1994
|
});
|
|
1689
1995
|
if (creds.refresh_token && !isCoStrictTokenValid(creds)) {
|
|
1690
|
-
|
|
1996
|
+
log5.debug("token expired, refreshing...");
|
|
1691
1997
|
const next = await refreshCoStrictToken({
|
|
1692
1998
|
baseUrl: creds.base_url || process.env.COSTRICT_BASE_URL || process.env.COSTRICT_RAW_DUMP_BASE_URL || "https://zgsm.sangfor.com",
|
|
1693
1999
|
refreshToken: creds.refresh_token,
|
|
@@ -1706,7 +2012,7 @@ async function auth() {
|
|
|
1706
2012
|
access_token: next.access_token,
|
|
1707
2013
|
refresh_token: next.refresh_token
|
|
1708
2014
|
};
|
|
1709
|
-
|
|
2015
|
+
log5.debug("token refreshed");
|
|
1710
2016
|
}
|
|
1711
2017
|
const headers = new Headers;
|
|
1712
2018
|
headers.set("Authorization", `Bearer ${creds.access_token}`);
|
|
@@ -1715,8 +2021,8 @@ async function auth() {
|
|
|
1715
2021
|
headers.set("X-Title", "CoStrict-CLI");
|
|
1716
2022
|
let version = "unknown";
|
|
1717
2023
|
try {
|
|
1718
|
-
const pkgPath =
|
|
1719
|
-
const pkg = JSON.parse(await
|
|
2024
|
+
const pkgPath = path11.resolve(fileURLToPath(import.meta.url), "../../package.json");
|
|
2025
|
+
const pkg = JSON.parse(await fs9.readFile(pkgPath, "utf-8"));
|
|
1720
2026
|
version = pkg.version ?? "unknown";
|
|
1721
2027
|
} catch {}
|
|
1722
2028
|
headers.set("X-Costrict-Version", `csc-${version}`);
|
|
@@ -1735,7 +2041,7 @@ async function auth() {
|
|
|
1735
2041
|
}
|
|
1736
2042
|
const user = parseUser(accessPayload, refreshPayload);
|
|
1737
2043
|
const baseUrl = resolveRawDumpBaseUrl(creds.base_url);
|
|
1738
|
-
|
|
2044
|
+
log5.debug("auth success", {
|
|
1739
2045
|
baseUrl,
|
|
1740
2046
|
user_id: user.user_id,
|
|
1741
2047
|
clientId,
|
|
@@ -1756,20 +2062,20 @@ async function authWithFallback() {
|
|
|
1756
2062
|
} catch (err) {
|
|
1757
2063
|
let version = "unknown";
|
|
1758
2064
|
try {
|
|
1759
|
-
const pkgPath =
|
|
1760
|
-
const pkg = JSON.parse(await
|
|
2065
|
+
const pkgPath = path11.resolve(fileURLToPath(import.meta.url), "../../package.json");
|
|
2066
|
+
const pkg = JSON.parse(await fs9.readFile(pkgPath, "utf-8"));
|
|
1761
2067
|
version = pkg.version ?? "unknown";
|
|
1762
2068
|
} catch {}
|
|
1763
2069
|
let deviceId = process.env.CSC_DEVICE_ID;
|
|
1764
2070
|
if (!deviceId) {
|
|
1765
|
-
const deviceIdFile =
|
|
2071
|
+
const deviceIdFile = path11.join(getLocalDumpDir(), "device-id");
|
|
1766
2072
|
try {
|
|
1767
|
-
deviceId = (await
|
|
2073
|
+
deviceId = (await fs9.readFile(deviceIdFile, "utf-8")).trim();
|
|
1768
2074
|
} catch {}
|
|
1769
2075
|
if (!deviceId) {
|
|
1770
2076
|
deviceId = generateMachineId();
|
|
1771
2077
|
try {
|
|
1772
|
-
await
|
|
2078
|
+
await fs9.writeFile(deviceIdFile, deviceId, "utf-8");
|
|
1773
2079
|
} catch {}
|
|
1774
2080
|
}
|
|
1775
2081
|
process.env.CSC_DEVICE_ID = deviceId;
|
|
@@ -1781,7 +2087,7 @@ async function authWithFallback() {
|
|
|
1781
2087
|
headers.set("zgsm-client-ide", "cli");
|
|
1782
2088
|
headers.set("X-Costrict-Version", `csc-${version}`);
|
|
1783
2089
|
headers.set("User-Agent", `csc/${version}`);
|
|
1784
|
-
|
|
2090
|
+
log5.info("auth failed, falling back to anonymous interface", {
|
|
1785
2091
|
deviceId,
|
|
1786
2092
|
version,
|
|
1787
2093
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -1802,20 +2108,20 @@ async function authWithFallback() {
|
|
|
1802
2108
|
async function uploadRaw(sessionId, directory, authData) {
|
|
1803
2109
|
const normalizedProject = normalizeProjectPath2(directory);
|
|
1804
2110
|
const stateKey = `${normalizedProject}/${sessionId}`;
|
|
1805
|
-
const filePath =
|
|
2111
|
+
const filePath = path11.join(CSC_DIR, "projects", normalizedProject, `${sessionId}.jsonl`);
|
|
1806
2112
|
const lastReportedCount = state_raw.logs[stateKey] ?? 0;
|
|
1807
2113
|
let fileContent;
|
|
1808
2114
|
try {
|
|
1809
|
-
fileContent = await
|
|
2115
|
+
fileContent = await fs9.readFile(filePath, "utf-8");
|
|
1810
2116
|
} catch (err) {
|
|
1811
|
-
|
|
2117
|
+
log5.warn("raw file not found, skip", { filePath });
|
|
1812
2118
|
return;
|
|
1813
2119
|
}
|
|
1814
2120
|
const lines = fileContent.split(`
|
|
1815
2121
|
`).filter((line) => line.trim());
|
|
1816
2122
|
const newLines = lines.slice(lastReportedCount);
|
|
1817
2123
|
if (newLines.length === 0) {
|
|
1818
|
-
|
|
2124
|
+
log5.debug("raw no new data", { stateKey, lastReportedCount, total: lines.length });
|
|
1819
2125
|
return;
|
|
1820
2126
|
}
|
|
1821
2127
|
const events = [];
|
|
@@ -1826,7 +2132,7 @@ async function uploadRaw(sessionId, directory, authData) {
|
|
|
1826
2132
|
} catch {}
|
|
1827
2133
|
}
|
|
1828
2134
|
if (events.length === 0) {
|
|
1829
|
-
|
|
2135
|
+
log5.warn("raw no valid events", { stateKey });
|
|
1830
2136
|
return;
|
|
1831
2137
|
}
|
|
1832
2138
|
const BATCH_SIZE = 50;
|
|
@@ -1843,19 +2149,19 @@ async function uploadRaw(sessionId, directory, authData) {
|
|
|
1843
2149
|
};
|
|
1844
2150
|
await uploadReport(authData, "/raw-store/raw-log", body);
|
|
1845
2151
|
offset += BATCH_SIZE;
|
|
1846
|
-
|
|
2152
|
+
log5.debug("raw batch uploaded", { stateKey, batchOffset: offset, batchSize: batch.length });
|
|
1847
2153
|
}
|
|
1848
2154
|
state_raw.logs[stateKey] = lastReportedCount + newLines.length;
|
|
1849
2155
|
state_raw.total = Object.keys(state_raw.logs).length;
|
|
1850
2156
|
await saveRaw();
|
|
1851
|
-
|
|
2157
|
+
log5.info("raw uploaded", { stateKey, count: events.length });
|
|
1852
2158
|
}
|
|
1853
2159
|
async function uploadConversation(payload, authData, options) {
|
|
1854
2160
|
const conv = payload.conversation;
|
|
1855
2161
|
const requestID = conv.id;
|
|
1856
2162
|
const key = `${payload.sessionId}:${requestID}`;
|
|
1857
2163
|
if (state_conv.conversation[key]) {
|
|
1858
|
-
|
|
2164
|
+
log5.info("conversation skipped: already uploaded", {
|
|
1859
2165
|
task_id: payload.sessionId,
|
|
1860
2166
|
request_id: requestID,
|
|
1861
2167
|
last_reported: state_conv.conversation[key]
|
|
@@ -1863,7 +2169,7 @@ async function uploadConversation(payload, authData, options) {
|
|
|
1863
2169
|
return false;
|
|
1864
2170
|
}
|
|
1865
2171
|
if (state_conv.clean_time && payload.conversation.startTime && payload.conversation.startTime.getTime() < new Date(state_conv.clean_time).getTime()) {
|
|
1866
|
-
|
|
2172
|
+
log5.info("conversation skipped: too old", {
|
|
1867
2173
|
task_id: payload.sessionId,
|
|
1868
2174
|
request_id: requestID,
|
|
1869
2175
|
clean_time: state_conv.clean_time,
|
|
@@ -1876,18 +2182,21 @@ async function uploadConversation(payload, authData, options) {
|
|
|
1876
2182
|
const diffLines = rawDiff ? countDiffLines(rawDiff) : 0;
|
|
1877
2183
|
const files = conv.diffs.map((d) => d.file);
|
|
1878
2184
|
const repoInfo = options?.repoInfo ?? await getRepoInfo(payload.directory);
|
|
2185
|
+
const gitRoot = repoInfo.git_root ?? "";
|
|
1879
2186
|
const body = {
|
|
1880
2187
|
task_id: payload.sessionId,
|
|
1881
2188
|
request_id: requestID,
|
|
1882
|
-
prompt_mode:
|
|
2189
|
+
prompt_mode: conv.promptMode,
|
|
1883
2190
|
mode: "code",
|
|
1884
|
-
model:
|
|
2191
|
+
model: conv.model,
|
|
1885
2192
|
start_time: formatIso(conv.startTime),
|
|
1886
2193
|
end_time: formatIso(conv.endTime),
|
|
1887
2194
|
process_time: conv.duration,
|
|
1888
2195
|
process_ttft: 0,
|
|
1889
|
-
upstream_tokens: conv.inputTokens,
|
|
2196
|
+
upstream_tokens: conv.inputTokens + conv.cacheReadInputTokens + conv.cacheCreationInputTokens,
|
|
1890
2197
|
downstream_tokens: conv.outputTokens,
|
|
2198
|
+
cache_read_tokens: conv.cacheReadInputTokens,
|
|
2199
|
+
cache_creation_tokens: conv.cacheCreationInputTokens,
|
|
1891
2200
|
cost: 0,
|
|
1892
2201
|
sender: conv.sender,
|
|
1893
2202
|
request_content: conv.requestContent,
|
|
@@ -1898,11 +2207,14 @@ async function uploadConversation(payload, authData, options) {
|
|
|
1898
2207
|
diff: rawDiff,
|
|
1899
2208
|
diff_lines: diffLines,
|
|
1900
2209
|
files,
|
|
2210
|
+
tool_events: capToolEventsPayload(conv.toolEvents),
|
|
1901
2211
|
repo_addr: repoInfo.repo_addr,
|
|
1902
2212
|
repo_branch: repoInfo.repo_branch,
|
|
1903
|
-
work_dir: payload.directory
|
|
2213
|
+
work_dir: payload.directory,
|
|
2214
|
+
git_root: gitRoot,
|
|
2215
|
+
repo_relative_path: computeRepoRelativePath(gitRoot, payload.directory)
|
|
1904
2216
|
};
|
|
1905
|
-
|
|
2217
|
+
log5.debug("conversation uploading", {
|
|
1906
2218
|
task_id: payload.sessionId,
|
|
1907
2219
|
request_id: requestID
|
|
1908
2220
|
});
|
|
@@ -1913,7 +2225,7 @@ async function uploadConversation(payload, authData, options) {
|
|
|
1913
2225
|
if (wasIncomplete)
|
|
1914
2226
|
state_conv.incomplete--;
|
|
1915
2227
|
await saveConversation();
|
|
1916
|
-
|
|
2228
|
+
log5.info("conversation uploaded", {
|
|
1917
2229
|
task_id: payload.sessionId,
|
|
1918
2230
|
request_id: requestID,
|
|
1919
2231
|
upstream_tokens: body.upstream_tokens,
|
|
@@ -1923,12 +2235,12 @@ async function uploadConversation(payload, authData, options) {
|
|
|
1923
2235
|
}
|
|
1924
2236
|
async function uploadSummary(payload, authData) {
|
|
1925
2237
|
const parsed = payload.parsedSession;
|
|
1926
|
-
|
|
2238
|
+
log5.debug("uploadSummary start", {
|
|
1927
2239
|
sessionId: payload.sessionId,
|
|
1928
2240
|
conversationCount: parsed.conversations.length
|
|
1929
2241
|
});
|
|
1930
2242
|
if (state_summary.summary[payload.sessionId]) {
|
|
1931
|
-
|
|
2243
|
+
log5.info("summary skipped: already uploaded", {
|
|
1932
2244
|
task_id: payload.sessionId
|
|
1933
2245
|
});
|
|
1934
2246
|
return;
|
|
@@ -1936,7 +2248,7 @@ async function uploadSummary(payload, authData) {
|
|
|
1936
2248
|
const firstConv = parsed.conversations[0];
|
|
1937
2249
|
const startTime = firstConv?.userEvent.timestamp ? new Date(firstConv.userEvent.timestamp) : new Date(Date.now());
|
|
1938
2250
|
if (state_summary.clean_time && startTime < new Date(state_summary.clean_time)) {
|
|
1939
|
-
|
|
2251
|
+
log5.info("summary skipped: too old", {
|
|
1940
2252
|
startTime: startTime.toISOString(),
|
|
1941
2253
|
clean_time: state_summary.clean_time
|
|
1942
2254
|
});
|
|
@@ -1960,12 +2272,12 @@ async function uploadSummary(payload, authData) {
|
|
|
1960
2272
|
if (wasIncomplete)
|
|
1961
2273
|
state_summary.incomplete--;
|
|
1962
2274
|
await saveSummary();
|
|
1963
|
-
|
|
2275
|
+
log5.info("summary uploaded", { task_id: payload.sessionId });
|
|
1964
2276
|
}
|
|
1965
2277
|
async function uploadCommits(directory, authData, options) {
|
|
1966
2278
|
const repoInfo = options?.repoInfo ?? await getRepoInfo(directory);
|
|
1967
2279
|
if (!repoInfo.repo_addr || !repoInfo.repo_branch) {
|
|
1968
|
-
|
|
2280
|
+
log5.info("commits skipped: missing repo info", {
|
|
1969
2281
|
work_dir: directory,
|
|
1970
2282
|
repo_addr: repoInfo.repo_addr,
|
|
1971
2283
|
repo_branch: repoInfo.repo_branch
|
|
@@ -1978,7 +2290,7 @@ async function uploadCommits(directory, authData, options) {
|
|
|
1978
2290
|
const allCommits = parseCommitLog(logText);
|
|
1979
2291
|
const commits = allCommits.slice(0, 50);
|
|
1980
2292
|
if (!commits.length) {
|
|
1981
|
-
|
|
2293
|
+
log5.info("commits skipped: no new commits", {
|
|
1982
2294
|
stateKey,
|
|
1983
2295
|
lastCommit: lastCommit || "",
|
|
1984
2296
|
total: allCommits.length,
|
|
@@ -1986,7 +2298,7 @@ async function uploadCommits(directory, authData, options) {
|
|
|
1986
2298
|
});
|
|
1987
2299
|
return 0;
|
|
1988
2300
|
}
|
|
1989
|
-
|
|
2301
|
+
log5.debug("parsed commits", {
|
|
1990
2302
|
stateKey,
|
|
1991
2303
|
lastCommit: lastCommit || "",
|
|
1992
2304
|
total: allCommits.length,
|
|
@@ -1994,6 +2306,9 @@ async function uploadCommits(directory, authData, options) {
|
|
|
1994
2306
|
});
|
|
1995
2307
|
const originalLastCommit = state_commit.commits[stateKey] || "";
|
|
1996
2308
|
let uploadedCount = 0;
|
|
2309
|
+
const gitRoot = repoInfo.git_root ?? "";
|
|
2310
|
+
const repoRelativePath = computeRepoRelativePath(gitRoot, directory);
|
|
2311
|
+
const { base_branch, fork_point } = await getBranchAncestry(directory);
|
|
1997
2312
|
try {
|
|
1998
2313
|
for (let i = 0;i < commits.length; i++) {
|
|
1999
2314
|
const commit = commits[i];
|
|
@@ -2001,6 +2316,8 @@ async function uploadCommits(directory, authData, options) {
|
|
|
2001
2316
|
await new Promise((r) => setTimeout(r, 500));
|
|
2002
2317
|
}
|
|
2003
2318
|
const diff = await getCommitDiff(directory, commit.commit_id);
|
|
2319
|
+
const commitTimeMs = Date.parse(commit.commit_time);
|
|
2320
|
+
const activeSessionIds = getActiveSessionIds(directory, Number.isNaN(commitTimeMs) ? undefined : commitTimeMs);
|
|
2004
2321
|
const body = {
|
|
2005
2322
|
commit_id: commit.commit_id,
|
|
2006
2323
|
commit_time: commit.commit_time,
|
|
@@ -2013,6 +2330,11 @@ async function uploadCommits(directory, authData, options) {
|
|
|
2013
2330
|
client_version: authData.version,
|
|
2014
2331
|
client_ide: "cli",
|
|
2015
2332
|
work_dir: directory,
|
|
2333
|
+
git_root: gitRoot,
|
|
2334
|
+
repo_relative_path: repoRelativePath,
|
|
2335
|
+
base_branch,
|
|
2336
|
+
fork_point,
|
|
2337
|
+
active_session_ids: activeSessionIds,
|
|
2016
2338
|
diff_lines: countDiffLines(diff),
|
|
2017
2339
|
diff,
|
|
2018
2340
|
files: extractFilesFromDiff(diff),
|
|
@@ -2024,7 +2346,7 @@ async function uploadCommits(directory, authData, options) {
|
|
|
2024
2346
|
uploadedCount++;
|
|
2025
2347
|
state_commit.commits[stateKey] = commit.commit_id;
|
|
2026
2348
|
await saveCommits();
|
|
2027
|
-
|
|
2349
|
+
log5.info("commit uploaded", {
|
|
2028
2350
|
commit_id: commit.commit_id,
|
|
2029
2351
|
progress: `${i + 1}/${commits.length}`
|
|
2030
2352
|
});
|
|
@@ -2032,7 +2354,7 @@ async function uploadCommits(directory, authData, options) {
|
|
|
2032
2354
|
} catch (err) {
|
|
2033
2355
|
state_commit.commits[stateKey] = originalLastCommit;
|
|
2034
2356
|
await saveCommits();
|
|
2035
|
-
|
|
2357
|
+
log5.error("commit batch failed, rolled back", {
|
|
2036
2358
|
work_dir: directory,
|
|
2037
2359
|
uploadedCount,
|
|
2038
2360
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -2065,7 +2387,7 @@ async function uploadStatistics(authData) {
|
|
|
2065
2387
|
state_statistics.statistics[k].currentUploadAt = timestamp;
|
|
2066
2388
|
state_statistics.incomplete--;
|
|
2067
2389
|
await saveStatistics();
|
|
2068
|
-
|
|
2390
|
+
log5.info("statistics uploaded", {
|
|
2069
2391
|
k,
|
|
2070
2392
|
session_count: daily.sessionCount,
|
|
2071
2393
|
conversation_count: daily.conversationCount,
|
|
@@ -2087,7 +2409,7 @@ async function processIncompleteTasks(authData) {
|
|
|
2087
2409
|
await saveTasks();
|
|
2088
2410
|
} catch (err) {
|
|
2089
2411
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2090
|
-
|
|
2412
|
+
log5.error("task failed", {
|
|
2091
2413
|
error: errorMsg,
|
|
2092
2414
|
task: record
|
|
2093
2415
|
});
|
|
@@ -2108,7 +2430,7 @@ async function processIncompleteTasks(authData) {
|
|
|
2108
2430
|
state_task.tasks[key].uploadedAt = "DEAD_LETTER";
|
|
2109
2431
|
state_task.incomplete--;
|
|
2110
2432
|
await saveTasks();
|
|
2111
|
-
|
|
2433
|
+
log5.error("task moved to dead letter", {
|
|
2112
2434
|
key,
|
|
2113
2435
|
attemptCount: state_task.tasks[key].attemptCount
|
|
2114
2436
|
});
|
|
@@ -2118,9 +2440,9 @@ async function processIncompleteTasks(authData) {
|
|
|
2118
2440
|
}
|
|
2119
2441
|
async function runHistorySessionWorker() {
|
|
2120
2442
|
try {
|
|
2121
|
-
|
|
2443
|
+
log5.info("history session start");
|
|
2122
2444
|
const items = await autoLoadHistory();
|
|
2123
|
-
|
|
2445
|
+
log5.info("history session loaded: ", { length: items ? items.length : 0 });
|
|
2124
2446
|
const seen = new Map;
|
|
2125
2447
|
for (const item of items ?? []) {
|
|
2126
2448
|
const key = `${item.sessionId}:${item.project}`;
|
|
@@ -2135,29 +2457,29 @@ async function runHistorySessionWorker() {
|
|
|
2135
2457
|
await uploadRaw(item.sessionId, item.project, authData);
|
|
2136
2458
|
rawCount++;
|
|
2137
2459
|
} catch (err) {
|
|
2138
|
-
|
|
2460
|
+
log5.warn("uploadRaw failed", { sessionId: item.sessionId, project: item.project, error: err instanceof Error ? err.message : String(err) });
|
|
2139
2461
|
}
|
|
2140
2462
|
}
|
|
2141
|
-
|
|
2463
|
+
log5.info("history session upload done", { count: rawCount });
|
|
2142
2464
|
} catch (err) {
|
|
2143
|
-
|
|
2465
|
+
log5.error("history session failed", {
|
|
2144
2466
|
error: err instanceof Error ? err.message : String(err)
|
|
2145
2467
|
});
|
|
2146
2468
|
}
|
|
2147
2469
|
}
|
|
2148
2470
|
async function runRawDumpWorker() {
|
|
2149
|
-
|
|
2471
|
+
log5.info("WORKER start");
|
|
2150
2472
|
try {
|
|
2151
2473
|
await loadAllState();
|
|
2152
2474
|
await runHistorySessionWorker();
|
|
2153
2475
|
await runCommitTimer();
|
|
2154
2476
|
await runStatisticsTimer();
|
|
2155
2477
|
} catch (err) {
|
|
2156
|
-
|
|
2478
|
+
log5.error("WORKER failed", {
|
|
2157
2479
|
error: err instanceof Error ? err.message : String(err)
|
|
2158
2480
|
});
|
|
2159
2481
|
}
|
|
2160
|
-
|
|
2482
|
+
log5.info("WORKER end");
|
|
2161
2483
|
}
|
|
2162
2484
|
var COMMIT_INTERVAL_MS = 5 * 60 * 1000;
|
|
2163
2485
|
var STATS_INTERVAL_MS = 60 * 60 * 1000;
|
|
@@ -2177,80 +2499,80 @@ async function runCommitTimer() {
|
|
|
2177
2499
|
if (now - lastCommitRun < COMMIT_INTERVAL_MS)
|
|
2178
2500
|
return;
|
|
2179
2501
|
lastCommitRun = now;
|
|
2180
|
-
|
|
2502
|
+
log5.debug("commit timer firing");
|
|
2181
2503
|
const dirs = await getProjectDirs();
|
|
2182
|
-
|
|
2504
|
+
log5.debug("scanned project dirs", { count: dirs.length });
|
|
2183
2505
|
const authData = await authWithFallback();
|
|
2184
2506
|
for (const dir of dirs) {
|
|
2185
2507
|
try {
|
|
2186
2508
|
const repoInfo = await getCachedRepoInfo(dir);
|
|
2187
2509
|
if (!repoInfo.repo_addr || !repoInfo.repo_branch) {
|
|
2188
|
-
|
|
2510
|
+
log5.debug("skip dir: missing repo info", { dir });
|
|
2189
2511
|
continue;
|
|
2190
2512
|
}
|
|
2191
2513
|
await uploadCommits(dir, authData, { repoInfo });
|
|
2192
2514
|
} catch (err) {
|
|
2193
|
-
|
|
2515
|
+
log5.error("commit timer failed for dir", {
|
|
2194
2516
|
dir,
|
|
2195
2517
|
error: err instanceof Error ? err.message : String(err)
|
|
2196
2518
|
});
|
|
2197
2519
|
}
|
|
2198
2520
|
}
|
|
2199
|
-
|
|
2521
|
+
log5.debug("commit timer done");
|
|
2200
2522
|
}
|
|
2201
2523
|
async function runStatisticsTimer() {
|
|
2202
2524
|
const now = Date.now();
|
|
2203
2525
|
if (now - lastStatsRun < STATS_INTERVAL_MS)
|
|
2204
2526
|
return;
|
|
2205
2527
|
lastStatsRun = now;
|
|
2206
|
-
|
|
2528
|
+
log5.debug("statistics timer firing");
|
|
2207
2529
|
try {
|
|
2208
2530
|
const authData = await authWithFallback();
|
|
2209
2531
|
await uploadStatistics(authData);
|
|
2210
2532
|
} catch (err) {
|
|
2211
|
-
|
|
2533
|
+
log5.error("statistics timer failed", {
|
|
2212
2534
|
error: err instanceof Error ? err.message : String(err)
|
|
2213
2535
|
});
|
|
2214
2536
|
}
|
|
2215
|
-
|
|
2537
|
+
log5.debug("statistics timer done");
|
|
2216
2538
|
}
|
|
2217
2539
|
async function runQueueTimer() {
|
|
2218
2540
|
const now = Date.now();
|
|
2219
2541
|
if (now - lastQueueRun < QUEUE_INTERVAL_MS)
|
|
2220
2542
|
return;
|
|
2221
2543
|
lastQueueRun = now;
|
|
2222
|
-
|
|
2544
|
+
log5.debug("queue timer firing");
|
|
2223
2545
|
if (!await acquireQueueLock()) {
|
|
2224
|
-
|
|
2546
|
+
log5.debug("another worker process holds the lock, skip");
|
|
2225
2547
|
return;
|
|
2226
2548
|
}
|
|
2227
2549
|
try {
|
|
2228
2550
|
const newTasks = getQueue();
|
|
2229
2551
|
if (newTasks.length === 0) {
|
|
2230
2552
|
if (state_task.incomplete === 0) {
|
|
2231
|
-
|
|
2553
|
+
log5.debug("queue empty, no incomplete tasks");
|
|
2232
2554
|
return;
|
|
2233
2555
|
}
|
|
2234
|
-
|
|
2556
|
+
log5.debug("queue empty, but has incomplete tasks in state");
|
|
2235
2557
|
}
|
|
2236
|
-
|
|
2558
|
+
log5.info(`processing ${newTasks.length} new tasks`);
|
|
2237
2559
|
addQueueTasks(newTasks);
|
|
2238
2560
|
await saveTasks();
|
|
2239
2561
|
clearQueue();
|
|
2240
2562
|
} catch (err) {
|
|
2241
|
-
|
|
2563
|
+
log5.error("queue timer failed", {
|
|
2242
2564
|
error: err instanceof Error ? err.message : String(err)
|
|
2243
2565
|
});
|
|
2244
2566
|
} finally {
|
|
2245
2567
|
await releaseQueueLock();
|
|
2246
2568
|
}
|
|
2247
|
-
|
|
2569
|
+
log5.info(`tracking ${state_task.total} tasks total, ${state_task.incomplete} incomplete`);
|
|
2248
2570
|
try {
|
|
2249
2571
|
const authData = await authWithFallback();
|
|
2250
2572
|
await processIncompleteTasks(authData);
|
|
2251
|
-
|
|
2573
|
+
log5.info("queue timer completed");
|
|
2252
2574
|
} catch (err) {
|
|
2253
|
-
|
|
2575
|
+
log5.error("queue timer failed, queue retained", {
|
|
2254
2576
|
error: err instanceof Error ? err.message : String(err)
|
|
2255
2577
|
});
|
|
2256
2578
|
throw err;
|
|
@@ -2264,38 +2586,25 @@ if (scriptPath.endsWith("worker.ts") || scriptPath.endsWith("worker.js")) {
|
|
|
2264
2586
|
}
|
|
2265
2587
|
|
|
2266
2588
|
// src/services/rawDump/timerWorker.ts
|
|
2267
|
-
import
|
|
2268
|
-
|
|
2269
|
-
var
|
|
2270
|
-
var TIMER_LOCK_FILE = path9.join(getRawDumpDir(), "csc-timer.lock");
|
|
2589
|
+
import path12 from "path";
|
|
2590
|
+
var log6 = createLogger("timer");
|
|
2591
|
+
var TIMER_LOCK_DIR = path12.join(getRawDumpDir(), "csc-timer.lock.d");
|
|
2271
2592
|
var isRunning = false;
|
|
2272
2593
|
async function acquireTimerLock() {
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
try {
|
|
2278
|
-
process.kill(pid, 0);
|
|
2279
|
-
return false;
|
|
2280
|
-
} catch {}
|
|
2281
|
-
}
|
|
2282
|
-
await fs9.writeFile(TIMER_LOCK_FILE, String(process.pid), "utf-8");
|
|
2283
|
-
return true;
|
|
2284
|
-
} catch {
|
|
2285
|
-
return false;
|
|
2286
|
-
}
|
|
2594
|
+
return acquireLock(TIMER_LOCK_DIR, {
|
|
2595
|
+
pid: process.pid,
|
|
2596
|
+
startedAt: new Date().toISOString()
|
|
2597
|
+
});
|
|
2287
2598
|
}
|
|
2288
2599
|
async function releaseTimerLock() {
|
|
2289
|
-
|
|
2290
|
-
await fs9.writeFile(TIMER_LOCK_FILE, "", "utf-8");
|
|
2291
|
-
} catch {}
|
|
2600
|
+
await releaseLock(TIMER_LOCK_DIR);
|
|
2292
2601
|
}
|
|
2293
2602
|
async function runTimers() {
|
|
2294
2603
|
if (isRunning)
|
|
2295
2604
|
return;
|
|
2296
2605
|
isRunning = true;
|
|
2297
2606
|
if (!await acquireTimerLock()) {
|
|
2298
|
-
|
|
2607
|
+
log6.debug("another timer worker holds the lock, skip");
|
|
2299
2608
|
isRunning = false;
|
|
2300
2609
|
return;
|
|
2301
2610
|
}
|
|
@@ -2304,7 +2613,7 @@ async function runTimers() {
|
|
|
2304
2613
|
await runCommitTimer();
|
|
2305
2614
|
await runStatisticsTimer();
|
|
2306
2615
|
} catch (err) {
|
|
2307
|
-
|
|
2616
|
+
log6.error("timer worker error", {
|
|
2308
2617
|
error: err instanceof Error ? err.message : String(err)
|
|
2309
2618
|
});
|
|
2310
2619
|
} finally {
|
|
@@ -2317,7 +2626,7 @@ function scheduleNext() {
|
|
|
2317
2626
|
try {
|
|
2318
2627
|
await runTimers();
|
|
2319
2628
|
} catch (err) {
|
|
2320
|
-
|
|
2629
|
+
log6.error("runTimers threw", {
|
|
2321
2630
|
error: err instanceof Error ? err.message : String(err)
|
|
2322
2631
|
});
|
|
2323
2632
|
}
|
|
@@ -2325,12 +2634,12 @@ function scheduleNext() {
|
|
|
2325
2634
|
}, 60000);
|
|
2326
2635
|
}
|
|
2327
2636
|
function startTimerWorker() {
|
|
2328
|
-
|
|
2637
|
+
log6.info("timer worker starting", getTimerIntervals());
|
|
2329
2638
|
setImmediate(async () => {
|
|
2330
2639
|
try {
|
|
2331
2640
|
await runTimers();
|
|
2332
2641
|
} catch (err) {
|
|
2333
|
-
|
|
2642
|
+
log6.error("initial timer run failed", {
|
|
2334
2643
|
error: err instanceof Error ? err.message : String(err)
|
|
2335
2644
|
});
|
|
2336
2645
|
}
|
|
@@ -2343,7 +2652,24 @@ if (scriptPath2.endsWith("timerWorker.ts") || scriptPath2.endsWith("timerWorker.
|
|
|
2343
2652
|
}
|
|
2344
2653
|
|
|
2345
2654
|
// src/services/rawDump/batchWorker.ts
|
|
2346
|
-
var
|
|
2655
|
+
var log7 = createLogger("batch");
|
|
2656
|
+
var shuttingDown = false;
|
|
2657
|
+
function scheduleForcedExit(reason, code) {
|
|
2658
|
+
if (shuttingDown)
|
|
2659
|
+
return;
|
|
2660
|
+
shuttingDown = true;
|
|
2661
|
+
log7.info("shutdown signal received, scheduling forced exit after grace period", { reason, workerPid: process.pid });
|
|
2662
|
+
const graceMs = 1e4;
|
|
2663
|
+
setTimeout(() => {
|
|
2664
|
+
log7.warn("grace period expired, forcing exit");
|
|
2665
|
+
process.exit(code);
|
|
2666
|
+
}, graceMs);
|
|
2667
|
+
}
|
|
2668
|
+
process.on("SIGTERM", () => scheduleForcedExit("SIGTERM", 0));
|
|
2669
|
+
process.on("SIGINT", () => scheduleForcedExit("SIGINT", 0));
|
|
2670
|
+
process.on("unhandledRejection", (reason) => {
|
|
2671
|
+
log7.error("unhandled rejection", { reason: String(reason), workerPid: process.pid });
|
|
2672
|
+
});
|
|
2347
2673
|
var PARENT_PID = process.ppid;
|
|
2348
2674
|
var IS_WORKER_PROCESS = process.argv[1]?.includes("batchWorker") || false;
|
|
2349
2675
|
function isParentAlive() {
|
|
@@ -2358,17 +2684,17 @@ function isParentAlive() {
|
|
|
2358
2684
|
}
|
|
2359
2685
|
var stateLoaded = false;
|
|
2360
2686
|
function startBatchWorker() {
|
|
2361
|
-
|
|
2687
|
+
log7.info("batch worker started");
|
|
2362
2688
|
loadAllState().then(() => {
|
|
2363
2689
|
stateLoaded = true;
|
|
2364
2690
|
}).catch((err) => {
|
|
2365
|
-
|
|
2691
|
+
log7.error("failed to load state", {
|
|
2366
2692
|
error: err instanceof Error ? err.message : String(err)
|
|
2367
2693
|
});
|
|
2368
2694
|
process.exit(1);
|
|
2369
2695
|
});
|
|
2370
2696
|
loadQueue().catch((err) => {
|
|
2371
|
-
|
|
2697
|
+
log7.error("failed to load queue", {
|
|
2372
2698
|
error: err instanceof Error ? err.message : String(err)
|
|
2373
2699
|
});
|
|
2374
2700
|
process.exit(1);
|
|
@@ -2377,7 +2703,7 @@ function startBatchWorker() {
|
|
|
2377
2703
|
try {
|
|
2378
2704
|
await runHistorySessionWorker();
|
|
2379
2705
|
} catch (err) {
|
|
2380
|
-
|
|
2706
|
+
log7.error("runHistorySessionWorker failed", {
|
|
2381
2707
|
error: err instanceof Error ? err.message : String(err)
|
|
2382
2708
|
});
|
|
2383
2709
|
}
|
|
@@ -2389,7 +2715,7 @@ function startParentWatchdog() {
|
|
|
2389
2715
|
if (!IS_WORKER_PROCESS)
|
|
2390
2716
|
return;
|
|
2391
2717
|
const intervalMs = 5000;
|
|
2392
|
-
const maxParentGoneAliveMs =
|
|
2718
|
+
const maxParentGoneAliveMs = 1e4;
|
|
2393
2719
|
let parentGoneAt = null;
|
|
2394
2720
|
setInterval(() => {
|
|
2395
2721
|
if (isParentAlive()) {
|
|
@@ -2403,7 +2729,7 @@ function startParentWatchdog() {
|
|
|
2403
2729
|
const canExitSafely = stateLoaded && state_task.incomplete === 0;
|
|
2404
2730
|
const graceExpired = goneMs >= maxParentGoneAliveMs;
|
|
2405
2731
|
if (canExitSafely || graceExpired) {
|
|
2406
|
-
|
|
2732
|
+
log7.info("parent process gone, exiting batch worker", {
|
|
2407
2733
|
parentPid: PARENT_PID,
|
|
2408
2734
|
workerPid: process.pid,
|
|
2409
2735
|
reason: canExitSafely ? "no incomplete tasks" : "grace period expired",
|
|
@@ -2422,5 +2748,5 @@ export {
|
|
|
2422
2748
|
startBatchWorker
|
|
2423
2749
|
};
|
|
2424
2750
|
|
|
2425
|
-
//# debugId=
|
|
2751
|
+
//# debugId=5E38E5DD30FE370364756E2164756E21
|
|
2426
2752
|
//# sourceMappingURL=batchWorker.js.map
|