hanoman 0.9.26 → 0.9.28
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 +86 -0
- package/dist/server.js +677 -207
- package/docs/agent-integration.md +1 -0
- package/package.json +1 -1
- package/web/assets/{DalangHanomanScreen-BMQjWbwC.js → DalangHanomanScreen-C3UMPHFg.js} +1 -1
- package/web/assets/{DocPreviewModal-Vysb114M.js → DocPreviewModal-Bp5stb4M.js} +1 -1
- package/web/assets/{DocsWorkspace-Bo2dJRqt.js → DocsWorkspace-C9yFxK4D.js} +1 -1
- package/web/assets/{IdeScreen-1V2L_82g.js → IdeScreen-CRFRAP6E.js} +1 -1
- package/web/assets/{LeadScreen-D7njTsNH.js → LeadScreen-DfVUawoK.js} +1 -1
- package/web/assets/{ReviewScreen-BytyE-o3.js → ReviewScreen-CMHOlmgX.js} +1 -1
- package/web/assets/{SchedulerScreen-b4hvb1Sy.js → SchedulerScreen-CK5QvX5c.js} +1 -1
- package/web/assets/{SettingsScreen-C9YeMz1R.js → SettingsScreen-D7P5JgjU.js} +1 -1
- package/web/assets/{TeamScreen-WvQNIYf0.js → TeamScreen-D4tA34Mj.js} +1 -1
- package/web/assets/TerminalScreen-BjFMJnq6.css +1 -0
- package/web/assets/TerminalScreen-CKySL63X.js +2 -0
- package/web/assets/{TriageScreen-CfzqvbI2.js → TriageScreen-B8ZCexaX.js} +1 -1
- package/web/assets/{VpsScreen-XWHjdq4i.js → VpsScreen-BCrLfZh7.js} +1 -1
- package/web/assets/{index-BvFCEJud.js → index-BRR3JL_o.js} +210 -210
- package/web/assets/{index-B8xChq6y.css → index-BqAP-VDq.css} +1 -1
- package/web/assets/{live-B2RFSX3l.js → live-DuBdI0MZ.js} +1 -1
- package/web/assets/{sched-lead-shared-0mpQqjza.js → sched-lead-shared-QqbtjoVv.js} +1 -1
- package/web/assets/{settings-rebase-BVFljdPX.js → settings-rebase-Cbh-JGJ-.js} +1 -1
- package/web/index.html +2 -2
- package/web/assets/TerminalScreen-BYx1OK27.js +0 -2
- package/web/assets/TerminalScreen-D0NdFmR-.css +0 -1
package/dist/server.js
CHANGED
|
@@ -8381,6 +8381,8 @@ var init_api = __esm({
|
|
|
8381
8381
|
// SPEC-199 · WebSocket siar dashboard (global, bukan per-sesi)
|
|
8382
8382
|
// SPEC-919 · ADR-0147 · muat awal + fallback halaman Klien. Selama WS sehat tak ada yang men-poll-nya.
|
|
8383
8383
|
presence: `${API}/presence`,
|
|
8384
|
+
// ADR-0220 · muat awal agen/MCP untuk agregat Overview; dashboard menerimanya lewat grup siar `overview`.
|
|
8385
|
+
overview: `${API}/overview`,
|
|
8384
8386
|
wsTickets: `${API}/ws-tickets`,
|
|
8385
8387
|
vps: `${API}/vps`,
|
|
8386
8388
|
vpsOne: (id2) => `${API}/vps/${id2}`,
|
|
@@ -8721,12 +8723,88 @@ var init_session_kind = __esm({
|
|
|
8721
8723
|
});
|
|
8722
8724
|
|
|
8723
8725
|
// ../shared/src/session-end.ts
|
|
8726
|
+
function sessionOutcome(r) {
|
|
8727
|
+
if (!r.endedAt) return "running";
|
|
8728
|
+
if (r.endedReason === "reconciled") return "interrupted";
|
|
8729
|
+
return r.exitCode === null || r.exitCode === 0 ? "completed" : "failed";
|
|
8730
|
+
}
|
|
8724
8731
|
var init_session_end = __esm({
|
|
8725
8732
|
"../shared/src/session-end.ts"() {
|
|
8726
8733
|
"use strict";
|
|
8727
8734
|
}
|
|
8728
8735
|
});
|
|
8729
8736
|
|
|
8737
|
+
// ../shared/src/overview.ts
|
|
8738
|
+
var zHeadlinePart, zOverviewDay, zOverviewRiver, zOverviewTokens, zOverviewWait, BENSIN_VERDICTS, zOverviewBensin, zOverviewHeatmap, zOverviewRecords, zOverview;
|
|
8739
|
+
var init_overview = __esm({
|
|
8740
|
+
"../shared/src/overview.ts"() {
|
|
8741
|
+
"use strict";
|
|
8742
|
+
init_zod();
|
|
8743
|
+
zHeadlinePart = external_exports.object({ text: external_exports.string(), tone: external_exports.enum(["bad", "good"]).optional() });
|
|
8744
|
+
zOverviewDay = external_exports.object({ landed: external_exports.number().int(), failed: external_exports.number().int() });
|
|
8745
|
+
zOverviewRiver = external_exports.object({
|
|
8746
|
+
created: external_exports.number().int(),
|
|
8747
|
+
started: external_exports.number().int(),
|
|
8748
|
+
landed: external_exports.number().int(),
|
|
8749
|
+
// Median 7 hari terakhir atas spec yang selesai; null bila tak ada.
|
|
8750
|
+
leadTimeMedMs: external_exports.number().nullable(),
|
|
8751
|
+
cycleTimeMedMs: external_exports.number().nullable()
|
|
8752
|
+
});
|
|
8753
|
+
zOverviewTokens = external_exports.object({
|
|
8754
|
+
input: external_exports.number(),
|
|
8755
|
+
output: external_exports.number(),
|
|
8756
|
+
cached: external_exports.number(),
|
|
8757
|
+
total: external_exports.number(),
|
|
8758
|
+
cacheHitPct: external_exports.number().nullable(),
|
|
8759
|
+
perLanded: external_exports.number().nullable(),
|
|
8760
|
+
topAgent: external_exports.object({ name: external_exports.string(), total: external_exports.number() }).nullable(),
|
|
8761
|
+
yesterdayTotal: external_exports.number()
|
|
8762
|
+
});
|
|
8763
|
+
zOverviewWait = external_exports.object({
|
|
8764
|
+
todayMin: external_exports.number(),
|
|
8765
|
+
yesterdayMin: external_exports.number(),
|
|
8766
|
+
pctOfLive: external_exports.number().nullable()
|
|
8767
|
+
});
|
|
8768
|
+
BENSIN_VERDICTS = ["habis", "aman", "stabil", "kurang-data"];
|
|
8769
|
+
zOverviewBensin = external_exports.object({
|
|
8770
|
+
source: external_exports.enum(["claude", "codex"]),
|
|
8771
|
+
key: external_exports.string(),
|
|
8772
|
+
label: external_exports.string(),
|
|
8773
|
+
usedPct: external_exports.number(),
|
|
8774
|
+
resetsAt: external_exports.string().nullable(),
|
|
8775
|
+
exhaustAt: external_exports.string().nullable(),
|
|
8776
|
+
verdict: external_exports.enum(BENSIN_VERDICTS),
|
|
8777
|
+
samples: external_exports.array(external_exports.object({ at: external_exports.string(), pct: external_exports.number() }))
|
|
8778
|
+
});
|
|
8779
|
+
zOverviewHeatmap = external_exports.object({
|
|
8780
|
+
days: external_exports.array(external_exports.string()),
|
|
8781
|
+
// YYYY-MM-DD lokal, terlama dulu
|
|
8782
|
+
minutes: external_exports.array(external_exports.array(external_exports.number()))
|
|
8783
|
+
// [hari][jam 0..23] = menit sesi agen hidup
|
|
8784
|
+
});
|
|
8785
|
+
zOverviewRecords = external_exports.object({
|
|
8786
|
+
streakDays: external_exports.number().int(),
|
|
8787
|
+
bestStreak: external_exports.number().int(),
|
|
8788
|
+
bestLandedDay: external_exports.object({ date: external_exports.string(), count: external_exports.number().int() }).nullable(),
|
|
8789
|
+
landedRecordToday: external_exports.boolean()
|
|
8790
|
+
});
|
|
8791
|
+
zOverview = external_exports.object({
|
|
8792
|
+
generatedAt: external_exports.string(),
|
|
8793
|
+
recordingSince: external_exports.string(),
|
|
8794
|
+
// awal perekam di memori (menunggu & bensin); restart mengulangnya
|
|
8795
|
+
today: zOverviewDay.nullable(),
|
|
8796
|
+
yesterday: zOverviewDay.nullable(),
|
|
8797
|
+
river: zOverviewRiver.nullable(),
|
|
8798
|
+
tokens: zOverviewTokens.nullable(),
|
|
8799
|
+
wait: zOverviewWait.nullable(),
|
|
8800
|
+
bensin: external_exports.array(zOverviewBensin).nullable(),
|
|
8801
|
+
heatmap: zOverviewHeatmap.nullable(),
|
|
8802
|
+
records: zOverviewRecords.nullable(),
|
|
8803
|
+
headline: external_exports.object({ text: external_exports.string(), parts: external_exports.array(zHeadlinePart) })
|
|
8804
|
+
});
|
|
8805
|
+
}
|
|
8806
|
+
});
|
|
8807
|
+
|
|
8730
8808
|
// ../shared/src/config.ts
|
|
8731
8809
|
var zHanomanConfig;
|
|
8732
8810
|
var init_config = __esm({
|
|
@@ -14003,6 +14081,18 @@ var init_system = __esm({
|
|
|
14003
14081
|
build: () => ({ method: "GET", path: "/limits/codex" }),
|
|
14004
14082
|
shape: (raw) => raw
|
|
14005
14083
|
},
|
|
14084
|
+
{
|
|
14085
|
+
name: "hanoman_overview_get",
|
|
14086
|
+
title: "Ringkasan kinerja hari ini (Overview)",
|
|
14087
|
+
description: 'Agregat kinerja workspace hari ini vs kemarin, persis yang tampil di layar Overview "Koran Pagi": spec mendarat, sesi agen tumbang, menit agen menunggu jawaban manusia, token agen tercatat (hanya subagen/agen fase), prakiraan kapan limit habis dibanding reset, peta panas jam kerja agen 7\xD724, streak/rekor, dan headline-nya. Menit menunggu & prakiraan limit direkam di memori server dan hilang saat restart (`recordingSince`). Panel `null` = sumbernya gagal dibaca. Tak menuntut capability apa pun.',
|
|
14088
|
+
inputSchema: obj({ properties: {} }),
|
|
14089
|
+
mode: "read",
|
|
14090
|
+
capability: null,
|
|
14091
|
+
samplePath: "/overview",
|
|
14092
|
+
sampleMethod: "GET",
|
|
14093
|
+
build: () => ({ method: "GET", path: "/overview" }),
|
|
14094
|
+
shape: (raw) => raw
|
|
14095
|
+
},
|
|
14006
14096
|
{
|
|
14007
14097
|
name: "hanoman_update_status",
|
|
14008
14098
|
title: "Status versi & pembaruan",
|
|
@@ -16543,6 +16633,7 @@ var init_src = __esm({
|
|
|
16543
16633
|
init_session_kind();
|
|
16544
16634
|
init_session_admission();
|
|
16545
16635
|
init_session_end();
|
|
16636
|
+
init_overview();
|
|
16546
16637
|
init_config();
|
|
16547
16638
|
init_config_registry();
|
|
16548
16639
|
init_semver();
|
|
@@ -17427,7 +17518,7 @@ var init_settings2 = __esm({
|
|
|
17427
17518
|
const f = `'${decisionFile.split("'").join("'\\''")}'`;
|
|
17428
17519
|
hooks2.Notification = [{ hooks: [{
|
|
17429
17520
|
type: "command",
|
|
17430
|
-
command: `grep -qiE 'idle|permission|waiting for|needs.?input' &&
|
|
17521
|
+
command: `grep -qiE 'idle|permission|waiting for|needs.?input' && date +%s > ${f} || true`
|
|
17431
17522
|
}] }];
|
|
17432
17523
|
hooks2.UserPromptSubmit = [{ hooks: [{ type: "command", command: `: > ${f}` }] }];
|
|
17433
17524
|
}
|
|
@@ -17444,7 +17535,7 @@ var init_settings2 = __esm({
|
|
|
17444
17535
|
(hooks2.Stop ??= []).push({ hooks: [{ type: "command", command: `${busySnapshotCommand(sub)}; exit 0` }] });
|
|
17445
17536
|
hooks2.Notification = [{ hooks: [{
|
|
17446
17537
|
type: "command",
|
|
17447
|
-
command: `m=$(cat); if [ -s ${sub} ]; then echo "$m" | grep -qiE 'permission|needs.?input'; else echo "$m" | grep -qiE 'idle|permission|waiting for|needs.?input'; fi &&
|
|
17538
|
+
command: `m=$(cat); if [ -s ${sub} ]; then echo "$m" | grep -qiE 'permission|needs.?input'; else echo "$m" | grep -qiE 'idle|permission|waiting for|needs.?input'; fi && date +%s > ${f} || true`
|
|
17448
17539
|
}] }];
|
|
17449
17540
|
hooks2.UserPromptSubmit = [{ hooks: [{ type: "command", command: `: > ${f}; : > ${sub}` }] }];
|
|
17450
17541
|
}
|
|
@@ -17522,7 +17613,7 @@ function codexHookArgs(o) {
|
|
|
17522
17613
|
const stop = [];
|
|
17523
17614
|
const submit = [];
|
|
17524
17615
|
if (o.decisionFile) {
|
|
17525
|
-
stop.push(`
|
|
17616
|
+
stop.push(`date +%s > ${shq(o.decisionFile)}`);
|
|
17526
17617
|
submit.push(`: > ${shq(o.decisionFile)}`);
|
|
17527
17618
|
}
|
|
17528
17619
|
if (o.eventHook) stop.push(EVENT_HOOK_COMMAND);
|
|
@@ -20996,7 +21087,7 @@ var require_timers = __commonJS({
|
|
|
20996
21087
|
"use strict";
|
|
20997
21088
|
var fastNow = 0;
|
|
20998
21089
|
var RESOLUTION_MS = 1e3;
|
|
20999
|
-
var
|
|
21090
|
+
var TICK_MS6 = (RESOLUTION_MS >> 1) - 1;
|
|
21000
21091
|
var fastNowTimeout;
|
|
21001
21092
|
var kFastTimer = Symbol("kFastTimer");
|
|
21002
21093
|
var fastTimers = [];
|
|
@@ -21005,21 +21096,21 @@ var require_timers = __commonJS({
|
|
|
21005
21096
|
var PENDING = 0;
|
|
21006
21097
|
var ACTIVE = 1;
|
|
21007
21098
|
function onTick() {
|
|
21008
|
-
fastNow +=
|
|
21099
|
+
fastNow += TICK_MS6;
|
|
21009
21100
|
let idx = 0;
|
|
21010
21101
|
let len = fastTimers.length;
|
|
21011
21102
|
while (idx < len) {
|
|
21012
|
-
const
|
|
21013
|
-
if (
|
|
21014
|
-
|
|
21015
|
-
|
|
21016
|
-
} else if (
|
|
21017
|
-
|
|
21018
|
-
|
|
21019
|
-
|
|
21020
|
-
}
|
|
21021
|
-
if (
|
|
21022
|
-
|
|
21103
|
+
const timer11 = fastTimers[idx];
|
|
21104
|
+
if (timer11._state === PENDING) {
|
|
21105
|
+
timer11._idleStart = fastNow - TICK_MS6;
|
|
21106
|
+
timer11._state = ACTIVE;
|
|
21107
|
+
} else if (timer11._state === ACTIVE && fastNow >= timer11._idleStart + timer11._idleTimeout) {
|
|
21108
|
+
timer11._state = TO_BE_CLEARED;
|
|
21109
|
+
timer11._idleStart = -1;
|
|
21110
|
+
timer11._onTimeout(timer11._timerArg);
|
|
21111
|
+
}
|
|
21112
|
+
if (timer11._state === TO_BE_CLEARED) {
|
|
21113
|
+
timer11._state = NOT_IN_LIST;
|
|
21023
21114
|
if (--len !== 0) {
|
|
21024
21115
|
fastTimers[idx] = fastTimers[len];
|
|
21025
21116
|
}
|
|
@@ -21037,7 +21128,7 @@ var require_timers = __commonJS({
|
|
|
21037
21128
|
fastNowTimeout.refresh();
|
|
21038
21129
|
} else {
|
|
21039
21130
|
clearTimeout(fastNowTimeout);
|
|
21040
|
-
fastNowTimeout = setTimeout(onTick,
|
|
21131
|
+
fastNowTimeout = setTimeout(onTick, TICK_MS6);
|
|
21041
21132
|
fastNowTimeout?.unref();
|
|
21042
21133
|
}
|
|
21043
21134
|
}
|
|
@@ -33764,22 +33855,22 @@ var require_mock_utils = __commonJS({
|
|
|
33764
33855
|
return true;
|
|
33765
33856
|
}
|
|
33766
33857
|
let aborted = false;
|
|
33767
|
-
let
|
|
33858
|
+
let timer11 = null;
|
|
33768
33859
|
function abort(err) {
|
|
33769
33860
|
if (aborted) {
|
|
33770
33861
|
return;
|
|
33771
33862
|
}
|
|
33772
33863
|
aborted = true;
|
|
33773
|
-
if (
|
|
33774
|
-
clearTimeout(
|
|
33775
|
-
|
|
33864
|
+
if (timer11 !== null) {
|
|
33865
|
+
clearTimeout(timer11);
|
|
33866
|
+
timer11 = null;
|
|
33776
33867
|
}
|
|
33777
33868
|
handler.onError(err);
|
|
33778
33869
|
}
|
|
33779
33870
|
handler.onConnect?.(abort, null);
|
|
33780
33871
|
if (typeof delay2 === "number" && delay2 > 0) {
|
|
33781
|
-
|
|
33782
|
-
|
|
33872
|
+
timer11 = setTimeout(() => {
|
|
33873
|
+
timer11 = null;
|
|
33783
33874
|
handleReply(this[kDispatches]);
|
|
33784
33875
|
}, delay2);
|
|
33785
33876
|
} else {
|
|
@@ -46913,9 +47004,9 @@ async function pinnedRequest(input) {
|
|
|
46913
47004
|
});
|
|
46914
47005
|
response.on("error", reject2);
|
|
46915
47006
|
});
|
|
46916
|
-
const
|
|
46917
|
-
|
|
46918
|
-
req.once("close", () => clearTimeout(
|
|
47007
|
+
const timer11 = setTimeout(() => req.destroy(Object.assign(new Error("outbound timeout"), { name: "AbortError" })), input.totalMs);
|
|
47008
|
+
timer11.unref?.();
|
|
47009
|
+
req.once("close", () => clearTimeout(timer11));
|
|
46919
47010
|
req.once("error", reject2);
|
|
46920
47011
|
if (input.body) req.write(input.body);
|
|
46921
47012
|
req.end();
|
|
@@ -46923,13 +47014,13 @@ async function pinnedRequest(input) {
|
|
|
46923
47014
|
}
|
|
46924
47015
|
function withLookupTimeout(p3, ms) {
|
|
46925
47016
|
return new Promise((resolve24, reject2) => {
|
|
46926
|
-
const
|
|
46927
|
-
|
|
47017
|
+
const timer11 = setTimeout(() => reject2(Object.assign(new Error("outbound timeout"), { name: "AbortError" })), ms);
|
|
47018
|
+
timer11.unref?.();
|
|
46928
47019
|
p3.then((v) => {
|
|
46929
|
-
clearTimeout(
|
|
47020
|
+
clearTimeout(timer11);
|
|
46930
47021
|
resolve24(v);
|
|
46931
47022
|
}, (e) => {
|
|
46932
|
-
clearTimeout(
|
|
47023
|
+
clearTimeout(timer11);
|
|
46933
47024
|
reject2(e);
|
|
46934
47025
|
});
|
|
46935
47026
|
});
|
|
@@ -47041,19 +47132,19 @@ async function dropSessionUploads(sessionId2) {
|
|
|
47041
47132
|
});
|
|
47042
47133
|
}
|
|
47043
47134
|
async function readUpload(storageKey) {
|
|
47044
|
-
const
|
|
47045
|
-
return readFile2(join15(uploadDir(),
|
|
47135
|
+
const safe3 = storageKey.replace(/[/\\]/g, "");
|
|
47136
|
+
return readFile2(join15(uploadDir(), safe3));
|
|
47046
47137
|
}
|
|
47047
47138
|
async function readUploadOrFetch(storageKey) {
|
|
47048
|
-
const
|
|
47049
|
-
const target3 = join15(uploadDir(),
|
|
47139
|
+
const safe3 = storageKey.replace(/[/\\]/g, "");
|
|
47140
|
+
const target3 = join15(uploadDir(), safe3);
|
|
47050
47141
|
try {
|
|
47051
47142
|
return await readFile2(target3);
|
|
47052
47143
|
} catch {
|
|
47053
47144
|
const base3 = effectiveStr("SYNC_SERVER_URL");
|
|
47054
47145
|
const token = effectiveStr("SYNC_DEVICE_TOKEN");
|
|
47055
|
-
if (!base3 || !token) throw new Error(`lampiran ${
|
|
47056
|
-
const url2 = new URL(`/api/sync/attachments/${encodeURIComponent(
|
|
47146
|
+
if (!base3 || !token) throw new Error(`lampiran ${safe3} tak ada lokal & bukan client sync`);
|
|
47147
|
+
const url2 = new URL(`/api/sync/attachments/${encodeURIComponent(safe3)}`, `${base3.replace(/\/$/, "")}/`);
|
|
47057
47148
|
const loopback = ["localhost", "127.0.0.1", "::1"].includes(url2.hostname);
|
|
47058
47149
|
const res = await safeRequest({
|
|
47059
47150
|
url: url2,
|
|
@@ -47072,8 +47163,8 @@ async function readUploadOrFetch(storageKey) {
|
|
|
47072
47163
|
}
|
|
47073
47164
|
}
|
|
47074
47165
|
async function deleteUpload(storageKey) {
|
|
47075
|
-
const
|
|
47076
|
-
await unlink(join15(uploadDir(),
|
|
47166
|
+
const safe3 = storageKey.replace(/[/\\]/g, "");
|
|
47167
|
+
await unlink(join15(uploadDir(), safe3)).catch(() => {
|
|
47077
47168
|
});
|
|
47078
47169
|
}
|
|
47079
47170
|
var EXT, SESSION_ID;
|
|
@@ -47713,7 +47804,7 @@ function parsePanes(out4) {
|
|
|
47713
47804
|
// `UserPromptSubmit`, jadi ia tetap terisi sepanjang agen bekerja sesudah pertanyaannya
|
|
47714
47805
|
// dijawab lewat jalur lain (dialog di TUI, route SPEC-899, Esc, codex yang melanjutkan
|
|
47715
47806
|
// sendiri). Pane yang masih mengeluarkan sesuatu berarti agen sudah lanjut bekerja.
|
|
47716
|
-
decision: !exited && !!decisionFile && markerFilled(decisionFile) && paneQuiet(activityAt),
|
|
47807
|
+
decision: !exited && !!decisionFile && markerFilled(decisionFile) && paneQuiet(activityAt) && markerCurrent(decisionFile, activityAt),
|
|
47717
47808
|
// SPEC-338 · sesi yang lahir sebelum ADR-0074 tak punya opsi ini → claude.
|
|
47718
47809
|
agent: agent === "codex" ? "codex" : "claude",
|
|
47719
47810
|
altScreen: alternate === "1",
|
|
@@ -48459,7 +48550,7 @@ function spawnPty(...args) {
|
|
|
48459
48550
|
);
|
|
48460
48551
|
}
|
|
48461
48552
|
}
|
|
48462
|
-
var socket, PREFIX, MAX_SCROLLBACK, SCROLLBACK_SLACK, POLL_MS, markerFilled, markerFilledAsync, clearMarker, PANE_QUIET_MS, paneQuiet, markerOnset, decisionOnset, attached, phaseInvocations, PHASE_DONE_SEEN_OPT, phaseDoneSeen, claudeBin, shellBin, codexBin, agentBin, rootBypassEnv, frame, name, promptFilePath, goalGatePath, goalStatePath, agentTempDir, agentsFilePath, phaseContextFilePath, AGENTS_ARG_SAFE_BYTES, agentContractFilePath, askpassDenyPath, ASKPASS_DENY, SUBAGENT_CONCURRENCY_CAP, NO_SERVER, TmuxError, sq, idFor, FMT, toSessionInfo, listSessions, listSessionsAsync, decisionsOf, getSession, getSessionAsync, hooks, emitBirth, emitDeath, customAgentSource, codexNativeAgentSupport, nativeAgentsAvailable, customAgentsFor, runGitDiff, sleep, paneText, paneIO, DIALOG_CAPTURE_LINES, GOAL_ARMED_MARKERS, goalArmed, COALESCE_MS, COALESCE_MAX_BYTES, trimScrollback, phaseKey, sessionFinishedAsync, poll, polling, TERMINAL_QUERY, stripTerminalQueries, CAPTURE_ARGS, detach;
|
|
48553
|
+
var socket, PREFIX, MAX_SCROLLBACK, SCROLLBACK_SLACK, POLL_MS, markerFilled, markerFilledAsync, clearMarker, PANE_QUIET_MS, paneQuiet, MARKER_STALE_SLACK_S, markerOnset, markerCurrent, decisionOnset, attached, phaseInvocations, PHASE_DONE_SEEN_OPT, phaseDoneSeen, claudeBin, shellBin, codexBin, agentBin, rootBypassEnv, frame, name, promptFilePath, goalGatePath, goalStatePath, agentTempDir, agentsFilePath, phaseContextFilePath, AGENTS_ARG_SAFE_BYTES, agentContractFilePath, askpassDenyPath, ASKPASS_DENY, SUBAGENT_CONCURRENCY_CAP, NO_SERVER, TmuxError, sq, idFor, FMT, toSessionInfo, listSessions, listSessionsAsync, decisionsOf, getSession, getSessionAsync, hooks, emitBirth, emitDeath, customAgentSource, codexNativeAgentSupport, nativeAgentsAvailable, customAgentsFor, runGitDiff, sleep, paneText, paneIO, DIALOG_CAPTURE_LINES, GOAL_ARMED_MARKERS, goalArmed, COALESCE_MS, COALESCE_MAX_BYTES, trimScrollback, phaseKey, sessionFinishedAsync, poll, polling, TERMINAL_QUERY, stripTerminalQueries, CAPTURE_ARGS, detach;
|
|
48463
48554
|
var init_pty = __esm({
|
|
48464
48555
|
"src/services/pty.ts"() {
|
|
48465
48556
|
"use strict";
|
|
@@ -48505,6 +48596,7 @@ var init_pty = __esm({
|
|
|
48505
48596
|
};
|
|
48506
48597
|
PANE_QUIET_MS = 3e3;
|
|
48507
48598
|
paneQuiet = (activityAt, now = Date.now()) => !(activityAt > 0) || now - activityAt * 1e3 >= PANE_QUIET_MS;
|
|
48599
|
+
MARKER_STALE_SLACK_S = 10;
|
|
48508
48600
|
markerOnset = (f) => {
|
|
48509
48601
|
let raw;
|
|
48510
48602
|
try {
|
|
@@ -48515,6 +48607,10 @@ var init_pty = __esm({
|
|
|
48515
48607
|
const secs = Number(raw.trim());
|
|
48516
48608
|
return Number.isInteger(secs) && secs > 0 ? secs : 0;
|
|
48517
48609
|
};
|
|
48610
|
+
markerCurrent = (f, activityAt) => {
|
|
48611
|
+
const onset = markerOnset(f);
|
|
48612
|
+
return onset === 0 || !(activityAt > 0) || activityAt <= onset + MARKER_STALE_SLACK_S;
|
|
48613
|
+
};
|
|
48518
48614
|
decisionOnset = (f, activityAt) => {
|
|
48519
48615
|
const secs = Math.max(markerOnset(f), activityAt > 0 ? Math.floor(activityAt) : 0);
|
|
48520
48616
|
return secs > 0 ? new Date(secs * 1e3).toISOString() : void 0;
|
|
@@ -54168,9 +54264,9 @@ function recordBytes(rec) {
|
|
|
54168
54264
|
return Buffer.byteLength(JSON.stringify(rec));
|
|
54169
54265
|
}
|
|
54170
54266
|
async function pull(sinceCursor, limit = 500, maxBytes = PULL_MAX_BYTES, opts2 = {}) {
|
|
54171
|
-
const
|
|
54267
|
+
const since2 = Number(sinceCursor || "0");
|
|
54172
54268
|
const rows = await prisma.syncLog.findMany({
|
|
54173
|
-
where: { seq: { gt:
|
|
54269
|
+
where: { seq: { gt: since2 } },
|
|
54174
54270
|
orderBy: { seq: "asc" },
|
|
54175
54271
|
take: limit
|
|
54176
54272
|
});
|
|
@@ -55361,12 +55457,12 @@ function requestRelay(deviceId, r, opts2 = {}) {
|
|
|
55361
55457
|
return Promise.reject(new RelayError("too-large"));
|
|
55362
55458
|
const id2 = `r${++counter}`;
|
|
55363
55459
|
return new Promise((resolve24, reject2) => {
|
|
55364
|
-
const
|
|
55460
|
+
const timer11 = setTimeout(() => {
|
|
55365
55461
|
sendCancel(link2, id2);
|
|
55366
55462
|
settle(link2, id2, new RelayError("timeout"));
|
|
55367
55463
|
}, opts2.timeoutMs ?? RELAY_REQ_TIMEOUT_MS);
|
|
55368
|
-
|
|
55369
|
-
link2.pending.set(id2, { resolve: resolve24, reject: reject2, timer:
|
|
55464
|
+
timer11.unref?.();
|
|
55465
|
+
link2.pending.set(id2, { resolve: resolve24, reject: reject2, timer: timer11, contentType: null, chunks: [], bytes: 0 });
|
|
55370
55466
|
try {
|
|
55371
55467
|
link2.socket.send(JSON.stringify({
|
|
55372
55468
|
t: "req",
|
|
@@ -56689,8 +56785,8 @@ var init_store = __esm({
|
|
|
56689
56785
|
async bindSession(chatId, sessionId2) {
|
|
56690
56786
|
await this.db.telegramChat.update({ where: { chatId }, data: { sessionId: sessionId2 } });
|
|
56691
56787
|
}
|
|
56692
|
-
async rateLimitExceeded(userId,
|
|
56693
|
-
return await this.db.telegramUpdate.count({ where: { userId, receivedAt: { gte:
|
|
56788
|
+
async rateLimitExceeded(userId, since2, limit) {
|
|
56789
|
+
return await this.db.telegramUpdate.count({ where: { userId, receivedAt: { gte: since2 } } }) > limit;
|
|
56694
56790
|
}
|
|
56695
56791
|
async markUpdateUncertain(updateId, reason) {
|
|
56696
56792
|
await this.db.telegramUpdate.updateMany({
|
|
@@ -56709,9 +56805,9 @@ var init_store = __esm({
|
|
|
56709
56805
|
* SELAMANYA, dan tanpa pagar ini gateway akan mengetik selamanya sekaligus mengunci long-poll
|
|
56710
56806
|
* di 4 detik selamanya.
|
|
56711
56807
|
*/
|
|
56712
|
-
async chatsAwaitingReply(
|
|
56808
|
+
async chatsAwaitingReply(since2) {
|
|
56713
56809
|
const pending2 = await this.db.telegramUpdate.findMany({
|
|
56714
|
-
where: { state: "dispatched", chatId: { not: null }, dispatchedAt: { gte:
|
|
56810
|
+
where: { state: "dispatched", chatId: { not: null }, dispatchedAt: { gte: since2 } },
|
|
56715
56811
|
select: { updateId: true, chatId: true },
|
|
56716
56812
|
orderBy: { updateId: "asc" }
|
|
56717
56813
|
});
|
|
@@ -57318,12 +57414,12 @@ function startPresenceSender(o) {
|
|
|
57318
57414
|
heartbeatMs: o.heartbeatMs
|
|
57319
57415
|
});
|
|
57320
57416
|
void sender.tick(Date.now());
|
|
57321
|
-
const
|
|
57417
|
+
const timer11 = setInterval(() => {
|
|
57322
57418
|
void sender.tick(Date.now());
|
|
57323
57419
|
}, o.tickMs ?? PRESENCE_TICK_MS);
|
|
57324
|
-
|
|
57420
|
+
timer11.unref?.();
|
|
57325
57421
|
return { stop() {
|
|
57326
|
-
clearInterval(
|
|
57422
|
+
clearInterval(timer11);
|
|
57327
57423
|
} };
|
|
57328
57424
|
}
|
|
57329
57425
|
var init_sender = __esm({
|
|
@@ -57374,7 +57470,7 @@ function capabilityForRoute(method, path) {
|
|
|
57374
57470
|
if (top === "devices") return "relay:exec";
|
|
57375
57471
|
if (top === "logs") return rw("logs");
|
|
57376
57472
|
if (top === "presence") return read2 ? "presence:read" : "COOKIE_ONLY";
|
|
57377
|
-
if (top === "limits" || top === "update" || top === "events" || top === "fs" || top === "health" || top === "models" || top === "methods" || top === "codex")
|
|
57473
|
+
if (top === "limits" || top === "update" || top === "events" || top === "fs" || top === "health" || top === "models" || top === "methods" || top === "codex" || top === "overview")
|
|
57378
57474
|
return read2 ? "GLOBAL_READ" : "COOKIE_ONLY";
|
|
57379
57475
|
if (top === "session-results") return rw("activity");
|
|
57380
57476
|
if (top === "portal-chat") return rw("portal-chat");
|
|
@@ -60991,7 +61087,7 @@ var require_stream = __commonJS({
|
|
|
60991
61087
|
};
|
|
60992
61088
|
duplex._final = function(callback) {
|
|
60993
61089
|
if (ws2.readyState === ws2.CONNECTING) {
|
|
60994
|
-
ws2.once("open", function
|
|
61090
|
+
ws2.once("open", function open6() {
|
|
60995
61091
|
duplex._final(callback);
|
|
60996
61092
|
});
|
|
60997
61093
|
return;
|
|
@@ -61012,7 +61108,7 @@ var require_stream = __commonJS({
|
|
|
61012
61108
|
};
|
|
61013
61109
|
duplex._write = function(chunk, encoding, callback) {
|
|
61014
61110
|
if (ws2.readyState === ws2.CONNECTING) {
|
|
61015
|
-
ws2.once("open", function
|
|
61111
|
+
ws2.once("open", function open6() {
|
|
61016
61112
|
duplex._write(chunk, encoding, callback);
|
|
61017
61113
|
});
|
|
61018
61114
|
return;
|
|
@@ -61744,14 +61840,14 @@ async function loadCursor(lane) {
|
|
|
61744
61840
|
const c = await prisma.logCursor.findUnique({ where: { deviceId_lane: { deviceId: LOCAL_DEVICE_ID, lane } } });
|
|
61745
61841
|
return c?.seq ?? 0n;
|
|
61746
61842
|
}
|
|
61747
|
-
async function nextBatch(lane,
|
|
61843
|
+
async function nextBatch(lane, since2) {
|
|
61748
61844
|
if (lane === "server") {
|
|
61749
61845
|
const segs = await readSpoolSegments("server");
|
|
61750
61846
|
const flat = segs.flatMap((s2) => s2.entries);
|
|
61751
61847
|
return flat.slice(0, LOG_BATCH_MAX_ENTRIES);
|
|
61752
61848
|
}
|
|
61753
61849
|
const rows = await prisma.logEntry.findMany({
|
|
61754
|
-
where: { deviceId: LOCAL_DEVICE_ID, lane, seq: { gt:
|
|
61850
|
+
where: { deviceId: LOCAL_DEVICE_ID, lane, seq: { gt: since2 } },
|
|
61755
61851
|
orderBy: { seq: "asc" },
|
|
61756
61852
|
take: LOG_BATCH_MAX_ENTRIES
|
|
61757
61853
|
});
|
|
@@ -61770,9 +61866,9 @@ async function nextBatch(lane, since) {
|
|
|
61770
61866
|
async function shipLane(lane, transport) {
|
|
61771
61867
|
const now = Date.now();
|
|
61772
61868
|
if ((pausedUntil.get(lane) ?? 0) > now) return;
|
|
61773
|
-
let
|
|
61869
|
+
let since2 = await loadCursor(lane);
|
|
61774
61870
|
for (let i = 0; i < LOG_SHIP_MAX_BATCHES_PER_TICK; i++) {
|
|
61775
|
-
const entries4 = await nextBatch(lane,
|
|
61871
|
+
const entries4 = await nextBatch(lane, since2);
|
|
61776
61872
|
if (entries4.length === 0) return;
|
|
61777
61873
|
const res = await transport("POST", "/api/sync/logs", { lane, entries: entries4 });
|
|
61778
61874
|
if (res.status === 200) {
|
|
@@ -61783,7 +61879,7 @@ async function shipLane(lane, transport) {
|
|
|
61783
61879
|
update: { seq: last2 },
|
|
61784
61880
|
create: { deviceId: LOCAL_DEVICE_ID, lane, seq: last2 }
|
|
61785
61881
|
});
|
|
61786
|
-
|
|
61882
|
+
since2 = last2;
|
|
61787
61883
|
if (lane === "server") {
|
|
61788
61884
|
const segs = await readSpoolSegments("server");
|
|
61789
61885
|
for (const s2 of segs) if (s2.entries.every((e) => BigInt(e.seq) <= last2)) await removeSpoolSegment(s2.file);
|
|
@@ -61792,7 +61888,7 @@ async function shipLane(lane, transport) {
|
|
|
61792
61888
|
}
|
|
61793
61889
|
if (res.status === 400) {
|
|
61794
61890
|
await appendGap("rejected");
|
|
61795
|
-
|
|
61891
|
+
since2 = BigInt(entries4[entries4.length - 1].seq);
|
|
61796
61892
|
continue;
|
|
61797
61893
|
}
|
|
61798
61894
|
if (res.status === 404) {
|
|
@@ -62241,7 +62337,7 @@ async function startSyncClient(base3, token, tickMs2) {
|
|
|
62241
62337
|
started = true;
|
|
62242
62338
|
startRelayClient(base3, token);
|
|
62243
62339
|
const transport = fetchTransport(base3, token);
|
|
62244
|
-
const
|
|
62340
|
+
const tick7 = () => syncTick(transport);
|
|
62245
62341
|
const connectWs = async () => {
|
|
62246
62342
|
const { WebSocket: WebSocket2 } = await Promise.resolve().then(() => (init_wrapper(), wrapper_exports));
|
|
62247
62343
|
const wsUrl = base3.replace(/^http/, "ws").replace(/\/$/, "") + "/api/sync/ws?" + OPTIONAL_QUERY;
|
|
@@ -62249,14 +62345,14 @@ async function startSyncClient(base3, token, tickMs2) {
|
|
|
62249
62345
|
ws = sock2;
|
|
62250
62346
|
sock2.on("open", () => {
|
|
62251
62347
|
reconnectDelay = 0;
|
|
62252
|
-
void
|
|
62348
|
+
void tick7();
|
|
62253
62349
|
presence = startPresenceSender({ send: (json) => sock2.send(json) });
|
|
62254
62350
|
});
|
|
62255
62351
|
sock2.on("message", async (raw) => {
|
|
62256
62352
|
try {
|
|
62257
62353
|
const msg = JSON.parse(raw.toString());
|
|
62258
62354
|
if (msg.t !== "sync") return;
|
|
62259
|
-
if (!await applyFeedFrame(msg)) void
|
|
62355
|
+
if (!await applyFeedFrame(msg)) void tick7();
|
|
62260
62356
|
} catch {
|
|
62261
62357
|
}
|
|
62262
62358
|
});
|
|
@@ -62278,11 +62374,11 @@ async function startSyncClient(base3, token, tickMs2) {
|
|
|
62278
62374
|
});
|
|
62279
62375
|
};
|
|
62280
62376
|
await bootstrapOnce(transport).catch(() => null);
|
|
62281
|
-
await
|
|
62377
|
+
await tick7();
|
|
62282
62378
|
void connectWs();
|
|
62283
62379
|
const ms = tickMs2 && tickMs2 > 0 ? tickMs2 : 15e3;
|
|
62284
62380
|
timer3 = setInterval(() => {
|
|
62285
|
-
void
|
|
62381
|
+
void tick7();
|
|
62286
62382
|
}, ms);
|
|
62287
62383
|
timer3.unref?.();
|
|
62288
62384
|
}
|
|
@@ -65492,14 +65588,14 @@ var require_secure_json_parse = __commonJS({
|
|
|
65492
65588
|
}
|
|
65493
65589
|
return filter(obj2, { protoAction, constructorAction, safe: options2 && options2.safe });
|
|
65494
65590
|
}
|
|
65495
|
-
function filter(obj2, { protoAction = "error", constructorAction = "error", safe:
|
|
65591
|
+
function filter(obj2, { protoAction = "error", constructorAction = "error", safe: safe3 } = {}) {
|
|
65496
65592
|
let next = [obj2];
|
|
65497
65593
|
while (next.length) {
|
|
65498
65594
|
const nodes = next;
|
|
65499
65595
|
next = [];
|
|
65500
65596
|
for (const node of nodes) {
|
|
65501
65597
|
if (protoAction !== "ignore" && Object.prototype.hasOwnProperty.call(node, "__proto__")) {
|
|
65502
|
-
if (
|
|
65598
|
+
if (safe3 === true) {
|
|
65503
65599
|
return null;
|
|
65504
65600
|
} else if (protoAction === "error") {
|
|
65505
65601
|
throw new SyntaxError("Object contains forbidden prototype property");
|
|
@@ -65507,7 +65603,7 @@ var require_secure_json_parse = __commonJS({
|
|
|
65507
65603
|
delete node.__proto__;
|
|
65508
65604
|
}
|
|
65509
65605
|
if (constructorAction !== "ignore" && Object.prototype.hasOwnProperty.call(node, "constructor") && node.constructor !== null && typeof node.constructor === "object" && Object.prototype.hasOwnProperty.call(node.constructor, "prototype")) {
|
|
65510
|
-
if (
|
|
65606
|
+
if (safe3 === true) {
|
|
65511
65607
|
return null;
|
|
65512
65608
|
} else if (constructorAction === "error") {
|
|
65513
65609
|
throw new SyntaxError("Object contains forbidden prototype property");
|
|
@@ -66142,12 +66238,12 @@ var UploadError = class extends Error {
|
|
|
66142
66238
|
};
|
|
66143
66239
|
function timeout(promise, ms, code) {
|
|
66144
66240
|
return new Promise((resolve24, reject2) => {
|
|
66145
|
-
const
|
|
66241
|
+
const timer11 = setTimeout(() => reject2(new UploadError(code, "upload operation timed out")), ms);
|
|
66146
66242
|
promise.then((value) => {
|
|
66147
|
-
clearTimeout(
|
|
66243
|
+
clearTimeout(timer11);
|
|
66148
66244
|
resolve24(value);
|
|
66149
66245
|
}, (error) => {
|
|
66150
|
-
clearTimeout(
|
|
66246
|
+
clearTimeout(timer11);
|
|
66151
66247
|
reject2(error);
|
|
66152
66248
|
});
|
|
66153
66249
|
});
|
|
@@ -66170,16 +66266,16 @@ function scannerFromEnv(path) {
|
|
|
66170
66266
|
if (!isAbsolute8(command)) return Promise.reject(new UploadError("UPLOAD_SCAN", "scanner path must be absolute"));
|
|
66171
66267
|
return new Promise((resolve24, reject2) => {
|
|
66172
66268
|
const child = spawn2(command, [path], { shell: false, stdio: "ignore" });
|
|
66173
|
-
const
|
|
66269
|
+
const timer11 = setTimeout(() => {
|
|
66174
66270
|
child.kill("SIGKILL");
|
|
66175
66271
|
reject2(new UploadError("UPLOAD_SCAN", "scanner timeout"));
|
|
66176
66272
|
}, UPLOAD_LIMITS.scanMs);
|
|
66177
66273
|
child.once("error", (error) => {
|
|
66178
|
-
clearTimeout(
|
|
66274
|
+
clearTimeout(timer11);
|
|
66179
66275
|
reject2(error);
|
|
66180
66276
|
});
|
|
66181
66277
|
child.once("exit", (code) => {
|
|
66182
|
-
clearTimeout(
|
|
66278
|
+
clearTimeout(timer11);
|
|
66183
66279
|
if (code === 0) resolve24();
|
|
66184
66280
|
else reject2(new UploadError("UPLOAD_SCAN", `scanner exit ${code}`));
|
|
66185
66281
|
});
|
|
@@ -66335,12 +66431,12 @@ async function addSpecAttachments(spec, files2) {
|
|
|
66335
66431
|
continue;
|
|
66336
66432
|
}
|
|
66337
66433
|
const ext = attachmentExt(name2);
|
|
66338
|
-
let
|
|
66434
|
+
let safe3;
|
|
66339
66435
|
try {
|
|
66340
66436
|
const image = IMAGE_TYPES[f.mime];
|
|
66341
66437
|
if (image) {
|
|
66342
66438
|
if (!image.includes(ext)) throw new UploadError("UPLOAD_TYPE", "extension mismatch");
|
|
66343
|
-
|
|
66439
|
+
safe3 = await processUpload({
|
|
66344
66440
|
buffer: f.buf,
|
|
66345
66441
|
clientName: name2,
|
|
66346
66442
|
clientMime: f.mime,
|
|
@@ -66348,7 +66444,7 @@ async function addSpecAttachments(spec, files2) {
|
|
|
66348
66444
|
parentBytes: bytes
|
|
66349
66445
|
});
|
|
66350
66446
|
} else if (DOCUMENT_TYPES[f.mime]) {
|
|
66351
|
-
|
|
66447
|
+
safe3 = await processDocumentUpload({
|
|
66352
66448
|
buffer: f.buf,
|
|
66353
66449
|
clientName: name2,
|
|
66354
66450
|
clientMime: f.mime,
|
|
@@ -66367,17 +66463,17 @@ async function addSpecAttachments(spec, files2) {
|
|
|
66367
66463
|
row = await prisma.specAttachment.create({ data: {
|
|
66368
66464
|
specId: spec.id,
|
|
66369
66465
|
projectId: spec.projectId,
|
|
66370
|
-
filename:
|
|
66371
|
-
mimeType:
|
|
66372
|
-
size:
|
|
66373
|
-
storageKey:
|
|
66466
|
+
filename: safe3.filename,
|
|
66467
|
+
mimeType: safe3.mimeType,
|
|
66468
|
+
size: safe3.size,
|
|
66469
|
+
storageKey: safe3.storageKey
|
|
66374
66470
|
} });
|
|
66375
66471
|
} catch (error) {
|
|
66376
|
-
await deleteUpload(
|
|
66472
|
+
await deleteUpload(safe3.storageKey);
|
|
66377
66473
|
throw error;
|
|
66378
66474
|
}
|
|
66379
66475
|
count += 1;
|
|
66380
|
-
bytes +=
|
|
66476
|
+
bytes += safe3.size;
|
|
66381
66477
|
saved.push(view2(row));
|
|
66382
66478
|
}
|
|
66383
66479
|
return { saved, rejected };
|
|
@@ -66585,14 +66681,14 @@ async function addQaAttachments(report, owner, files2) {
|
|
|
66585
66681
|
continue;
|
|
66586
66682
|
}
|
|
66587
66683
|
const ext = attachmentExt(name2);
|
|
66588
|
-
let
|
|
66684
|
+
let safe3;
|
|
66589
66685
|
try {
|
|
66590
66686
|
const image = IMAGE_TYPES[f.mime];
|
|
66591
66687
|
if (image) {
|
|
66592
66688
|
if (!image.includes(ext)) throw new UploadError("UPLOAD_TYPE", "extension mismatch");
|
|
66593
|
-
|
|
66689
|
+
safe3 = await processUpload({ buffer: f.buf, clientName: name2, clientMime: f.mime, projectId: report.projectId, parentBytes: bytes });
|
|
66594
66690
|
} else if (DOCUMENT_TYPES[f.mime]) {
|
|
66595
|
-
|
|
66691
|
+
safe3 = await processDocumentUpload({ buffer: f.buf, clientName: name2, clientMime: f.mime, clientExt: ext });
|
|
66596
66692
|
} else {
|
|
66597
66693
|
throw new UploadError("UPLOAD_TYPE", "unsupported type");
|
|
66598
66694
|
}
|
|
@@ -66602,28 +66698,28 @@ async function addQaAttachments(report, owner, files2) {
|
|
|
66602
66698
|
continue;
|
|
66603
66699
|
}
|
|
66604
66700
|
try {
|
|
66605
|
-
const sha256 = createHash6("sha256").update(await readUpload(
|
|
66701
|
+
const sha256 = createHash6("sha256").update(await readUpload(safe3.storageKey)).digest("hex");
|
|
66606
66702
|
const row = await prisma.qaAttachment.create({ data: {
|
|
66607
66703
|
reportId: report.id,
|
|
66608
66704
|
projectId: report.projectId,
|
|
66609
66705
|
ownerType: owner.ownerType,
|
|
66610
66706
|
ownerId: owner.ownerId,
|
|
66611
|
-
filename:
|
|
66612
|
-
mimeType:
|
|
66613
|
-
size:
|
|
66707
|
+
filename: safe3.filename,
|
|
66708
|
+
mimeType: safe3.mimeType,
|
|
66709
|
+
size: safe3.size,
|
|
66614
66710
|
sha256,
|
|
66615
|
-
storageKey:
|
|
66711
|
+
storageKey: safe3.storageKey,
|
|
66616
66712
|
// Client: byte baru ada di sini saja → menunggu unggah ke hub. Hub/standalone: tak ada atasan → langsung tersedia.
|
|
66617
66713
|
syncState: effectiveStr("SYNC_SERVER_URL") ? "local-only" : "available"
|
|
66618
66714
|
} });
|
|
66619
66715
|
await notifySynced("qaAttachment", row.id);
|
|
66620
66716
|
saved.push(attachmentView(row));
|
|
66621
66717
|
} catch (error) {
|
|
66622
|
-
await deleteUpload(
|
|
66718
|
+
await deleteUpload(safe3.storageKey);
|
|
66623
66719
|
throw error;
|
|
66624
66720
|
}
|
|
66625
66721
|
count += 1;
|
|
66626
|
-
bytes +=
|
|
66722
|
+
bytes += safe3.size;
|
|
66627
66723
|
}
|
|
66628
66724
|
return { saved, rejected };
|
|
66629
66725
|
}
|
|
@@ -66959,9 +67055,9 @@ async function toProjectView(p3, sessions, devices2) {
|
|
|
66959
67055
|
const specs = await prisma.spec.findMany({ where: { projectId: p3.id } });
|
|
66960
67056
|
const { coverage } = await scanRepoDocs(await resolveRepoDir(p3.id));
|
|
66961
67057
|
const binding = await getBinding(p3.id);
|
|
66962
|
-
const
|
|
67058
|
+
const open6 = specs.filter((s2) => s2.stage !== "done");
|
|
66963
67059
|
const { session, commit } = sessionOf(p3.id, sessions);
|
|
66964
|
-
const topStage =
|
|
67060
|
+
const topStage = open6.length ? open6.map((s2) => s2.stage).sort((a, b) => STAGES.indexOf(b) - STAGES.indexOf(a))[0] : "spec";
|
|
66965
67061
|
return {
|
|
66966
67062
|
id: p3.id,
|
|
66967
67063
|
name: p3.name,
|
|
@@ -66974,7 +67070,7 @@ async function toProjectView(p3, sessions, devices2) {
|
|
|
66974
67070
|
docStatus: docStatusFor(coverage),
|
|
66975
67071
|
coverage,
|
|
66976
67072
|
createdAt: p3.createdAt.toISOString(),
|
|
66977
|
-
backlog:
|
|
67073
|
+
backlog: open6.length,
|
|
66978
67074
|
topStage,
|
|
66979
67075
|
session,
|
|
66980
67076
|
activity: session.status === "running" ? `running \xB7 ${session.flow ?? "sesi"}` : "idle",
|
|
@@ -68443,9 +68539,9 @@ async function deleteWorktrees(repoDir, names, opts2) {
|
|
|
68443
68539
|
try {
|
|
68444
68540
|
if (w.locked) await opts2.unlock(report.repoDir, w.path);
|
|
68445
68541
|
if (w.session) {
|
|
68446
|
-
const
|
|
68542
|
+
const closed2 = await opts2.closeSession(w.session.id);
|
|
68447
68543
|
row.closedSession = w.session.id;
|
|
68448
|
-
row.cleanup =
|
|
68544
|
+
row.cleanup = closed2?.cleanup ?? null;
|
|
68449
68545
|
}
|
|
68450
68546
|
if (!row.cleanup) row.cleanup = opts2.release(report.repoDir, w.path);
|
|
68451
68547
|
await opts2.prune(report.repoDir);
|
|
@@ -68752,11 +68848,11 @@ async function beginSession(b) {
|
|
|
68752
68848
|
});
|
|
68753
68849
|
}
|
|
68754
68850
|
async function finishSession(d) {
|
|
68755
|
-
const
|
|
68851
|
+
const open6 = await prisma.sessionHistory.findFirst({
|
|
68756
68852
|
where: { sessionId: d.sessionId, endedAt: null },
|
|
68757
68853
|
orderBy: { startedAt: "desc" }
|
|
68758
68854
|
});
|
|
68759
|
-
if (!
|
|
68855
|
+
if (!open6) return;
|
|
68760
68856
|
let t = { key: "", bytes: 0 };
|
|
68761
68857
|
if (d.transcript) {
|
|
68762
68858
|
try {
|
|
@@ -68766,7 +68862,7 @@ async function finishSession(d) {
|
|
|
68766
68862
|
}
|
|
68767
68863
|
}
|
|
68768
68864
|
await prisma.sessionHistory.update({
|
|
68769
|
-
where: { id:
|
|
68865
|
+
where: { id: open6.id },
|
|
68770
68866
|
data: {
|
|
68771
68867
|
endedAt: /* @__PURE__ */ new Date(),
|
|
68772
68868
|
endedReason: CLOSED,
|
|
@@ -68881,22 +68977,22 @@ async function reconcileTranscripts(opts2 = {}) {
|
|
|
68881
68977
|
return report;
|
|
68882
68978
|
}
|
|
68883
68979
|
async function reconcileHistory(liveSessionIds) {
|
|
68884
|
-
const
|
|
68980
|
+
const open6 = await prisma.sessionHistory.findMany({
|
|
68885
68981
|
where: { endedAt: null },
|
|
68886
68982
|
select: { id: true, sessionId: true, updatedAt: true }
|
|
68887
68983
|
});
|
|
68888
68984
|
const live = new Set(liveSessionIds);
|
|
68889
68985
|
const at = /* @__PURE__ */ new Date();
|
|
68890
|
-
let
|
|
68891
|
-
for (const r of
|
|
68986
|
+
let closed2 = 0;
|
|
68987
|
+
for (const r of open6) {
|
|
68892
68988
|
if (live.has(r.sessionId)) continue;
|
|
68893
68989
|
await prisma.sessionHistory.update({
|
|
68894
68990
|
where: { id: r.id },
|
|
68895
68991
|
data: { endedAt: r.updatedAt, endedReason: RECONCILED, reconciledAt: at }
|
|
68896
68992
|
});
|
|
68897
|
-
|
|
68993
|
+
closed2++;
|
|
68898
68994
|
}
|
|
68899
|
-
return
|
|
68995
|
+
return closed2;
|
|
68900
68996
|
}
|
|
68901
68997
|
async function reconciledSpecIdsSince(cutoff) {
|
|
68902
68998
|
const rows = await prisma.sessionHistory.findMany({
|
|
@@ -68968,13 +69064,13 @@ async function detectOrphanWorktrees(deps = { repos: prodReaperDeps.repos, input
|
|
|
68968
69064
|
// src/services/worktree-delete.ts
|
|
68969
69065
|
var execAsync = promisify11(execFile12);
|
|
68970
69066
|
async function lockInputs(id2) {
|
|
68971
|
-
const
|
|
69067
|
+
const open6 = await prisma.spec.findMany({
|
|
68972
69068
|
where: { projectId: id2, stage: { not: "done" } },
|
|
68973
69069
|
select: { id: true }
|
|
68974
69070
|
});
|
|
68975
69071
|
const sessions = listSessions().filter((s2) => s2.projectId === id2 && !s2.exited).map((s2) => s2.branch || (s2.specId ? `hanoman/${s2.id}` : "")).filter(Boolean);
|
|
68976
69072
|
return {
|
|
68977
|
-
openSpecBranches: new Set(
|
|
69073
|
+
openSpecBranches: new Set(open6.map((s2) => sourceBranch(s2.id))),
|
|
68978
69074
|
sessionBranches: new Set(sessions)
|
|
68979
69075
|
};
|
|
68980
69076
|
}
|
|
@@ -74241,7 +74337,7 @@ function createBoundedSender(ws2, o = {}) {
|
|
|
74241
74337
|
const drainMs = o.drainMs ?? DRAIN_MS;
|
|
74242
74338
|
let pending2 = [];
|
|
74243
74339
|
let pendingBytes = 0;
|
|
74244
|
-
let
|
|
74340
|
+
let timer11;
|
|
74245
74341
|
let resyncNeeded = false;
|
|
74246
74342
|
const trim = () => {
|
|
74247
74343
|
for (let i = 0; i < pending2.length && pendingBytes > cap; ) {
|
|
@@ -74255,10 +74351,10 @@ function createBoundedSender(ws2, o = {}) {
|
|
|
74255
74351
|
}
|
|
74256
74352
|
};
|
|
74257
74353
|
const drain2 = () => {
|
|
74258
|
-
|
|
74354
|
+
timer11 = void 0;
|
|
74259
74355
|
if (ws2.bufferedAmount > high) {
|
|
74260
|
-
|
|
74261
|
-
|
|
74356
|
+
timer11 = setTimeout(drain2, drainMs);
|
|
74357
|
+
timer11.unref?.();
|
|
74262
74358
|
return;
|
|
74263
74359
|
}
|
|
74264
74360
|
const out4 = pending2;
|
|
@@ -74279,14 +74375,14 @@ function createBoundedSender(ws2, o = {}) {
|
|
|
74279
74375
|
pending2.push(frame2);
|
|
74280
74376
|
pendingBytes += frame2.length;
|
|
74281
74377
|
trim();
|
|
74282
|
-
if (!
|
|
74283
|
-
|
|
74284
|
-
|
|
74378
|
+
if (!timer11) {
|
|
74379
|
+
timer11 = setTimeout(drain2, drainMs);
|
|
74380
|
+
timer11.unref?.();
|
|
74285
74381
|
}
|
|
74286
74382
|
},
|
|
74287
74383
|
dispose() {
|
|
74288
|
-
if (
|
|
74289
|
-
|
|
74384
|
+
if (timer11) clearTimeout(timer11);
|
|
74385
|
+
timer11 = void 0;
|
|
74290
74386
|
pending2 = [];
|
|
74291
74387
|
pendingBytes = 0;
|
|
74292
74388
|
}
|
|
@@ -74434,7 +74530,7 @@ function startSessionEventRelay(app3, options2 = {}) {
|
|
|
74434
74530
|
const root = options2.root ?? sessionEventSpoolRoot();
|
|
74435
74531
|
const host2 = controlHost(loadIngressPolicy(options2.env ?? process.env));
|
|
74436
74532
|
let running = false;
|
|
74437
|
-
const
|
|
74533
|
+
const tick7 = async () => {
|
|
74438
74534
|
if (running) return;
|
|
74439
74535
|
running = true;
|
|
74440
74536
|
try {
|
|
@@ -74445,14 +74541,14 @@ function startSessionEventRelay(app3, options2 = {}) {
|
|
|
74445
74541
|
running = false;
|
|
74446
74542
|
}
|
|
74447
74543
|
};
|
|
74448
|
-
const
|
|
74449
|
-
void
|
|
74544
|
+
const timer11 = setInterval(() => {
|
|
74545
|
+
void tick7();
|
|
74450
74546
|
}, options2.intervalMs ?? 250);
|
|
74451
|
-
|
|
74547
|
+
timer11.unref();
|
|
74452
74548
|
app3.addHook("onClose", async () => {
|
|
74453
|
-
clearInterval(
|
|
74549
|
+
clearInterval(timer11);
|
|
74454
74550
|
});
|
|
74455
|
-
void
|
|
74551
|
+
void tick7();
|
|
74456
74552
|
}
|
|
74457
74553
|
|
|
74458
74554
|
// src/services/agent-invocations.ts
|
|
@@ -74676,10 +74772,10 @@ async function stopAgentInvocation(input, io = {}) {
|
|
|
74676
74772
|
}
|
|
74677
74773
|
async function reconcileAgentInvocations(liveSessionIds) {
|
|
74678
74774
|
const live = new Set(liveSessionIds);
|
|
74679
|
-
const
|
|
74775
|
+
const open6 = await prisma.agentInvocation.findMany({ where: { status: "running" } });
|
|
74680
74776
|
let changed = 0;
|
|
74681
74777
|
const endedAt = /* @__PURE__ */ new Date();
|
|
74682
|
-
for (const row of
|
|
74778
|
+
for (const row of open6) {
|
|
74683
74779
|
if (live.has(row.sessionId)) continue;
|
|
74684
74780
|
await prisma.agentInvocation.update({
|
|
74685
74781
|
where: { id: row.id },
|
|
@@ -75075,8 +75171,8 @@ async function expireFlows(now) {
|
|
|
75075
75171
|
});
|
|
75076
75172
|
const out4 = [];
|
|
75077
75173
|
for (const f of due) {
|
|
75078
|
-
const
|
|
75079
|
-
if (
|
|
75174
|
+
const closed2 = await closeFlow(f.id, "kedaluwarsa");
|
|
75175
|
+
if (closed2) out4.push(closed2);
|
|
75080
75176
|
}
|
|
75081
75177
|
return out4;
|
|
75082
75178
|
}
|
|
@@ -76763,7 +76859,7 @@ async function terminal_default(app3, opts2) {
|
|
|
76763
76859
|
});
|
|
76764
76860
|
const client = { send: (m) => bounded.send(m), close: () => socket2.close() };
|
|
76765
76861
|
let ready = false;
|
|
76766
|
-
let
|
|
76862
|
+
let closed2 = false;
|
|
76767
76863
|
const early = [];
|
|
76768
76864
|
const attachReady = (async () => {
|
|
76769
76865
|
const found = await getSessionAsync(id2);
|
|
@@ -76771,9 +76867,9 @@ async function terminal_default(app3, opts2) {
|
|
|
76771
76867
|
socket2.close(4004, "not found");
|
|
76772
76868
|
return;
|
|
76773
76869
|
}
|
|
76774
|
-
if (
|
|
76870
|
+
if (closed2) return;
|
|
76775
76871
|
await attachAsync(id2, client);
|
|
76776
|
-
if (
|
|
76872
|
+
if (closed2) {
|
|
76777
76873
|
detach(id2, client);
|
|
76778
76874
|
return;
|
|
76779
76875
|
}
|
|
@@ -76821,7 +76917,7 @@ async function terminal_default(app3, opts2) {
|
|
|
76821
76917
|
const revalidate = watch ? setInterval(() => watch.refresh(), 6e4) : void 0;
|
|
76822
76918
|
revalidate?.unref?.();
|
|
76823
76919
|
socket2.on("close", () => {
|
|
76824
|
-
|
|
76920
|
+
closed2 = true;
|
|
76825
76921
|
bounded.dispose();
|
|
76826
76922
|
if (revalidate) clearInterval(revalidate);
|
|
76827
76923
|
watch?.dispose();
|
|
@@ -77179,9 +77275,9 @@ function sshExec(t, remoteCmd, opts2 = {}) {
|
|
|
77179
77275
|
return new Promise((resolve24) => {
|
|
77180
77276
|
const p3 = spawn4(sshBin(), args, { stdio: ["pipe", "pipe", "pipe"], env });
|
|
77181
77277
|
let out4 = "";
|
|
77182
|
-
const
|
|
77278
|
+
const timer11 = setTimeout(() => p3.kill("SIGKILL"), opts2.timeoutMs ?? 6e4);
|
|
77183
77279
|
const done = (r) => {
|
|
77184
|
-
clearTimeout(
|
|
77280
|
+
clearTimeout(timer11);
|
|
77185
77281
|
if (askpass) rmSync12(dirname15(askpass), { recursive: true, force: true });
|
|
77186
77282
|
resolve24(r);
|
|
77187
77283
|
};
|
|
@@ -78253,7 +78349,7 @@ function runCatalogCommand(command, agent, cwd, env, timeoutMs = TIMEOUT_MS) {
|
|
|
78253
78349
|
const finish = (error, result) => {
|
|
78254
78350
|
if (settled) return;
|
|
78255
78351
|
settled = true;
|
|
78256
|
-
clearTimeout(
|
|
78352
|
+
clearTimeout(timer11);
|
|
78257
78353
|
child.stdin.end();
|
|
78258
78354
|
if (child.exitCode === null && child.signalCode === null) {
|
|
78259
78355
|
child.kill("SIGTERM");
|
|
@@ -78263,7 +78359,7 @@ function runCatalogCommand(command, agent, cwd, env, timeoutMs = TIMEOUT_MS) {
|
|
|
78263
78359
|
if (error) reject2(error);
|
|
78264
78360
|
else resolve24(result);
|
|
78265
78361
|
};
|
|
78266
|
-
const
|
|
78362
|
+
const timer11 = setTimeout(() => finish(new Error("Pemeriksaan model melewati batas waktu")), timeoutMs);
|
|
78267
78363
|
child.on("error", () => finish(new Error("CLI model tidak dapat dijalankan")));
|
|
78268
78364
|
child.stdin.on("error", () => finish(new Error("Kanal katalog CLI terputus")));
|
|
78269
78365
|
child.stderr.on("data", () => {
|
|
@@ -78404,11 +78500,11 @@ var modelCatalogService = createModelCatalogService({
|
|
|
78404
78500
|
});
|
|
78405
78501
|
function startModelDiscovery() {
|
|
78406
78502
|
void modelCatalogService.refresh();
|
|
78407
|
-
const
|
|
78503
|
+
const timer11 = setInterval(() => {
|
|
78408
78504
|
void modelCatalogService.refresh();
|
|
78409
78505
|
}, 5 * 6e4);
|
|
78410
|
-
|
|
78411
|
-
return () => clearInterval(
|
|
78506
|
+
timer11.unref();
|
|
78507
|
+
return () => clearInterval(timer11);
|
|
78412
78508
|
}
|
|
78413
78509
|
|
|
78414
78510
|
// src/routes/models.ts
|
|
@@ -78433,18 +78529,18 @@ function requestConfigRestart(delayMs = 50) {
|
|
|
78433
78529
|
}
|
|
78434
78530
|
function afterReplyFlushed(raw, fn, maxWaitMs = 5e3) {
|
|
78435
78531
|
let done = false;
|
|
78436
|
-
let
|
|
78532
|
+
let timer11;
|
|
78437
78533
|
const run5 = () => {
|
|
78438
78534
|
if (done) return;
|
|
78439
78535
|
done = true;
|
|
78440
|
-
if (
|
|
78536
|
+
if (timer11) clearTimeout(timer11);
|
|
78441
78537
|
raw.removeListener("finish", run5);
|
|
78442
78538
|
raw.removeListener("close", run5);
|
|
78443
78539
|
fn();
|
|
78444
78540
|
};
|
|
78445
78541
|
raw.once("finish", run5);
|
|
78446
78542
|
raw.once("close", run5);
|
|
78447
|
-
|
|
78543
|
+
timer11 = setTimeout(run5, maxWaitMs);
|
|
78448
78544
|
}
|
|
78449
78545
|
function requestManualRestart(delayMs = 250) {
|
|
78450
78546
|
setTimeout(() => process.exit(MANUAL_RESTART_EXIT), delayMs);
|
|
@@ -78511,6 +78607,364 @@ async function pendingCounts(now = Date.now(), prds = listAllPrds) {
|
|
|
78511
78607
|
return { ...EMPTY_PENDING, triage: tickets2 + issues, backlog, prd, lead };
|
|
78512
78608
|
}
|
|
78513
78609
|
|
|
78610
|
+
// src/services/overview-source.ts
|
|
78611
|
+
init_db();
|
|
78612
|
+
|
|
78613
|
+
// src/services/overview-metrics.ts
|
|
78614
|
+
init_src();
|
|
78615
|
+
var AGENT_KINDS = /* @__PURE__ */ new Set(["spec", "reverse", "prd", "scaffold", "breakdown", "telegram"]);
|
|
78616
|
+
var MIN_MS = 6e4;
|
|
78617
|
+
var HOUR_MS2 = 36e5;
|
|
78618
|
+
var startOfDay = (t) => {
|
|
78619
|
+
const d = new Date(t);
|
|
78620
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
78621
|
+
};
|
|
78622
|
+
var addDays = (t, n2) => {
|
|
78623
|
+
const d = new Date(t);
|
|
78624
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate() + n2, d.getHours(), d.getMinutes()).getTime();
|
|
78625
|
+
};
|
|
78626
|
+
var dayKey = (t) => {
|
|
78627
|
+
const d = new Date(t);
|
|
78628
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
78629
|
+
};
|
|
78630
|
+
var overlapMs = (a0, a1, b0, b1) => Math.max(0, Math.min(a1, b1) - Math.max(a0, b0));
|
|
78631
|
+
function median2(xs) {
|
|
78632
|
+
if (xs.length === 0) return null;
|
|
78633
|
+
const s2 = [...xs].sort((a, b) => a - b);
|
|
78634
|
+
const m = s2.length >> 1;
|
|
78635
|
+
return s2.length % 2 ? s2[m] : (s2[m - 1] + s2[m]) / 2;
|
|
78636
|
+
}
|
|
78637
|
+
var inRange = (d, t0, t1) => d !== null && d.getTime() >= t0 && d.getTime() < t1;
|
|
78638
|
+
var isFailed = (s2) => AGENT_KINDS.has(s2.kind) && s2.endedAt !== null && sessionOutcome({ endedAt: s2.endedAt.toISOString(), endedReason: s2.endedReason, exitCode: s2.exitCode }) === "failed";
|
|
78639
|
+
function dayStats(dayStart2, specs, sessions) {
|
|
78640
|
+
const dayEnd2 = addDays(dayStart2, 1);
|
|
78641
|
+
return {
|
|
78642
|
+
landed: specs.filter((s2) => inRange(s2.doneAt, dayStart2, dayEnd2)).length,
|
|
78643
|
+
failed: sessions.filter((s2) => isFailed(s2) && inRange(s2.endedAt, dayStart2, dayEnd2)).length
|
|
78644
|
+
};
|
|
78645
|
+
}
|
|
78646
|
+
function riverStats(now, specs) {
|
|
78647
|
+
const t0 = startOfDay(now);
|
|
78648
|
+
const t1 = addDays(t0, 1);
|
|
78649
|
+
const week0 = addDays(t0, -6);
|
|
78650
|
+
const done7 = specs.filter((s2) => inRange(s2.doneAt, week0, t1));
|
|
78651
|
+
return {
|
|
78652
|
+
created: specs.filter((s2) => inRange(s2.createdAt, t0, t1)).length,
|
|
78653
|
+
started: specs.filter((s2) => inRange(s2.startedAt, t0, t1)).length,
|
|
78654
|
+
landed: specs.filter((s2) => inRange(s2.doneAt, t0, t1)).length,
|
|
78655
|
+
leadTimeMedMs: median2(done7.map((s2) => s2.doneAt.getTime() - s2.createdAt.getTime())),
|
|
78656
|
+
cycleTimeMedMs: median2(done7.filter((s2) => s2.startedAt).map((s2) => s2.doneAt.getTime() - s2.startedAt.getTime()))
|
|
78657
|
+
};
|
|
78658
|
+
}
|
|
78659
|
+
var invTotal = (i) => (i.inputTokens ?? 0) + (i.outputTokens ?? 0) + (i.cachedTokens ?? 0);
|
|
78660
|
+
function tokenStats(now, invs, landedToday) {
|
|
78661
|
+
const t0 = startOfDay(now);
|
|
78662
|
+
const y0 = addDays(t0, -1);
|
|
78663
|
+
const today = invs.filter((i) => i.startedAt.getTime() >= t0);
|
|
78664
|
+
const input = today.reduce((n2, i) => n2 + (i.inputTokens ?? 0), 0);
|
|
78665
|
+
const output = today.reduce((n2, i) => n2 + (i.outputTokens ?? 0), 0);
|
|
78666
|
+
const cached3 = today.reduce((n2, i) => n2 + (i.cachedTokens ?? 0), 0);
|
|
78667
|
+
const total = input + output + cached3;
|
|
78668
|
+
const byAgent = /* @__PURE__ */ new Map();
|
|
78669
|
+
for (const i of today) byAgent.set(i.agentName, (byAgent.get(i.agentName) ?? 0) + invTotal(i));
|
|
78670
|
+
let topAgent = null;
|
|
78671
|
+
for (const [name2, n2] of byAgent) if (!topAgent || n2 > topAgent.total) topAgent = { name: name2, total: n2 };
|
|
78672
|
+
return {
|
|
78673
|
+
input,
|
|
78674
|
+
output,
|
|
78675
|
+
cached: cached3,
|
|
78676
|
+
total,
|
|
78677
|
+
cacheHitPct: input + cached3 > 0 ? Math.round(cached3 / (input + cached3) * 100) : null,
|
|
78678
|
+
perLanded: landedToday && landedToday > 0 ? Math.round(total / landedToday) : null,
|
|
78679
|
+
topAgent,
|
|
78680
|
+
yesterdayTotal: invs.filter((i) => i.startedAt.getTime() >= y0 && i.startedAt.getTime() < t0).reduce((n2, i) => n2 + invTotal(i), 0)
|
|
78681
|
+
};
|
|
78682
|
+
}
|
|
78683
|
+
function liveMs(sessions, t0, t1, now) {
|
|
78684
|
+
let ms = 0;
|
|
78685
|
+
for (const s2 of sessions) {
|
|
78686
|
+
if (!AGENT_KINDS.has(s2.kind)) continue;
|
|
78687
|
+
ms += overlapMs(s2.startedAt.getTime(), s2.endedAt ? s2.endedAt.getTime() : now, t0, t1);
|
|
78688
|
+
}
|
|
78689
|
+
return ms;
|
|
78690
|
+
}
|
|
78691
|
+
var spanMs = (spans, t0, t1, now) => spans.reduce((n2, w) => n2 + overlapMs(w.start, w.end ?? now, t0, t1), 0);
|
|
78692
|
+
function waitStats(now, waits, sessions) {
|
|
78693
|
+
const t0 = startOfDay(now);
|
|
78694
|
+
const t1 = addDays(t0, 1);
|
|
78695
|
+
const y0 = addDays(t0, -1);
|
|
78696
|
+
const todayMs = spanMs(waits, t0, t1, now);
|
|
78697
|
+
const live = sessions ? liveMs(sessions, t0, t1, now) : 0;
|
|
78698
|
+
return {
|
|
78699
|
+
todayMin: Math.round(todayMs / MIN_MS),
|
|
78700
|
+
yesterdayMin: Math.round(spanMs(waits, y0, t0, now) / MIN_MS),
|
|
78701
|
+
pctOfLive: sessions && live > 0 ? Math.min(100, Math.round(todayMs / live * 100)) : null
|
|
78702
|
+
};
|
|
78703
|
+
}
|
|
78704
|
+
function heatmapStats(now, sessions) {
|
|
78705
|
+
const t0 = startOfDay(now);
|
|
78706
|
+
const days2 = [];
|
|
78707
|
+
const minutes = [];
|
|
78708
|
+
for (let i = 6; i >= 0; i--) {
|
|
78709
|
+
const d0 = addDays(t0, -i);
|
|
78710
|
+
const base3 = new Date(d0);
|
|
78711
|
+
days2.push(dayKey(d0));
|
|
78712
|
+
const row = [];
|
|
78713
|
+
for (let h2 = 0; h2 < 24; h2++) {
|
|
78714
|
+
const h0 = new Date(base3.getFullYear(), base3.getMonth(), base3.getDate(), h2).getTime();
|
|
78715
|
+
const h1 = new Date(base3.getFullYear(), base3.getMonth(), base3.getDate(), h2 + 1).getTime();
|
|
78716
|
+
row.push(Math.round(liveMs(sessions, h0, h1, now) / MIN_MS));
|
|
78717
|
+
}
|
|
78718
|
+
minutes.push(row);
|
|
78719
|
+
}
|
|
78720
|
+
return { days: days2, minutes };
|
|
78721
|
+
}
|
|
78722
|
+
var RECORD_WINDOW_DAYS = 90;
|
|
78723
|
+
function recordStats(now, specs, sessions) {
|
|
78724
|
+
const t0 = startOfDay(now);
|
|
78725
|
+
const windowStart = addDays(t0, -(RECORD_WINDOW_DAYS - 1));
|
|
78726
|
+
const agent = sessions.filter((s2) => AGENT_KINDS.has(s2.kind));
|
|
78727
|
+
const failedDays = new Set(agent.filter(isFailed).map((s2) => dayKey(s2.endedAt.getTime())));
|
|
78728
|
+
let streakDays = 0;
|
|
78729
|
+
let bestStreak = 0;
|
|
78730
|
+
if (agent.length > 0) {
|
|
78731
|
+
const first = Math.max(windowStart, startOfDay(Math.min(...agent.map((s2) => s2.startedAt.getTime()))));
|
|
78732
|
+
let run5 = 0;
|
|
78733
|
+
for (let d = first; d <= t0; d = addDays(d, 1)) {
|
|
78734
|
+
run5 = failedDays.has(dayKey(d)) ? 0 : run5 + 1;
|
|
78735
|
+
bestStreak = Math.max(bestStreak, run5);
|
|
78736
|
+
}
|
|
78737
|
+
streakDays = run5;
|
|
78738
|
+
}
|
|
78739
|
+
const landedByDay = /* @__PURE__ */ new Map();
|
|
78740
|
+
for (const s2 of specs) {
|
|
78741
|
+
if (!s2.doneAt || s2.doneAt.getTime() < windowStart) continue;
|
|
78742
|
+
const k = dayKey(s2.doneAt.getTime());
|
|
78743
|
+
landedByDay.set(k, (landedByDay.get(k) ?? 0) + 1);
|
|
78744
|
+
}
|
|
78745
|
+
let bestLandedDay = null;
|
|
78746
|
+
for (const [date, count] of [...landedByDay].sort(([a], [b]) => a.localeCompare(b)))
|
|
78747
|
+
if (!bestLandedDay || count > bestLandedDay.count) bestLandedDay = { date, count };
|
|
78748
|
+
const todayKey = dayKey(t0);
|
|
78749
|
+
const todayCount = landedByDay.get(todayKey) ?? 0;
|
|
78750
|
+
const prevMax = Math.max(0, ...[...landedByDay].filter(([k]) => k !== todayKey).map(([, n2]) => n2));
|
|
78751
|
+
return { streakDays, bestStreak, bestLandedDay, landedRecordToday: prevMax > 0 && todayCount > prevMax };
|
|
78752
|
+
}
|
|
78753
|
+
var BENSIN_LOOKBACK_MS = 60 * MIN_MS;
|
|
78754
|
+
var BENSIN_MIN_SAMPLES = 4;
|
|
78755
|
+
var BENSIN_MIN_SPAN_MS = 15 * MIN_MS;
|
|
78756
|
+
function slopeOf(pts) {
|
|
78757
|
+
const n2 = pts.length;
|
|
78758
|
+
const mx = pts.reduce((s2, p3) => s2 + p3.at, 0) / n2;
|
|
78759
|
+
const my = pts.reduce((s2, p3) => s2 + p3.pct, 0) / n2;
|
|
78760
|
+
let num = 0;
|
|
78761
|
+
let den = 0;
|
|
78762
|
+
for (const p3 of pts) {
|
|
78763
|
+
num += (p3.at - mx) * (p3.pct - my);
|
|
78764
|
+
den += (p3.at - mx) ** 2;
|
|
78765
|
+
}
|
|
78766
|
+
return den === 0 ? 0 : num / den;
|
|
78767
|
+
}
|
|
78768
|
+
function bensinStats(now, series2) {
|
|
78769
|
+
const out4 = [];
|
|
78770
|
+
for (const s2 of series2) {
|
|
78771
|
+
const last2 = s2.samples.at(-1);
|
|
78772
|
+
if (!last2) continue;
|
|
78773
|
+
const recent = s2.samples.filter((p3) => p3.at >= now - BENSIN_LOOKBACK_MS);
|
|
78774
|
+
const base3 = {
|
|
78775
|
+
source: s2.source,
|
|
78776
|
+
key: s2.key,
|
|
78777
|
+
label: s2.label,
|
|
78778
|
+
usedPct: last2.pct,
|
|
78779
|
+
resetsAt: s2.resetsAt,
|
|
78780
|
+
samples: recent.map((p3) => ({ at: new Date(p3.at).toISOString(), pct: p3.pct }))
|
|
78781
|
+
};
|
|
78782
|
+
if (recent.length < BENSIN_MIN_SAMPLES || last2.at - recent[0].at < BENSIN_MIN_SPAN_MS) {
|
|
78783
|
+
out4.push({ ...base3, exhaustAt: null, verdict: "kurang-data" });
|
|
78784
|
+
continue;
|
|
78785
|
+
}
|
|
78786
|
+
const slope = slopeOf(recent);
|
|
78787
|
+
if (slope <= 0) {
|
|
78788
|
+
out4.push({ ...base3, exhaustAt: null, verdict: "stabil" });
|
|
78789
|
+
continue;
|
|
78790
|
+
}
|
|
78791
|
+
const exhaust = Math.round(last2.at + (100 - last2.pct) / slope);
|
|
78792
|
+
const reset = s2.resetsAt ? Date.parse(s2.resetsAt) : Number.NaN;
|
|
78793
|
+
if (Number.isFinite(reset) && exhaust >= reset) out4.push({ ...base3, exhaustAt: null, verdict: "aman" });
|
|
78794
|
+
else out4.push({ ...base3, exhaustAt: new Date(exhaust).toISOString(), verdict: "habis" });
|
|
78795
|
+
}
|
|
78796
|
+
return out4;
|
|
78797
|
+
}
|
|
78798
|
+
function fmtMinutes(min) {
|
|
78799
|
+
if (min < 60) return `${min} menit`;
|
|
78800
|
+
const h2 = Math.floor(min / 60);
|
|
78801
|
+
const m = min % 60;
|
|
78802
|
+
return m ? `${h2} jam ${m} menit` : `${h2} jam`;
|
|
78803
|
+
}
|
|
78804
|
+
function fmtClock(t) {
|
|
78805
|
+
const d = new Date(t);
|
|
78806
|
+
return `${String(d.getHours()).padStart(2, "0")}.${String(d.getMinutes()).padStart(2, "0")}`;
|
|
78807
|
+
}
|
|
78808
|
+
var WAIT_HEADLINE_MIN = 30;
|
|
78809
|
+
function headline(o) {
|
|
78810
|
+
const facts = [];
|
|
78811
|
+
if (o.today && o.today.failed > 0) facts.push({ text: `${o.today.failed} sesi tumbang`, tone: "bad" });
|
|
78812
|
+
if (o.wait && o.wait.todayMin >= WAIT_HEADLINE_MIN)
|
|
78813
|
+
facts.push({ text: `agen menunggumu ${fmtMinutes(o.wait.todayMin)}`, tone: "bad" });
|
|
78814
|
+
const record2 = !!o.records?.landedRecordToday && !!o.today && o.today.landed > 0;
|
|
78815
|
+
if (record2) facts.push({ text: `rekor baru: ${o.today.landed} spec mendarat`, tone: "good" });
|
|
78816
|
+
const empty = o.bensin?.find((b) => b.verdict === "habis" && b.exhaustAt);
|
|
78817
|
+
if (empty) facts.push({ text: `limit ${empty.label} habis \xB1${fmtClock(Date.parse(empty.exhaustAt))}`, tone: "bad" });
|
|
78818
|
+
if (!record2 && o.today && o.today.landed > 0) facts.push({ text: `${o.today.landed} spec mendarat`, tone: "good" });
|
|
78819
|
+
const top = facts.slice(0, 3);
|
|
78820
|
+
if (top.length === 0) return { text: "Hari yang tenang.", parts: [{ text: "Hari yang tenang." }] };
|
|
78821
|
+
top[0] = { ...top[0], text: top[0].text.charAt(0).toUpperCase() + top[0].text.slice(1) };
|
|
78822
|
+
const parts = [];
|
|
78823
|
+
top.forEach((f, i) => {
|
|
78824
|
+
if (i > 0) parts.push({ text: i === top.length - 1 ? top.length === 2 ? " dan " : ", dan " : ", " });
|
|
78825
|
+
parts.push(f);
|
|
78826
|
+
});
|
|
78827
|
+
parts.push({ text: "." });
|
|
78828
|
+
return { text: parts.map((p3) => p3.text).join(""), parts };
|
|
78829
|
+
}
|
|
78830
|
+
function safe(label2, f) {
|
|
78831
|
+
try {
|
|
78832
|
+
return f();
|
|
78833
|
+
} catch (err) {
|
|
78834
|
+
console.warn(`overview: panel ${label2} gagal dihitung:`, err);
|
|
78835
|
+
return null;
|
|
78836
|
+
}
|
|
78837
|
+
}
|
|
78838
|
+
function computeOverview(now, src) {
|
|
78839
|
+
const { specs, sessions, invocations } = src;
|
|
78840
|
+
const t0 = startOfDay(now);
|
|
78841
|
+
const today = specs && sessions ? safe("today", () => dayStats(t0, specs, sessions)) : null;
|
|
78842
|
+
const yesterday = specs && sessions ? safe("yesterday", () => dayStats(addDays(t0, -1), specs, sessions)) : null;
|
|
78843
|
+
const wait = safe("wait", () => waitStats(now, src.waits, sessions));
|
|
78844
|
+
const bensin = safe("bensin", () => bensinStats(now, src.limits));
|
|
78845
|
+
const records = specs && sessions ? safe("records", () => recordStats(now, specs, sessions)) : null;
|
|
78846
|
+
return {
|
|
78847
|
+
generatedAt: new Date(now).toISOString(),
|
|
78848
|
+
recordingSince: new Date(src.recordingSince).toISOString(),
|
|
78849
|
+
today,
|
|
78850
|
+
yesterday,
|
|
78851
|
+
wait,
|
|
78852
|
+
bensin,
|
|
78853
|
+
records,
|
|
78854
|
+
river: specs ? safe("river", () => riverStats(now, specs)) : null,
|
|
78855
|
+
tokens: invocations ? safe("tokens", () => tokenStats(now, invocations, today?.landed ?? null)) : null,
|
|
78856
|
+
heatmap: sessions ? safe("heatmap", () => heatmapStats(now, sessions)) : null,
|
|
78857
|
+
headline: safe("headline", () => headline({ today, wait, records, bensin })) ?? { text: "Hari yang tenang.", parts: [{ text: "Hari yang tenang." }] }
|
|
78858
|
+
};
|
|
78859
|
+
}
|
|
78860
|
+
|
|
78861
|
+
// src/services/overview-recorder.ts
|
|
78862
|
+
init_pty();
|
|
78863
|
+
var MAX_EPISODES = 2e3;
|
|
78864
|
+
var KEEP_MS = 2 * HOUR_MS2;
|
|
78865
|
+
var MIN_GAP_MS = 25e3;
|
|
78866
|
+
var TICK_MS2 = 1e4;
|
|
78867
|
+
var LIMIT_EVERY_TICKS = 3;
|
|
78868
|
+
var since = Date.now();
|
|
78869
|
+
var open5 = /* @__PURE__ */ new Map();
|
|
78870
|
+
var closed = [];
|
|
78871
|
+
var series = /* @__PURE__ */ new Map();
|
|
78872
|
+
var timer4;
|
|
78873
|
+
var ticks = 0;
|
|
78874
|
+
function observeSessions(list2, now = Date.now()) {
|
|
78875
|
+
const waiting2 = /* @__PURE__ */ new Set();
|
|
78876
|
+
for (const s2 of list2) {
|
|
78877
|
+
if (s2.exited || !s2.decision || s2.deciding) continue;
|
|
78878
|
+
waiting2.add(s2.id);
|
|
78879
|
+
if (open5.has(s2.id)) continue;
|
|
78880
|
+
const at = s2.decisionAt ? Date.parse(s2.decisionAt) : Number.NaN;
|
|
78881
|
+
open5.set(s2.id, Number.isFinite(at) ? Math.min(at, now) : now);
|
|
78882
|
+
}
|
|
78883
|
+
for (const [id2, start] of open5) {
|
|
78884
|
+
if (waiting2.has(id2)) continue;
|
|
78885
|
+
open5.delete(id2);
|
|
78886
|
+
closed.push({ start, end: now });
|
|
78887
|
+
}
|
|
78888
|
+
if (closed.length > MAX_EPISODES) closed = closed.slice(-MAX_EPISODES);
|
|
78889
|
+
}
|
|
78890
|
+
function observeLimits(source, windows, now = Date.now()) {
|
|
78891
|
+
for (const w of windows) {
|
|
78892
|
+
const k = `${source}:${w.key}`;
|
|
78893
|
+
let s2 = series.get(k);
|
|
78894
|
+
if (!s2) {
|
|
78895
|
+
s2 = { source, key: w.key, label: w.label, resetsAt: w.resetsAt, samples: [] };
|
|
78896
|
+
series.set(k, s2);
|
|
78897
|
+
}
|
|
78898
|
+
s2.label = w.label;
|
|
78899
|
+
s2.resetsAt = w.resetsAt;
|
|
78900
|
+
const last2 = s2.samples.at(-1);
|
|
78901
|
+
if (last2 && w.usedPct < last2.pct) s2.samples = [];
|
|
78902
|
+
else if (last2 && now - last2.at < MIN_GAP_MS) continue;
|
|
78903
|
+
s2.samples.push({ at: now, pct: w.usedPct });
|
|
78904
|
+
s2.samples = s2.samples.filter((p3) => p3.at >= now - KEEP_MS);
|
|
78905
|
+
}
|
|
78906
|
+
}
|
|
78907
|
+
var waitSpans = () => [...closed, ...[...open5.values()].map((start) => ({ start, end: null }))];
|
|
78908
|
+
var limitSeries = () => [...series.values()].map((s2) => ({ ...s2, samples: [...s2.samples] }));
|
|
78909
|
+
var recordingSince = () => since;
|
|
78910
|
+
async function tick() {
|
|
78911
|
+
const list2 = await listSessionsAsync();
|
|
78912
|
+
observeSessions(list2.map((s2) => ({ ...s2, deciding: isDeciding(s2.id) })));
|
|
78913
|
+
if (ticks++ % LIMIT_EVERY_TICKS !== 0) return;
|
|
78914
|
+
const [claude, codex2] = await Promise.all([getLimits(), getCodexLimits()]);
|
|
78915
|
+
if (claude.status !== "unavailable") observeLimits("claude", claude.windows);
|
|
78916
|
+
if (codex2.status !== "unavailable") observeLimits("codex", codex2.windows);
|
|
78917
|
+
}
|
|
78918
|
+
function startOverviewRecorder() {
|
|
78919
|
+
if (timer4) return;
|
|
78920
|
+
void tick().catch((e) => console.warn("perekam overview:", e));
|
|
78921
|
+
timer4 = setInterval(() => {
|
|
78922
|
+
void tick().catch(() => {
|
|
78923
|
+
});
|
|
78924
|
+
}, TICK_MS2);
|
|
78925
|
+
timer4.unref();
|
|
78926
|
+
}
|
|
78927
|
+
|
|
78928
|
+
// src/services/overview-source.ts
|
|
78929
|
+
async function settle2(label2, p3) {
|
|
78930
|
+
try {
|
|
78931
|
+
return await p3;
|
|
78932
|
+
} catch (err) {
|
|
78933
|
+
console.warn(`overview: sumber ${label2} gagal:`, err);
|
|
78934
|
+
return null;
|
|
78935
|
+
}
|
|
78936
|
+
}
|
|
78937
|
+
var RECORD_WINDOW_DAYS2 = 90;
|
|
78938
|
+
async function getOverview(now = Date.now()) {
|
|
78939
|
+
const since2 = new Date(addDays(startOfDay(now), -(RECORD_WINDOW_DAYS2 - 1)));
|
|
78940
|
+
const sinceYesterday = new Date(addDays(startOfDay(now), -1));
|
|
78941
|
+
const [specs, sessions, invocations] = await Promise.all([
|
|
78942
|
+
settle2("spec", prisma.spec.findMany({
|
|
78943
|
+
where: { OR: [{ createdAt: { gte: since2 } }, { startedAt: { gte: since2 } }, { doneAt: { gte: since2 } }] },
|
|
78944
|
+
select: { createdAt: true, startedAt: true, doneAt: true }
|
|
78945
|
+
})),
|
|
78946
|
+
settle2("sessionHistory", prisma.sessionHistory.findMany({
|
|
78947
|
+
where: {
|
|
78948
|
+
kind: { in: [...AGENT_KINDS] },
|
|
78949
|
+
OR: [{ startedAt: { gte: since2 } }, { endedAt: null }, { endedAt: { gte: since2 } }]
|
|
78950
|
+
},
|
|
78951
|
+
select: { kind: true, startedAt: true, endedAt: true, exitCode: true, endedReason: true }
|
|
78952
|
+
})),
|
|
78953
|
+
settle2("agentInvocation", prisma.agentInvocation.findMany({
|
|
78954
|
+
where: { startedAt: { gte: sinceYesterday } },
|
|
78955
|
+
select: { agentName: true, startedAt: true, inputTokens: true, outputTokens: true, cachedTokens: true }
|
|
78956
|
+
}))
|
|
78957
|
+
]);
|
|
78958
|
+
return computeOverview(now, {
|
|
78959
|
+
specs,
|
|
78960
|
+
sessions,
|
|
78961
|
+
invocations,
|
|
78962
|
+
waits: waitSpans(),
|
|
78963
|
+
limits: limitSeries(),
|
|
78964
|
+
recordingSince: recordingSince()
|
|
78965
|
+
});
|
|
78966
|
+
}
|
|
78967
|
+
|
|
78514
78968
|
// src/services/events.ts
|
|
78515
78969
|
init_db();
|
|
78516
78970
|
init_config2();
|
|
@@ -79293,11 +79747,11 @@ var HOUSEKEEPING_MS = 6e4;
|
|
|
79293
79747
|
var busyPulse = false;
|
|
79294
79748
|
var lastPulseAt = 0;
|
|
79295
79749
|
var pulseEndedAt = 0;
|
|
79296
|
-
var
|
|
79750
|
+
var timer5;
|
|
79297
79751
|
function lastPulse() {
|
|
79298
79752
|
return lastPulseAt;
|
|
79299
79753
|
}
|
|
79300
|
-
async function
|
|
79754
|
+
async function tick2(now, deps = {}) {
|
|
79301
79755
|
let cfg;
|
|
79302
79756
|
try {
|
|
79303
79757
|
cfg = await (deps.cfg ?? getLead)();
|
|
@@ -79348,10 +79802,10 @@ async function tick(now, deps = {}) {
|
|
|
79348
79802
|
await Promise.all(jobs);
|
|
79349
79803
|
}
|
|
79350
79804
|
function startLead(deps = {}) {
|
|
79351
|
-
if (
|
|
79352
|
-
|
|
79353
|
-
|
|
79354
|
-
void
|
|
79805
|
+
if (timer5) return;
|
|
79806
|
+
timer5 = setInterval(() => void tick2(Date.now(), deps), HOUSEKEEPING_MS);
|
|
79807
|
+
timer5.unref();
|
|
79808
|
+
void tick2(Date.now(), deps);
|
|
79355
79809
|
}
|
|
79356
79810
|
|
|
79357
79811
|
// src/services/lead/views.ts
|
|
@@ -79362,7 +79816,7 @@ async function buildLeadStatus() {
|
|
|
79362
79816
|
select: { id: true, name: true },
|
|
79363
79817
|
orderBy: { id: "asc" }
|
|
79364
79818
|
});
|
|
79365
|
-
const
|
|
79819
|
+
const since2 = new Date(Date.now() - 24 * 60 * 60 * 1e3);
|
|
79366
79820
|
let live = [];
|
|
79367
79821
|
try {
|
|
79368
79822
|
live = (await listSessionsAsync()).filter((s2) => !s2.exited);
|
|
@@ -79378,7 +79832,7 @@ async function buildLeadStatus() {
|
|
|
79378
79832
|
name: p3.name,
|
|
79379
79833
|
optIn: true,
|
|
79380
79834
|
paused: !leadActive(cfg, p3.id),
|
|
79381
|
-
decisions24h: await prisma.leadDecision.count({ where: { projectId: p3.id, createdAt: { gte:
|
|
79835
|
+
decisions24h: await prisma.leadDecision.count({ where: { projectId: p3.id, createdAt: { gte: since2 } } }),
|
|
79382
79836
|
openSessions: live.filter((s2) => s2.projectId === p3.id).length
|
|
79383
79837
|
})));
|
|
79384
79838
|
const last2 = lastPulse();
|
|
@@ -79550,7 +80004,16 @@ var GROUPS = [
|
|
|
79550
80004
|
// bukan empat grup: keempatnya berubah dari peristiwa yang sama (item diajukan/diputuskan) dan
|
|
79551
80005
|
// dibaca satu komponen yang sama. `cookieOnly` TIDAK dipasang — muatannya jumlah agregat tanpa
|
|
79552
80006
|
// id/judul, dan agent token sudah boleh membaca daftar sumbernya lewat capability masing-masing.
|
|
79553
|
-
{ everyTicks: 5, last: "", build: async () => ({ t: "pending", counts: await pendingCounts() }) }
|
|
80007
|
+
{ everyTicks: 5, last: "", build: async () => ({ t: "pending", counts: await pendingCounts() }) },
|
|
80008
|
+
// ADR-0220 · grup GLOBAL ke-12 · agregat Overview "Koran Pagi". 30 dtk: angkanya harian, bukan
|
|
80009
|
+
// detik-an. `generatedAt` berubah tiap build, jadi kunci dedup mengosongkannya; frame lahir hanya
|
|
80010
|
+
// saat isi agregat benar-benar berubah. Tidak `cookieOnly`: angka agregat tanpa id/judul.
|
|
80011
|
+
{
|
|
80012
|
+
everyTicks: 30,
|
|
80013
|
+
last: "",
|
|
80014
|
+
build: async () => ({ t: "overview", overview: await getOverview() }),
|
|
80015
|
+
sig: (m) => JSON.stringify({ ...m.overview, generatedAt: null })
|
|
80016
|
+
}
|
|
79554
80017
|
];
|
|
79555
80018
|
function broadcast2(msg, s2, cookieOnly = false) {
|
|
79556
80019
|
for (const c of clients) {
|
|
@@ -79637,9 +80100,9 @@ function subscribeClient(c, subs) {
|
|
|
79637
80100
|
if (e.subscribers.size === 0) entries2.delete(key);
|
|
79638
80101
|
}
|
|
79639
80102
|
}
|
|
79640
|
-
var
|
|
80103
|
+
var tick3 = 0;
|
|
79641
80104
|
var busy = false;
|
|
79642
|
-
var
|
|
80105
|
+
var timer6;
|
|
79643
80106
|
async function runGroup(g) {
|
|
79644
80107
|
const t0 = profStart();
|
|
79645
80108
|
let msg;
|
|
@@ -79679,9 +80142,9 @@ async function runGroup(g) {
|
|
|
79679
80142
|
async function __tick() {
|
|
79680
80143
|
if (busy) return;
|
|
79681
80144
|
busy = true;
|
|
79682
|
-
|
|
80145
|
+
tick3++;
|
|
79683
80146
|
try {
|
|
79684
|
-
const results = await Promise.all(GROUPS.filter((g) =>
|
|
80147
|
+
const results = await Promise.all(GROUPS.filter((g) => tick3 % g.everyTicks === 0).map(runGroup));
|
|
79685
80148
|
for (const r of results) {
|
|
79686
80149
|
if (!r) continue;
|
|
79687
80150
|
r.g.last = r.json;
|
|
@@ -79699,19 +80162,19 @@ async function __tick() {
|
|
|
79699
80162
|
}
|
|
79700
80163
|
}
|
|
79701
80164
|
function startLoop() {
|
|
79702
|
-
if (
|
|
80165
|
+
if (timer6) return;
|
|
79703
80166
|
startLagMonitor();
|
|
79704
|
-
|
|
80167
|
+
timer6 = setInterval(() => {
|
|
79705
80168
|
void __tick();
|
|
79706
80169
|
}, tickMs());
|
|
79707
|
-
|
|
80170
|
+
timer6.unref();
|
|
79708
80171
|
}
|
|
79709
80172
|
function stopLoop() {
|
|
79710
|
-
if (
|
|
79711
|
-
clearInterval(
|
|
79712
|
-
|
|
80173
|
+
if (timer6) {
|
|
80174
|
+
clearInterval(timer6);
|
|
80175
|
+
timer6 = void 0;
|
|
79713
80176
|
}
|
|
79714
|
-
|
|
80177
|
+
tick3 = 0;
|
|
79715
80178
|
for (const g of GROUPS) {
|
|
79716
80179
|
g.last = "";
|
|
79717
80180
|
g.lastSig = void 0;
|
|
@@ -80356,6 +80819,11 @@ async function presence_default(app3) {
|
|
|
80356
80819
|
app3.get("/presence", async () => presenceView());
|
|
80357
80820
|
}
|
|
80358
80821
|
|
|
80822
|
+
// src/routes/overview.ts
|
|
80823
|
+
async function overview_default(app3) {
|
|
80824
|
+
app3.get("/overview", async () => getOverview());
|
|
80825
|
+
}
|
|
80826
|
+
|
|
80359
80827
|
// src/routes/session-results.ts
|
|
80360
80828
|
init_db();
|
|
80361
80829
|
var view6 = (r) => ({
|
|
@@ -80666,7 +81134,7 @@ async function intakeTicket(input) {
|
|
|
80666
81134
|
let ticketBytes = 0;
|
|
80667
81135
|
for (const f of input.files) {
|
|
80668
81136
|
try {
|
|
80669
|
-
const
|
|
81137
|
+
const safe3 = await processUpload({
|
|
80670
81138
|
buffer: f.buf,
|
|
80671
81139
|
clientName: f.name,
|
|
80672
81140
|
clientMime: f.mime,
|
|
@@ -80679,17 +81147,17 @@ async function intakeTicket(input) {
|
|
|
80679
81147
|
data: {
|
|
80680
81148
|
ticketId: ticket.id,
|
|
80681
81149
|
projectId: input.projectId,
|
|
80682
|
-
filename:
|
|
80683
|
-
mimeType:
|
|
80684
|
-
size:
|
|
80685
|
-
storageKey:
|
|
81150
|
+
filename: safe3.filename,
|
|
81151
|
+
mimeType: safe3.mimeType,
|
|
81152
|
+
size: safe3.size,
|
|
81153
|
+
storageKey: safe3.storageKey
|
|
80686
81154
|
}
|
|
80687
81155
|
});
|
|
80688
81156
|
} catch (error) {
|
|
80689
|
-
await deleteUpload(
|
|
81157
|
+
await deleteUpload(safe3.storageKey);
|
|
80690
81158
|
throw error;
|
|
80691
81159
|
}
|
|
80692
|
-
ticketBytes +=
|
|
81160
|
+
ticketBytes += safe3.size;
|
|
80693
81161
|
await notifySynced("ticketAttachment", att.id);
|
|
80694
81162
|
} catch (error) {
|
|
80695
81163
|
if (!(error instanceof UploadError)) throw error;
|
|
@@ -82709,7 +83177,7 @@ var QaImportError = class extends Error {
|
|
|
82709
83177
|
this.status = status2;
|
|
82710
83178
|
}
|
|
82711
83179
|
};
|
|
82712
|
-
var
|
|
83180
|
+
var safe2 = (s2) => s2.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
82713
83181
|
var MIME_BY_EXT = {
|
|
82714
83182
|
png: "image/png",
|
|
82715
83183
|
jpg: "image/jpeg",
|
|
@@ -82737,7 +83205,7 @@ async function exportReport(projectId, reportId) {
|
|
|
82737
83205
|
const owner = codeOf(a.ownerType, a.ownerId);
|
|
82738
83206
|
const n2 = (seq.get(owner) ?? 0) + 1;
|
|
82739
83207
|
seq.set(owner, n2);
|
|
82740
|
-
paths2[a.id] = `attachments/${owner}-${n2}-${
|
|
83208
|
+
paths2[a.id] = `attachments/${owner}-${n2}-${safe2(a.filename)}`;
|
|
82741
83209
|
files2.push({ name: paths2[a.id], data });
|
|
82742
83210
|
present.push(a);
|
|
82743
83211
|
}
|
|
@@ -84163,7 +84631,7 @@ async function testTelegramConnection(deps = {}) {
|
|
|
84163
84631
|
}
|
|
84164
84632
|
const timeoutMs = deps.timeoutMs ?? 1e4;
|
|
84165
84633
|
const controller = new AbortController();
|
|
84166
|
-
const
|
|
84634
|
+
const timer11 = setTimeout(() => controller.abort(), timeoutMs);
|
|
84167
84635
|
const send = deps.transport ?? ((url2, init) => fetch(url2, init));
|
|
84168
84636
|
const transport = (url2, init) => send(url2, { ...init, signal: controller.signal });
|
|
84169
84637
|
const client = new TelegramApiClient(botToken, transport);
|
|
@@ -84177,7 +84645,7 @@ async function testTelegramConnection(deps = {}) {
|
|
|
84177
84645
|
const message = controller.signal.aborted ? `Timeout ${timeoutMs} ms \u2014 Telegram tidak menjawab.` : raw;
|
|
84178
84646
|
return { ok: false, inbound, error: sanitizeTelegramOutput(message, [botToken]).slice(0, 500) };
|
|
84179
84647
|
} finally {
|
|
84180
|
-
clearTimeout(
|
|
84648
|
+
clearTimeout(timer11);
|
|
84181
84649
|
}
|
|
84182
84650
|
}
|
|
84183
84651
|
function resolveTestChatId() {
|
|
@@ -84411,7 +84879,7 @@ async function sendOnce(o, deps = {}) {
|
|
|
84411
84879
|
nowSec: o.nowSec ?? Math.floor(Date.now() / 1e3)
|
|
84412
84880
|
});
|
|
84413
84881
|
const ac = new AbortController();
|
|
84414
|
-
const
|
|
84882
|
+
const timer11 = setTimeout(() => ac.abort(), WEBHOOK_TIMEOUT_MS);
|
|
84415
84883
|
try {
|
|
84416
84884
|
const res = deps.fetcher ? await deps.fetcher(parsed.url.toString(), {
|
|
84417
84885
|
method: "POST",
|
|
@@ -84445,7 +84913,7 @@ async function sendOnce(o, deps = {}) {
|
|
|
84445
84913
|
const msg = e.name === "AbortError" ? `timeout ${WEBHOOK_TIMEOUT_MS} ms` : e.message;
|
|
84446
84914
|
return fail4(msg);
|
|
84447
84915
|
} finally {
|
|
84448
|
-
clearTimeout(
|
|
84916
|
+
clearTimeout(timer11);
|
|
84449
84917
|
}
|
|
84450
84918
|
}
|
|
84451
84919
|
|
|
@@ -84840,10 +85308,10 @@ function sanitizeClientText(text2) {
|
|
|
84840
85308
|
return text2.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "").slice(0, MAX_PESAN);
|
|
84841
85309
|
}
|
|
84842
85310
|
function wrapClientMessage(text2, nonce) {
|
|
84843
|
-
const
|
|
85311
|
+
const open6 = `<pesan-klien-${nonce}>`;
|
|
84844
85312
|
const close = `</pesan-klien-${nonce}>`;
|
|
84845
85313
|
const jinak = sanitizeClientText(text2).replaceAll("</pesan-klien", "<\u200B/pesan-klien").replaceAll("<pesan-klien", "<\u200Bpesan-klien");
|
|
84846
|
-
return `${
|
|
85314
|
+
return `${open6}
|
|
84847
85315
|
${jinak}
|
|
84848
85316
|
${close}`;
|
|
84849
85317
|
}
|
|
@@ -86731,6 +87199,7 @@ function buildApp({ requireAuth = true, agentDocFile, env = process.env } = {})
|
|
|
86731
87199
|
await api.register(bindings_default);
|
|
86732
87200
|
await api.register(sync_default);
|
|
86733
87201
|
await api.register(presence_default);
|
|
87202
|
+
await api.register(overview_default);
|
|
86734
87203
|
await api.register(session_results_default);
|
|
86735
87204
|
await api.register(session_history_default);
|
|
86736
87205
|
await api.register(session_events_default);
|
|
@@ -86776,17 +87245,17 @@ function createShutdown(o) {
|
|
|
86776
87245
|
let running;
|
|
86777
87246
|
return (sig) => running ??= (async () => {
|
|
86778
87247
|
log(`${sig} \u2014 menutup`);
|
|
86779
|
-
const
|
|
87248
|
+
const timer11 = setTimeout(() => {
|
|
86780
87249
|
log(`penutupan melewati ${o.timeoutMs ?? DEFAULT_TIMEOUT_MS} ms \u2014 keluar paksa`);
|
|
86781
87250
|
o.exit(1);
|
|
86782
87251
|
}, o.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
86783
|
-
|
|
87252
|
+
timer11.unref?.();
|
|
86784
87253
|
try {
|
|
86785
87254
|
await o.close();
|
|
86786
87255
|
} catch (e) {
|
|
86787
87256
|
log(`penutupan gagal: ${e.message}`);
|
|
86788
87257
|
}
|
|
86789
|
-
clearTimeout(
|
|
87258
|
+
clearTimeout(timer11);
|
|
86790
87259
|
o.exit(0);
|
|
86791
87260
|
})();
|
|
86792
87261
|
}
|
|
@@ -87051,7 +87520,7 @@ var prodEnd = {
|
|
|
87051
87520
|
reconcile: () => reconcile(reconcileProdDeps),
|
|
87052
87521
|
scanDecisions: () => scanDecisions()
|
|
87053
87522
|
};
|
|
87054
|
-
async function
|
|
87523
|
+
async function tick4(now, deps, end2 = prodEnd) {
|
|
87055
87524
|
const cfg = await getScheduler();
|
|
87056
87525
|
if (!cfg.enabled) return;
|
|
87057
87526
|
for (const src of listSources()) {
|
|
@@ -87116,13 +87585,13 @@ var prodDeps = {
|
|
|
87116
87585
|
launchCron: async (cron) => (await startCronSession(cron)).id
|
|
87117
87586
|
})
|
|
87118
87587
|
};
|
|
87119
|
-
var
|
|
87120
|
-
var
|
|
87588
|
+
var TICK_MS3 = 1e4;
|
|
87589
|
+
var timer7;
|
|
87121
87590
|
function startScheduler(deps = prodDeps) {
|
|
87122
|
-
if (
|
|
87123
|
-
|
|
87124
|
-
|
|
87125
|
-
void
|
|
87591
|
+
if (timer7) return;
|
|
87592
|
+
timer7 = setInterval(() => void tick4(Date.now(), deps), TICK_MS3);
|
|
87593
|
+
timer7.unref();
|
|
87594
|
+
void tick4(Date.now(), deps);
|
|
87126
87595
|
}
|
|
87127
87596
|
|
|
87128
87597
|
// src/services/scheduler/sources/backlog.ts
|
|
@@ -87294,7 +87763,7 @@ async function installWebhooks() {
|
|
|
87294
87763
|
// src/services/webhooks/engine.ts
|
|
87295
87764
|
init_src();
|
|
87296
87765
|
init_db();
|
|
87297
|
-
var
|
|
87766
|
+
var TICK_MS4 = 2e3;
|
|
87298
87767
|
var MAX_IN_FLIGHT = 4;
|
|
87299
87768
|
var PRUNE_EVERY_TICKS = 30;
|
|
87300
87769
|
function backoffAt(from, attempt) {
|
|
@@ -87437,15 +87906,15 @@ async function pruneHistory() {
|
|
|
87437
87906
|
}
|
|
87438
87907
|
return removed;
|
|
87439
87908
|
}
|
|
87440
|
-
var
|
|
87441
|
-
var
|
|
87909
|
+
var timer8;
|
|
87910
|
+
var ticks2 = 0;
|
|
87442
87911
|
var busy2 = false;
|
|
87443
|
-
async function
|
|
87912
|
+
async function tick5() {
|
|
87444
87913
|
if (busy2) return;
|
|
87445
87914
|
busy2 = true;
|
|
87446
87915
|
try {
|
|
87447
87916
|
await deliverDue(/* @__PURE__ */ new Date());
|
|
87448
|
-
if (++
|
|
87917
|
+
if (++ticks2 % PRUNE_EVERY_TICKS === 0) await pruneHistory();
|
|
87449
87918
|
} catch (e) {
|
|
87450
87919
|
console.error("webhook engine:", e);
|
|
87451
87920
|
} finally {
|
|
@@ -87453,9 +87922,9 @@ async function tick4() {
|
|
|
87453
87922
|
}
|
|
87454
87923
|
}
|
|
87455
87924
|
function startWebhookEngine() {
|
|
87456
|
-
if (
|
|
87457
|
-
|
|
87458
|
-
|
|
87925
|
+
if (timer8) return;
|
|
87926
|
+
timer8 = setInterval(() => void tick5(), TICK_MS4);
|
|
87927
|
+
timer8.unref();
|
|
87459
87928
|
void resetStuckDeliveries().then((n2) => {
|
|
87460
87929
|
if (n2) console.log(`webhook: ${n2} pengiriman tertinggal dikembalikan ke antrean`);
|
|
87461
87930
|
}).catch((e) => console.error("webhook reset:", e));
|
|
@@ -87470,7 +87939,7 @@ init_local_binding();
|
|
|
87470
87939
|
init_notifications2();
|
|
87471
87940
|
var AUTO_MERGE_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
87472
87941
|
var AUTO_MERGE_GRACE_MS = 15 * 60 * 1e3;
|
|
87473
|
-
var
|
|
87942
|
+
var TICK_MS5 = 6e4;
|
|
87474
87943
|
var exec11 = promisify17(execFile20);
|
|
87475
87944
|
var GIT8 = { timeout: 3e4, maxBuffer: 1 << 24, encoding: "utf8" };
|
|
87476
87945
|
var gitOut = async (cwd, args) => {
|
|
@@ -87493,9 +87962,9 @@ var prodAutoMergeDeps = {
|
|
|
87493
87962
|
deleteBranch: deleteMergedBranch
|
|
87494
87963
|
};
|
|
87495
87964
|
async function candidates(now) {
|
|
87496
|
-
const
|
|
87965
|
+
const since2 = new Date(now.getTime() - AUTO_MERGE_WINDOW_MS);
|
|
87497
87966
|
const done = await prisma.notification.findMany({
|
|
87498
|
-
where: { type: "done", createdAt: { gte:
|
|
87967
|
+
where: { type: "done", createdAt: { gte: since2 }, specId: { not: null } },
|
|
87499
87968
|
select: { specId: true, projectId: true, createdAt: true }
|
|
87500
87969
|
});
|
|
87501
87970
|
if (!done.length) return [];
|
|
@@ -87559,9 +88028,9 @@ async function settleOne(c, deps, now) {
|
|
|
87559
88028
|
}
|
|
87560
88029
|
return report(`Auto-merge ${spec.id} \u2192 ${target3} GAGAL: ${res.error}`);
|
|
87561
88030
|
}
|
|
87562
|
-
var
|
|
88031
|
+
var timer9;
|
|
87563
88032
|
var busy3 = false;
|
|
87564
|
-
async function
|
|
88033
|
+
async function tick6() {
|
|
87565
88034
|
if (busy3) return;
|
|
87566
88035
|
busy3 = true;
|
|
87567
88036
|
try {
|
|
@@ -87573,9 +88042,9 @@ async function tick5() {
|
|
|
87573
88042
|
}
|
|
87574
88043
|
}
|
|
87575
88044
|
function startAutoMerge() {
|
|
87576
|
-
if (
|
|
87577
|
-
|
|
87578
|
-
|
|
88045
|
+
if (timer9) return;
|
|
88046
|
+
timer9 = setInterval(() => void tick6(), TICK_MS5);
|
|
88047
|
+
timer9.unref();
|
|
87579
88048
|
}
|
|
87580
88049
|
|
|
87581
88050
|
// src/server.ts
|
|
@@ -87874,9 +88343,9 @@ async function pruneSyncFeed(now, dryRun = false) {
|
|
|
87874
88343
|
}
|
|
87875
88344
|
return pruned;
|
|
87876
88345
|
}
|
|
87877
|
-
var
|
|
88346
|
+
var timer10;
|
|
87878
88347
|
function startRetentionSweep() {
|
|
87879
|
-
if (
|
|
88348
|
+
if (timer10) return;
|
|
87880
88349
|
const sweep = () => {
|
|
87881
88350
|
const holds = new Set((process.env.HANOMAN_RETENTION_HOLDS ?? "").split(",").map((v) => v.trim()).filter(Boolean));
|
|
87882
88351
|
void runRetention({ holds }).then((report) => {
|
|
@@ -87886,8 +88355,8 @@ function startRetentionSweep() {
|
|
|
87886
88355
|
}).catch((error) => console.error("retention sweep:", error));
|
|
87887
88356
|
};
|
|
87888
88357
|
sweep();
|
|
87889
|
-
|
|
87890
|
-
|
|
88358
|
+
timer10 = setInterval(sweep, 24 * 60 * 6e4);
|
|
88359
|
+
timer10.unref?.();
|
|
87891
88360
|
}
|
|
87892
88361
|
|
|
87893
88362
|
// src/server.ts
|
|
@@ -87987,6 +88456,7 @@ bootstrapReady.then(async () => {
|
|
|
87987
88456
|
startAutoMerge();
|
|
87988
88457
|
startWorktreeReaper();
|
|
87989
88458
|
startRetentionSweep();
|
|
88459
|
+
startOverviewRecorder();
|
|
87990
88460
|
}).catch((err) => {
|
|
87991
88461
|
console.error("listen gagal:", err);
|
|
87992
88462
|
process.exit(1);
|