hanoman 0.1.28 → 0.1.30
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/build-info.json +3 -3
- package/dist/cli.js +3 -0
- package/dist/server.js +344 -200
- package/package.json +1 -1
- package/web/assets/{index-CJNgL7_5.js → index-Bz729E_V.js} +50 -49
- package/web/index.html +1 -1
package/dist/server.js
CHANGED
|
@@ -6091,6 +6091,9 @@ var init_api = __esm({
|
|
|
6091
6091
|
// SPEC-233 · drop commit isolasi
|
|
6092
6092
|
fsBrowse: (path) => `${API}/fs/browse${path ? `?path=${encodeURIComponent(path)}` : ""}`,
|
|
6093
6093
|
terminalSessions: `${API}/terminal/sessions`,
|
|
6094
|
+
// SPEC-742 · ADR-0116 · pembersihan worktree yang masih tertunda sesudah sesi ditutup. Di bawah
|
|
6095
|
+
// prefix /terminal supaya ikut capability `sessions` yang sudah ada (cermin sessionHistory).
|
|
6096
|
+
terminalCleanups: `${API}/terminal/cleanups`,
|
|
6094
6097
|
terminalSession: (id) => `${API}/terminal/sessions/${id}`,
|
|
6095
6098
|
terminalSteer: (id) => `${API}/terminal/sessions/${id}/steer`,
|
|
6096
6099
|
terminalInterrupt: (id) => `${API}/terminal/sessions/${id}/interrupt`,
|
|
@@ -8235,17 +8238,30 @@ ${lines2.join("\n")}` : "";
|
|
|
8235
8238
|
|
|
8236
8239
|
// ../runner/src/git.ts
|
|
8237
8240
|
import { spawnSync } from "node:child_process";
|
|
8238
|
-
import { rmSync, mkdirSync, realpathSync } from "node:fs";
|
|
8239
|
-
import { isAbsolute, resolve as resolve4 } from "node:path";
|
|
8241
|
+
import { rmSync, mkdirSync, realpathSync, existsSync as existsSync2, renameSync } from "node:fs";
|
|
8242
|
+
import { basename, isAbsolute, resolve as resolve4, sep } from "node:path";
|
|
8243
|
+
function trashWorktree(repo, path) {
|
|
8244
|
+
const target2 = isAbsolute(path) ? resolve4(path) : resolve4(repo, path);
|
|
8245
|
+
if (target2 === resolve4(repo)) {
|
|
8246
|
+
throw new Error(`trashWorktree menolak memindahkan repo itu sendiri: ${target2}`);
|
|
8247
|
+
}
|
|
8248
|
+
const trash = resolve4(repo, ".worktrees", ".trash");
|
|
8249
|
+
if (target2 === trash || target2.startsWith(trash + sep) || !existsSync2(target2)) return null;
|
|
8250
|
+
mkdirSync(trash, { recursive: true });
|
|
8251
|
+
const dest = resolve4(trash, `${basename(target2)}.${Date.now().toString(36)}-${(trashSeq++).toString(36)}`);
|
|
8252
|
+
renameSync(target2, dest);
|
|
8253
|
+
return dest;
|
|
8254
|
+
}
|
|
8240
8255
|
function git(cwd, args) {
|
|
8241
8256
|
const r = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
8242
8257
|
if (r.status !== 0) throw new Error(`git ${args.join(" ")} failed: ${r.stderr || r.stdout || r.error?.message || "gagal spawn"}`);
|
|
8243
8258
|
return r.stdout;
|
|
8244
8259
|
}
|
|
8245
|
-
var tryGit, resolveCommit, samePath, realGit;
|
|
8260
|
+
var trashSeq, tryGit, resolveCommit, samePath, realGit;
|
|
8246
8261
|
var init_git = __esm({
|
|
8247
8262
|
"../runner/src/git.ts"() {
|
|
8248
8263
|
"use strict";
|
|
8264
|
+
trashSeq = 0;
|
|
8249
8265
|
tryGit = (cwd, args) => {
|
|
8250
8266
|
spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
8251
8267
|
};
|
|
@@ -8269,9 +8285,13 @@ var init_git = __esm({
|
|
|
8269
8285
|
// menolak meng-checkout branch yang sudah dipakai). Agen sendiri yang mem-push HEAD ke
|
|
8270
8286
|
// branchTo saat pekerjaannya selesai (SPEC-162).
|
|
8271
8287
|
addWorktree: (repo, path, branchFrom) => {
|
|
8272
|
-
|
|
8288
|
+
try {
|
|
8289
|
+
trashWorktree(repo, path);
|
|
8290
|
+
} catch {
|
|
8291
|
+
tryGit(repo, ["worktree", "remove", "--force", path]);
|
|
8292
|
+
rmSync(isAbsolute(path) ? path : resolve4(repo, path), { recursive: true, force: true });
|
|
8293
|
+
}
|
|
8273
8294
|
tryGit(repo, ["worktree", "prune"]);
|
|
8274
|
-
rmSync(isAbsolute(path) ? path : resolve4(repo, path), { recursive: true, force: true });
|
|
8275
8295
|
const base2 = resolveCommit(repo, branchFrom);
|
|
8276
8296
|
git(repo, ["worktree", "add", "--detach", path, base2]);
|
|
8277
8297
|
return base2;
|
|
@@ -8289,6 +8309,7 @@ var init_git = __esm({
|
|
|
8289
8309
|
tryGit(repo, ["worktree", "prune"]);
|
|
8290
8310
|
rmSync(target2, { recursive: true, force: true });
|
|
8291
8311
|
},
|
|
8312
|
+
trashWorktree,
|
|
8292
8313
|
// Dibaca di worktree sesi (bukan repo utama) tepat sebelum removeWorktree: HEAD-nya =
|
|
8293
8314
|
// ujung range diff review sesudah item selesai (SPEC-176, ADR-0030).
|
|
8294
8315
|
headSha: (worktree) => git(worktree, ["rev-parse", "HEAD"]).trim(),
|
|
@@ -8657,8 +8678,8 @@ var init_paths = __esm({
|
|
|
8657
8678
|
|
|
8658
8679
|
// ../runner/src/skills.ts
|
|
8659
8680
|
import { homedir as homedir2 } from "node:os";
|
|
8660
|
-
import { basename, dirname as dirname3, join as join2, resolve as resolve6 } from "node:path";
|
|
8661
|
-
import { existsSync as
|
|
8681
|
+
import { basename as basename2, dirname as dirname3, join as join2, resolve as resolve6 } from "node:path";
|
|
8682
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
8662
8683
|
function agentSkillHome(agent, env = process.env, osHome = homedir2()) {
|
|
8663
8684
|
const own = (agent === "codex" ? env.HANOMAN_CODEX_HOME : env.HANOMAN_CLAUDE_HOME)?.trim();
|
|
8664
8685
|
if (own) return own;
|
|
@@ -8686,9 +8707,9 @@ function readJson(file) {
|
|
|
8686
8707
|
}
|
|
8687
8708
|
}
|
|
8688
8709
|
function isSkillDir(dir2) {
|
|
8689
|
-
return
|
|
8710
|
+
return existsSync3(join2(dir2, "SKILL.md"));
|
|
8690
8711
|
}
|
|
8691
|
-
function asSkill(dir2, pkg, name2 =
|
|
8712
|
+
function asSkill(dir2, pkg, name2 = basename2(dir2)) {
|
|
8692
8713
|
return { id: pkg ? `${pkg}:${name2}` : name2, name: name2, pkg, dir: dir2 };
|
|
8693
8714
|
}
|
|
8694
8715
|
function skillsUnder(dir2, pkg, depth = 2) {
|
|
@@ -8709,7 +8730,7 @@ function manifestSkills(installPath, pkg) {
|
|
|
8709
8730
|
for (const rel of declared) {
|
|
8710
8731
|
if (typeof rel !== "string" || !rel) continue;
|
|
8711
8732
|
const p = resolve6(installPath, rel);
|
|
8712
|
-
const dir2 =
|
|
8733
|
+
const dir2 = basename2(p) === "SKILL.md" ? dirname3(p) : p;
|
|
8713
8734
|
if (isSkillDir(dir2)) out3.push(asSkill(dir2, pkg));
|
|
8714
8735
|
}
|
|
8715
8736
|
if (out3.length) return out3;
|
|
@@ -9429,7 +9450,7 @@ var init_tui_dialog = __esm({
|
|
|
9429
9450
|
});
|
|
9430
9451
|
|
|
9431
9452
|
// src/services/secret-box.ts
|
|
9432
|
-
import { chmodSync, existsSync as
|
|
9453
|
+
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync } from "node:fs";
|
|
9433
9454
|
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
9434
9455
|
import { dirname as dirname5, join as join3 } from "node:path";
|
|
9435
9456
|
function isEncrypted(value) {
|
|
@@ -9475,7 +9496,7 @@ function secretKey() {
|
|
|
9475
9496
|
return key2;
|
|
9476
9497
|
}
|
|
9477
9498
|
const path = secretKeyPath();
|
|
9478
|
-
if (
|
|
9499
|
+
if (existsSync4(path)) {
|
|
9479
9500
|
const key2 = Buffer.from(readFileSync4(path, "utf8").trim(), "base64url");
|
|
9480
9501
|
if (key2.length !== KEY_BYTES) throw new Error(`kunci di ${path} rusak \u2014 panjangnya bukan 32 byte`);
|
|
9481
9502
|
cached = key2;
|
|
@@ -11227,7 +11248,7 @@ var init_gateway = __esm({
|
|
|
11227
11248
|
const message = sanitizeTelegramOutput(error.message, this.deps.exactSecrets).slice(0, 500);
|
|
11228
11249
|
updateTelegramRuntimeStatus({ running: false, readiness: "error", lastError: message });
|
|
11229
11250
|
if (error instanceof TelegramApiError && error.code === 409) return;
|
|
11230
|
-
await new Promise((
|
|
11251
|
+
await new Promise((resolve17) => setTimeout(resolve17, 1e3));
|
|
11231
11252
|
}
|
|
11232
11253
|
}
|
|
11233
11254
|
}
|
|
@@ -11590,21 +11611,21 @@ var init_store = __esm({
|
|
|
11590
11611
|
* di 4 detik selamanya.
|
|
11591
11612
|
*/
|
|
11592
11613
|
async chatsAwaitingReply(since) {
|
|
11593
|
-
const
|
|
11614
|
+
const pending2 = await this.db.telegramUpdate.findMany({
|
|
11594
11615
|
where: { state: "dispatched", chatId: { not: null }, dispatchedAt: { gte: since } },
|
|
11595
11616
|
select: { updateId: true, chatId: true },
|
|
11596
11617
|
orderBy: { updateId: "asc" }
|
|
11597
11618
|
});
|
|
11598
|
-
if (!
|
|
11619
|
+
if (!pending2.length) return [];
|
|
11599
11620
|
const answered = new Set((await this.db.telegramOutbox.findMany({
|
|
11600
11621
|
where: {
|
|
11601
|
-
updateId: { in:
|
|
11622
|
+
updateId: { in: pending2.map((row) => row.updateId) },
|
|
11602
11623
|
kind: { in: [...TELEGRAM_FINAL_REPLY_KINDS] }
|
|
11603
11624
|
},
|
|
11604
11625
|
select: { updateId: true }
|
|
11605
11626
|
})).map((row) => row.updateId));
|
|
11606
11627
|
const chats = [];
|
|
11607
|
-
for (const row of
|
|
11628
|
+
for (const row of pending2) {
|
|
11608
11629
|
if (answered.has(row.updateId) || !row.chatId || chats.includes(row.chatId)) continue;
|
|
11609
11630
|
chats.push(row.chatId);
|
|
11610
11631
|
}
|
|
@@ -15731,7 +15752,7 @@ async function syncOnce(transport) {
|
|
|
15731
15752
|
let pulled = 0, pushed = 0, conflicts = 0;
|
|
15732
15753
|
const cursor = await getCursor();
|
|
15733
15754
|
const outbox = await listOutbox();
|
|
15734
|
-
const
|
|
15755
|
+
const pending2 = new Set(outbox.map((o) => `${o.entity}:${o.recordId}`));
|
|
15735
15756
|
const conflicted = /* @__PURE__ */ new Set();
|
|
15736
15757
|
const markConflict = async (entity, recordId, local, server) => {
|
|
15737
15758
|
await recordConflict(entity, recordId, local, server);
|
|
@@ -15746,7 +15767,7 @@ async function syncOnce(transport) {
|
|
|
15746
15767
|
const deferred = [];
|
|
15747
15768
|
for (const rec of records) {
|
|
15748
15769
|
if (!isEntity(rec.entity)) continue;
|
|
15749
|
-
if (
|
|
15770
|
+
if (pending2.has(`${rec.entity}:${rec.recordId}`)) {
|
|
15750
15771
|
const local = await snapshot(rec.entity, rec.recordId);
|
|
15751
15772
|
if (local && JSON.stringify(local.data) !== JSON.stringify(rec.data)) {
|
|
15752
15773
|
await markConflict(
|
|
@@ -15926,16 +15947,16 @@ async function startSyncClient(base2, token, tickMs2) {
|
|
|
15926
15947
|
await tick6();
|
|
15927
15948
|
void connectWs();
|
|
15928
15949
|
const ms = tickMs2 && tickMs2 > 0 ? tickMs2 : 15e3;
|
|
15929
|
-
|
|
15950
|
+
timer3 = setInterval(() => {
|
|
15930
15951
|
void tick6();
|
|
15931
15952
|
}, ms);
|
|
15932
|
-
|
|
15953
|
+
timer3.unref?.();
|
|
15933
15954
|
}
|
|
15934
15955
|
function stopSyncClient() {
|
|
15935
15956
|
started = false;
|
|
15936
|
-
if (
|
|
15937
|
-
clearInterval(
|
|
15938
|
-
|
|
15957
|
+
if (timer3) {
|
|
15958
|
+
clearInterval(timer3);
|
|
15959
|
+
timer3 = void 0;
|
|
15939
15960
|
}
|
|
15940
15961
|
try {
|
|
15941
15962
|
ws?.close();
|
|
@@ -15949,7 +15970,7 @@ async function applySyncConfig() {
|
|
|
15949
15970
|
const token = effectiveStr("SYNC_DEVICE_TOKEN");
|
|
15950
15971
|
if (base2 && token) await startSyncClient(base2, token, effectiveInt("SYNC_TICK_MS"));
|
|
15951
15972
|
}
|
|
15952
|
-
var feedHole, FULL_PULL_MAX_PAGES,
|
|
15973
|
+
var feedHole, FULL_PULL_MAX_PAGES, timer3, ws, started;
|
|
15953
15974
|
var init_sync_client = __esm({
|
|
15954
15975
|
"src/services/sync-client.ts"() {
|
|
15955
15976
|
"use strict";
|
|
@@ -17237,7 +17258,7 @@ var require_parseParams = __commonJS({
|
|
|
17237
17258
|
var require_basename = __commonJS({
|
|
17238
17259
|
"../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/basename.js"(exports, module) {
|
|
17239
17260
|
"use strict";
|
|
17240
|
-
module.exports = function
|
|
17261
|
+
module.exports = function basename5(path) {
|
|
17241
17262
|
if (typeof path !== "string") {
|
|
17242
17263
|
return "";
|
|
17243
17264
|
}
|
|
@@ -17264,7 +17285,7 @@ var require_multipart = __commonJS({
|
|
|
17264
17285
|
var Dicer = require_Dicer();
|
|
17265
17286
|
var parseParams = require_parseParams();
|
|
17266
17287
|
var decodeText = require_decodeText();
|
|
17267
|
-
var
|
|
17288
|
+
var basename5 = require_basename();
|
|
17268
17289
|
var getLimit = require_getLimit();
|
|
17269
17290
|
var RE_BOUNDARY = /^boundary$/i;
|
|
17270
17291
|
var RE_FIELD = /^form-data$/i;
|
|
@@ -17381,7 +17402,7 @@ var require_multipart = __commonJS({
|
|
|
17381
17402
|
} else if (RE_FILENAME.test(parsed[i][0])) {
|
|
17382
17403
|
filename = parsed[i][1];
|
|
17383
17404
|
if (!preservePath) {
|
|
17384
|
-
filename =
|
|
17405
|
+
filename = basename5(filename);
|
|
17385
17406
|
}
|
|
17386
17407
|
}
|
|
17387
17408
|
}
|
|
@@ -18465,17 +18486,17 @@ var require_stream_wormhole = __commonJS({
|
|
|
18465
18486
|
"../node_modules/.pnpm/stream-wormhole@1.1.0/node_modules/stream-wormhole/index.js"(exports, module) {
|
|
18466
18487
|
"use strict";
|
|
18467
18488
|
module.exports = (stream, throwError) => {
|
|
18468
|
-
return new Promise((
|
|
18489
|
+
return new Promise((resolve17, reject2) => {
|
|
18469
18490
|
if (typeof stream.resume !== "function") {
|
|
18470
|
-
return
|
|
18491
|
+
return resolve17();
|
|
18471
18492
|
}
|
|
18472
18493
|
stream.unpipe && stream.unpipe();
|
|
18473
18494
|
stream.resume();
|
|
18474
18495
|
if (stream._readableState && stream._readableState.ended) {
|
|
18475
|
-
return
|
|
18496
|
+
return resolve17();
|
|
18476
18497
|
}
|
|
18477
18498
|
if (!stream.readable || stream.destroyed) {
|
|
18478
|
-
return
|
|
18499
|
+
return resolve17();
|
|
18479
18500
|
}
|
|
18480
18501
|
function cleanup() {
|
|
18481
18502
|
stream.removeListener("end", onEnd);
|
|
@@ -18484,14 +18505,14 @@ var require_stream_wormhole = __commonJS({
|
|
|
18484
18505
|
}
|
|
18485
18506
|
function onEnd() {
|
|
18486
18507
|
cleanup();
|
|
18487
|
-
|
|
18508
|
+
resolve17();
|
|
18488
18509
|
}
|
|
18489
18510
|
function onError(err) {
|
|
18490
18511
|
cleanup();
|
|
18491
18512
|
if (throwError) {
|
|
18492
18513
|
reject2(err);
|
|
18493
18514
|
} else {
|
|
18494
|
-
|
|
18515
|
+
resolve17();
|
|
18495
18516
|
}
|
|
18496
18517
|
}
|
|
18497
18518
|
stream.on("end", onEnd);
|
|
@@ -18926,16 +18947,16 @@ var require_multipart2 = __commonJS({
|
|
|
18926
18947
|
}
|
|
18927
18948
|
};
|
|
18928
18949
|
const parts = () => {
|
|
18929
|
-
return new Promise((
|
|
18950
|
+
return new Promise((resolve17, reject2) => {
|
|
18930
18951
|
handle((val) => {
|
|
18931
18952
|
if (val instanceof Error) {
|
|
18932
18953
|
if (val.message === "Unexpected end of multipart data") {
|
|
18933
|
-
|
|
18954
|
+
resolve17(null);
|
|
18934
18955
|
} else {
|
|
18935
18956
|
reject2(val);
|
|
18936
18957
|
}
|
|
18937
18958
|
} else {
|
|
18938
|
-
|
|
18959
|
+
resolve17(val);
|
|
18939
18960
|
}
|
|
18940
18961
|
});
|
|
18941
18962
|
});
|
|
@@ -19202,7 +19223,7 @@ import websocket from "@fastify/websocket";
|
|
|
19202
19223
|
import cookie from "@fastify/cookie";
|
|
19203
19224
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
19204
19225
|
import { dirname as dirname14 } from "node:path";
|
|
19205
|
-
import { existsSync as
|
|
19226
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
19206
19227
|
|
|
19207
19228
|
// src/web-dir.ts
|
|
19208
19229
|
import { resolve } from "node:path";
|
|
@@ -19264,8 +19285,8 @@ init_stage_machine();
|
|
|
19264
19285
|
init_src();
|
|
19265
19286
|
import { execFile } from "node:child_process";
|
|
19266
19287
|
import { promisify } from "node:util";
|
|
19267
|
-
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as
|
|
19268
|
-
import { resolve as resolve8, dirname as dirname7, sep } from "node:path";
|
|
19288
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, rmSync as rmSync2, mkdirSync as mkdirSync5 } from "node:fs";
|
|
19289
|
+
import { resolve as resolve8, dirname as dirname7, sep as sep2 } from "node:path";
|
|
19269
19290
|
var exec = promisify(execFile);
|
|
19270
19291
|
async function listRepoDocs(repoDir) {
|
|
19271
19292
|
try {
|
|
@@ -19289,12 +19310,12 @@ function docsDirOf(repoDir) {
|
|
|
19289
19310
|
}
|
|
19290
19311
|
function resolveIndex(repoDir, docsDir) {
|
|
19291
19312
|
const rel = `${docsDir}/README.md`;
|
|
19292
|
-
return
|
|
19313
|
+
return existsSync5(resolve8(repoDir, rel)) ? rel : "";
|
|
19293
19314
|
}
|
|
19294
19315
|
var catOf = (rel) => rel.includes("/") ? rel.slice(0, rel.lastIndexOf("/")) : ".";
|
|
19295
19316
|
var nameOf = (rel) => rel.includes("/") ? rel.slice(rel.lastIndexOf("/") + 1) : rel;
|
|
19296
19317
|
async function scanRepoDocs(repoDir) {
|
|
19297
|
-
if (!repoDir || !
|
|
19318
|
+
if (!repoDir || !existsSync5(repoDir)) return { coverage: 0, tree: [] };
|
|
19298
19319
|
const files = await listRepoDocs(repoDir);
|
|
19299
19320
|
const docsDir = docsDirOf(repoDir);
|
|
19300
19321
|
const index = resolveIndex(repoDir, docsDir);
|
|
@@ -19324,7 +19345,7 @@ function docAbsPath(repoDir, rel) {
|
|
|
19324
19345
|
if (!rel.endsWith(".md")) throw new Error("hanya file .md yang diizinkan");
|
|
19325
19346
|
if (rel.split("/").includes(".git")) throw new Error("tidak boleh menyentuh .git");
|
|
19326
19347
|
const abs = resolve8(repoDir, rel);
|
|
19327
|
-
if (abs !== repoDir && !abs.startsWith(repoDir +
|
|
19348
|
+
if (abs !== repoDir && !abs.startsWith(repoDir + sep2)) throw new Error("path keluar dari repo");
|
|
19328
19349
|
return abs;
|
|
19329
19350
|
}
|
|
19330
19351
|
function readDocFile(repoDir, rel) {
|
|
@@ -19341,7 +19362,7 @@ function writeDocFile(repoDir, rel, content) {
|
|
|
19341
19362
|
}
|
|
19342
19363
|
function deleteDocFile(repoDir, rel) {
|
|
19343
19364
|
const abs = docAbsPath(repoDir, rel);
|
|
19344
|
-
if (!
|
|
19365
|
+
if (!existsSync5(abs)) return false;
|
|
19345
19366
|
rmSync2(abs);
|
|
19346
19367
|
return true;
|
|
19347
19368
|
}
|
|
@@ -19628,7 +19649,7 @@ async function projects_default(app2) {
|
|
|
19628
19649
|
// src/routes/specs.ts
|
|
19629
19650
|
init_src();
|
|
19630
19651
|
init_src2();
|
|
19631
|
-
import { existsSync as
|
|
19652
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
19632
19653
|
|
|
19633
19654
|
// src/services/integrate.ts
|
|
19634
19655
|
import { execFile as execFile3 } from "node:child_process";
|
|
@@ -20072,6 +20093,18 @@ async function recordAutoMerge(specId, projectId, title) {
|
|
|
20072
20093
|
}).catch(() => {
|
|
20073
20094
|
});
|
|
20074
20095
|
}
|
|
20096
|
+
async function recordCleanupFailure(sessionId2, projectId, entry, reason) {
|
|
20097
|
+
await prisma.notification.create({
|
|
20098
|
+
data: {
|
|
20099
|
+
type: "cleanup",
|
|
20100
|
+
key: `cleanup:${entry}`,
|
|
20101
|
+
sessionId: sessionId2,
|
|
20102
|
+
projectId,
|
|
20103
|
+
title: `Worktree sesi ${sessionId2} gagal dibersihkan \u2014 ${reason}`
|
|
20104
|
+
}
|
|
20105
|
+
}).catch(() => {
|
|
20106
|
+
});
|
|
20107
|
+
}
|
|
20075
20108
|
async function recordSourceChange(specId, projectId, title, from, to, seq) {
|
|
20076
20109
|
const sessionId2 = specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
20077
20110
|
await prisma.notification.create({
|
|
@@ -22724,7 +22757,7 @@ function renderDocPdf(text, name2, meta) {
|
|
|
22724
22757
|
});
|
|
22725
22758
|
const chunks = [];
|
|
22726
22759
|
doc.on("data", (c) => chunks.push(c));
|
|
22727
|
-
const done = new Promise((
|
|
22760
|
+
const done = new Promise((resolve17) => doc.on("end", () => resolve17(Buffer.concat(chunks))));
|
|
22728
22761
|
doc.font("Helvetica-Bold").fontSize(8).fillColor(BRASS).text(toWinAnsi(meta.eyebrow.toUpperCase()), { characterSpacing: 0.8 });
|
|
22729
22762
|
doc.moveDown(0.25);
|
|
22730
22763
|
doc.font("Helvetica-Bold").fontSize(17).fillColor(STRONG).text(toWinAnsi(name2.split("/").pop() ?? name2));
|
|
@@ -23106,7 +23139,7 @@ ${item.context}` : item.context;
|
|
|
23106
23139
|
});
|
|
23107
23140
|
const specWithProject = (id) => prisma.spec.findUnique({ where: { id }, include: { project: true } });
|
|
23108
23141
|
const resolveReview = async (repoDir, spec) => {
|
|
23109
|
-
if (
|
|
23142
|
+
if (existsSync6(worktreeDir(repoDir, spec.id))) return { wt: true };
|
|
23110
23143
|
if (spec.baseSha && spec.headSha && await shaResolvable(repoDir, spec.baseSha) && await shaResolvable(repoDir, spec.headSha))
|
|
23111
23144
|
return { wt: false, base: spec.baseSha, head: spec.headSha };
|
|
23112
23145
|
const r = await specCommitRange(repoDir, spec.id);
|
|
@@ -23341,13 +23374,13 @@ async function docs_default(app2) {
|
|
|
23341
23374
|
|
|
23342
23375
|
// src/routes/ide.ts
|
|
23343
23376
|
init_src2();
|
|
23344
|
-
import { basename as
|
|
23377
|
+
import { basename as basename3 } from "node:path";
|
|
23345
23378
|
import { spawn as spawn2 } from "node:child_process";
|
|
23346
23379
|
|
|
23347
23380
|
// src/services/git-remotes.ts
|
|
23348
23381
|
import { execFile as execFile5 } from "node:child_process";
|
|
23349
23382
|
import { promisify as promisify5 } from "node:util";
|
|
23350
|
-
import { existsSync as
|
|
23383
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
23351
23384
|
var exec5 = promisify5(execFile5);
|
|
23352
23385
|
var GIT4 = { maxBuffer: 1 << 24, encoding: "utf8" };
|
|
23353
23386
|
function parseRemote(url2) {
|
|
@@ -23366,7 +23399,7 @@ function prUrl(remoteUrl, branch, base2 = "main") {
|
|
|
23366
23399
|
return null;
|
|
23367
23400
|
}
|
|
23368
23401
|
async function listRemotes(repoDir) {
|
|
23369
|
-
if (!repoDir || !
|
|
23402
|
+
if (!repoDir || !existsSync7(repoDir)) return [];
|
|
23370
23403
|
try {
|
|
23371
23404
|
const { stdout } = await exec5("git", ["remote", "-v"], { cwd: repoDir, ...GIT4 });
|
|
23372
23405
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -23408,8 +23441,8 @@ import { promisify as promisify7 } from "node:util";
|
|
|
23408
23441
|
import { execFile as execFile6 } from "node:child_process";
|
|
23409
23442
|
import { promisify as promisify6 } from "node:util";
|
|
23410
23443
|
import { readFile as readFile3, writeFile, mkdir } from "node:fs/promises";
|
|
23411
|
-
import { existsSync as
|
|
23412
|
-
import { resolve as resolve10, sep as
|
|
23444
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
23445
|
+
import { resolve as resolve10, sep as sep3, dirname as dirname9 } from "node:path";
|
|
23413
23446
|
var exec6 = promisify6(execFile6);
|
|
23414
23447
|
var GIT5 = { maxBuffer: 1 << 24 };
|
|
23415
23448
|
var MAX2 = 256 * 1024;
|
|
@@ -23417,11 +23450,11 @@ var splitZ2 = (s2) => s2.split("\0").filter(Boolean);
|
|
|
23417
23450
|
function repoAbsPath(repoDir, rel) {
|
|
23418
23451
|
if (rel.split(/[\\/]/).includes(".git")) throw new Error("tidak boleh menyentuh .git");
|
|
23419
23452
|
const abs = resolve10(repoDir, rel);
|
|
23420
|
-
if (abs !== repoDir && !abs.startsWith(repoDir +
|
|
23453
|
+
if (abs !== repoDir && !abs.startsWith(repoDir + sep3)) throw new Error("path keluar dari repo");
|
|
23421
23454
|
return abs;
|
|
23422
23455
|
}
|
|
23423
23456
|
async function listRepoTree(repoDir, ref = "") {
|
|
23424
|
-
if (!repoDir || !
|
|
23457
|
+
if (!repoDir || !existsSync8(repoDir)) return [];
|
|
23425
23458
|
try {
|
|
23426
23459
|
const { stdout } = ref ? await exec6("git", ["ls-tree", "-r", "--name-only", "-z", ref], { cwd: repoDir, ...GIT5 }) : await exec6("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], { cwd: repoDir, ...GIT5 });
|
|
23427
23460
|
return [...new Set(splitZ2(stdout))].sort();
|
|
@@ -23447,7 +23480,7 @@ async function currentBranch(repoDir) {
|
|
|
23447
23480
|
}
|
|
23448
23481
|
async function repoStatus(repoDir) {
|
|
23449
23482
|
const empty = { branch: "", ahead: 0, behind: 0, staged: [], unstaged: [], untracked: [], clean: true };
|
|
23450
|
-
if (!repoDir || !
|
|
23483
|
+
if (!repoDir || !existsSync8(repoDir)) return empty;
|
|
23451
23484
|
try {
|
|
23452
23485
|
const { stdout } = await exec6("git", ["status", "--porcelain=v1", "-z", "-b"], { cwd: repoDir, ...GIT5 });
|
|
23453
23486
|
const parts = stdout.split("\0");
|
|
@@ -23480,7 +23513,7 @@ async function repoStatus(repoDir) {
|
|
|
23480
23513
|
}
|
|
23481
23514
|
}
|
|
23482
23515
|
async function listStashes(repoDir) {
|
|
23483
|
-
if (!repoDir || !
|
|
23516
|
+
if (!repoDir || !existsSync8(repoDir)) return [];
|
|
23484
23517
|
try {
|
|
23485
23518
|
const { stdout } = await exec6("git", ["stash", "list", `--format=%gd${US}%s${US}%aI`], { cwd: repoDir, ...GIT5 });
|
|
23486
23519
|
return stdout.split("\n").filter(Boolean).map((line) => {
|
|
@@ -23492,7 +23525,7 @@ async function listStashes(repoDir) {
|
|
|
23492
23525
|
}
|
|
23493
23526
|
}
|
|
23494
23527
|
async function listGraph(repoDir, limit = 200, opts = {}) {
|
|
23495
|
-
if (!repoDir || !
|
|
23528
|
+
if (!repoDir || !existsSync8(repoDir)) return { commits: [], current: "", total: 0 };
|
|
23496
23529
|
try {
|
|
23497
23530
|
const fmt = ["%H", "%P", "%an", "%aI", "%s", "%D"].join(US);
|
|
23498
23531
|
const refArgs = opts.branches?.length ? ["--end-of-options", ...opts.branches] : [
|
|
@@ -23562,7 +23595,7 @@ async function changedOf(repoDir, sha) {
|
|
|
23562
23595
|
}
|
|
23563
23596
|
async function compareCommits(repoDir, from, to) {
|
|
23564
23597
|
const empty = { from, to, changed: [] };
|
|
23565
|
-
if (!repoDir || !
|
|
23598
|
+
if (!repoDir || !existsSync8(repoDir)) return empty;
|
|
23566
23599
|
try {
|
|
23567
23600
|
const [num, name2] = await Promise.all([
|
|
23568
23601
|
exec6("git", ["diff", "--numstat", "-z", "--no-renames", "--end-of-options", from, to], { cwd: repoDir, ...GIT5 }),
|
|
@@ -23574,7 +23607,7 @@ async function compareCommits(repoDir, from, to) {
|
|
|
23574
23607
|
}
|
|
23575
23608
|
}
|
|
23576
23609
|
async function searchCommits(repoDir, q, by = "all") {
|
|
23577
|
-
if (!repoDir || !
|
|
23610
|
+
if (!repoDir || !existsSync8(repoDir) || !q) return [];
|
|
23578
23611
|
const run2 = async (extra) => {
|
|
23579
23612
|
try {
|
|
23580
23613
|
return (await exec6("git", ["log", "--all", "--format=%H", ...extra], { cwd: repoDir, ...GIT5 })).stdout.split("\n").filter(Boolean);
|
|
@@ -23823,7 +23856,7 @@ async function runGitOp(repoDir, op) {
|
|
|
23823
23856
|
}
|
|
23824
23857
|
}
|
|
23825
23858
|
async function workingStatus(repoDir) {
|
|
23826
|
-
if (!repoDir || !
|
|
23859
|
+
if (!repoDir || !existsSync8(repoDir)) return { branch: "", staged: [], unstaged: [] };
|
|
23827
23860
|
const [branch, staged, unstaged] = await Promise.all([
|
|
23828
23861
|
currentBranch(repoDir),
|
|
23829
23862
|
changedFiles(repoDir, ["--cached"]),
|
|
@@ -23832,7 +23865,7 @@ async function workingStatus(repoDir) {
|
|
|
23832
23865
|
return { branch, staged, unstaged };
|
|
23833
23866
|
}
|
|
23834
23867
|
async function workingFileDiff(repoDir, path, staged) {
|
|
23835
|
-
if (!repoDir || !
|
|
23868
|
+
if (!repoDir || !existsSync8(repoDir)) return null;
|
|
23836
23869
|
repoAbsPath(repoDir, path);
|
|
23837
23870
|
const changed = staged ? await changedFiles(repoDir, ["--cached"]) : await withTempIndex(repoDir, (env) => changedFiles(repoDir, [], env));
|
|
23838
23871
|
const cf = changed.find((c) => c.path === path);
|
|
@@ -24151,7 +24184,7 @@ async function ide_default(app2) {
|
|
|
24151
24184
|
const fmt = q.format === "tar" ? "tar" : "zip";
|
|
24152
24185
|
const child = spawn2("git", ["archive", `--format=${fmt}`, "--end-of-options", ref], { cwd: repoDir });
|
|
24153
24186
|
reply.header("content-type", fmt === "zip" ? "application/zip" : "application/x-tar");
|
|
24154
|
-
reply.header("content-disposition", `attachment; filename="${
|
|
24187
|
+
reply.header("content-disposition", `attachment; filename="${basename3(repoDir)}-${ref.replace(/[^\w.-]/g, "_")}.${fmt}"`);
|
|
24155
24188
|
return reply.send(child.stdout);
|
|
24156
24189
|
});
|
|
24157
24190
|
app2.get("/projects/:id/commit/:sha", async (req, reply) => {
|
|
@@ -24303,7 +24336,7 @@ async function finishGraphOp(reply, id, repoDir, r, verb) {
|
|
|
24303
24336
|
CODE_STYLE_CLAUSE,
|
|
24304
24337
|
`${verb} via git graph project ${id}.`
|
|
24305
24338
|
].join("\n\n");
|
|
24306
|
-
const s2 = createSession(id, r.worktree, { id:
|
|
24339
|
+
const s2 = createSession(id, r.worktree, { id: basename3(r.worktree), model, effort, agent, prompt });
|
|
24307
24340
|
return { status: "conflict", sessionId: s2.id };
|
|
24308
24341
|
}
|
|
24309
24342
|
|
|
@@ -24331,7 +24364,7 @@ init_db();
|
|
|
24331
24364
|
init_src();
|
|
24332
24365
|
init_src2();
|
|
24333
24366
|
init_session_phases();
|
|
24334
|
-
import { existsSync as
|
|
24367
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
24335
24368
|
init_settings2();
|
|
24336
24369
|
init_codex_trust();
|
|
24337
24370
|
|
|
@@ -24476,12 +24509,117 @@ function installCommand(m, agent, shell = shellBin()) {
|
|
|
24476
24509
|
}
|
|
24477
24510
|
|
|
24478
24511
|
// src/services/session-worktree.ts
|
|
24479
|
-
import { resolve as resolve12, sep as
|
|
24512
|
+
import { resolve as resolve12, sep as sep4 } from "node:path";
|
|
24480
24513
|
function ownsWorktree(repoDir, cwd) {
|
|
24481
24514
|
const base2 = resolve12(repoDir);
|
|
24482
24515
|
const dir2 = resolve12(cwd);
|
|
24483
24516
|
const container = resolve12(base2, ".worktrees");
|
|
24484
|
-
return dir2 !== base2 && dir2 !== container && dir2.startsWith(container +
|
|
24517
|
+
return dir2 !== base2 && dir2 !== container && dir2.startsWith(container + sep4);
|
|
24518
|
+
}
|
|
24519
|
+
|
|
24520
|
+
// src/services/worktree-reaper.ts
|
|
24521
|
+
init_src2();
|
|
24522
|
+
init_db();
|
|
24523
|
+
import { execFile as execFile8 } from "node:child_process";
|
|
24524
|
+
import { readdir as readdir2, rm as rm2 } from "node:fs/promises";
|
|
24525
|
+
import { promisify as promisify8 } from "node:util";
|
|
24526
|
+
import { join as join7, resolve as resolve13 } from "node:path";
|
|
24527
|
+
var TICK_MS = 6e4;
|
|
24528
|
+
var trashDirOf = (repoDir) => resolve13(repoDir, ".worktrees", ".trash");
|
|
24529
|
+
var exec8 = promisify8(execFile8);
|
|
24530
|
+
var prodReaperDeps = {
|
|
24531
|
+
rm: (path) => rm2(path, { recursive: true, force: true }),
|
|
24532
|
+
prune: async (repoDir) => {
|
|
24533
|
+
try {
|
|
24534
|
+
await exec8("git", ["worktree", "prune"], { cwd: repoDir, timeout: 3e4 });
|
|
24535
|
+
} catch {
|
|
24536
|
+
}
|
|
24537
|
+
},
|
|
24538
|
+
repos: async () => {
|
|
24539
|
+
const projects = await prisma.project.findMany({ select: { id: true } });
|
|
24540
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24541
|
+
const out3 = [];
|
|
24542
|
+
for (const p of projects) {
|
|
24543
|
+
const repoDir = await resolveRepoDir(p.id);
|
|
24544
|
+
if (!repoDir || seen.has(repoDir)) continue;
|
|
24545
|
+
seen.add(repoDir);
|
|
24546
|
+
out3.push({ projectId: p.id, repoDir });
|
|
24547
|
+
}
|
|
24548
|
+
return out3;
|
|
24549
|
+
}
|
|
24550
|
+
};
|
|
24551
|
+
var pending = /* @__PURE__ */ new Map();
|
|
24552
|
+
var sessionIdOf = (entry) => entry.split(".")[0] ?? entry;
|
|
24553
|
+
function releaseWorktree(repoDir, cwd, projectId, deps = prodReaperDeps) {
|
|
24554
|
+
let path;
|
|
24555
|
+
try {
|
|
24556
|
+
path = realGit.trashWorktree(repoDir, cwd);
|
|
24557
|
+
} catch {
|
|
24558
|
+
realGit.removeWorktree(repoDir, cwd);
|
|
24559
|
+
return null;
|
|
24560
|
+
}
|
|
24561
|
+
if (!path) return null;
|
|
24562
|
+
const entry = path.slice(trashDirOf(repoDir).length + 1);
|
|
24563
|
+
pending.set(path, { path, repoDir, projectId, entry, sessionId: sessionIdOf(entry), since: Date.now() });
|
|
24564
|
+
reapSoon(repoDir, projectId, deps);
|
|
24565
|
+
return entry;
|
|
24566
|
+
}
|
|
24567
|
+
var listCleanups = () => [...pending.values()].sort((a, b) => a.since - b.since).map((p) => ({
|
|
24568
|
+
sessionId: p.sessionId,
|
|
24569
|
+
projectId: p.projectId,
|
|
24570
|
+
entry: p.entry,
|
|
24571
|
+
since: new Date(p.since).toISOString(),
|
|
24572
|
+
state: p.error ? "failed" : "closing",
|
|
24573
|
+
...p.error ? { error: p.error } : {}
|
|
24574
|
+
}));
|
|
24575
|
+
async function sweepRepo(repoDir, projectId, deps = prodReaperDeps) {
|
|
24576
|
+
const dir2 = trashDirOf(repoDir);
|
|
24577
|
+
let entries;
|
|
24578
|
+
try {
|
|
24579
|
+
entries = await readdir2(dir2);
|
|
24580
|
+
} catch {
|
|
24581
|
+
return 0;
|
|
24582
|
+
}
|
|
24583
|
+
let removed = 0;
|
|
24584
|
+
for (const entry of entries) {
|
|
24585
|
+
const path = join7(dir2, entry);
|
|
24586
|
+
const known = pending.get(path);
|
|
24587
|
+
const row = known ?? { path, repoDir, projectId, entry, sessionId: sessionIdOf(entry), since: Date.now() };
|
|
24588
|
+
pending.set(path, row);
|
|
24589
|
+
try {
|
|
24590
|
+
await deps.rm(path);
|
|
24591
|
+
pending.delete(path);
|
|
24592
|
+
removed++;
|
|
24593
|
+
} catch (e) {
|
|
24594
|
+
row.error = e.message;
|
|
24595
|
+
await recordCleanupFailure(row.sessionId, row.projectId, entry, row.error);
|
|
24596
|
+
}
|
|
24597
|
+
}
|
|
24598
|
+
if (removed) await deps.prune(repoDir);
|
|
24599
|
+
return removed;
|
|
24600
|
+
}
|
|
24601
|
+
async function sweepAll(deps = prodReaperDeps) {
|
|
24602
|
+
let removed = 0;
|
|
24603
|
+
for (const { projectId, repoDir } of await deps.repos()) {
|
|
24604
|
+
removed += await sweepRepo(repoDir, projectId, deps).catch(() => 0);
|
|
24605
|
+
}
|
|
24606
|
+
return removed;
|
|
24607
|
+
}
|
|
24608
|
+
var inFlight = /* @__PURE__ */ new Set();
|
|
24609
|
+
function reapSoon(repoDir, projectId, deps = prodReaperDeps) {
|
|
24610
|
+
if (inFlight.has(repoDir)) return;
|
|
24611
|
+
inFlight.add(repoDir);
|
|
24612
|
+
void sweepRepo(repoDir, projectId, deps).catch((e) => console.error("sapuan worktree:", e)).finally(() => inFlight.delete(repoDir));
|
|
24613
|
+
}
|
|
24614
|
+
var timer;
|
|
24615
|
+
function startWorktreeReaper(deps = prodReaperDeps) {
|
|
24616
|
+
if (timer) return;
|
|
24617
|
+
void sweepAll(deps).catch((e) => console.error("sapuan worktree saat boot:", e));
|
|
24618
|
+
timer = setInterval(() => {
|
|
24619
|
+
void sweepAll(deps).catch(() => {
|
|
24620
|
+
});
|
|
24621
|
+
}, TICK_MS);
|
|
24622
|
+
timer.unref();
|
|
24485
24623
|
}
|
|
24486
24624
|
|
|
24487
24625
|
// src/services/session-result.ts
|
|
@@ -24760,7 +24898,7 @@ async function terminal_default(app2) {
|
|
|
24760
24898
|
if (!s2) return { ok: false, code: 404, msg: "not found" };
|
|
24761
24899
|
const repoDir = await resolveRepoDir(s2.projectId);
|
|
24762
24900
|
if (!repoDir) return { ok: false, code: 409, msg: "project belum punya repoDir" };
|
|
24763
|
-
if (!s2.cwd.includes("/.worktrees/") || !
|
|
24901
|
+
if (!s2.cwd.includes("/.worktrees/") || !existsSync9(s2.cwd))
|
|
24764
24902
|
return { ok: false, code: 409, msg: "belum ada worktree untuk di-review \u2014 jalankan/lanjutkan sesi dulu" };
|
|
24765
24903
|
return { ok: true, id: s2.id, repoDir };
|
|
24766
24904
|
};
|
|
@@ -24811,6 +24949,7 @@ async function terminal_default(app2) {
|
|
|
24811
24949
|
});
|
|
24812
24950
|
return { status: "conflict", sessionId: cs.id };
|
|
24813
24951
|
});
|
|
24952
|
+
app2.get("/terminal/cleanups", async () => ({ items: listCleanups() }));
|
|
24814
24953
|
app2.delete("/terminal/sessions/:id", async (req, reply) => {
|
|
24815
24954
|
const { id } = req.params;
|
|
24816
24955
|
const s2 = getSession(id);
|
|
@@ -24823,12 +24962,12 @@ async function terminal_default(app2) {
|
|
|
24823
24962
|
await recordHeadSha(s2.specId, s2.cwd);
|
|
24824
24963
|
}
|
|
24825
24964
|
killSession(id);
|
|
24826
|
-
|
|
24827
|
-
return reply.code(
|
|
24965
|
+
const cleanup = ownsWorktree(repoDir, s2.cwd) ? releaseWorktree(repoDir, s2.cwd, s2.projectId) : null;
|
|
24966
|
+
return reply.code(202).send({ cleanup });
|
|
24828
24967
|
}
|
|
24829
24968
|
}
|
|
24830
24969
|
killSession(id);
|
|
24831
|
-
return reply.code(
|
|
24970
|
+
return reply.code(202).send({ cleanup: null });
|
|
24832
24971
|
});
|
|
24833
24972
|
app2.get("/terminal/sessions/:id/ws", { websocket: true }, (socket2, req) => {
|
|
24834
24973
|
const { id } = req.params;
|
|
@@ -24852,7 +24991,7 @@ async function terminal_default(app2) {
|
|
|
24852
24991
|
// src/routes/vps.ts
|
|
24853
24992
|
init_db();
|
|
24854
24993
|
init_src();
|
|
24855
|
-
import { readFileSync as readFileSync10, existsSync as
|
|
24994
|
+
import { readFileSync as readFileSync10, existsSync as existsSync12 } from "node:fs";
|
|
24856
24995
|
import { homedir as homedir6 } from "node:os";
|
|
24857
24996
|
|
|
24858
24997
|
// src/vps/catalog/catalog.data.ts
|
|
@@ -24942,7 +25081,7 @@ init_config2();
|
|
|
24942
25081
|
import { spawn as spawn3 } from "node:child_process";
|
|
24943
25082
|
import { mkdtempSync, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
24944
25083
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
24945
|
-
import { dirname as dirname11, join as
|
|
25084
|
+
import { dirname as dirname11, join as join8 } from "node:path";
|
|
24946
25085
|
var sshBin = () => effectiveStr("HANOMAN_SSH_BIN") ?? "ssh";
|
|
24947
25086
|
function consoleArgv(t) {
|
|
24948
25087
|
return [
|
|
@@ -24957,8 +25096,8 @@ function consoleArgv(t) {
|
|
|
24957
25096
|
];
|
|
24958
25097
|
}
|
|
24959
25098
|
function askpassScript() {
|
|
24960
|
-
const dir2 = mkdtempSync(
|
|
24961
|
-
const path =
|
|
25099
|
+
const dir2 = mkdtempSync(join8(tmpdir3(), "hanoman-askpass-"));
|
|
25100
|
+
const path = join8(dir2, "askpass.sh");
|
|
24962
25101
|
writeFileSync4(path, `#!/bin/sh
|
|
24963
25102
|
printf '%s' "$HANOMAN_SSH_PASSWORD"
|
|
24964
25103
|
`, { mode: 448 });
|
|
@@ -24992,14 +25131,14 @@ function sshExec(t, remoteCmd, opts = {}) {
|
|
|
24992
25131
|
SSH_ASKPASS_REQUIRE: "force",
|
|
24993
25132
|
HANOMAN_SSH_PASSWORD: opts.password
|
|
24994
25133
|
} : process.env;
|
|
24995
|
-
return new Promise((
|
|
25134
|
+
return new Promise((resolve17) => {
|
|
24996
25135
|
const p = spawn3(sshBin(), args, { stdio: ["pipe", "pipe", "pipe"], env });
|
|
24997
25136
|
let out3 = "";
|
|
24998
|
-
const
|
|
25137
|
+
const timer8 = setTimeout(() => p.kill("SIGKILL"), opts.timeoutMs ?? 6e4);
|
|
24999
25138
|
const done = (r) => {
|
|
25000
|
-
clearTimeout(
|
|
25139
|
+
clearTimeout(timer8);
|
|
25001
25140
|
if (askpass) rmSync4(dirname11(askpass), { recursive: true, force: true });
|
|
25002
|
-
|
|
25141
|
+
resolve17(r);
|
|
25003
25142
|
};
|
|
25004
25143
|
p.stdout.on("data", (d) => {
|
|
25005
25144
|
out3 += d;
|
|
@@ -25019,15 +25158,15 @@ function sshExec(t, remoteCmd, opts = {}) {
|
|
|
25019
25158
|
// src/services/vps-audit.ts
|
|
25020
25159
|
init_db();
|
|
25021
25160
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
25022
|
-
import { join as
|
|
25161
|
+
import { join as join10 } from "node:path";
|
|
25023
25162
|
|
|
25024
25163
|
// src/runner/deps.ts
|
|
25025
|
-
import { existsSync as
|
|
25026
|
-
import { dirname as dirname12, join as
|
|
25164
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
25165
|
+
import { dirname as dirname12, join as join9 } from "node:path";
|
|
25027
25166
|
function repoRootFrom(startDir) {
|
|
25028
25167
|
let dir2 = startDir;
|
|
25029
25168
|
for (let i = 0; i < 8; i++) {
|
|
25030
|
-
if (
|
|
25169
|
+
if (existsSync10(join9(dir2, "pnpm-workspace.yaml"))) return dir2;
|
|
25031
25170
|
const parent = dirname12(dir2);
|
|
25032
25171
|
if (parent === dir2) break;
|
|
25033
25172
|
dir2 = parent;
|
|
@@ -25132,7 +25271,7 @@ function parseHealth(out3) {
|
|
|
25132
25271
|
return { uptime: h.uptime ?? "", disk: h.disk ?? "", mem: h.mem ?? "", load: h.load ?? "" };
|
|
25133
25272
|
}
|
|
25134
25273
|
var target = (v) => ({ host: v.host, port: v.port, user: v.user, keyPath: v.keyPath });
|
|
25135
|
-
var scriptPath = (f) =>
|
|
25274
|
+
var scriptPath = (f) => join10(repoRoot(), "server", "scripts", "vps", f);
|
|
25136
25275
|
async function itemStatesOf(vpsId) {
|
|
25137
25276
|
const rows = await prisma.vpsItemState.findMany({ where: { vpsId } });
|
|
25138
25277
|
return Object.fromEntries(rows.map((s2) => [s2.itemId, { na: s2.na, attested: s2.attested }]));
|
|
@@ -25279,15 +25418,15 @@ async function buildChecklist(vpsId) {
|
|
|
25279
25418
|
// src/services/vps-key.ts
|
|
25280
25419
|
init_config2();
|
|
25281
25420
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
25282
|
-
import { existsSync as
|
|
25421
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync9 } from "node:fs";
|
|
25283
25422
|
import { homedir as homedir5 } from "node:os";
|
|
25284
|
-
import { join as
|
|
25285
|
-
var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ??
|
|
25423
|
+
import { join as join11 } from "node:path";
|
|
25424
|
+
var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ?? join11(homedir5(), ".hanoman");
|
|
25286
25425
|
function ensureHanomanKey() {
|
|
25287
25426
|
const dir2 = keyDir();
|
|
25288
|
-
const privPath =
|
|
25427
|
+
const privPath = join11(dir2, "id_ed25519");
|
|
25289
25428
|
const pubPath = `${privPath}.pub`;
|
|
25290
|
-
if (!
|
|
25429
|
+
if (!existsSync11(privPath)) {
|
|
25291
25430
|
mkdirSync8(dir2, { recursive: true, mode: 448 });
|
|
25292
25431
|
execFileSync2(
|
|
25293
25432
|
"ssh-keygen",
|
|
@@ -25314,7 +25453,7 @@ async function bootstrapKey(t, password) {
|
|
|
25314
25453
|
init_pty();
|
|
25315
25454
|
init_settings2();
|
|
25316
25455
|
function keyMissing(v) {
|
|
25317
|
-
return !!v.keyPath && !
|
|
25456
|
+
return !!v.keyPath && !existsSync12(v.keyPath);
|
|
25318
25457
|
}
|
|
25319
25458
|
async function vps_default(app2) {
|
|
25320
25459
|
app2.get("/vps", async () => prisma.vps.findMany({ orderBy: { createdAt: "asc" } }));
|
|
@@ -25526,7 +25665,7 @@ init_config2();
|
|
|
25526
25665
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
25527
25666
|
import { readFileSync as readFileSync11 } from "node:fs";
|
|
25528
25667
|
import { homedir as homedir7 } from "node:os";
|
|
25529
|
-
import { join as
|
|
25668
|
+
import { join as join12 } from "node:path";
|
|
25530
25669
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
25531
25670
|
var TTL_MS2 = 3e4;
|
|
25532
25671
|
var lastOk = null;
|
|
@@ -25539,7 +25678,7 @@ var LABELS = {
|
|
|
25539
25678
|
var humanize = (k) => k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
25540
25679
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
25541
25680
|
function credsFile() {
|
|
25542
|
-
return
|
|
25681
|
+
return join12(effectiveStr("CLAUDE_CONFIG_DIR") ?? join12(homedir7(), ".claude"), ".credentials.json");
|
|
25543
25682
|
}
|
|
25544
25683
|
function readAccessToken() {
|
|
25545
25684
|
if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
|
|
@@ -25611,16 +25750,16 @@ async function getLimits() {
|
|
|
25611
25750
|
}
|
|
25612
25751
|
|
|
25613
25752
|
// src/services/codex-limits.ts
|
|
25614
|
-
import { open as open2, readdir as
|
|
25753
|
+
import { open as open2, readdir as readdir3, stat } from "node:fs/promises";
|
|
25615
25754
|
import { homedir as homedir8 } from "node:os";
|
|
25616
|
-
import { join as
|
|
25755
|
+
import { join as join13 } from "node:path";
|
|
25617
25756
|
var TTL_MS3 = 3e4;
|
|
25618
25757
|
var STALE_AFTER_MS = 12 * 36e5;
|
|
25619
25758
|
var TAIL_BYTES = 512 * 1024;
|
|
25620
25759
|
var MAX_FILES = 8;
|
|
25621
25760
|
var cache2 = null;
|
|
25622
25761
|
var freshUntil2 = 0;
|
|
25623
|
-
var codexSessionsDir = () =>
|
|
25762
|
+
var codexSessionsDir = () => join13(process.env.CODEX_HOME ?? join13(homedir8(), ".codex"), "sessions");
|
|
25624
25763
|
var UNAVAILABLE = { status: "unavailable", windows: [], fetchedAt: null, plan: null };
|
|
25625
25764
|
function windowLabel(minutes) {
|
|
25626
25765
|
if (minutes === 300) return "Sesi 5 jam";
|
|
@@ -25648,12 +25787,12 @@ function toWindow(slot, raw, reached) {
|
|
|
25648
25787
|
async function recentRollouts(dir2) {
|
|
25649
25788
|
let entries;
|
|
25650
25789
|
try {
|
|
25651
|
-
entries = (await
|
|
25790
|
+
entries = (await readdir3(dir2, { recursive: true })).filter((f) => f.endsWith(".jsonl"));
|
|
25652
25791
|
} catch {
|
|
25653
25792
|
return [];
|
|
25654
25793
|
}
|
|
25655
25794
|
const stamped = await Promise.all(entries.map(async (rel) => {
|
|
25656
|
-
const full =
|
|
25795
|
+
const full = join13(dir2, rel);
|
|
25657
25796
|
try {
|
|
25658
25797
|
return { full, mtime: (await stat(full)).mtimeMs };
|
|
25659
25798
|
} catch {
|
|
@@ -25733,9 +25872,9 @@ async function limits(app2) {
|
|
|
25733
25872
|
// src/services/codex-version.ts
|
|
25734
25873
|
init_src();
|
|
25735
25874
|
init_config2();
|
|
25736
|
-
import { execFile as
|
|
25737
|
-
import { promisify as
|
|
25738
|
-
var run =
|
|
25875
|
+
import { execFile as execFile9 } from "node:child_process";
|
|
25876
|
+
import { promisify as promisify9 } from "node:util";
|
|
25877
|
+
var run = promisify9(execFile9);
|
|
25739
25878
|
var CODEX_MIN_CLIENT = "0.144.0";
|
|
25740
25879
|
var TTL_MS4 = 5 * 6e4;
|
|
25741
25880
|
var cache3 = null;
|
|
@@ -25781,7 +25920,7 @@ init_src();
|
|
|
25781
25920
|
init_src();
|
|
25782
25921
|
init_config2();
|
|
25783
25922
|
import { readFileSync as readFileSync12 } from "node:fs";
|
|
25784
|
-
import { resolve as
|
|
25923
|
+
import { resolve as resolve14, dirname as dirname13 } from "node:path";
|
|
25785
25924
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
25786
25925
|
var UPDATE_COMMAND = "npm i -g hanoman@latest";
|
|
25787
25926
|
function composeUpdate(x) {
|
|
@@ -25807,7 +25946,7 @@ var lastLatest = null;
|
|
|
25807
25946
|
var lastStatus = "unavailable";
|
|
25808
25947
|
function runningVersion() {
|
|
25809
25948
|
const here = dirname13(fileURLToPath3(import.meta.url));
|
|
25810
|
-
for (const p of [
|
|
25949
|
+
for (const p of [resolve14(here, "build-info.json"), resolve14(here, "../package.json")]) {
|
|
25811
25950
|
try {
|
|
25812
25951
|
const v = JSON.parse(readFileSync12(p, "utf8")).version;
|
|
25813
25952
|
if (typeof v === "string" && v) return v;
|
|
@@ -25927,6 +26066,10 @@ var GROUPS = [
|
|
|
25927
26066
|
}) },
|
|
25928
26067
|
{ everyTicks: 1, last: "", build: async () => ({ t: "specs", specs: await liveSpecs() }) },
|
|
25929
26068
|
{ everyTicks: 3, last: "", build: async () => ({ t: "notifications", ...await notificationsFeed() }) },
|
|
26069
|
+
// SPEC-742 · ADR-0116 · pembersihan worktree yang masih jalan. `listCleanups()` membaca peta di
|
|
26070
|
+
// memori, bukan disk — nol I/O per tick, dan dedup signature membuat frame lahir hanya saat
|
|
26071
|
+
// daftarnya berubah (nyaris selalu kosong).
|
|
26072
|
+
{ everyTicks: 3, last: "", build: async () => ({ t: "cleanups", cleanups: listCleanups() }) },
|
|
25930
26073
|
// ponytail: cermin GET /vps (orderBy createdAt asc). Query sepele — tak diekstrak.
|
|
25931
26074
|
{ everyTicks: 15, last: "", build: async () => ({ t: "vps", vps: await prisma.vps.findMany({ orderBy: { createdAt: "asc" } }) }) },
|
|
25932
26075
|
{ everyTicks: 30, last: "", build: async () => ({ t: "limits", limits: await getLimits() }) },
|
|
@@ -25950,7 +26093,7 @@ function broadcast2(msg) {
|
|
|
25950
26093
|
}
|
|
25951
26094
|
var tick = 0;
|
|
25952
26095
|
var busy = false;
|
|
25953
|
-
var
|
|
26096
|
+
var timer2;
|
|
25954
26097
|
async function __tick() {
|
|
25955
26098
|
if (busy) return;
|
|
25956
26099
|
busy = true;
|
|
@@ -25974,16 +26117,16 @@ async function __tick() {
|
|
|
25974
26117
|
}
|
|
25975
26118
|
}
|
|
25976
26119
|
function startLoop() {
|
|
25977
|
-
if (
|
|
25978
|
-
|
|
26120
|
+
if (timer2) return;
|
|
26121
|
+
timer2 = setInterval(() => {
|
|
25979
26122
|
void __tick();
|
|
25980
26123
|
}, tickMs());
|
|
25981
|
-
|
|
26124
|
+
timer2.unref();
|
|
25982
26125
|
}
|
|
25983
26126
|
function stopLoop() {
|
|
25984
|
-
if (
|
|
25985
|
-
clearInterval(
|
|
25986
|
-
|
|
26127
|
+
if (timer2) {
|
|
26128
|
+
clearInterval(timer2);
|
|
26129
|
+
timer2 = void 0;
|
|
25987
26130
|
}
|
|
25988
26131
|
tick = 0;
|
|
25989
26132
|
for (const g of GROUPS) g.last = "";
|
|
@@ -26157,7 +26300,7 @@ init_conflicts();
|
|
|
26157
26300
|
init_config2();
|
|
26158
26301
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
26159
26302
|
import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile4, unlink } from "node:fs/promises";
|
|
26160
|
-
import { join as
|
|
26303
|
+
import { join as join14, resolve as resolve15 } from "node:path";
|
|
26161
26304
|
var EXT = {
|
|
26162
26305
|
"image/png": ".png",
|
|
26163
26306
|
"image/jpeg": ".jpg",
|
|
@@ -26167,22 +26310,22 @@ function extFor(mimeType) {
|
|
|
26167
26310
|
return EXT[mimeType] ?? ".bin";
|
|
26168
26311
|
}
|
|
26169
26312
|
function uploadDir() {
|
|
26170
|
-
return
|
|
26313
|
+
return resolve15(effectiveStr("HANOMAN_UPLOAD_DIR") ?? join14(process.cwd(), "data", "uploads"));
|
|
26171
26314
|
}
|
|
26172
26315
|
async function saveUpload(buf, mimeType) {
|
|
26173
26316
|
const dir2 = uploadDir();
|
|
26174
26317
|
await mkdir2(dir2, { recursive: true });
|
|
26175
26318
|
const storageKey = `${randomUUID3()}${extFor(mimeType)}`;
|
|
26176
|
-
await writeFile2(
|
|
26319
|
+
await writeFile2(join14(dir2, storageKey), buf);
|
|
26177
26320
|
return { storageKey, size: buf.length };
|
|
26178
26321
|
}
|
|
26179
26322
|
async function readUpload(storageKey) {
|
|
26180
26323
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
26181
|
-
return readFile4(
|
|
26324
|
+
return readFile4(join14(uploadDir(), safe));
|
|
26182
26325
|
}
|
|
26183
26326
|
async function readUploadOrFetch(storageKey) {
|
|
26184
26327
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
26185
|
-
const target2 =
|
|
26328
|
+
const target2 = join14(uploadDir(), safe);
|
|
26186
26329
|
try {
|
|
26187
26330
|
return await readFile4(target2);
|
|
26188
26331
|
} catch {
|
|
@@ -26201,7 +26344,7 @@ async function readUploadOrFetch(storageKey) {
|
|
|
26201
26344
|
}
|
|
26202
26345
|
async function deleteUpload(storageKey) {
|
|
26203
26346
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
26204
|
-
await unlink(
|
|
26347
|
+
await unlink(join14(uploadDir(), safe)).catch(() => {
|
|
26205
26348
|
});
|
|
26206
26349
|
}
|
|
26207
26350
|
|
|
@@ -26332,10 +26475,10 @@ import { randomUUID as randomUUID5 } from "node:crypto";
|
|
|
26332
26475
|
init_config2();
|
|
26333
26476
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
26334
26477
|
import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile5, unlink as unlink2 } from "node:fs/promises";
|
|
26335
|
-
import { join as
|
|
26478
|
+
import { join as join15, resolve as resolve16, basename as basename4 } from "node:path";
|
|
26336
26479
|
var MAX_TRANSCRIPT_BYTES = 1024 * 1024;
|
|
26337
26480
|
function transcriptDir() {
|
|
26338
|
-
return
|
|
26481
|
+
return resolve16(effectiveStr("HANOMAN_TRANSCRIPT_DIR") ?? join15(process.cwd(), "data", "transcripts"));
|
|
26339
26482
|
}
|
|
26340
26483
|
function clamp(text) {
|
|
26341
26484
|
const buf = Buffer.from(text, "utf8");
|
|
@@ -26353,20 +26496,20 @@ async function saveTranscript(text) {
|
|
|
26353
26496
|
const dir2 = transcriptDir();
|
|
26354
26497
|
await mkdir3(dir2, { recursive: true });
|
|
26355
26498
|
const key = `${randomUUID4()}.log`;
|
|
26356
|
-
await writeFile3(
|
|
26499
|
+
await writeFile3(join15(dir2, key), body, "utf8");
|
|
26357
26500
|
return { key, bytes: Buffer.byteLength(body, "utf8"), truncated };
|
|
26358
26501
|
}
|
|
26359
26502
|
async function readTranscript(key) {
|
|
26360
26503
|
if (!key) return null;
|
|
26361
26504
|
try {
|
|
26362
|
-
return await readFile5(
|
|
26505
|
+
return await readFile5(join15(transcriptDir(), basename4(key)), "utf8");
|
|
26363
26506
|
} catch {
|
|
26364
26507
|
return null;
|
|
26365
26508
|
}
|
|
26366
26509
|
}
|
|
26367
26510
|
async function deleteTranscript(key) {
|
|
26368
26511
|
if (!key) return;
|
|
26369
|
-
await unlink2(
|
|
26512
|
+
await unlink2(join15(transcriptDir(), basename4(key))).catch(() => {
|
|
26370
26513
|
});
|
|
26371
26514
|
}
|
|
26372
26515
|
|
|
@@ -26789,7 +26932,7 @@ init_db();
|
|
|
26789
26932
|
|
|
26790
26933
|
// src/services/ticket-accept.ts
|
|
26791
26934
|
init_db();
|
|
26792
|
-
import { join as
|
|
26935
|
+
import { join as join16 } from "node:path";
|
|
26793
26936
|
async function materializeAttachments(atts) {
|
|
26794
26937
|
const ready = /* @__PURE__ */ new Set();
|
|
26795
26938
|
for (const a of atts) {
|
|
@@ -26804,7 +26947,7 @@ async function materializeAttachments(atts) {
|
|
|
26804
26947
|
var attachmentInstruction = (t, atts, ready) => {
|
|
26805
26948
|
if (atts.length === 0) return "Tanpa lampiran.";
|
|
26806
26949
|
const list2 = atts.map(
|
|
26807
|
-
(a) => ready.has(a.id) ? `- ${a.filename} (${a.mimeType}) \u2192 ${
|
|
26950
|
+
(a) => ready.has(a.id) ? `- ${a.filename} (${a.mimeType}) \u2192 ${join16(uploadDir(), a.storageKey)}` : `- ${a.filename} (${a.mimeType}) \u2192 BELUM TERUNDUH, ambil via GET /api/tickets/${t.id}/attachments/${a.id}`
|
|
26808
26951
|
).join("\n");
|
|
26809
26952
|
return `LAMPIRAN (${atts.length}) dari pelapor \u2014 biasanya screenshot yang menunjukkan masalah. PERIKSA setiap lampiran untuk memahami konteks keluhan sebelum bekerja; jangan berasumsi dari teks saja. Berkas ada di direktori upload server (baca langsung dengan tool Read):
|
|
26810
26953
|
${list2}
|
|
@@ -27377,8 +27520,8 @@ async function scheduler_default(app2) {
|
|
|
27377
27520
|
const cfg = await getScheduler();
|
|
27378
27521
|
if (!cfg.enabled) return reply.code(409).send({ error: "scheduler sedang berhenti \u2014 aktifkan dulu di panel Kendali" });
|
|
27379
27522
|
if (cfg.paused) return reply.code(409).send({ error: "scheduler sedang dijeda \u2014 lanjutkan dulu di panel Kendali" });
|
|
27380
|
-
const
|
|
27381
|
-
if (
|
|
27523
|
+
const pending2 = await prisma.schedulerCronRun.findFirst({ where: { cronId: id, status: "queued" } });
|
|
27524
|
+
if (pending2) return reply.code(409).send({ error: "masih ada run yang menunggu dijalankan", runId: pending2.id });
|
|
27382
27525
|
try {
|
|
27383
27526
|
const run2 = await prisma.schedulerCronRun.create({
|
|
27384
27527
|
data: { cronId: id, projectId: cron.projectId, dueAt: /* @__PURE__ */ new Date(), manual: true }
|
|
@@ -27530,7 +27673,7 @@ function leadPrompt(q, c) {
|
|
|
27530
27673
|
|
|
27531
27674
|
// src/services/lead/verdict.ts
|
|
27532
27675
|
init_src();
|
|
27533
|
-
import { existsSync as
|
|
27676
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
27534
27677
|
import { isAbsolute as isAbsolute3, resolve as resolvePath, relative } from "node:path";
|
|
27535
27678
|
function extractJsonBlock(raw) {
|
|
27536
27679
|
const re = /```json\s*\n([\s\S]*?)```/g;
|
|
@@ -27568,7 +27711,7 @@ function keepExistingRefs(refs2, repoDir) {
|
|
|
27568
27711
|
const full = resolvePath(repoDir, ref);
|
|
27569
27712
|
const rel = relative(repoDir, full);
|
|
27570
27713
|
if (rel.startsWith("..")) continue;
|
|
27571
|
-
if (
|
|
27714
|
+
if (existsSync13(full)) out3.push(ref);
|
|
27572
27715
|
}
|
|
27573
27716
|
return [...new Set(out3)];
|
|
27574
27717
|
}
|
|
@@ -27718,7 +27861,7 @@ function toDecisionView(r) {
|
|
|
27718
27861
|
// src/services/lead/brain.ts
|
|
27719
27862
|
init_config2();
|
|
27720
27863
|
init_pty();
|
|
27721
|
-
import { execFile as
|
|
27864
|
+
import { execFile as execFile10 } from "node:child_process";
|
|
27722
27865
|
var binFor = (agent) => agent === "codex" ? effectiveStr("HANOMAN_CODEX_BIN") ?? "codex" : effectiveStr("HANOMAN_CLAUDE_BIN") ?? "claude";
|
|
27723
27866
|
function leadArgv(o) {
|
|
27724
27867
|
if (o.agent === "codex") {
|
|
@@ -27754,8 +27897,8 @@ function leadFailureReason(agent, timeoutMs, err, stdout, stderr) {
|
|
|
27754
27897
|
function think(prompt, o) {
|
|
27755
27898
|
const bin = binFor(o.agent);
|
|
27756
27899
|
const args = leadArgv({ agent: o.agent, model: o.model, effort: o.effort, prompt });
|
|
27757
|
-
return new Promise((
|
|
27758
|
-
const child =
|
|
27900
|
+
return new Promise((resolve17, reject2) => {
|
|
27901
|
+
const child = execFile10(bin, args, {
|
|
27759
27902
|
cwd: o.cwd,
|
|
27760
27903
|
timeout: o.timeoutMs,
|
|
27761
27904
|
maxBuffer: 16 * 1024 * 1024,
|
|
@@ -27767,7 +27910,7 @@ function think(prompt, o) {
|
|
|
27767
27910
|
reject2(new Error(leadFailureReason(o.agent, o.timeoutMs, err, stdout, stderr)));
|
|
27768
27911
|
return;
|
|
27769
27912
|
}
|
|
27770
|
-
|
|
27913
|
+
resolve17(stdout);
|
|
27771
27914
|
});
|
|
27772
27915
|
child.stdin?.end();
|
|
27773
27916
|
});
|
|
@@ -27782,31 +27925,31 @@ var LeadBusyError = class extends Error {
|
|
|
27782
27925
|
this.name = "LeadBusyError";
|
|
27783
27926
|
}
|
|
27784
27927
|
};
|
|
27785
|
-
var
|
|
27928
|
+
var inFlight2 = 0;
|
|
27786
27929
|
var capacity = 1;
|
|
27787
27930
|
var queue = [];
|
|
27788
27931
|
function leadGateStats() {
|
|
27789
|
-
return { inFlight, queued: queue.length };
|
|
27932
|
+
return { inFlight: inFlight2, queued: queue.length };
|
|
27790
27933
|
}
|
|
27791
27934
|
function pump() {
|
|
27792
|
-
while (queue.length &&
|
|
27935
|
+
while (queue.length && inFlight2 < capacity) {
|
|
27793
27936
|
const w = queue.shift();
|
|
27794
27937
|
clearTimeout(w.timer);
|
|
27795
|
-
|
|
27938
|
+
inFlight2++;
|
|
27796
27939
|
w.grant();
|
|
27797
27940
|
}
|
|
27798
27941
|
}
|
|
27799
27942
|
function acquire(cap, waitMs) {
|
|
27800
27943
|
capacity = Math.max(1, cap);
|
|
27801
27944
|
pump();
|
|
27802
|
-
if (!queue.length &&
|
|
27803
|
-
|
|
27945
|
+
if (!queue.length && inFlight2 < capacity) {
|
|
27946
|
+
inFlight2++;
|
|
27804
27947
|
return Promise.resolve();
|
|
27805
27948
|
}
|
|
27806
27949
|
const startedAt = Date.now();
|
|
27807
|
-
return new Promise((
|
|
27950
|
+
return new Promise((resolve17, reject2) => {
|
|
27808
27951
|
const w = {
|
|
27809
|
-
grant:
|
|
27952
|
+
grant: resolve17,
|
|
27810
27953
|
deny: reject2,
|
|
27811
27954
|
timer: setTimeout(() => {
|
|
27812
27955
|
const i = queue.indexOf(w);
|
|
@@ -27822,7 +27965,7 @@ async function runGated(o, fn) {
|
|
|
27822
27965
|
try {
|
|
27823
27966
|
return await fn();
|
|
27824
27967
|
} finally {
|
|
27825
|
-
|
|
27968
|
+
inFlight2--;
|
|
27826
27969
|
pump();
|
|
27827
27970
|
}
|
|
27828
27971
|
}
|
|
@@ -28873,27 +29016,27 @@ async function orderProject(projectId, deps) {
|
|
|
28873
29016
|
where: { specId: { in: ready.map((r) => r.id) } },
|
|
28874
29017
|
select: { specId: true }
|
|
28875
29018
|
})).map((q) => q.specId));
|
|
28876
|
-
const
|
|
28877
|
-
if (
|
|
28878
|
-
const sig =
|
|
29019
|
+
const pending2 = ready.filter((r) => !already.has(r.id));
|
|
29020
|
+
if (pending2.length < 2) return 0;
|
|
29021
|
+
const sig = pending2.map((r) => r.id).join(",");
|
|
28879
29022
|
if (sig === lastReadySig.get(projectId)) return 0;
|
|
28880
29023
|
lastReadySig.set(projectId, sig);
|
|
28881
29024
|
const row = await decideOrSkip(deps, {
|
|
28882
29025
|
projectId,
|
|
28883
29026
|
gate: "pulse",
|
|
28884
29027
|
kind: "order",
|
|
28885
|
-
question: `Ada ${
|
|
28886
|
-
options:
|
|
28887
|
-
notes:
|
|
29028
|
+
question: `Ada ${pending2.length} backlog siap dikerjakan. Urutkan mana yang lebih dulu berdasarkan isi pekerjaannya, lalu tuliskan urutan id-nya (dipisah koma) di \`decision\`.`,
|
|
29029
|
+
options: pending2.map((r) => `${r.id} \xB7 [${r.priority}] ${r.title}`),
|
|
29030
|
+
notes: pending2.map((r) => `${r.id}: ${r.objective.slice(0, 200)}`)
|
|
28888
29031
|
});
|
|
28889
29032
|
if (!row || row.status !== "berlaku") return 0;
|
|
28890
|
-
const byId2 = new Map(
|
|
29033
|
+
const byId2 = new Map(pending2.map((r) => [r.id.toLowerCase(), r]));
|
|
28891
29034
|
const named = [];
|
|
28892
29035
|
for (const tok of row.answer.split(/[,\s]+/).map((t) => t.trim().toLowerCase()).filter(Boolean)) {
|
|
28893
29036
|
const hit = byId2.get(tok);
|
|
28894
29037
|
if (hit && !named.includes(hit)) named.push(hit);
|
|
28895
29038
|
}
|
|
28896
|
-
const ordered = [...named, ...
|
|
29039
|
+
const ordered = [...named, ...pending2.filter((r) => !named.includes(r))];
|
|
28897
29040
|
for (const r of ordered) {
|
|
28898
29041
|
await deps.enqueue({ specId: r.id, projectId: r.projectId, source: "lead", priority: r.priority });
|
|
28899
29042
|
}
|
|
@@ -28902,12 +29045,12 @@ async function orderProject(projectId, deps) {
|
|
|
28902
29045
|
}
|
|
28903
29046
|
|
|
28904
29047
|
// src/services/lead/engine.ts
|
|
28905
|
-
var
|
|
29048
|
+
var TICK_MS2 = 5e3;
|
|
28906
29049
|
var busyDetect = false;
|
|
28907
29050
|
var busyPulse = false;
|
|
28908
29051
|
var lastPulseAt = 0;
|
|
28909
29052
|
var pulseEndedAt = 0;
|
|
28910
|
-
var
|
|
29053
|
+
var timer4;
|
|
28911
29054
|
function lastPulse() {
|
|
28912
29055
|
return lastPulseAt;
|
|
28913
29056
|
}
|
|
@@ -28960,9 +29103,9 @@ async function tick2(now, deps = {}) {
|
|
|
28960
29103
|
await Promise.all(jobs);
|
|
28961
29104
|
}
|
|
28962
29105
|
function startLead(deps = {}) {
|
|
28963
|
-
if (
|
|
28964
|
-
|
|
28965
|
-
|
|
29106
|
+
if (timer4) return;
|
|
29107
|
+
timer4 = setInterval(() => void tick2(Date.now(), deps), TICK_MS2);
|
|
29108
|
+
timer4.unref();
|
|
28966
29109
|
void tick2(Date.now(), deps);
|
|
28967
29110
|
}
|
|
28968
29111
|
|
|
@@ -29164,9 +29307,9 @@ init_db();
|
|
|
29164
29307
|
|
|
29165
29308
|
// src/services/changelog/collect.ts
|
|
29166
29309
|
init_db();
|
|
29167
|
-
import { execFile as
|
|
29168
|
-
import { promisify as
|
|
29169
|
-
import { existsSync as
|
|
29310
|
+
import { execFile as execFile11 } from "node:child_process";
|
|
29311
|
+
import { promisify as promisify10 } from "node:util";
|
|
29312
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
29170
29313
|
|
|
29171
29314
|
// src/services/changelog/scrub.ts
|
|
29172
29315
|
var MERGE = /^merge\s+(branch|pull request|remote-tracking branch|tag)\b/i;
|
|
@@ -29227,32 +29370,32 @@ async function collectBacklog(projectId, from, to) {
|
|
|
29227
29370
|
notes.push(`${stampless} item selesai tanpa stempel waktu (selesai sebelum stempel ini ada) dan tak ikut dihitung.`);
|
|
29228
29371
|
return { ok: true, input: { mode: "backlog", title: `${from} \u2013 ${to}`, items, notes } };
|
|
29229
29372
|
}
|
|
29230
|
-
var
|
|
29373
|
+
var exec9 = promisify10(execFile11);
|
|
29231
29374
|
var GIT7 = { maxBuffer: 1 << 24, encoding: "utf8" };
|
|
29232
29375
|
var US2 = "";
|
|
29233
29376
|
var RS = "";
|
|
29234
29377
|
var NO_REPO = "project ini belum ditautkan ke repo di mesin ini";
|
|
29235
29378
|
var NO_TAG = "repo project ini belum punya tag rilis";
|
|
29236
|
-
var usable = (repoDir) => !!repoDir &&
|
|
29379
|
+
var usable = (repoDir) => !!repoDir && existsSync14(repoDir);
|
|
29237
29380
|
async function listTags(repoDir) {
|
|
29238
29381
|
if (!usable(repoDir)) return { tags: [], head: null, reason: NO_REPO };
|
|
29239
29382
|
try {
|
|
29240
|
-
const { stdout } = await
|
|
29383
|
+
const { stdout } = await exec9("git", ["tag", "--list", "--sort=-creatordate"], { cwd: repoDir, ...GIT7 });
|
|
29241
29384
|
const tags = stdout.split("\n").map((s2) => s2.trim()).filter(Boolean);
|
|
29242
|
-
const head = await
|
|
29385
|
+
const head = await exec9("git", ["rev-parse", "--short", "HEAD"], { cwd: repoDir, ...GIT7 }).then((r) => r.stdout.trim()).catch(() => null);
|
|
29243
29386
|
return { tags, head, reason: tags.length ? null : NO_TAG };
|
|
29244
29387
|
} catch {
|
|
29245
29388
|
return { tags: [], head: null, reason: NO_TAG };
|
|
29246
29389
|
}
|
|
29247
29390
|
}
|
|
29248
29391
|
async function previousTag(repoDir, tag2) {
|
|
29249
|
-
return
|
|
29392
|
+
return exec9("git", ["describe", "--tags", "--abbrev=0", "--end-of-options", `${tag2}^`], { cwd: repoDir, ...GIT7 }).then((r) => r.stdout.trim() || null).catch(() => null);
|
|
29250
29393
|
}
|
|
29251
29394
|
async function revExists(repoDir, rev) {
|
|
29252
|
-
return
|
|
29395
|
+
return exec9("git", ["rev-parse", "--verify", "--quiet", "--end-of-options", `${rev}^{commit}`], { cwd: repoDir, ...GIT7 }).then(() => true).catch(() => false);
|
|
29253
29396
|
}
|
|
29254
29397
|
async function logRange(repoDir, range) {
|
|
29255
|
-
const { stdout } = await
|
|
29398
|
+
const { stdout } = await exec9(
|
|
29256
29399
|
"git",
|
|
29257
29400
|
["log", "--no-merges", `--pretty=format:%s${US2}%b${RS}`, ...range],
|
|
29258
29401
|
{ cwd: repoDir, ...GIT7 }
|
|
@@ -29496,7 +29639,7 @@ init_db();
|
|
|
29496
29639
|
init_src();
|
|
29497
29640
|
import { readFileSync as readFileSync13 } from "node:fs";
|
|
29498
29641
|
import { homedir as homedir9 } from "node:os";
|
|
29499
|
-
import { join as
|
|
29642
|
+
import { join as join17 } from "node:path";
|
|
29500
29643
|
var home = () => process.env.HOME || homedir9();
|
|
29501
29644
|
var readJson2 = (path) => {
|
|
29502
29645
|
try {
|
|
@@ -29513,7 +29656,7 @@ var serversOf = (node) => {
|
|
|
29513
29656
|
var codexServers = () => {
|
|
29514
29657
|
let text;
|
|
29515
29658
|
try {
|
|
29516
|
-
text = readFileSync13(
|
|
29659
|
+
text = readFileSync13(join17(home(), ".codex", "config.toml"), "utf8");
|
|
29517
29660
|
} catch {
|
|
29518
29661
|
return [];
|
|
29519
29662
|
}
|
|
@@ -29526,12 +29669,12 @@ var codexServers = () => {
|
|
|
29526
29669
|
};
|
|
29527
29670
|
function mcpServerNames(repoDir) {
|
|
29528
29671
|
const names = [];
|
|
29529
|
-
const claudeJson = readJson2(
|
|
29672
|
+
const claudeJson = readJson2(join17(home(), ".claude.json"));
|
|
29530
29673
|
names.push(...serversOf(claudeJson));
|
|
29531
29674
|
if (repoDir) {
|
|
29532
29675
|
const projects = claudeJson?.projects;
|
|
29533
29676
|
if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
|
|
29534
|
-
names.push(...serversOf(readJson2(
|
|
29677
|
+
names.push(...serversOf(readJson2(join17(repoDir, ".mcp.json"))));
|
|
29535
29678
|
}
|
|
29536
29679
|
names.push(...codexServers());
|
|
29537
29680
|
return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
@@ -29830,7 +29973,7 @@ async function resolveGithubRepo(projectId) {
|
|
|
29830
29973
|
|
|
29831
29974
|
// src/services/github-fetch.ts
|
|
29832
29975
|
init_config2();
|
|
29833
|
-
import { execFile as
|
|
29976
|
+
import { execFile as execFile12 } from "node:child_process";
|
|
29834
29977
|
var labelNames = (l) => (l ?? []).map((x) => typeof x === "string" ? x : x.name ?? "").filter(Boolean);
|
|
29835
29978
|
var norm2 = (s2) => s2.toLowerCase() === "closed" ? "closed" : "open";
|
|
29836
29979
|
function issueFromGh(raw) {
|
|
@@ -29870,15 +30013,15 @@ function issuesFromRest(raw) {
|
|
|
29870
30013
|
}
|
|
29871
30014
|
var GH_FIELDS = "number,title,body,author,labels,url,state,createdAt,updatedAt";
|
|
29872
30015
|
var API2 = "https://api.github.com";
|
|
29873
|
-
var defaultRunGh = (args, env) => new Promise((
|
|
29874
|
-
|
|
30016
|
+
var defaultRunGh = (args, env) => new Promise((resolve17, reject2) => {
|
|
30017
|
+
execFile12(
|
|
29875
30018
|
args[0],
|
|
29876
30019
|
args.slice(1),
|
|
29877
30020
|
{ env, maxBuffer: 1 << 26, encoding: "utf8", timeout: 6e4 },
|
|
29878
30021
|
(err, stdout, stderr) => {
|
|
29879
30022
|
const e = err;
|
|
29880
30023
|
if (e && (e.code === "ENOENT" || e.code === "EACCES")) return reject2(e);
|
|
29881
|
-
|
|
30024
|
+
resolve17({ code: err ? Number(err.code ?? 1) : 0, stdout, stderr });
|
|
29882
30025
|
}
|
|
29883
30026
|
);
|
|
29884
30027
|
});
|
|
@@ -30295,7 +30438,7 @@ async function testTelegramConnection(deps = {}) {
|
|
|
30295
30438
|
}
|
|
30296
30439
|
const timeoutMs = deps.timeoutMs ?? 1e4;
|
|
30297
30440
|
const controller = new AbortController();
|
|
30298
|
-
const
|
|
30441
|
+
const timer8 = setTimeout(() => controller.abort(), timeoutMs);
|
|
30299
30442
|
const send = deps.transport ?? ((url2, init) => fetch(url2, init));
|
|
30300
30443
|
const transport = (url2, init) => send(url2, { ...init, signal: controller.signal });
|
|
30301
30444
|
const client = new TelegramApiClient(botToken, transport);
|
|
@@ -30309,7 +30452,7 @@ async function testTelegramConnection(deps = {}) {
|
|
|
30309
30452
|
const message = controller.signal.aborted ? `Timeout ${timeoutMs} ms \u2014 Telegram tidak menjawab.` : raw;
|
|
30310
30453
|
return { ok: false, inbound, error: sanitizeTelegramOutput(message, [botToken]).slice(0, 500) };
|
|
30311
30454
|
} finally {
|
|
30312
|
-
clearTimeout(
|
|
30455
|
+
clearTimeout(timer8);
|
|
30313
30456
|
}
|
|
30314
30457
|
}
|
|
30315
30458
|
function resolveTestChatId() {
|
|
@@ -30464,7 +30607,7 @@ function matchingEndpoints(eventType, projectId) {
|
|
|
30464
30607
|
var newSecret = () => randomBytes6(32).toString("base64url");
|
|
30465
30608
|
var encryptEndpointSecret = (plain) => encryptSecret(plain);
|
|
30466
30609
|
var secretOf = (row) => decryptSecret(row.secret);
|
|
30467
|
-
function endpointView(r,
|
|
30610
|
+
function endpointView(r, pending2, plainSecret) {
|
|
30468
30611
|
const n2 = normalize(r);
|
|
30469
30612
|
const plain = plainSecret ?? secretOf(r);
|
|
30470
30613
|
return {
|
|
@@ -30485,7 +30628,7 @@ function endpointView(r, pending, plainSecret) {
|
|
|
30485
30628
|
lastSuccessAt: n2.lastSuccessAt?.toISOString() ?? null,
|
|
30486
30629
|
lastFailureAt: n2.lastFailureAt?.toISOString() ?? null,
|
|
30487
30630
|
failureStreak: n2.failureStreak,
|
|
30488
|
-
pending,
|
|
30631
|
+
pending: pending2,
|
|
30489
30632
|
createdAt: n2.createdAt.toISOString(),
|
|
30490
30633
|
updatedAt: n2.updatedAt.toISOString(),
|
|
30491
30634
|
...plainSecret ? { secret: plainSecret } : {}
|
|
@@ -30615,7 +30758,7 @@ async function sendOnce(o, deps = {}) {
|
|
|
30615
30758
|
nowSec: o.nowSec ?? Math.floor(Date.now() / 1e3)
|
|
30616
30759
|
});
|
|
30617
30760
|
const ac = new AbortController();
|
|
30618
|
-
const
|
|
30761
|
+
const timer8 = setTimeout(() => ac.abort(), WEBHOOK_TIMEOUT_MS);
|
|
30619
30762
|
try {
|
|
30620
30763
|
const f = deps.fetcher ?? ((u, init) => fetch(u, init));
|
|
30621
30764
|
const res = await f(parsed.url.toString(), { method: "POST", headers, body: o.body, signal: ac.signal });
|
|
@@ -30629,7 +30772,7 @@ async function sendOnce(o, deps = {}) {
|
|
|
30629
30772
|
const msg = e.name === "AbortError" ? `timeout ${WEBHOOK_TIMEOUT_MS} ms` : e.message;
|
|
30630
30773
|
return fail2(msg);
|
|
30631
30774
|
} finally {
|
|
30632
|
-
clearTimeout(
|
|
30775
|
+
clearTimeout(timer8);
|
|
30633
30776
|
}
|
|
30634
30777
|
}
|
|
30635
30778
|
|
|
@@ -30953,8 +31096,8 @@ init_db();
|
|
|
30953
31096
|
// src/services/auth.ts
|
|
30954
31097
|
init_db();
|
|
30955
31098
|
import { randomBytes as randomBytes7, scrypt as scryptCb, timingSafeEqual as timingSafeEqual2, createHash as createHash7 } from "node:crypto";
|
|
30956
|
-
import { promisify as
|
|
30957
|
-
var scrypt =
|
|
31099
|
+
import { promisify as promisify11 } from "node:util";
|
|
31100
|
+
var scrypt = promisify11(scryptCb);
|
|
30958
31101
|
var COOKIE_NAME = "hn_session";
|
|
30959
31102
|
var SESSION_TTL_MS = 7 * 24 * 3600 * 1e3;
|
|
30960
31103
|
async function hashPassword(pw) {
|
|
@@ -31417,7 +31560,7 @@ var PUBLIC = /* @__PURE__ */ new Set([
|
|
|
31417
31560
|
"GET /api/agent-integration.md"
|
|
31418
31561
|
]);
|
|
31419
31562
|
function buildApp({ requireAuth = true, agentDocFile } = {}) {
|
|
31420
|
-
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname14(fileURLToPath4(import.meta.url)), process.env,
|
|
31563
|
+
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname14(fileURLToPath4(import.meta.url)), process.env, existsSync15);
|
|
31421
31564
|
const app2 = Fastify({ logger: false, trustProxy: true });
|
|
31422
31565
|
app2.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => {
|
|
31423
31566
|
if (!body) return done(null, void 0);
|
|
@@ -31508,7 +31651,7 @@ function buildApp({ requireAuth = true, agentDocFile } = {}) {
|
|
|
31508
31651
|
await api.register(client_accounts_default);
|
|
31509
31652
|
}, { prefix: "/api" });
|
|
31510
31653
|
if (process.env.NODE_ENV === "production") {
|
|
31511
|
-
const dist = pickWebDir(dirname14(fileURLToPath4(import.meta.url)), process.env,
|
|
31654
|
+
const dist = pickWebDir(dirname14(fileURLToPath4(import.meta.url)), process.env, existsSync15);
|
|
31512
31655
|
if (dist) {
|
|
31513
31656
|
app2.register(fastifyStatic, { root: dist });
|
|
31514
31657
|
app2.setNotFoundHandler((req, reply) => req.url.startsWith("/api") ? reply.code(404).send({ error: "not found" }) : reply.sendFile("index.html"));
|
|
@@ -31826,12 +31969,12 @@ var prodDeps = {
|
|
|
31826
31969
|
launchCron: async (cron) => (await startCronSession(cron)).id
|
|
31827
31970
|
})
|
|
31828
31971
|
};
|
|
31829
|
-
var
|
|
31830
|
-
var
|
|
31972
|
+
var TICK_MS3 = 1e4;
|
|
31973
|
+
var timer5;
|
|
31831
31974
|
function startScheduler(deps = prodDeps) {
|
|
31832
|
-
if (
|
|
31833
|
-
|
|
31834
|
-
|
|
31975
|
+
if (timer5) return;
|
|
31976
|
+
timer5 = setInterval(() => void tick3(Date.now(), deps), TICK_MS3);
|
|
31977
|
+
timer5.unref();
|
|
31835
31978
|
void tick3(Date.now(), deps);
|
|
31836
31979
|
}
|
|
31837
31980
|
|
|
@@ -31928,10 +32071,10 @@ function buildEnvelope(i, projectName, nowIso2, eventId) {
|
|
|
31928
32071
|
}
|
|
31929
32072
|
async function enqueueEnvelope(env, endpoints) {
|
|
31930
32073
|
for (const e of endpoints) {
|
|
31931
|
-
const
|
|
32074
|
+
const pending2 = await prisma.webhookDelivery.count({
|
|
31932
32075
|
where: { endpointId: e.id, status: { in: ["pending", "sending"] } }
|
|
31933
32076
|
});
|
|
31934
|
-
const full =
|
|
32077
|
+
const full = pending2 >= WEBHOOK_QUEUE_CAP;
|
|
31935
32078
|
await prisma.webhookDelivery.create({
|
|
31936
32079
|
data: {
|
|
31937
32080
|
endpointId: e.id,
|
|
@@ -31979,7 +32122,7 @@ async function installWebhooks() {
|
|
|
31979
32122
|
// src/services/webhooks/engine.ts
|
|
31980
32123
|
init_src();
|
|
31981
32124
|
init_db();
|
|
31982
|
-
var
|
|
32125
|
+
var TICK_MS4 = 2e3;
|
|
31983
32126
|
var MAX_IN_FLIGHT = 4;
|
|
31984
32127
|
var PRUNE_EVERY_TICKS = 30;
|
|
31985
32128
|
function backoffAt(from, attempt) {
|
|
@@ -32122,7 +32265,7 @@ async function pruneHistory() {
|
|
|
32122
32265
|
}
|
|
32123
32266
|
return removed;
|
|
32124
32267
|
}
|
|
32125
|
-
var
|
|
32268
|
+
var timer6;
|
|
32126
32269
|
var ticks = 0;
|
|
32127
32270
|
var busy2 = false;
|
|
32128
32271
|
async function tick4() {
|
|
@@ -32138,9 +32281,9 @@ async function tick4() {
|
|
|
32138
32281
|
}
|
|
32139
32282
|
}
|
|
32140
32283
|
function startWebhookEngine() {
|
|
32141
|
-
if (
|
|
32142
|
-
|
|
32143
|
-
|
|
32284
|
+
if (timer6) return;
|
|
32285
|
+
timer6 = setInterval(() => void tick4(), TICK_MS4);
|
|
32286
|
+
timer6.unref();
|
|
32144
32287
|
void resetStuckDeliveries().then((n2) => {
|
|
32145
32288
|
if (n2) console.log(`webhook: ${n2} pengiriman tertinggal dikembalikan ke antrean`);
|
|
32146
32289
|
}).catch((e) => console.error("webhook reset:", e));
|
|
@@ -32149,16 +32292,16 @@ function startWebhookEngine() {
|
|
|
32149
32292
|
// src/services/auto-merge.ts
|
|
32150
32293
|
init_src();
|
|
32151
32294
|
init_db();
|
|
32152
|
-
import { execFile as
|
|
32153
|
-
import { promisify as
|
|
32295
|
+
import { execFile as execFile13 } from "node:child_process";
|
|
32296
|
+
import { promisify as promisify12 } from "node:util";
|
|
32154
32297
|
var AUTO_MERGE_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
32155
32298
|
var AUTO_MERGE_GRACE_MS = 15 * 60 * 1e3;
|
|
32156
|
-
var
|
|
32157
|
-
var
|
|
32299
|
+
var TICK_MS5 = 6e4;
|
|
32300
|
+
var exec10 = promisify12(execFile13);
|
|
32158
32301
|
var GIT8 = { timeout: 3e4, maxBuffer: 1 << 24, encoding: "utf8" };
|
|
32159
32302
|
var gitOut = async (cwd, args) => {
|
|
32160
32303
|
try {
|
|
32161
|
-
return (await
|
|
32304
|
+
return (await exec10("git", args, { cwd, ...GIT8 })).stdout.trim();
|
|
32162
32305
|
} catch {
|
|
32163
32306
|
return null;
|
|
32164
32307
|
}
|
|
@@ -32241,7 +32384,7 @@ async function settleOne(c, deps, now) {
|
|
|
32241
32384
|
}
|
|
32242
32385
|
return report(`Auto-merge ${spec.id} \u2192 ${target2} GAGAL: ${res.error}`);
|
|
32243
32386
|
}
|
|
32244
|
-
var
|
|
32387
|
+
var timer7;
|
|
32245
32388
|
var busy3 = false;
|
|
32246
32389
|
async function tick5() {
|
|
32247
32390
|
if (busy3) return;
|
|
@@ -32255,9 +32398,9 @@ async function tick5() {
|
|
|
32255
32398
|
}
|
|
32256
32399
|
}
|
|
32257
32400
|
function startAutoMerge() {
|
|
32258
|
-
if (
|
|
32259
|
-
|
|
32260
|
-
|
|
32401
|
+
if (timer7) return;
|
|
32402
|
+
timer7 = setInterval(() => void tick5(), TICK_MS5);
|
|
32403
|
+
timer7.unref();
|
|
32261
32404
|
}
|
|
32262
32405
|
|
|
32263
32406
|
// src/server.ts
|
|
@@ -32307,6 +32450,7 @@ app.listen({ port, host }).then(async () => {
|
|
|
32307
32450
|
startLead();
|
|
32308
32451
|
startWebhookEngine();
|
|
32309
32452
|
startAutoMerge();
|
|
32453
|
+
startWorktreeReaper();
|
|
32310
32454
|
}).catch((err) => {
|
|
32311
32455
|
console.error("listen gagal:", err);
|
|
32312
32456
|
process.exit(1);
|