hanoman 0.7.1 → 0.8.0
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 +11 -11
- package/dist/server.js +786 -441
- package/package.json +1 -1
- package/web/assets/{ChangelogScreen-D6brfukI.js → ChangelogScreen-_gDN94cU.js} +1 -1
- package/web/assets/{DalangHanomanScreen-C8GIJv8X.js → DalangHanomanScreen-DYgYsjHL.js} +1 -1
- package/web/assets/{DocPreviewModal-4m5Afjx2.js → DocPreviewModal-BmiV6G97.js} +1 -1
- package/web/assets/{DocsWorkspace-8U-UYo4-.js → DocsWorkspace-B235YOee.js} +1 -1
- package/web/assets/{IdeScreen-8QAVPotm.js → IdeScreen-CKtV6kQg.js} +1 -1
- package/web/assets/{LeadScreen-CRs2p9QY.js → LeadScreen-CCbmOwDK.js} +1 -1
- package/web/assets/{ReviewScreen-CBmaRAHG.js → ReviewScreen-Cjn2UblA.js} +1 -1
- package/web/assets/{SchedulerScreen-C1dPfo1w.js → SchedulerScreen-CgWbTZRW.js} +1 -1
- package/web/assets/{SettingsScreen-rE_jbjMw.js → SettingsScreen-CgKsiBYb.js} +2 -2
- package/web/assets/{TeamScreen-Dz0rA4Tk.js → TeamScreen-BQaDdG1N.js} +1 -1
- package/web/assets/TerminalScreen-XuGax_Pi.js +2 -0
- package/web/assets/{TriageScreen-BZQvccje.js → TriageScreen-Dwb-RSdX.js} +1 -1
- package/web/assets/{VpsScreen-CPlPXExd.js → VpsScreen-B98EElUN.js} +1 -1
- package/web/assets/index-B5A1QnO4.js +1005 -0
- package/web/assets/{live-BWD2sDTC.js → live-DanRrGMa.js} +1 -1
- package/web/index.html +1 -1
- package/web/assets/TerminalScreen-Ca7K60wq.js +0 -2
- package/web/assets/index-C_btxEiC.js +0 -931
package/dist/server.js
CHANGED
|
@@ -3612,15 +3612,15 @@ var init_types = __esm({
|
|
|
3612
3612
|
}
|
|
3613
3613
|
}
|
|
3614
3614
|
if (effect.type === "refinement") {
|
|
3615
|
-
const executeRefinement = (
|
|
3616
|
-
const result = effect.refinement(
|
|
3615
|
+
const executeRefinement = (acc2) => {
|
|
3616
|
+
const result = effect.refinement(acc2, checkCtx);
|
|
3617
3617
|
if (ctx.common.async) {
|
|
3618
3618
|
return Promise.resolve(result);
|
|
3619
3619
|
}
|
|
3620
3620
|
if (result instanceof Promise) {
|
|
3621
3621
|
throw new Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead.");
|
|
3622
3622
|
}
|
|
3623
|
-
return
|
|
3623
|
+
return acc2;
|
|
3624
3624
|
};
|
|
3625
3625
|
if (ctx.common.async === false) {
|
|
3626
3626
|
const inner = this._def.schema._parseSync({
|
|
@@ -5023,7 +5023,7 @@ function cmpVersion(a, b) {
|
|
|
5023
5023
|
}
|
|
5024
5024
|
return 0;
|
|
5025
5025
|
}
|
|
5026
|
-
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, zManualDone, zSpec, NOTIFY_SOUNDS, MODELS, EFFORTS, E_5_6, E_LUNA, E_BASE, CODEX_MODELS, RETIRED_CODEX_MODELS, zCodex, CODEX_DEFAULTS, zSourceCommon, zScheduler, SCHEDULER_DEFAULTS, zGoal, GOAL_DEFAULTS, zConflict, CONFLICT_DEFAULTS, CHANGELOG_ENGINE_DEFAULTS, zLeadEngine, zLead, LEAD_DEFAULTS, zPortalChat, PORTAL_CHAT_DEFAULTS, zPhaseCell, zPhaseOverride, zPhaseOverrides, zFlowOrchestration, zOrchestration, ORCHESTRATION_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
|
|
5026
|
+
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, zManualDone, zSpec, zSpecListItem, zSpecSlim, NOTIFY_SOUNDS, MODELS, EFFORTS, E_5_6, E_LUNA, E_BASE, CODEX_MODELS, RETIRED_CODEX_MODELS, zCodex, CODEX_DEFAULTS, zSourceCommon, zScheduler, SCHEDULER_DEFAULTS, zGoal, GOAL_DEFAULTS, zConflict, CONFLICT_DEFAULTS, CHANGELOG_ENGINE_DEFAULTS, zLeadEngine, zLead, LEAD_DEFAULTS, zPortalChat, PORTAL_CHAT_DEFAULTS, zPhaseCell, zPhaseOverride, zPhaseOverrides, zFlowOrchestration, zOrchestration, ORCHESTRATION_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
|
|
5027
5027
|
var init_entities = __esm({
|
|
5028
5028
|
"../shared/src/entities.ts"() {
|
|
5029
5029
|
"use strict";
|
|
@@ -5129,6 +5129,11 @@ var init_entities = __esm({
|
|
|
5129
5129
|
// sesi atau belum selesai. `.nullable().default(null)` menjaga respons/klien versi lama parse.
|
|
5130
5130
|
manualDone: zManualDone.nullable().default(null)
|
|
5131
5131
|
});
|
|
5132
|
+
zSpecListItem = zSpec.omit({ payload: true, sourceHistory: true }).extend({
|
|
5133
|
+
version: external_exports.number().optional(),
|
|
5134
|
+
updatedAt: external_exports.string().optional()
|
|
5135
|
+
});
|
|
5136
|
+
zSpecSlim = zSpecListItem.omit({ objective: true });
|
|
5132
5137
|
NOTIFY_SOUNDS = [
|
|
5133
5138
|
"off",
|
|
5134
5139
|
"short",
|
|
@@ -6738,9 +6743,9 @@ function nextRun(spec, after, limitDays = 400) {
|
|
|
6738
6743
|
for (let d = 0; d <= limitDays; d++) {
|
|
6739
6744
|
const day2 = new Date(after.getFullYear(), after.getMonth(), after.getDate() + d);
|
|
6740
6745
|
if (!dayMatches(spec, day2)) continue;
|
|
6741
|
-
for (const
|
|
6746
|
+
for (const h2 of hours) {
|
|
6742
6747
|
for (const mi of minutes) {
|
|
6743
|
-
const cand = new Date(day2.getFullYear(), day2.getMonth(), day2.getDate(),
|
|
6748
|
+
const cand = new Date(day2.getFullYear(), day2.getMonth(), day2.getDate(), h2, mi, 0, 0);
|
|
6744
6749
|
if (cand.getTime() > t) return cand;
|
|
6745
6750
|
}
|
|
6746
6751
|
}
|
|
@@ -7760,8 +7765,8 @@ function coverageOf(docs) {
|
|
|
7760
7765
|
const linked = [...byCat.values()].filter(Boolean).length;
|
|
7761
7766
|
return Math.round(linked / byCat.size * 100);
|
|
7762
7767
|
}
|
|
7763
|
-
function docStatusFor(
|
|
7764
|
-
return
|
|
7768
|
+
function docStatusFor(pct3) {
|
|
7769
|
+
return pct3 >= 90 ? "ok" : pct3 >= 60 ? "drift" : "broken";
|
|
7765
7770
|
}
|
|
7766
7771
|
function isExternalLink(target3) {
|
|
7767
7772
|
return !target3 || /^(https?:|#|mailto:)/.test(target3);
|
|
@@ -9252,17 +9257,12 @@ var init_backlog = __esm({
|
|
|
9252
9257
|
inputSchema: obj({ properties: { spec: str(ID_HINT) }, required: ["spec"] }),
|
|
9253
9258
|
mode: "read",
|
|
9254
9259
|
capability: "backlog:read",
|
|
9255
|
-
samplePath: "/specs",
|
|
9260
|
+
samplePath: "/specs/SPEC-1",
|
|
9256
9261
|
sampleMethod: "GET",
|
|
9257
|
-
|
|
9258
|
-
//
|
|
9259
|
-
|
|
9260
|
-
shape: (raw
|
|
9261
|
-
const want = String(a.spec).trim().toLowerCase();
|
|
9262
|
-
const items = raw?.items ?? [];
|
|
9263
|
-
const hit = items.find((i) => String(i.id).toLowerCase() === want);
|
|
9264
|
-
return hit ? shapeSpecDetail(hit) : { error: `backlog "${String(a.spec)}" tidak ada. Cek ejaannya (bentuknya SPEC-nnn) atau cari dengan hanoman_backlog_search.` };
|
|
9265
|
-
}
|
|
9262
|
+
// SPEC-1267 · `GET /specs` tak lagi membawa `payload`/`sourceHistory`, jadi isi penuh HANYA ada di
|
|
9263
|
+
// `GET /specs/:id`. Pencocokan id persis kini urusan server (404 bila tak ada).
|
|
9264
|
+
build: (a) => ({ method: "GET", path: `/specs/${enc(String(a.spec).trim())}` }),
|
|
9265
|
+
shape: (raw) => shapeSpecDetail(raw)
|
|
9266
9266
|
},
|
|
9267
9267
|
{
|
|
9268
9268
|
name: "hanoman_backlog_docs_list",
|
|
@@ -13042,8 +13042,8 @@ function guideLine(guide, phase) {
|
|
|
13042
13042
|
}
|
|
13043
13043
|
function phaseSkillsFor(flow2, phase, method) {
|
|
13044
13044
|
const own = method.phaseSkills[phase] ?? [];
|
|
13045
|
-
const
|
|
13046
|
-
return writesCode(flow2) && phase ===
|
|
13045
|
+
const phases2 = PIPELINES[flow2];
|
|
13046
|
+
return writesCode(flow2) && phase === phases2[phases2.length - 1] ? [.../* @__PURE__ */ new Set([...own, ...method.exitSkills])] : [...own];
|
|
13047
13047
|
}
|
|
13048
13048
|
function orchestratorClause(plan, o = {}) {
|
|
13049
13049
|
const codex2 = plan.runtime === "codex";
|
|
@@ -13320,15 +13320,15 @@ var init_prompt = __esm({
|
|
|
13320
13320
|
AUTONOMY_CLAUSE = `Jalankan seluruh pipeline sampai tuntas tanpa berhenti di batas antar-fase. Checkpoint "review"/"approval"/"need review" milik skill superpowers BUKAN titik berhenti di sini \u2014 lanjut saja ke fase berikutnya. Berhenti HANYA untuk ${AMBIGUOUS}: JANGAN memutuskannya sendiri, ${ASK_ROUTE} Selain itu, terus lanjut.`;
|
|
13321
13321
|
AUTONOMY_CLAUSE_FULL = `Kamu diluncurkan scheduler \u2014 mungkin tak ada manusia yang sedang menonton terminal ini. Tembus seluruh pipeline sampai stage \`done\`, lalu commit & push: checkpoint "review"/"approval" milik skill BUKAN titik berhenti dan jangan menunggu persetujuan siapa pun. Tetapi JANGAN memutuskan sendiri ${AMBIGUOUS}: ${ASK_ROUTE} Catat keputusan penting di pesan commit. Merge ke branch utama tetap dilakukan manusia, bukan kamu.`;
|
|
13322
13322
|
autonomyClause = (mode) => mode === "full-control" ? AUTONOMY_CLAUSE_FULL : AUTONOMY_CLAUSE;
|
|
13323
|
-
phaseInstruction = (
|
|
13324
|
-
const base2 = `Kerjakan fase berurutan: ${
|
|
13323
|
+
phaseInstruction = (phases2, method) => {
|
|
13324
|
+
const base2 = `Kerjakan fase berurutan: ${phases2.join(" \u2192 ")}.
|
|
13325
13325
|
Setiap kali sebuah fase selesai (atau kamu putuskan dilewati), append satu baris ke berkas di $HANOMAN_PHASE_FILE \u2014 persis: \`echo "<Nama Fase> done" >> "$HANOMAN_PHASE_FILE"\`, atau \`skipped\` sebagai ganti \`done\`. Nama fase ditulis apa adanya seperti di atas.`;
|
|
13326
|
-
if (!
|
|
13326
|
+
if (!phases2.includes("Plan") || !phases2.includes("Execute")) return base2;
|
|
13327
13327
|
return base2 + `
|
|
13328
13328
|
Execute BELUM selesai selama plan (\`${method.planDir}/**\`) masih punya task \`- [ ]\`: kerjakan SEMUA PR/task sampai tiap kotak jadi \`- [x]\` sebelum menulis \`Execute done\`. hanoman menahan backlog di \`executing\`, bukan \`done\`, selama masih ada \`- [ ]\`.`;
|
|
13329
13329
|
};
|
|
13330
|
-
skillInstruction = (flow2,
|
|
13331
|
-
const lines2 =
|
|
13330
|
+
skillInstruction = (flow2, phases2, method) => {
|
|
13331
|
+
const lines2 = phases2.map((p3) => {
|
|
13332
13332
|
const skills = phaseSkillsFor(flow2, p3, method);
|
|
13333
13333
|
return skills.length ? `- ${p3}: ${skills.join(", ")}` : "";
|
|
13334
13334
|
}).filter(Boolean);
|
|
@@ -13671,10 +13671,10 @@ function goalFlowCondition(flow2, specId, branchTo, spec) {
|
|
|
13671
13671
|
}
|
|
13672
13672
|
function defaultGoalCondition({ flow: flow2, specId, branchTo, spec }) {
|
|
13673
13673
|
if (isGoalShapedFlow(flow2)) return goalFlowCondition(flow2, specId, branchTo, spec);
|
|
13674
|
-
const
|
|
13675
|
-
const planGate =
|
|
13674
|
+
const phases2 = PIPELINES[flow2];
|
|
13675
|
+
const planGate = phases2.includes("Plan") && phases2.includes("Execute");
|
|
13676
13676
|
const clauses = [
|
|
13677
|
-
`1. output \`cat "$HANOMAN_PHASE_FILE"\` yang memuat satu baris untuk SETIAP fase ${
|
|
13677
|
+
`1. output \`cat "$HANOMAN_PHASE_FILE"\` yang memuat satu baris untuk SETIAP fase ${phases2.join(" \u2192 ")}, masing-masing berakhiran \`done\` atau \`skipped\`;`
|
|
13678
13678
|
];
|
|
13679
13679
|
if (planGate) {
|
|
13680
13680
|
const dirs = PLAN_DIRS.map((d) => `${d}/`).join(" ");
|
|
@@ -13734,8 +13734,8 @@ function codexHookArgs(o) {
|
|
|
13734
13734
|
return args;
|
|
13735
13735
|
}
|
|
13736
13736
|
function codexGoalScript(o) {
|
|
13737
|
-
const
|
|
13738
|
-
const planGate =
|
|
13737
|
+
const phases2 = PIPELINES[o.flow];
|
|
13738
|
+
const planGate = phases2.includes("Plan") && phases2.includes("Execute");
|
|
13739
13739
|
const max = o.maxBlocks ?? GOAL_MAX_BLOCKS;
|
|
13740
13740
|
const lines2 = [
|
|
13741
13741
|
"#!/bin/sh",
|
|
@@ -13745,7 +13745,7 @@ function codexGoalScript(o) {
|
|
|
13745
13745
|
`ST=${shq(o.stateFile)}`,
|
|
13746
13746
|
"missing=''"
|
|
13747
13747
|
];
|
|
13748
|
-
for (const p3 of
|
|
13748
|
+
for (const p3 of phases2) {
|
|
13749
13749
|
lines2.push(
|
|
13750
13750
|
`grep -qE ${shq(`^${p3} (done|skipped)[[:space:]]*$`)} "$PF" 2>/dev/null || missing="$missing\\n- fase ${p3} belum tercatat di \\$HANOMAN_PHASE_FILE"`
|
|
13751
13751
|
);
|
|
@@ -14494,8 +14494,8 @@ var init_custom_agent_eval_evidence = __esm({
|
|
|
14494
14494
|
};
|
|
14495
14495
|
textFile = (root, path) => {
|
|
14496
14496
|
const target3 = evidencePath(root, path);
|
|
14497
|
-
const
|
|
14498
|
-
if (!
|
|
14497
|
+
const stat7 = lstatSync(target3);
|
|
14498
|
+
if (!stat7.isFile() || stat7.size > 1024 * 1024) throw new Error("Evidence must be a bounded regular file");
|
|
14499
14499
|
return readFileSync2(target3, "utf8");
|
|
14500
14500
|
};
|
|
14501
14501
|
snapshotEvalEvidence = (context) => {
|
|
@@ -14867,9 +14867,9 @@ var init_custom_agent_eval = __esm({
|
|
|
14867
14867
|
hash4.update(`missing:${rel}\0`);
|
|
14868
14868
|
continue;
|
|
14869
14869
|
}
|
|
14870
|
-
const
|
|
14871
|
-
if (
|
|
14872
|
-
else hash4.update(`f:${
|
|
14870
|
+
const stat7 = lstatSync2(path);
|
|
14871
|
+
if (stat7.isSymbolicLink()) hash4.update(`l:${rel}\0${readlinkSync(path)}\0`);
|
|
14872
|
+
else hash4.update(`f:${stat7.mode}:${rel}\0`).update(readFileSync3(path));
|
|
14873
14873
|
}
|
|
14874
14874
|
return hash4.digest("hex");
|
|
14875
14875
|
};
|
|
@@ -15022,8 +15022,8 @@ ${result.error.message}` : result.stderr ?? ""
|
|
|
15022
15022
|
for (const name2 of readdirSync2(eventDir).filter((entry) => entry.endsWith(".json")).sort()) {
|
|
15023
15023
|
try {
|
|
15024
15024
|
const path = evidencePath(eventDir, name2);
|
|
15025
|
-
const
|
|
15026
|
-
if (!
|
|
15025
|
+
const stat7 = lstatSync2(path);
|
|
15026
|
+
if (!stat7.isFile() || stat7.size > 1024 * 1024) continue;
|
|
15027
15027
|
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
15028
15028
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
15029
15029
|
events.push(parsed);
|
|
@@ -15986,14 +15986,9 @@ var init_stage_machine = __esm({
|
|
|
15986
15986
|
|
|
15987
15987
|
// src/services/session-phases.ts
|
|
15988
15988
|
import { readFileSync as readFileSync6, readdirSync as readdirSync5 } from "node:fs";
|
|
15989
|
-
|
|
15989
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
15990
|
+
function parseRecorded(raw) {
|
|
15990
15991
|
const out4 = /* @__PURE__ */ new Map();
|
|
15991
|
-
let raw;
|
|
15992
|
-
try {
|
|
15993
|
-
raw = readFileSync6(file, "utf8");
|
|
15994
|
-
} catch {
|
|
15995
|
-
return out4;
|
|
15996
|
-
}
|
|
15997
15992
|
for (const line of raw.split("\n")) {
|
|
15998
15993
|
const trimmed = line.trimEnd();
|
|
15999
15994
|
const i = trimmed.lastIndexOf(" ");
|
|
@@ -16004,19 +15999,40 @@ function recorded(file) {
|
|
|
16004
15999
|
}
|
|
16005
16000
|
return out4;
|
|
16006
16001
|
}
|
|
16007
|
-
function
|
|
16008
|
-
|
|
16009
|
-
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
|
|
16015
|
-
|
|
16016
|
-
|
|
16002
|
+
function recorded(file) {
|
|
16003
|
+
let raw;
|
|
16004
|
+
try {
|
|
16005
|
+
raw = readFileSync6(file, "utf8");
|
|
16006
|
+
} catch {
|
|
16007
|
+
return /* @__PURE__ */ new Map();
|
|
16008
|
+
}
|
|
16009
|
+
return parseRecorded(raw);
|
|
16010
|
+
}
|
|
16011
|
+
async function recordedAsync(file) {
|
|
16012
|
+
let st;
|
|
16013
|
+
try {
|
|
16014
|
+
st = await stat(file);
|
|
16015
|
+
} catch {
|
|
16016
|
+
phaseFileMemo.delete(file);
|
|
16017
|
+
return /* @__PURE__ */ new Map();
|
|
16018
|
+
}
|
|
16019
|
+
const hit = phaseFileMemo.get(file);
|
|
16020
|
+
if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) return hit.seen;
|
|
16021
|
+
let raw;
|
|
16022
|
+
try {
|
|
16023
|
+
raw = await readFile(file, "utf8");
|
|
16024
|
+
} catch {
|
|
16025
|
+
return /* @__PURE__ */ new Map();
|
|
16026
|
+
}
|
|
16027
|
+
const seen2 = parseRecorded(raw);
|
|
16028
|
+
phaseFileMemo.set(file, { mtimeMs: st.mtimeMs, size: st.size, seen: seen2 });
|
|
16029
|
+
return seen2;
|
|
16030
|
+
}
|
|
16031
|
+
async function readPhasesAsync(file, flow2) {
|
|
16032
|
+
return derivePhases(await recordedAsync(file), flow2);
|
|
16017
16033
|
}
|
|
16018
|
-
function enrichPhases(
|
|
16019
|
-
return
|
|
16034
|
+
function enrichPhases(phases2, roster, invocations, doneSeenAt, now, bornAt2, doneAtBirth = /* @__PURE__ */ new Set()) {
|
|
16035
|
+
return phases2.map((p3) => {
|
|
16020
16036
|
const r = roster.find((entry) => entry.phase === p3.name);
|
|
16021
16037
|
if (!r) return p3;
|
|
16022
16038
|
const all = invocations.filter((i) => i.phase === p3.name);
|
|
@@ -16046,23 +16062,23 @@ function enrichPhases(phases, roster, invocations, doneSeenAt, now, bornAt2, don
|
|
|
16046
16062
|
};
|
|
16047
16063
|
});
|
|
16048
16064
|
}
|
|
16049
|
-
function trackDoneSeen(
|
|
16050
|
-
const byName = new Map(
|
|
16065
|
+
function trackDoneSeen(phases2, doneSeenAt, now) {
|
|
16066
|
+
const byName = new Map(phases2.map((p3) => [p3.name, p3.state]));
|
|
16051
16067
|
for (const name2 of [...doneSeenAt.keys()])
|
|
16052
16068
|
if (byName.get(name2) !== "done") doneSeenAt.delete(name2);
|
|
16053
|
-
for (const p3 of
|
|
16069
|
+
for (const p3 of phases2)
|
|
16054
16070
|
if (p3.state === "done" && !doneSeenAt.has(p3.name)) doneSeenAt.set(p3.name, now);
|
|
16055
16071
|
}
|
|
16056
|
-
function stageFor(
|
|
16072
|
+
function stageFor(phases2) {
|
|
16057
16073
|
let best = -1;
|
|
16058
|
-
for (const p3 of
|
|
16074
|
+
for (const p3 of phases2) {
|
|
16059
16075
|
if (WORK_PHASES.includes(p3.name) && p3.state === "active")
|
|
16060
16076
|
best = Math.max(best, STAGES.indexOf("executing"));
|
|
16061
16077
|
if (p3.state !== "done" && p3.state !== "skipped") continue;
|
|
16062
16078
|
const s2 = REACHED[p3.name];
|
|
16063
16079
|
if (s2) best = Math.max(best, STAGES.indexOf(s2));
|
|
16064
16080
|
}
|
|
16065
|
-
if (
|
|
16081
|
+
if (phases2[0]?.state === "active") best = Math.max(best, STAGES.indexOf("brainstorming"));
|
|
16066
16082
|
return best < 0 ? null : STAGES[best];
|
|
16067
16083
|
}
|
|
16068
16084
|
function planComplete(worktree, specId) {
|
|
@@ -16085,16 +16101,45 @@ function planComplete(worktree, specId) {
|
|
|
16085
16101
|
}
|
|
16086
16102
|
return true;
|
|
16087
16103
|
}
|
|
16088
|
-
function
|
|
16089
|
-
|
|
16104
|
+
async function planCompleteAsync(worktree, specId) {
|
|
16105
|
+
const re = new RegExp(`(^|[^a-z0-9])${specId.toLowerCase()}([^0-9]|$)`);
|
|
16106
|
+
for (const rel of PLAN_DIRS) {
|
|
16107
|
+
const dir2 = `${worktree}/${rel}`;
|
|
16108
|
+
let names;
|
|
16109
|
+
try {
|
|
16110
|
+
names = await readdir(dir2);
|
|
16111
|
+
} catch {
|
|
16112
|
+
continue;
|
|
16113
|
+
}
|
|
16114
|
+
for (const n2 of names) {
|
|
16115
|
+
if (!re.test(n2.toLowerCase())) continue;
|
|
16116
|
+
try {
|
|
16117
|
+
if (/^[ \t]*- \[ \]/m.test(await readFile(`${dir2}/${n2}`, "utf8"))) return false;
|
|
16118
|
+
} catch {
|
|
16119
|
+
}
|
|
16120
|
+
}
|
|
16121
|
+
}
|
|
16122
|
+
return true;
|
|
16123
|
+
}
|
|
16124
|
+
function sessionComplete(phases2, worktree, specId) {
|
|
16125
|
+
if (!phasesComplete(phases2)) return false;
|
|
16090
16126
|
return specId ? planComplete(worktree, specId) : true;
|
|
16091
16127
|
}
|
|
16092
|
-
function stageForRun(
|
|
16093
|
-
const s2 = stageFor(
|
|
16128
|
+
function stageForRun(phases2, worktree, specId) {
|
|
16129
|
+
const s2 = stageFor(phases2);
|
|
16094
16130
|
if (s2 === "done" && !planComplete(worktree, specId)) return "executing";
|
|
16095
16131
|
return s2;
|
|
16096
16132
|
}
|
|
16097
|
-
|
|
16133
|
+
async function stageForRunAsync(phases2, worktree, specId) {
|
|
16134
|
+
const s2 = stageFor(phases2);
|
|
16135
|
+
if (s2 === "done" && !await planCompleteAsync(worktree, specId)) return "executing";
|
|
16136
|
+
return s2;
|
|
16137
|
+
}
|
|
16138
|
+
async function sessionCompleteAsync(phases2, worktree, specId) {
|
|
16139
|
+
if (!phasesComplete(phases2)) return false;
|
|
16140
|
+
return specId ? planCompleteAsync(worktree, specId) : true;
|
|
16141
|
+
}
|
|
16142
|
+
var phaseFilePath, decisionFilePath, phaseFileMemo, derivePhases, readPhases, REACHED, phasesComplete;
|
|
16098
16143
|
var init_session_phases = __esm({
|
|
16099
16144
|
"src/services/session-phases.ts"() {
|
|
16100
16145
|
"use strict";
|
|
@@ -16103,6 +16148,18 @@ var init_session_phases = __esm({
|
|
|
16103
16148
|
init_stage_machine();
|
|
16104
16149
|
phaseFilePath = (repoDir, sessionId2) => `${repoDir}/.worktrees/.phases/${sessionId2}`;
|
|
16105
16150
|
decisionFilePath = (repoDir, sessionId2) => `${repoDir}/.worktrees/.decisions/${sessionId2}`;
|
|
16151
|
+
phaseFileMemo = /* @__PURE__ */ new Map();
|
|
16152
|
+
derivePhases = (seen2, flow2) => {
|
|
16153
|
+
let activeTaken = false;
|
|
16154
|
+
return PIPELINES[flow2].map((name2) => {
|
|
16155
|
+
const state = seen2.get(name2);
|
|
16156
|
+
if (state) return { name: name2, state };
|
|
16157
|
+
if (activeTaken) return { name: name2, state: "pending" };
|
|
16158
|
+
activeTaken = true;
|
|
16159
|
+
return { name: name2, state: "active" };
|
|
16160
|
+
});
|
|
16161
|
+
};
|
|
16162
|
+
readPhases = (file, flow2) => derivePhases(recorded(file), flow2);
|
|
16106
16163
|
REACHED = {
|
|
16107
16164
|
Objective: "objective",
|
|
16108
16165
|
Audit: "objective",
|
|
@@ -16118,7 +16175,7 @@ var init_session_phases = __esm({
|
|
|
16118
16175
|
// mencapai `done` — tak ada fase verifikasi untuk menutupnya.
|
|
16119
16176
|
Kerjakan: "done"
|
|
16120
16177
|
};
|
|
16121
|
-
phasesComplete = (
|
|
16178
|
+
phasesComplete = (phases2) => phases2.length > 0 && phases2.every((p3) => p3.state === "done" || p3.state === "skipped");
|
|
16122
16179
|
}
|
|
16123
16180
|
});
|
|
16124
16181
|
|
|
@@ -16467,7 +16524,7 @@ var init_safe_outbound_request = __esm({
|
|
|
16467
16524
|
|
|
16468
16525
|
// src/services/uploads.ts
|
|
16469
16526
|
import { randomUUID } from "node:crypto";
|
|
16470
|
-
import { mkdir, writeFile, readFile, unlink, rm } from "node:fs/promises";
|
|
16527
|
+
import { mkdir, writeFile, readFile as readFile2, unlink, rm } from "node:fs/promises";
|
|
16471
16528
|
import { join as join11, resolve as resolve8 } from "node:path";
|
|
16472
16529
|
function extFor(mimeType) {
|
|
16473
16530
|
return EXT[mimeType] ?? ".bin";
|
|
@@ -16498,13 +16555,13 @@ async function dropSessionUploads(sessionId2) {
|
|
|
16498
16555
|
}
|
|
16499
16556
|
async function readUpload(storageKey) {
|
|
16500
16557
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
16501
|
-
return
|
|
16558
|
+
return readFile2(join11(uploadDir(), safe));
|
|
16502
16559
|
}
|
|
16503
16560
|
async function readUploadOrFetch(storageKey) {
|
|
16504
16561
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
16505
16562
|
const target3 = join11(uploadDir(), safe);
|
|
16506
16563
|
try {
|
|
16507
|
-
return await
|
|
16564
|
+
return await readFile2(target3);
|
|
16508
16565
|
} catch {
|
|
16509
16566
|
const base2 = effectiveStr("SYNC_SERVER_URL");
|
|
16510
16567
|
const token = effectiveStr("SYNC_DEVICE_TOKEN");
|
|
@@ -16976,22 +17033,22 @@ import { execFile, execFileSync as execFileSync2 } from "node:child_process";
|
|
|
16976
17033
|
import { createRequire } from "node:module";
|
|
16977
17034
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
16978
17035
|
import { chmodSync as chmodSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync8, rmSync as rmSync4, statSync as statSync2, writeFileSync as writeFileSync8 } from "node:fs";
|
|
17036
|
+
import { stat as stat2 } from "node:fs/promises";
|
|
16979
17037
|
import { dirname as dirname8 } from "node:path";
|
|
16980
17038
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
16981
|
-
function phaseView(p3, a) {
|
|
16982
|
-
const phases = readPhases(p3.phaseFile, p3.flow);
|
|
17039
|
+
function phaseView(p3, a, phases2) {
|
|
16983
17040
|
const now = Date.now();
|
|
16984
|
-
trackDoneSeen(
|
|
17041
|
+
trackDoneSeen(phases2, a.doneSeenAt, now);
|
|
16985
17042
|
const roster = (p3.agentRoster ?? []).flatMap((r) => r.phase ? [{ name: r.name, phase: r.phase, model: r.model, effort: r.effort }] : []);
|
|
16986
17043
|
return roster.length ? enrichPhases(
|
|
16987
|
-
|
|
17044
|
+
phases2,
|
|
16988
17045
|
roster,
|
|
16989
17046
|
phaseInvocations.get(p3.id) ?? [],
|
|
16990
17047
|
a.doneSeenAt,
|
|
16991
17048
|
now,
|
|
16992
17049
|
p3.startedAt * 1e3,
|
|
16993
17050
|
new Set(p3.doneAtBirth ?? [])
|
|
16994
|
-
) :
|
|
17051
|
+
) : phases2;
|
|
16995
17052
|
}
|
|
16996
17053
|
function noTtyPromptEnv() {
|
|
16997
17054
|
const path = askpassDenyPath();
|
|
@@ -17164,10 +17221,10 @@ function sessionEventEnv(sessionId2, env = process.env) {
|
|
|
17164
17221
|
...host2 ? { HANOMAN_EVENT_HOST: host2 } : {}
|
|
17165
17222
|
};
|
|
17166
17223
|
}
|
|
17167
|
-
function registerSessionHooks(
|
|
17168
|
-
hooks.add(
|
|
17224
|
+
function registerSessionHooks(h2) {
|
|
17225
|
+
hooks.add(h2);
|
|
17169
17226
|
return () => {
|
|
17170
|
-
hooks.delete(
|
|
17227
|
+
hooks.delete(h2);
|
|
17171
17228
|
};
|
|
17172
17229
|
}
|
|
17173
17230
|
function registerCustomAgentSource(fn) {
|
|
@@ -17601,20 +17658,6 @@ function sessionPhases(id2) {
|
|
|
17601
17658
|
if (!p3?.flow || !p3.phaseFile) return null;
|
|
17602
17659
|
return readPhases(p3.phaseFile, p3.flow);
|
|
17603
17660
|
}
|
|
17604
|
-
function sessionPhasesBySpec() {
|
|
17605
|
-
const out4 = /* @__PURE__ */ new Map();
|
|
17606
|
-
let panes;
|
|
17607
|
-
try {
|
|
17608
|
-
panes = listPanes();
|
|
17609
|
-
} catch {
|
|
17610
|
-
return out4;
|
|
17611
|
-
}
|
|
17612
|
-
for (const p3 of panes) {
|
|
17613
|
-
if (!p3.specId || !p3.flow || !p3.phaseFile) continue;
|
|
17614
|
-
out4.set(p3.specId, { phases: readPhases(p3.phaseFile, p3.flow), cwd: p3.cwd });
|
|
17615
|
-
}
|
|
17616
|
-
return out4;
|
|
17617
|
-
}
|
|
17618
17661
|
function broadcast(a, f) {
|
|
17619
17662
|
const msg = frame(f);
|
|
17620
17663
|
for (const c of a.clients) c.send(msg);
|
|
@@ -17669,14 +17712,16 @@ function end(id2, code) {
|
|
|
17669
17712
|
broadcast(a, { t: "exit", code });
|
|
17670
17713
|
drop(id2);
|
|
17671
17714
|
}
|
|
17672
|
-
function pollPhases(p3, a) {
|
|
17715
|
+
async function pollPhases(p3, a) {
|
|
17673
17716
|
if (!p3.flow || !p3.phaseFile) return;
|
|
17674
|
-
const
|
|
17675
|
-
const complete =
|
|
17676
|
-
|
|
17717
|
+
const raw = await readPhasesAsync(p3.phaseFile, p3.flow);
|
|
17718
|
+
const complete = await sessionCompleteAsync(raw, p3.cwd, p3.specId);
|
|
17719
|
+
if (attached.get(p3.id) !== a) return;
|
|
17720
|
+
const phases2 = phaseView(p3, a, raw);
|
|
17721
|
+
const json = phaseKey(phases2, complete);
|
|
17677
17722
|
if (json === a.lastPhases) return;
|
|
17678
17723
|
a.lastPhases = json;
|
|
17679
|
-
broadcast(a, { t: "phase", phases, complete });
|
|
17724
|
+
broadcast(a, { t: "phase", phases: phases2, complete });
|
|
17680
17725
|
}
|
|
17681
17726
|
function setPhaseInvocations(sessionId2, rows) {
|
|
17682
17727
|
if (attached.has(sessionId2)) phaseInvocations.set(sessionId2, rows);
|
|
@@ -17692,7 +17737,7 @@ function startPoll() {
|
|
|
17692
17737
|
if (polling) return;
|
|
17693
17738
|
polling = true;
|
|
17694
17739
|
const snapshot3 = [...attached.entries()];
|
|
17695
|
-
listPanesAsync().then((panes) => {
|
|
17740
|
+
listPanesAsync().then(async (panes) => {
|
|
17696
17741
|
const live = new Map(panes.map((p3) => [p3.id, p3]));
|
|
17697
17742
|
for (const [id2, a] of snapshot3) {
|
|
17698
17743
|
if (attached.get(id2) !== a) continue;
|
|
@@ -17700,7 +17745,8 @@ function startPoll() {
|
|
|
17700
17745
|
if (!p3) end(id2, 0);
|
|
17701
17746
|
else if (p3.exited) end(id2, p3.code);
|
|
17702
17747
|
else {
|
|
17703
|
-
pollPhases(p3, a)
|
|
17748
|
+
await pollPhases(p3, a).catch(() => {
|
|
17749
|
+
});
|
|
17704
17750
|
pollAlt(p3, a);
|
|
17705
17751
|
}
|
|
17706
17752
|
}
|
|
@@ -17715,31 +17761,43 @@ function startPoll() {
|
|
|
17715
17761
|
}, POLL_MS);
|
|
17716
17762
|
poll.unref();
|
|
17717
17763
|
}
|
|
17718
|
-
function
|
|
17719
|
-
|
|
17764
|
+
function replayExited(c, p3, screen) {
|
|
17765
|
+
if (screen.trim()) c.send(frame({ t: "data", d: stripTerminalQueries(screen.replace(/\n/g, "\r\n")) }));
|
|
17766
|
+
c.send(frame({ t: "exit", code: p3.code }));
|
|
17767
|
+
c.close();
|
|
17768
|
+
}
|
|
17769
|
+
async function screenResyncFrame(id2) {
|
|
17770
|
+
try {
|
|
17771
|
+
const screen = await tmuxAsync("capture-pane", "-p", "-e", "-J", "-t", name(id2));
|
|
17772
|
+
return frame({ t: "data", d: `\x1B[2J\x1B[H${stripTerminalQueries(screen.replace(/\n/g, "\r\n"))}` });
|
|
17773
|
+
} catch {
|
|
17774
|
+
return null;
|
|
17775
|
+
}
|
|
17776
|
+
}
|
|
17777
|
+
async function attachAsync(id2, c) {
|
|
17778
|
+
const p3 = await getSessionAsync(id2);
|
|
17720
17779
|
if (!p3) {
|
|
17721
17780
|
c.close();
|
|
17722
17781
|
return;
|
|
17723
17782
|
}
|
|
17724
17783
|
if (p3.exited) {
|
|
17725
|
-
|
|
17726
|
-
if (screen.trim()) {
|
|
17727
|
-
c.send(frame({ t: "data", d: stripTerminalQueries(screen.replace(/\n/g, "\r\n")) }));
|
|
17728
|
-
}
|
|
17729
|
-
c.send(frame({ t: "exit", code: p3.code }));
|
|
17730
|
-
c.close();
|
|
17784
|
+
replayExited(c, p3, await tmuxAsync(...CAPTURE_ARGS(id2)));
|
|
17731
17785
|
return;
|
|
17732
17786
|
}
|
|
17787
|
+
attachLive(id2, c, p3);
|
|
17788
|
+
}
|
|
17789
|
+
function attachLive(id2, c, p3) {
|
|
17733
17790
|
const a = attached.get(id2) ?? open(id2);
|
|
17734
17791
|
a.clients.add(c);
|
|
17735
17792
|
if (a.scrollback) c.send(frame({ t: "data", d: stripTerminalQueries(a.scrollback) }));
|
|
17736
17793
|
a.lastAlt = p3.altScreen;
|
|
17737
17794
|
c.send(frame({ t: "alt", on: p3.altScreen }));
|
|
17738
17795
|
if (p3.flow && p3.phaseFile) {
|
|
17739
|
-
const
|
|
17740
|
-
const
|
|
17741
|
-
|
|
17742
|
-
|
|
17796
|
+
const raw = readPhases(p3.phaseFile, p3.flow);
|
|
17797
|
+
const phases2 = phaseView(p3, a, raw);
|
|
17798
|
+
const complete = sessionComplete(raw, p3.cwd, p3.specId);
|
|
17799
|
+
a.lastPhases = phaseKey(phases2, complete);
|
|
17800
|
+
c.send(frame({ t: "phase", phases: phases2, complete }));
|
|
17743
17801
|
}
|
|
17744
17802
|
}
|
|
17745
17803
|
function writeTo(id2, d, from) {
|
|
@@ -17791,7 +17849,7 @@ function spawnPty(...args) {
|
|
|
17791
17849
|
);
|
|
17792
17850
|
}
|
|
17793
17851
|
}
|
|
17794
|
-
var socket, PREFIX, MAX_SCROLLBACK, SCROLLBACK_SLACK, POLL_MS, markerFilled, clearMarker, PANE_QUIET_MS, paneQuiet, markerOnset, decisionOnset, attached, phaseInvocations, claudeBin, shellBin, codexBin, agentBin, rootBypassEnv, frame, name, promptFilePath, goalGatePath, goalStatePath, agentTempDir, agentsFilePath, askpassDenyPath, ASKPASS_DENY, NO_SERVER, TmuxError, sq, idFor, FMT, toSessionInfo, listSessions, listSessionsAsync,
|
|
17852
|
+
var socket, PREFIX, MAX_SCROLLBACK, SCROLLBACK_SLACK, POLL_MS, markerFilled, markerFilledAsync, clearMarker, PANE_QUIET_MS, paneQuiet, markerOnset, decisionOnset, attached, phaseInvocations, claudeBin, shellBin, codexBin, agentBin, rootBypassEnv, frame, name, promptFilePath, goalGatePath, goalStatePath, agentTempDir, agentsFilePath, askpassDenyPath, ASKPASS_DENY, 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;
|
|
17795
17853
|
var init_pty = __esm({
|
|
17796
17854
|
"src/services/pty.ts"() {
|
|
17797
17855
|
"use strict";
|
|
@@ -17820,6 +17878,13 @@ var init_pty = __esm({
|
|
|
17820
17878
|
return false;
|
|
17821
17879
|
}
|
|
17822
17880
|
};
|
|
17881
|
+
markerFilledAsync = async (f) => {
|
|
17882
|
+
try {
|
|
17883
|
+
return (await stat2(f)).size > 0;
|
|
17884
|
+
} catch {
|
|
17885
|
+
return false;
|
|
17886
|
+
}
|
|
17887
|
+
};
|
|
17823
17888
|
clearMarker = (f) => {
|
|
17824
17889
|
try {
|
|
17825
17890
|
writeFileSync8(f, "");
|
|
@@ -17937,7 +18002,7 @@ exit 1
|
|
|
17937
18002
|
});
|
|
17938
18003
|
listSessions = () => listPanes().map(toSessionInfo);
|
|
17939
18004
|
listSessionsAsync = async () => (await listPanesAsync()).map(toSessionInfo);
|
|
17940
|
-
|
|
18005
|
+
decisionsOf = (panes) => panes.filter((p3) => !p3.exited && p3.decisionFile).map((p3) => ({
|
|
17941
18006
|
id: p3.id,
|
|
17942
18007
|
specId: p3.specId,
|
|
17943
18008
|
projectId: p3.projectId,
|
|
@@ -17951,17 +18016,17 @@ exit 1
|
|
|
17951
18016
|
getSessionAsync = async (id2) => (await listPanesAsync()).find((p3) => p3.id === id2);
|
|
17952
18017
|
hooks = /* @__PURE__ */ new Set();
|
|
17953
18018
|
emitBirth = (b) => {
|
|
17954
|
-
for (const
|
|
18019
|
+
for (const h2 of hooks) {
|
|
17955
18020
|
try {
|
|
17956
|
-
|
|
18021
|
+
h2.onBirth?.(b);
|
|
17957
18022
|
} catch {
|
|
17958
18023
|
}
|
|
17959
18024
|
}
|
|
17960
18025
|
};
|
|
17961
18026
|
emitDeath = (d) => {
|
|
17962
|
-
for (const
|
|
18027
|
+
for (const h2 of hooks) {
|
|
17963
18028
|
try {
|
|
17964
|
-
|
|
18029
|
+
h2.onDeath?.(d);
|
|
17965
18030
|
} catch {
|
|
17966
18031
|
}
|
|
17967
18032
|
}
|
|
@@ -18017,11 +18082,10 @@ exit 1
|
|
|
18017
18082
|
COALESCE_MS = 16;
|
|
18018
18083
|
COALESCE_MAX_BYTES = 64 * 1024;
|
|
18019
18084
|
trimScrollback = (s2) => s2.length > MAX_SCROLLBACK + SCROLLBACK_SLACK ? s2.slice(-MAX_SCROLLBACK) : s2;
|
|
18020
|
-
phaseKey = (
|
|
18021
|
-
|
|
18022
|
-
|
|
18023
|
-
|
|
18024
|
-
return !!p3 && paneComplete(p3);
|
|
18085
|
+
phaseKey = (phases2, complete) => JSON.stringify({ phases: phases2, complete });
|
|
18086
|
+
sessionFinishedAsync = async (id2) => {
|
|
18087
|
+
const p3 = await getSessionAsync(id2);
|
|
18088
|
+
return !!p3 && !!p3.flow && !!p3.phaseFile && sessionCompleteAsync(await readPhasesAsync(p3.phaseFile, p3.flow), p3.cwd, p3.specId);
|
|
18025
18089
|
};
|
|
18026
18090
|
polling = false;
|
|
18027
18091
|
TERMINAL_QUERY = new RegExp([
|
|
@@ -18041,6 +18105,7 @@ exit 1
|
|
|
18041
18105
|
// XTWINOPS laporan — berparameter TUNGGAL
|
|
18042
18106
|
].join("|"), "g");
|
|
18043
18107
|
stripTerminalQueries = (s2) => s2.replace(TERMINAL_QUERY, "");
|
|
18108
|
+
CAPTURE_ARGS = (id2) => ["capture-pane", "-p", "-e", "-J", "-S", "-2000", "-t", name(id2)];
|
|
18044
18109
|
detach = (id2, c) => {
|
|
18045
18110
|
attached.get(id2)?.clients.delete(c);
|
|
18046
18111
|
};
|
|
@@ -18066,7 +18131,7 @@ var init_redact_known = __esm({
|
|
|
18066
18131
|
|
|
18067
18132
|
// src/services/transcript-store.ts
|
|
18068
18133
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
18069
|
-
import { mkdir as mkdir2, writeFile as writeFile2, readFile as
|
|
18134
|
+
import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile3, unlink as unlink2, readdir as readdir2, stat as stat3 } from "node:fs/promises";
|
|
18070
18135
|
import { join as join13, resolve as resolve9, basename as basename4 } from "node:path";
|
|
18071
18136
|
function transcriptDir() {
|
|
18072
18137
|
return resolve9(effectiveStr("HANOMAN_TRANSCRIPT_DIR")?.trim() || resolveDataDirs().transcripts);
|
|
@@ -18093,7 +18158,7 @@ async function saveTranscript(text) {
|
|
|
18093
18158
|
async function readTranscript(key) {
|
|
18094
18159
|
if (!key) return null;
|
|
18095
18160
|
try {
|
|
18096
|
-
return await
|
|
18161
|
+
return await readFile3(join13(transcriptDir(), basename4(key)), "utf8");
|
|
18097
18162
|
} catch {
|
|
18098
18163
|
return null;
|
|
18099
18164
|
}
|
|
@@ -18110,7 +18175,7 @@ async function listTranscripts() {
|
|
|
18110
18175
|
const dir2 = transcriptDir();
|
|
18111
18176
|
let names;
|
|
18112
18177
|
try {
|
|
18113
|
-
names = await
|
|
18178
|
+
names = await readdir2(dir2);
|
|
18114
18179
|
} catch (e) {
|
|
18115
18180
|
if (e.code === "ENOENT") return [];
|
|
18116
18181
|
throw e;
|
|
@@ -18119,7 +18184,7 @@ async function listTranscripts() {
|
|
|
18119
18184
|
for (const name2 of names) {
|
|
18120
18185
|
if (!name2.endsWith(".log")) continue;
|
|
18121
18186
|
try {
|
|
18122
|
-
rows.push({ key: name2, mtimeMs: (await
|
|
18187
|
+
rows.push({ key: name2, mtimeMs: (await stat3(join13(dir2, name2))).mtimeMs });
|
|
18123
18188
|
} catch {
|
|
18124
18189
|
}
|
|
18125
18190
|
}
|
|
@@ -19181,6 +19246,118 @@ var init_codex_trust = __esm({
|
|
|
19181
19246
|
}
|
|
19182
19247
|
});
|
|
19183
19248
|
|
|
19249
|
+
// src/services/logs/phase-tap.ts
|
|
19250
|
+
function observePhases(rows) {
|
|
19251
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
19252
|
+
for (const r of rows) {
|
|
19253
|
+
seen2.add(r.sessionId);
|
|
19254
|
+
const prev = known.get(r.sessionId);
|
|
19255
|
+
if (!known.has(r.sessionId)) {
|
|
19256
|
+
known.set(r.sessionId, r.phase);
|
|
19257
|
+
continue;
|
|
19258
|
+
}
|
|
19259
|
+
if (prev !== r.phase) {
|
|
19260
|
+
void appendEvent({
|
|
19261
|
+
kind: "session.phase",
|
|
19262
|
+
msg: `sesi ${r.sessionId} fase ${prev ?? "?"} \u2192 ${r.phase ?? "?"}`,
|
|
19263
|
+
projectId: r.projectId,
|
|
19264
|
+
specId: r.specId ?? null,
|
|
19265
|
+
sessionId: r.sessionId,
|
|
19266
|
+
data: { from: prev, to: r.phase }
|
|
19267
|
+
});
|
|
19268
|
+
known.set(r.sessionId, r.phase);
|
|
19269
|
+
}
|
|
19270
|
+
}
|
|
19271
|
+
for (const id2 of [...known.keys()]) if (!seen2.has(id2)) known.delete(id2);
|
|
19272
|
+
}
|
|
19273
|
+
var known;
|
|
19274
|
+
var init_phase_tap = __esm({
|
|
19275
|
+
"src/services/logs/phase-tap.ts"() {
|
|
19276
|
+
"use strict";
|
|
19277
|
+
init_event_log();
|
|
19278
|
+
known = /* @__PURE__ */ new Map();
|
|
19279
|
+
}
|
|
19280
|
+
});
|
|
19281
|
+
|
|
19282
|
+
// src/services/presence/snapshot.ts
|
|
19283
|
+
function paneToPresence(p3, phase) {
|
|
19284
|
+
return {
|
|
19285
|
+
sessionId: p3.id,
|
|
19286
|
+
projectId: p3.projectId,
|
|
19287
|
+
...p3.specId ? { specId: p3.specId } : {},
|
|
19288
|
+
...p3.flow ? { flow: p3.flow } : {},
|
|
19289
|
+
...phase ? { phase } : {},
|
|
19290
|
+
agent: p3.agent,
|
|
19291
|
+
// Presedensi: pane mati sudah berakhir apa pun isi markernya.
|
|
19292
|
+
status: p3.exited ? "exited" : p3.decision ? "waiting" : "working",
|
|
19293
|
+
startedAt: isoFromEpochSeconds(p3.startedAt)
|
|
19294
|
+
};
|
|
19295
|
+
}
|
|
19296
|
+
function activePhase(p3) {
|
|
19297
|
+
if (!p3.flow || !p3.phaseFile) return void 0;
|
|
19298
|
+
return readPhases(p3.phaseFile, p3.flow).find((f) => f.state === "active")?.name;
|
|
19299
|
+
}
|
|
19300
|
+
function listPanesShared(now = Date.now()) {
|
|
19301
|
+
if (panesMemo && now - panesMemo.at < PANES_MEMO_MS) return panesMemo.value;
|
|
19302
|
+
const value = listPanesAsync();
|
|
19303
|
+
panesMemo = { at: now, value };
|
|
19304
|
+
value.catch(() => {
|
|
19305
|
+
if (panesMemo?.value === value) panesMemo = null;
|
|
19306
|
+
});
|
|
19307
|
+
return value;
|
|
19308
|
+
}
|
|
19309
|
+
async function buildLocalPresence() {
|
|
19310
|
+
const panes = await listPanesShared();
|
|
19311
|
+
const rows = panes.slice(0, MAX_PRESENCE_SESSIONS).map((p3) => paneToPresence(p3, activePhase(p3)));
|
|
19312
|
+
observePhases(rows.map((r) => ({ sessionId: r.sessionId, projectId: r.projectId, specId: r.specId, phase: r.phase })));
|
|
19313
|
+
return rows;
|
|
19314
|
+
}
|
|
19315
|
+
async function buildLocalCapacity() {
|
|
19316
|
+
return currentLaunchStatus(await listPanesShared(), await getScheduler());
|
|
19317
|
+
}
|
|
19318
|
+
var isoFromEpochSeconds, PANES_MEMO_MS, panesMemo;
|
|
19319
|
+
var init_snapshot = __esm({
|
|
19320
|
+
"src/services/presence/snapshot.ts"() {
|
|
19321
|
+
"use strict";
|
|
19322
|
+
init_src();
|
|
19323
|
+
init_pty();
|
|
19324
|
+
init_session_phases();
|
|
19325
|
+
init_config3();
|
|
19326
|
+
init_session_launch_gate();
|
|
19327
|
+
init_phase_tap();
|
|
19328
|
+
isoFromEpochSeconds = (s2) => new Date((s2 || 0) * 1e3).toISOString();
|
|
19329
|
+
PANES_MEMO_MS = 1e3;
|
|
19330
|
+
panesMemo = null;
|
|
19331
|
+
}
|
|
19332
|
+
});
|
|
19333
|
+
|
|
19334
|
+
// src/services/live-phases.ts
|
|
19335
|
+
async function sessionPhasesBySpecAsync() {
|
|
19336
|
+
const out4 = /* @__PURE__ */ new Map();
|
|
19337
|
+
let panes;
|
|
19338
|
+
try {
|
|
19339
|
+
panes = await listPanesShared();
|
|
19340
|
+
} catch {
|
|
19341
|
+
return out4;
|
|
19342
|
+
}
|
|
19343
|
+
await Promise.all(panes.map(async (p3) => {
|
|
19344
|
+
if (!p3.specId || !p3.flow || !p3.phaseFile) return;
|
|
19345
|
+
out4.set(p3.specId, { phases: await readPhasesAsync(p3.phaseFile, p3.flow), cwd: p3.cwd });
|
|
19346
|
+
}));
|
|
19347
|
+
return out4;
|
|
19348
|
+
}
|
|
19349
|
+
async function liveDecisionsAsync() {
|
|
19350
|
+
return decisionsOf(await listPanesShared());
|
|
19351
|
+
}
|
|
19352
|
+
var init_live_phases = __esm({
|
|
19353
|
+
"src/services/live-phases.ts"() {
|
|
19354
|
+
"use strict";
|
|
19355
|
+
init_session_phases();
|
|
19356
|
+
init_snapshot();
|
|
19357
|
+
init_pty();
|
|
19358
|
+
}
|
|
19359
|
+
});
|
|
19360
|
+
|
|
19184
19361
|
// src/services/notifications.ts
|
|
19185
19362
|
async function recordDrift(vpsId, vpsName, drift, snapshotId) {
|
|
19186
19363
|
if (drift.length === 0) return;
|
|
@@ -19282,11 +19459,11 @@ async function recordCronRun(cronId, cronName, projectId, dueAt, status2, note)
|
|
|
19282
19459
|
}).catch(() => {
|
|
19283
19460
|
});
|
|
19284
19461
|
}
|
|
19285
|
-
async function scanDecisions(read2 =
|
|
19462
|
+
async function scanDecisions(read2 = liveDecisionsAsync) {
|
|
19286
19463
|
const next = /* @__PURE__ */ new Set();
|
|
19287
19464
|
const fresh = [];
|
|
19288
|
-
for (const s2 of read2()) {
|
|
19289
|
-
if (!
|
|
19465
|
+
for (const s2 of await read2()) {
|
|
19466
|
+
if (!await markerFilledAsync(s2.decisionFile)) continue;
|
|
19290
19467
|
if (awaiting.has(s2.id)) {
|
|
19291
19468
|
next.add(s2.id);
|
|
19292
19469
|
continue;
|
|
@@ -19322,96 +19499,12 @@ var init_notifications2 = __esm({
|
|
|
19322
19499
|
"use strict";
|
|
19323
19500
|
init_db();
|
|
19324
19501
|
init_pty();
|
|
19502
|
+
init_live_phases();
|
|
19325
19503
|
awaiting = /* @__PURE__ */ new Set();
|
|
19326
19504
|
DEFAULT_FEED_TAKE = 50;
|
|
19327
19505
|
}
|
|
19328
19506
|
});
|
|
19329
19507
|
|
|
19330
|
-
// src/services/logs/phase-tap.ts
|
|
19331
|
-
function observePhases(rows) {
|
|
19332
|
-
const seen2 = /* @__PURE__ */ new Set();
|
|
19333
|
-
for (const r of rows) {
|
|
19334
|
-
seen2.add(r.sessionId);
|
|
19335
|
-
const prev = known.get(r.sessionId);
|
|
19336
|
-
if (!known.has(r.sessionId)) {
|
|
19337
|
-
known.set(r.sessionId, r.phase);
|
|
19338
|
-
continue;
|
|
19339
|
-
}
|
|
19340
|
-
if (prev !== r.phase) {
|
|
19341
|
-
void appendEvent({
|
|
19342
|
-
kind: "session.phase",
|
|
19343
|
-
msg: `sesi ${r.sessionId} fase ${prev ?? "?"} \u2192 ${r.phase ?? "?"}`,
|
|
19344
|
-
projectId: r.projectId,
|
|
19345
|
-
specId: r.specId ?? null,
|
|
19346
|
-
sessionId: r.sessionId,
|
|
19347
|
-
data: { from: prev, to: r.phase }
|
|
19348
|
-
});
|
|
19349
|
-
known.set(r.sessionId, r.phase);
|
|
19350
|
-
}
|
|
19351
|
-
}
|
|
19352
|
-
for (const id2 of [...known.keys()]) if (!seen2.has(id2)) known.delete(id2);
|
|
19353
|
-
}
|
|
19354
|
-
var known;
|
|
19355
|
-
var init_phase_tap = __esm({
|
|
19356
|
-
"src/services/logs/phase-tap.ts"() {
|
|
19357
|
-
"use strict";
|
|
19358
|
-
init_event_log();
|
|
19359
|
-
known = /* @__PURE__ */ new Map();
|
|
19360
|
-
}
|
|
19361
|
-
});
|
|
19362
|
-
|
|
19363
|
-
// src/services/presence/snapshot.ts
|
|
19364
|
-
function paneToPresence(p3, phase) {
|
|
19365
|
-
return {
|
|
19366
|
-
sessionId: p3.id,
|
|
19367
|
-
projectId: p3.projectId,
|
|
19368
|
-
...p3.specId ? { specId: p3.specId } : {},
|
|
19369
|
-
...p3.flow ? { flow: p3.flow } : {},
|
|
19370
|
-
...phase ? { phase } : {},
|
|
19371
|
-
agent: p3.agent,
|
|
19372
|
-
// Presedensi: pane mati sudah berakhir apa pun isi markernya.
|
|
19373
|
-
status: p3.exited ? "exited" : p3.decision ? "waiting" : "working",
|
|
19374
|
-
startedAt: isoFromEpochSeconds(p3.startedAt)
|
|
19375
|
-
};
|
|
19376
|
-
}
|
|
19377
|
-
function activePhase(p3) {
|
|
19378
|
-
if (!p3.flow || !p3.phaseFile) return void 0;
|
|
19379
|
-
return readPhases(p3.phaseFile, p3.flow).find((f) => f.state === "active")?.name;
|
|
19380
|
-
}
|
|
19381
|
-
function listPanesShared(now = Date.now()) {
|
|
19382
|
-
if (panesMemo && now - panesMemo.at < PANES_MEMO_MS) return panesMemo.value;
|
|
19383
|
-
const value = listPanesAsync();
|
|
19384
|
-
panesMemo = { at: now, value };
|
|
19385
|
-
value.catch(() => {
|
|
19386
|
-
if (panesMemo?.value === value) panesMemo = null;
|
|
19387
|
-
});
|
|
19388
|
-
return value;
|
|
19389
|
-
}
|
|
19390
|
-
async function buildLocalPresence() {
|
|
19391
|
-
const panes = await listPanesShared();
|
|
19392
|
-
const rows = panes.slice(0, MAX_PRESENCE_SESSIONS).map((p3) => paneToPresence(p3, activePhase(p3)));
|
|
19393
|
-
observePhases(rows.map((r) => ({ sessionId: r.sessionId, projectId: r.projectId, specId: r.specId, phase: r.phase })));
|
|
19394
|
-
return rows;
|
|
19395
|
-
}
|
|
19396
|
-
async function buildLocalCapacity() {
|
|
19397
|
-
return currentLaunchStatus(await listPanesShared(), await getScheduler());
|
|
19398
|
-
}
|
|
19399
|
-
var isoFromEpochSeconds, PANES_MEMO_MS, panesMemo;
|
|
19400
|
-
var init_snapshot = __esm({
|
|
19401
|
-
"src/services/presence/snapshot.ts"() {
|
|
19402
|
-
"use strict";
|
|
19403
|
-
init_src();
|
|
19404
|
-
init_pty();
|
|
19405
|
-
init_session_phases();
|
|
19406
|
-
init_config3();
|
|
19407
|
-
init_session_launch_gate();
|
|
19408
|
-
init_phase_tap();
|
|
19409
|
-
isoFromEpochSeconds = (s2) => new Date((s2 || 0) * 1e3).toISOString();
|
|
19410
|
-
PANES_MEMO_MS = 1e3;
|
|
19411
|
-
panesMemo = null;
|
|
19412
|
-
}
|
|
19413
|
-
});
|
|
19414
|
-
|
|
19415
19508
|
// src/services/device-sockets.ts
|
|
19416
19509
|
function registerDeviceSocket(deviceId, kind, socket2) {
|
|
19417
19510
|
const entry = { kind, socket: socket2 };
|
|
@@ -19916,8 +20009,8 @@ var require_ms = __commonJS({
|
|
|
19916
20009
|
"../node_modules/.pnpm/ms@2.1.3/node_modules/ms/index.js"(exports, module) {
|
|
19917
20010
|
var s2 = 1e3;
|
|
19918
20011
|
var m = s2 * 60;
|
|
19919
|
-
var
|
|
19920
|
-
var d =
|
|
20012
|
+
var h2 = m * 60;
|
|
20013
|
+
var d = h2 * 24;
|
|
19921
20014
|
var w = d * 7;
|
|
19922
20015
|
var y = d * 365.25;
|
|
19923
20016
|
module.exports = function(val, options2) {
|
|
@@ -19965,7 +20058,7 @@ var require_ms = __commonJS({
|
|
|
19965
20058
|
case "hrs":
|
|
19966
20059
|
case "hr":
|
|
19967
20060
|
case "h":
|
|
19968
|
-
return n2 *
|
|
20061
|
+
return n2 * h2;
|
|
19969
20062
|
case "minutes":
|
|
19970
20063
|
case "minute":
|
|
19971
20064
|
case "mins":
|
|
@@ -19993,8 +20086,8 @@ var require_ms = __commonJS({
|
|
|
19993
20086
|
if (msAbs >= d) {
|
|
19994
20087
|
return Math.round(ms / d) + "d";
|
|
19995
20088
|
}
|
|
19996
|
-
if (msAbs >=
|
|
19997
|
-
return Math.round(ms /
|
|
20089
|
+
if (msAbs >= h2) {
|
|
20090
|
+
return Math.round(ms / h2) + "h";
|
|
19998
20091
|
}
|
|
19999
20092
|
if (msAbs >= m) {
|
|
20000
20093
|
return Math.round(ms / m) + "m";
|
|
@@ -20009,8 +20102,8 @@ var require_ms = __commonJS({
|
|
|
20009
20102
|
if (msAbs >= d) {
|
|
20010
20103
|
return plural(ms, msAbs, d, "day");
|
|
20011
20104
|
}
|
|
20012
|
-
if (msAbs >=
|
|
20013
|
-
return plural(ms, msAbs,
|
|
20105
|
+
if (msAbs >= h2) {
|
|
20106
|
+
return plural(ms, msAbs, h2, "hour");
|
|
20014
20107
|
}
|
|
20015
20108
|
if (msAbs >= m) {
|
|
20016
20109
|
return plural(ms, msAbs, m, "minute");
|
|
@@ -20726,7 +20819,7 @@ var init_runtime_defaults2 = __esm({
|
|
|
20726
20819
|
});
|
|
20727
20820
|
|
|
20728
20821
|
// src/services/agent-token.ts
|
|
20729
|
-
import { randomBytes as randomBytes2, createHash as
|
|
20822
|
+
import { randomBytes as randomBytes2, createHash as createHash5, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
20730
20823
|
function toAgentTokenView(t) {
|
|
20731
20824
|
return {
|
|
20732
20825
|
id: t.id,
|
|
@@ -20792,7 +20885,7 @@ var init_agent_token = __esm({
|
|
|
20792
20885
|
"src/services/agent-token.ts"() {
|
|
20793
20886
|
"use strict";
|
|
20794
20887
|
init_db();
|
|
20795
|
-
hash2 = (token) =>
|
|
20888
|
+
hash2 = (token) => createHash5("sha256").update(token).digest("hex");
|
|
20796
20889
|
}
|
|
20797
20890
|
});
|
|
20798
20891
|
|
|
@@ -20932,7 +21025,7 @@ var init_config4 = __esm({
|
|
|
20932
21025
|
});
|
|
20933
21026
|
|
|
20934
21027
|
// src/services/telegram/protocol.ts
|
|
20935
|
-
import { createHash as
|
|
21028
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
20936
21029
|
function normalizeTelegramCommand(text) {
|
|
20937
21030
|
const clean2 = text.trim();
|
|
20938
21031
|
const match = clean2.match(/^\/([a-z0-9_]+)(?:@[a-z0-9_]+)?(?:\s+([\s\S]*))?$/i);
|
|
@@ -20999,7 +21092,7 @@ function parseTelegramUpdate(update2, allowlist, maxText = 12e3) {
|
|
|
20999
21092
|
};
|
|
21000
21093
|
}
|
|
21001
21094
|
function inboundDigest(input) {
|
|
21002
|
-
return
|
|
21095
|
+
return createHash6("sha256").update(`${input.updateId}\0${input.chatId}\0${input.userId}\0${input.kind}\0${input.text}`, "utf8").digest("hex");
|
|
21003
21096
|
}
|
|
21004
21097
|
function sanitizeTelegramOutput(text, exactSecrets = []) {
|
|
21005
21098
|
let clean2 = text.replace(ANSI, "").replace(CONTROLS, "");
|
|
@@ -21166,7 +21259,7 @@ var init_typing = __esm({
|
|
|
21166
21259
|
});
|
|
21167
21260
|
|
|
21168
21261
|
// src/services/telegram/gateway.ts
|
|
21169
|
-
import { createHash as
|
|
21262
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
21170
21263
|
var digestRaw, TelegramGateway;
|
|
21171
21264
|
var init_gateway = __esm({
|
|
21172
21265
|
"src/services/telegram/gateway.ts"() {
|
|
@@ -21175,7 +21268,7 @@ var init_gateway = __esm({
|
|
|
21175
21268
|
init_protocol();
|
|
21176
21269
|
init_runtime();
|
|
21177
21270
|
init_typing();
|
|
21178
|
-
digestRaw = (update2) =>
|
|
21271
|
+
digestRaw = (update2) => createHash7("sha256").update(JSON.stringify(update2)).digest("hex");
|
|
21179
21272
|
TelegramGateway = class {
|
|
21180
21273
|
constructor(deps) {
|
|
21181
21274
|
this.deps = deps;
|
|
@@ -21482,14 +21575,14 @@ var init_engine_command = __esm({
|
|
|
21482
21575
|
});
|
|
21483
21576
|
|
|
21484
21577
|
// src/services/telegram/session.ts
|
|
21485
|
-
import { createHash as
|
|
21578
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
21486
21579
|
var chatHash, telegramOperatorSessionId, formatTelegramTurn, TelegramSessionCoordinator;
|
|
21487
21580
|
var init_session = __esm({
|
|
21488
21581
|
"src/services/telegram/session.ts"() {
|
|
21489
21582
|
"use strict";
|
|
21490
21583
|
init_src2();
|
|
21491
21584
|
init_engine_command();
|
|
21492
|
-
chatHash = (chatId) =>
|
|
21585
|
+
chatHash = (chatId) => createHash8("sha256").update(chatId).digest("hex").slice(0, 16);
|
|
21493
21586
|
telegramOperatorSessionId = (chatId) => `telegram-${chatHash(chatId)}`;
|
|
21494
21587
|
formatTelegramTurn = (input) => `[Telegram update ${input.updateId} \xB7 chat ${input.chatId} \xB7 kind ${input.kind}]
|
|
21495
21588
|
${input.text}`;
|
|
@@ -24949,7 +25042,7 @@ var require_websocket = __commonJS({
|
|
|
24949
25042
|
var http = __require("http");
|
|
24950
25043
|
var net = __require("net");
|
|
24951
25044
|
var tls = __require("tls");
|
|
24952
|
-
var { randomBytes: randomBytes12, createHash:
|
|
25045
|
+
var { randomBytes: randomBytes12, createHash: createHash16 } = __require("crypto");
|
|
24953
25046
|
var { Duplex, Readable } = __require("stream");
|
|
24954
25047
|
var { URL: URL2 } = __require("url");
|
|
24955
25048
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -25617,7 +25710,7 @@ var require_websocket = __commonJS({
|
|
|
25617
25710
|
abortHandshake(websocket2, socket2, "Invalid Upgrade header");
|
|
25618
25711
|
return;
|
|
25619
25712
|
}
|
|
25620
|
-
const digest2 =
|
|
25713
|
+
const digest2 = createHash16("sha1").update(key + GUID).digest("base64");
|
|
25621
25714
|
if (res.headers["sec-websocket-accept"] !== digest2) {
|
|
25622
25715
|
abortHandshake(websocket2, socket2, "Invalid Sec-WebSocket-Accept header");
|
|
25623
25716
|
return;
|
|
@@ -25986,7 +26079,7 @@ var require_websocket_server = __commonJS({
|
|
|
25986
26079
|
var EventEmitter = __require("events");
|
|
25987
26080
|
var http = __require("http");
|
|
25988
26081
|
var { Duplex } = __require("stream");
|
|
25989
|
-
var { createHash:
|
|
26082
|
+
var { createHash: createHash16 } = __require("crypto");
|
|
25990
26083
|
var extension2 = require_extension();
|
|
25991
26084
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
25992
26085
|
var subprotocol2 = require_subprotocol();
|
|
@@ -26293,7 +26386,7 @@ var require_websocket_server = __commonJS({
|
|
|
26293
26386
|
);
|
|
26294
26387
|
}
|
|
26295
26388
|
if (this._state > RUNNING) return abortHandshake(socket2, 503);
|
|
26296
|
-
const digest2 =
|
|
26389
|
+
const digest2 = createHash16("sha1").update(key + GUID).digest("base64");
|
|
26297
26390
|
const headers = [
|
|
26298
26391
|
"HTTP/1.1 101 Switching Protocols",
|
|
26299
26392
|
"Upgrade: websocket",
|
|
@@ -26365,7 +26458,7 @@ var require_websocket_server = __commonJS({
|
|
|
26365
26458
|
socket2.once("finish", socket2.destroy);
|
|
26366
26459
|
socket2.end(
|
|
26367
26460
|
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r
|
|
26368
|
-
` + Object.keys(headers).map((
|
|
26461
|
+
` + Object.keys(headers).map((h2) => `${h2}: ${headers[h2]}`).join("\r\n") + "\r\n\r\n" + message
|
|
26369
26462
|
);
|
|
26370
26463
|
}
|
|
26371
26464
|
function abortHandshakeOrEmitwsClientError(server, req, socket2, code, message, headers) {
|
|
@@ -26589,7 +26682,7 @@ var init_client2 = __esm({
|
|
|
26589
26682
|
});
|
|
26590
26683
|
|
|
26591
26684
|
// src/services/logs/spool.ts
|
|
26592
|
-
import { appendFile, mkdir as mkdir8, readFile as
|
|
26685
|
+
import { appendFile, mkdir as mkdir8, readFile as readFile7, readdir as readdir9, rm as rm8, stat as stat6 } from "node:fs/promises";
|
|
26593
26686
|
import { join as join35 } from "node:path";
|
|
26594
26687
|
function spoolDir(lane) {
|
|
26595
26688
|
return join35(resolveDataDirs().home, "log-spool", lane);
|
|
@@ -26613,7 +26706,7 @@ async function readSpoolSegments(lane) {
|
|
|
26613
26706
|
const dir2 = spoolDir(lane);
|
|
26614
26707
|
let names;
|
|
26615
26708
|
try {
|
|
26616
|
-
names = await
|
|
26709
|
+
names = await readdir9(dir2);
|
|
26617
26710
|
} catch {
|
|
26618
26711
|
return [];
|
|
26619
26712
|
}
|
|
@@ -26621,7 +26714,7 @@ async function readSpoolSegments(lane) {
|
|
|
26621
26714
|
const out4 = [];
|
|
26622
26715
|
for (const name2 of files) {
|
|
26623
26716
|
const file = join35(dir2, name2);
|
|
26624
|
-
const raw = await
|
|
26717
|
+
const raw = await readFile7(file, "utf8").catch(() => "");
|
|
26625
26718
|
const entries3 = raw.split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
26626
26719
|
out4.push({ file, entries: entries3 });
|
|
26627
26720
|
}
|
|
@@ -27229,7 +27322,7 @@ var init_agent_tool_catalog = __esm({
|
|
|
27229
27322
|
});
|
|
27230
27323
|
|
|
27231
27324
|
// src/services/builtin-agents.ts
|
|
27232
|
-
import { createHash as
|
|
27325
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
27233
27326
|
async function seedBuiltinAgents() {
|
|
27234
27327
|
try {
|
|
27235
27328
|
const setting = await getSetting();
|
|
@@ -27319,7 +27412,7 @@ var init_builtin_agents2 = __esm({
|
|
|
27319
27412
|
init_settings3();
|
|
27320
27413
|
init_tombstone();
|
|
27321
27414
|
init_sync_notify();
|
|
27322
|
-
digest = (parts) =>
|
|
27415
|
+
digest = (parts) => createHash13("sha256").update(parts.join(" ")).digest("hex").slice(0, 16);
|
|
27323
27416
|
legacyFingerprint = (name2, description, instructions, tools) => digest([name2, description, instructions, [...tools].join(",")]);
|
|
27324
27417
|
fingerprint = (a) => digest([
|
|
27325
27418
|
a.name,
|
|
@@ -27825,14 +27918,14 @@ var require_HeaderParser = __commonJS({
|
|
|
27825
27918
|
}
|
|
27826
27919
|
const lines2 = this.buffer.split(RE_CRLF);
|
|
27827
27920
|
const len = lines2.length;
|
|
27828
|
-
let m,
|
|
27921
|
+
let m, h2;
|
|
27829
27922
|
for (var i = 0; i < len; ++i) {
|
|
27830
27923
|
if (lines2[i].length === 0) {
|
|
27831
27924
|
continue;
|
|
27832
27925
|
}
|
|
27833
27926
|
if (lines2[i][0] === " " || lines2[i][0] === " ") {
|
|
27834
|
-
if (
|
|
27835
|
-
this.header[
|
|
27927
|
+
if (h2) {
|
|
27928
|
+
this.header[h2][this.header[h2].length - 1] += lines2[i];
|
|
27836
27929
|
continue;
|
|
27837
27930
|
}
|
|
27838
27931
|
}
|
|
@@ -27841,9 +27934,9 @@ var require_HeaderParser = __commonJS({
|
|
|
27841
27934
|
return;
|
|
27842
27935
|
}
|
|
27843
27936
|
m = RE_HDR.exec(lines2[i]);
|
|
27844
|
-
|
|
27845
|
-
this.header[
|
|
27846
|
-
this.header[
|
|
27937
|
+
h2 = m[1].toLowerCase();
|
|
27938
|
+
this.header[h2] = this.header[h2] || [];
|
|
27939
|
+
this.header[h2].push(m[2] || "");
|
|
27847
27940
|
if (++this.npairs === this.maxHeaderPairs) {
|
|
27848
27941
|
break;
|
|
27849
27942
|
}
|
|
@@ -30835,14 +30928,14 @@ async function health_default(app3) {
|
|
|
30835
30928
|
}
|
|
30836
30929
|
|
|
30837
30930
|
// src/routes/agent-doc.ts
|
|
30838
|
-
import { readFile as
|
|
30931
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
30839
30932
|
async function agent_doc_default(app3, opts) {
|
|
30840
30933
|
app3.get("/agent-integration.md", async (_req, reply) => {
|
|
30841
30934
|
if (!opts.file)
|
|
30842
30935
|
return reply.code(404).send({
|
|
30843
30936
|
error: `dokumen panduan tak ada di instalasi ini (${AGENT_DOC_REL}) \u2014 pasang ulang paket hanoman atau set HANOMAN_AGENT_DOC`
|
|
30844
30937
|
});
|
|
30845
|
-
const text = await
|
|
30938
|
+
const text = await readFile4(opts.file, "utf8");
|
|
30846
30939
|
return reply.type("text/markdown; charset=utf-8").send(text);
|
|
30847
30940
|
});
|
|
30848
30941
|
}
|
|
@@ -30883,8 +30976,8 @@ function components(rel) {
|
|
|
30883
30976
|
}
|
|
30884
30977
|
async function canonicalRoot(root) {
|
|
30885
30978
|
const out4 = await realpath(root).catch(() => denied("root tidak ada"));
|
|
30886
|
-
const
|
|
30887
|
-
if (!
|
|
30979
|
+
const stat7 = await lstat(out4);
|
|
30980
|
+
if (!stat7.isDirectory() || stat7.isSymbolicLink()) denied("root bukan direktori regular");
|
|
30888
30981
|
return out4;
|
|
30889
30982
|
}
|
|
30890
30983
|
function beneath(root, candidate) {
|
|
@@ -30902,8 +30995,8 @@ async function ensureRepoParents(root, rel) {
|
|
|
30902
30995
|
} catch (error) {
|
|
30903
30996
|
if (error.code !== "EEXIST") throw error;
|
|
30904
30997
|
}
|
|
30905
|
-
const
|
|
30906
|
-
if (
|
|
30998
|
+
const stat7 = await lstat(current);
|
|
30999
|
+
if (stat7.isSymbolicLink() || !stat7.isDirectory()) denied("parent tidak aman");
|
|
30907
31000
|
const canonical = await realpath(current);
|
|
30908
31001
|
if (!beneath(base2, canonical) || canonical !== current) denied("parent keluar root");
|
|
30909
31002
|
}
|
|
@@ -30919,8 +31012,8 @@ function ensureRepoParentsSync(root, rel) {
|
|
|
30919
31012
|
} catch (error) {
|
|
30920
31013
|
if (error.code !== "EEXIST") throw error;
|
|
30921
31014
|
}
|
|
30922
|
-
const
|
|
30923
|
-
if (
|
|
31015
|
+
const stat7 = lstatSync3(current);
|
|
31016
|
+
if (stat7.isSymbolicLink() || !stat7.isDirectory()) denied("parent tidak aman");
|
|
30924
31017
|
if (realpathSync4(current) !== current || !beneath(base2, current)) denied("parent keluar root");
|
|
30925
31018
|
}
|
|
30926
31019
|
}
|
|
@@ -30930,14 +31023,14 @@ async function resolveRepoEntry(root, rel, opts = {}) {
|
|
|
30930
31023
|
let current = base2;
|
|
30931
31024
|
for (let i = 0; i < parts.length; i++) {
|
|
30932
31025
|
current = join14(current, parts[i]);
|
|
30933
|
-
const
|
|
31026
|
+
const stat7 = await lstat(current).catch((error) => {
|
|
30934
31027
|
if (error.code === "ENOENT" && opts.allowMissingTail) return null;
|
|
30935
31028
|
if (error.code === "ENOENT" && opts.allowMissingFinal && i === parts.length - 1) return null;
|
|
30936
31029
|
return denied("komponen tidak ada");
|
|
30937
31030
|
});
|
|
30938
|
-
if (!
|
|
30939
|
-
if (
|
|
30940
|
-
if (i < parts.length - 1 && !
|
|
31031
|
+
if (!stat7) break;
|
|
31032
|
+
if (stat7.isSymbolicLink()) denied("symlink");
|
|
31033
|
+
if (i < parts.length - 1 && !stat7.isDirectory()) denied("parent bukan direktori");
|
|
30941
31034
|
}
|
|
30942
31035
|
if (!beneath(base2, resolve12(current))) denied("keluar root");
|
|
30943
31036
|
const parent = resolve12(current, "..");
|
|
@@ -30952,9 +31045,9 @@ function assertSafeRepoPathSync(root, rel, allowMissingFinal = false, allowMissi
|
|
|
30952
31045
|
for (let i = 0; i < parts.length; i++) {
|
|
30953
31046
|
current = join14(current, parts[i]);
|
|
30954
31047
|
try {
|
|
30955
|
-
const
|
|
30956
|
-
if (
|
|
30957
|
-
if (i < parts.length - 1 && !
|
|
31048
|
+
const stat7 = lstatSync3(current);
|
|
31049
|
+
if (stat7.isSymbolicLink()) denied("symlink");
|
|
31050
|
+
if (i < parts.length - 1 && !stat7.isDirectory()) denied("parent bukan direktori");
|
|
30958
31051
|
} catch (error) {
|
|
30959
31052
|
if (allowMissingTail && error.code === "ENOENT") break;
|
|
30960
31053
|
if (allowMissingFinal && error.code === "ENOENT" && i === parts.length - 1) break;
|
|
@@ -31009,8 +31102,8 @@ async function readRepoFile(root, rel) {
|
|
|
31009
31102
|
const entry = await resolveRepoEntry(root, rel);
|
|
31010
31103
|
const handle = await open2(entry.path, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0)).catch(() => denied("final component tidak aman"));
|
|
31011
31104
|
try {
|
|
31012
|
-
const
|
|
31013
|
-
if (!
|
|
31105
|
+
const stat7 = await handle.stat();
|
|
31106
|
+
if (!stat7.isFile()) denied("bukan file regular");
|
|
31014
31107
|
return await handle.readFile();
|
|
31015
31108
|
} finally {
|
|
31016
31109
|
await handle.close();
|
|
@@ -31338,7 +31431,7 @@ async function projects_default(app3) {
|
|
|
31338
31431
|
const needle = (q ?? "").trim().toLowerCase();
|
|
31339
31432
|
const filtered = needle ? views.filter((v) => `${v.name} ${v.desc} ${v.stack}`.toLowerCase().includes(needle)) : views;
|
|
31340
31433
|
const device = (handledBy ?? "").trim();
|
|
31341
|
-
const scoped = device ? filtered.filter((v) => v.handledBy.some((
|
|
31434
|
+
const scoped = device ? filtered.filter((v) => v.handledBy.some((h2) => h2.deviceId === device)) : filtered;
|
|
31342
31435
|
return paginate(scoped, page, limit);
|
|
31343
31436
|
});
|
|
31344
31437
|
app3.get("/projects/:id", async (req, reply) => {
|
|
@@ -31916,7 +32009,7 @@ async function deleteDoc(projectId, path) {
|
|
|
31916
32009
|
init_src2();
|
|
31917
32010
|
init_db();
|
|
31918
32011
|
import { execFile as execFile6 } from "node:child_process";
|
|
31919
|
-
import { readdir as
|
|
32012
|
+
import { readdir as readdir3, rm as rm3 } from "node:fs/promises";
|
|
31920
32013
|
import { promisify as promisify5 } from "node:util";
|
|
31921
32014
|
import { join as join17, resolve as resolve15 } from "node:path";
|
|
31922
32015
|
init_notifications2();
|
|
@@ -31974,7 +32067,7 @@ async function sweepRepo(repoDir, projectId, deps = prodReaperDeps) {
|
|
|
31974
32067
|
const dir2 = trashDirOf(repoDir);
|
|
31975
32068
|
let entries3;
|
|
31976
32069
|
try {
|
|
31977
|
-
entries3 = await
|
|
32070
|
+
entries3 = await readdir3(dir2);
|
|
31978
32071
|
} catch {
|
|
31979
32072
|
return 0;
|
|
31980
32073
|
}
|
|
@@ -32033,7 +32126,7 @@ function ownsWorktree(repoDir, cwd) {
|
|
|
32033
32126
|
import { execFile as execFile7 } from "node:child_process";
|
|
32034
32127
|
import { promisify as promisify6 } from "node:util";
|
|
32035
32128
|
import { existsSync as existsSync8 } from "node:fs";
|
|
32036
|
-
import { readdir as
|
|
32129
|
+
import { readdir as readdir4 } from "node:fs/promises";
|
|
32037
32130
|
import { resolve as resolve17 } from "node:path";
|
|
32038
32131
|
var exec6 = promisify6(execFile7);
|
|
32039
32132
|
var GIT4 = { maxBuffer: 1 << 24 };
|
|
@@ -32079,7 +32172,7 @@ async function listDirEntries(repoDir, rel) {
|
|
|
32079
32172
|
const abs = repoAbsPath(repoDir, rel);
|
|
32080
32173
|
let raw;
|
|
32081
32174
|
try {
|
|
32082
|
-
raw = await
|
|
32175
|
+
raw = await readdir4(abs, { withFileTypes: true });
|
|
32083
32176
|
} catch {
|
|
32084
32177
|
return empty;
|
|
32085
32178
|
}
|
|
@@ -32245,12 +32338,12 @@ async function searchCommits(repoDir, q, by = "all") {
|
|
|
32245
32338
|
};
|
|
32246
32339
|
const byMessage = () => run4(["-i", "-F", `--grep=${q}`]);
|
|
32247
32340
|
const byAuthor = () => run4(["-i", `--author=${q}`]);
|
|
32248
|
-
const byHash = async () => (await run4([])).filter((
|
|
32341
|
+
const byHash = async () => (await run4([])).filter((h3) => h3.toLowerCase().startsWith(q.toLowerCase()));
|
|
32249
32342
|
if (by === "message") return byMessage();
|
|
32250
32343
|
if (by === "author") return byAuthor();
|
|
32251
32344
|
if (by === "hash") return byHash();
|
|
32252
|
-
const [m, a,
|
|
32253
|
-
return [.../* @__PURE__ */ new Set([...m, ...a, ...
|
|
32345
|
+
const [m, a, h2] = await Promise.all([byMessage(), byAuthor(), byHash()]);
|
|
32346
|
+
return [.../* @__PURE__ */ new Set([...m, ...a, ...h2])];
|
|
32254
32347
|
}
|
|
32255
32348
|
async function compareFile(repoDir, from, to, rel) {
|
|
32256
32349
|
if (!repoDir) return null;
|
|
@@ -32271,9 +32364,9 @@ async function commitDetail(repoDir, sha) {
|
|
|
32271
32364
|
try {
|
|
32272
32365
|
const fmt = ["%H", "%P", "%an", "%aI", "%s", "%G?", "%cn", "%cI", "%ae", "%b"].join(US);
|
|
32273
32366
|
const parts = (await exec6("git", ["show", "-s", `--pretty=format:${fmt}`, sha], { cwd: repoDir, ...GIT4 })).stdout.split(US);
|
|
32274
|
-
const [
|
|
32367
|
+
const [h2, parents, author, at, subject, gsig, committer, committedAt, authorEmail] = parts;
|
|
32275
32368
|
return {
|
|
32276
|
-
sha:
|
|
32369
|
+
sha: h2,
|
|
32277
32370
|
parents: parents ? parents.split(" ") : [],
|
|
32278
32371
|
author: author ?? "",
|
|
32279
32372
|
at: at ?? "",
|
|
@@ -33328,8 +33421,8 @@ var FileTokenizer = class _FileTokenizer extends AbstractTokenizer {
|
|
|
33328
33421
|
*/
|
|
33329
33422
|
static async fromFile(sourceFilePath) {
|
|
33330
33423
|
const fileHandle = await fsOpen(sourceFilePath, "r");
|
|
33331
|
-
const
|
|
33332
|
-
return new _FileTokenizer(fileHandle, { fileInfo: { path: sourceFilePath, size:
|
|
33424
|
+
const stat7 = await fileHandle.stat();
|
|
33425
|
+
return new _FileTokenizer(fileHandle, { fileInfo: { path: sourceFilePath, size: stat7.size } });
|
|
33333
33426
|
}
|
|
33334
33427
|
constructor(fileHandle, options2) {
|
|
33335
33428
|
super(options2);
|
|
@@ -33979,7 +34072,7 @@ function indexOf(buffer, portion) {
|
|
|
33979
34072
|
return -1;
|
|
33980
34073
|
}
|
|
33981
34074
|
function mergeArrays(chunks) {
|
|
33982
|
-
const totalLength = chunks.reduce((
|
|
34075
|
+
const totalLength = chunks.reduce((acc2, curr) => acc2 + curr.length, 0);
|
|
33983
34076
|
const mergedArray = new Uint8Array(totalLength);
|
|
33984
34077
|
let offset = 0;
|
|
33985
34078
|
for (const chunk of chunks) {
|
|
@@ -36939,7 +37032,7 @@ async function dropSpecAttachments(specId) {
|
|
|
36939
37032
|
|
|
36940
37033
|
// src/services/spec-attachment-dir.ts
|
|
36941
37034
|
init_db();
|
|
36942
|
-
import { mkdir as mkdir5, readdir as
|
|
37035
|
+
import { mkdir as mkdir5, readdir as readdir5, readFile as readFile5, rm as rm4, writeFile as writeFile4 } from "node:fs/promises";
|
|
36943
37036
|
import { join as join20 } from "node:path";
|
|
36944
37037
|
init_session_id();
|
|
36945
37038
|
init_uploads();
|
|
@@ -36993,7 +37086,7 @@ async function syncSpecAttachmentsDir(specId, projectId) {
|
|
|
36993
37086
|
for (const a of rows) {
|
|
36994
37087
|
let bytes;
|
|
36995
37088
|
try {
|
|
36996
|
-
bytes = await
|
|
37089
|
+
bytes = await readFile5(join20(uploadDir(), a.storageKey));
|
|
36997
37090
|
} catch {
|
|
36998
37091
|
continue;
|
|
36999
37092
|
}
|
|
@@ -37004,7 +37097,7 @@ async function syncSpecAttachmentsDir(specId, projectId) {
|
|
|
37004
37097
|
}
|
|
37005
37098
|
await writeFile4(join20(dir2, INDEX), renderIndex(specId, items), { mode: 384 });
|
|
37006
37099
|
const keep = /* @__PURE__ */ new Set([INDEX, ...items.map((a) => a.filename)]);
|
|
37007
|
-
for (const name2 of await
|
|
37100
|
+
for (const name2 of await readdir5(dir2)) {
|
|
37008
37101
|
if (!keep.has(name2)) await rm4(join20(dir2, name2), { recursive: true, force: true }).catch(() => {
|
|
37009
37102
|
});
|
|
37010
37103
|
}
|
|
@@ -39439,12 +39532,12 @@ function drawCode(doc, text) {
|
|
|
39439
39532
|
used += hl;
|
|
39440
39533
|
}
|
|
39441
39534
|
const body = seg.join("\n");
|
|
39442
|
-
const
|
|
39535
|
+
const h2 = doc.heightOfString(body, opts) + 12;
|
|
39443
39536
|
const top = doc.y;
|
|
39444
|
-
doc.save().rect(left, top, right - left,
|
|
39537
|
+
doc.save().rect(left, top, right - left, h2).fill(PANEL).restore();
|
|
39445
39538
|
doc.fillColor(INK).text(body, left + 8, top + 6, opts);
|
|
39446
39539
|
doc.x = left;
|
|
39447
|
-
doc.y = top +
|
|
39540
|
+
doc.y = top + h2;
|
|
39448
39541
|
if (i < lines2.length) doc.addPage();
|
|
39449
39542
|
}
|
|
39450
39543
|
doc.moveDown(0.5);
|
|
@@ -39478,12 +39571,12 @@ function drawTable(doc, t) {
|
|
|
39478
39571
|
const row = (cells, bold) => {
|
|
39479
39572
|
const texts = cells.map((c) => toWinAnsi(flatten(c.tokens).map((r) => r.text).join("") || c.text || ""));
|
|
39480
39573
|
doc.font(bold ? "Helvetica-Bold" : "Helvetica").fontSize(9.2).fillColor(bold ? STRONG : INK);
|
|
39481
|
-
const
|
|
39482
|
-
if (doc.y +
|
|
39574
|
+
const h2 = Math.max(...texts.map((s2) => doc.heightOfString(s2, { width: w - 10 })), 0) + 7;
|
|
39575
|
+
if (doc.y + h2 > doc.page.height - doc.page.margins.bottom) doc.addPage();
|
|
39483
39576
|
const y0 = doc.y;
|
|
39484
39577
|
texts.forEach((s2, i) => doc.text(s2, left + i * w + 5, y0 + 3, { width: w - 10 }));
|
|
39485
39578
|
doc.x = left;
|
|
39486
|
-
doc.y = y0 +
|
|
39579
|
+
doc.y = y0 + h2;
|
|
39487
39580
|
rule(doc, HAIR, 2);
|
|
39488
39581
|
};
|
|
39489
39582
|
rule(doc, HAIR, 2);
|
|
@@ -39632,12 +39725,14 @@ function inDayRange(at, from, to) {
|
|
|
39632
39725
|
}
|
|
39633
39726
|
|
|
39634
39727
|
// src/services/live-specs.ts
|
|
39728
|
+
init_src();
|
|
39635
39729
|
init_db();
|
|
39636
|
-
|
|
39730
|
+
init_live_phases();
|
|
39637
39731
|
init_session_phases();
|
|
39638
39732
|
init_stage_machine();
|
|
39639
39733
|
init_notifications2();
|
|
39640
39734
|
init_sync_notify();
|
|
39735
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
39641
39736
|
|
|
39642
39737
|
// src/services/spec-head.ts
|
|
39643
39738
|
init_db();
|
|
@@ -39659,34 +39754,102 @@ async function recordHeadSha(specId, worktree, read2 = readHead) {
|
|
|
39659
39754
|
|
|
39660
39755
|
// src/services/live-specs.ts
|
|
39661
39756
|
var specNum = (id2) => Number.parseInt(id2.match(/\d+/)?.[0] ?? "0", 10);
|
|
39662
|
-
|
|
39663
|
-
|
|
39664
|
-
|
|
39665
|
-
|
|
39666
|
-
|
|
39667
|
-
|
|
39668
|
-
|
|
39757
|
+
var selectOf = (shape) => Object.fromEntries(
|
|
39758
|
+
Object.keys(shape).filter((k) => k !== "blockedBy").map((k) => [k, true])
|
|
39759
|
+
);
|
|
39760
|
+
var SLIM_SELECT = selectOf(zSpecSlim.shape);
|
|
39761
|
+
var LIST_SELECT = selectOf(zSpecListItem.shape);
|
|
39762
|
+
var phases = () => sessionPhasesBySpecAsync().catch(() => /* @__PURE__ */ new Map());
|
|
39763
|
+
var sortByNumber = (rows) => {
|
|
39764
|
+
const n2 = new Map(rows.map((r) => [r.id, specNum(r.id)]));
|
|
39765
|
+
return rows.sort((a, b) => n2.get(b.id) - n2.get(a.id));
|
|
39766
|
+
};
|
|
39767
|
+
async function applyOverlay(specs, live) {
|
|
39768
|
+
if (live.size === 0) return { out: specs, advanced: [], doneNow: [] };
|
|
39669
39769
|
const advanced = [];
|
|
39670
39770
|
const doneNow = [];
|
|
39671
|
-
const out4 = specs.map((s2) => {
|
|
39771
|
+
const out4 = await Promise.all(specs.map(async (s2) => {
|
|
39672
39772
|
const entry = live.get(s2.id);
|
|
39673
39773
|
if (!entry) return s2;
|
|
39674
|
-
const next =
|
|
39774
|
+
const next = await stageForRunAsync(entry.phases, entry.cwd, s2.id);
|
|
39675
39775
|
if (!next || STAGES.indexOf(next) <= STAGES.indexOf(s2.stage)) return s2;
|
|
39676
39776
|
advanced.push({ id: s2.id, from: s2.stage, stage: next, cwd: entry.cwd });
|
|
39677
39777
|
if (next === "done") doneNow.push({ specId: s2.id, title: s2.title, projectId: s2.projectId });
|
|
39678
39778
|
return { ...s2, stage: next };
|
|
39679
|
-
});
|
|
39680
|
-
|
|
39681
|
-
|
|
39682
|
-
|
|
39683
|
-
|
|
39684
|
-
|
|
39685
|
-
|
|
39686
|
-
}
|
|
39779
|
+
}));
|
|
39780
|
+
return { out: out4, advanced, doneNow };
|
|
39781
|
+
}
|
|
39782
|
+
async function liveOverlayTick() {
|
|
39783
|
+
const live = await phases();
|
|
39784
|
+
if (live.size === 0) return;
|
|
39785
|
+
const specs = await prisma.spec.findMany({
|
|
39786
|
+
where: { id: { in: [...live.keys()] } },
|
|
39787
|
+
select: { id: true, stage: true, title: true, projectId: true }
|
|
39788
|
+
});
|
|
39789
|
+
const { advanced, doneNow } = await applyOverlay(specs, live);
|
|
39790
|
+
await Promise.all(advanced.map(async (a) => {
|
|
39791
|
+
const res = await prisma.spec.updateMany({ where: { id: a.id, stage: a.from }, data: { stage: a.stage } }).catch(() => ({ count: 0 }));
|
|
39792
|
+
if (res.count === 0) return;
|
|
39793
|
+
if (a.stage === "done") await recordHeadSha(a.id, a.cwd);
|
|
39794
|
+
await notifySynced("spec", a.id);
|
|
39795
|
+
}));
|
|
39687
39796
|
await Promise.all(doneNow.map((d) => recordCompletion(d.specId, d.title, d.projectId)));
|
|
39797
|
+
}
|
|
39798
|
+
async function listSpecsLive(filter = {}) {
|
|
39799
|
+
const specs = sortByNumber(await prisma.spec.findMany({
|
|
39800
|
+
where: { projectId: filter.project, source: filter.source },
|
|
39801
|
+
orderBy: { id: "desc" }
|
|
39802
|
+
}));
|
|
39803
|
+
return decorateBlocked((await applyOverlay(specs, await phases())).out);
|
|
39804
|
+
}
|
|
39805
|
+
async function listSelected(select, where = {}) {
|
|
39806
|
+
const rows = sortByNumber(await prisma.spec.findMany({ where, select, orderBy: { id: "desc" } }));
|
|
39807
|
+
const { out: out4 } = await applyOverlay(rows, await phases());
|
|
39688
39808
|
return decorateBlocked(out4);
|
|
39689
39809
|
}
|
|
39810
|
+
var slimCache = null;
|
|
39811
|
+
async function slimRows() {
|
|
39812
|
+
const count = await prisma.spec.count();
|
|
39813
|
+
if (slimCache) {
|
|
39814
|
+
const changed = await prisma.spec.findMany({
|
|
39815
|
+
where: { updatedAt: { gte: slimCache.max } },
|
|
39816
|
+
select: SLIM_SELECT
|
|
39817
|
+
});
|
|
39818
|
+
for (const r of changed) {
|
|
39819
|
+
slimCache.rows.set(r.id, r);
|
|
39820
|
+
if (r.updatedAt > slimCache.max) slimCache.max = r.updatedAt;
|
|
39821
|
+
}
|
|
39822
|
+
if (slimCache.rows.size === count) return [...slimCache.rows.values()];
|
|
39823
|
+
}
|
|
39824
|
+
const rows = await prisma.spec.findMany({ select: SLIM_SELECT });
|
|
39825
|
+
slimCache = { rows: new Map(rows.map((r) => [r.id, r])), max: rows.reduce((m, r) => r.updatedAt > m ? r.updatedAt : m, /* @__PURE__ */ new Date(0)) };
|
|
39826
|
+
return rows;
|
|
39827
|
+
}
|
|
39828
|
+
async function listSpecsSlim() {
|
|
39829
|
+
const { out: out4 } = await applyOverlay(sortByNumber(await slimRows()), await phases());
|
|
39830
|
+
return await decorateBlocked(out4);
|
|
39831
|
+
}
|
|
39832
|
+
var listSpecsItems = (filter = {}) => listSelected(LIST_SELECT, { projectId: filter.project, source: filter.source });
|
|
39833
|
+
async function overlayOne(spec) {
|
|
39834
|
+
const live = await phases();
|
|
39835
|
+
return (await applyOverlay([spec], live)).out[0];
|
|
39836
|
+
}
|
|
39837
|
+
async function liveSignature() {
|
|
39838
|
+
const live = await phases();
|
|
39839
|
+
const parts = await Promise.all([...live.entries()].sort(([a], [b]) => a.localeCompare(b)).map(
|
|
39840
|
+
async ([id2, e]) => `${id2}:${e.phases.map((p3) => p3.state[0]).join("")}${phasesComplete(e.phases) ? `:${await planCompleteAsync(e.cwd, id2)}` : ""}`
|
|
39841
|
+
));
|
|
39842
|
+
return parts.join("|");
|
|
39843
|
+
}
|
|
39844
|
+
async function specsDigest() {
|
|
39845
|
+
const agg = await prisma.spec.aggregate({ _count: { _all: true }, _max: { updatedAt: true }, _sum: { version: true } });
|
|
39846
|
+
const db = `${agg._count._all}:${agg._max.updatedAt?.getTime() ?? 0}:${agg._sum.version ?? 0}`;
|
|
39847
|
+
return createHash4("sha1").update(`${db}|${await liveSignature()}`).digest("hex");
|
|
39848
|
+
}
|
|
39849
|
+
async function liveSpecs(filter = {}) {
|
|
39850
|
+
await liveOverlayTick();
|
|
39851
|
+
return listSpecsLive(filter);
|
|
39852
|
+
}
|
|
39690
39853
|
|
|
39691
39854
|
// src/services/launch-authority.ts
|
|
39692
39855
|
init_src();
|
|
@@ -39721,8 +39884,15 @@ function filterSpecs(specs, f) {
|
|
|
39721
39884
|
async function specs_default(app3) {
|
|
39722
39885
|
app3.get("/specs", async (req) => {
|
|
39723
39886
|
const { project, source, q, stage, priority, startable, dateField, from, to, page, limit } = req.query;
|
|
39724
|
-
|
|
39725
|
-
|
|
39887
|
+
await liveOverlayTick();
|
|
39888
|
+
const items = await listSpecsItems({ project, source });
|
|
39889
|
+
return paginate(filterSpecs(items, { q, stage, priority, startable, dateField, from, to }), page, limit);
|
|
39890
|
+
});
|
|
39891
|
+
app3.get("/specs/:id", async (req, reply) => {
|
|
39892
|
+
const { id: id2 } = req.params;
|
|
39893
|
+
const row = await prisma.spec.findUnique({ where: { id: id2 } });
|
|
39894
|
+
if (!row) return reply.code(404).send({ error: "spec tak ditemukan" });
|
|
39895
|
+
return (await decorateBlocked([await overlayOne(row)]))[0];
|
|
39726
39896
|
});
|
|
39727
39897
|
app3.post("/specs", async (req, reply) => {
|
|
39728
39898
|
const parsed = zCreateSpec.safeParse(req.body);
|
|
@@ -40583,7 +40753,7 @@ async function deleteBranches(repoDir, names, opts) {
|
|
|
40583
40753
|
// src/services/worktree-list.ts
|
|
40584
40754
|
import { execFile as execFile11 } from "node:child_process";
|
|
40585
40755
|
import { realpathSync as realpathSync6 } from "node:fs";
|
|
40586
|
-
import { stat as
|
|
40756
|
+
import { stat as stat4 } from "node:fs/promises";
|
|
40587
40757
|
import { basename as basename6, dirname as dirname11, join as join21, resolve as resolve19, sep as sep6 } from "node:path";
|
|
40588
40758
|
import { promisify as promisify10 } from "node:util";
|
|
40589
40759
|
var exec10 = promisify10(execFile11);
|
|
@@ -40638,7 +40808,7 @@ function hasWorktreeSession(path, sessionId2, sessions) {
|
|
|
40638
40808
|
}
|
|
40639
40809
|
async function bornAt(path) {
|
|
40640
40810
|
try {
|
|
40641
|
-
const st = await
|
|
40811
|
+
const st = await stat4(join21(path, ".git"));
|
|
40642
40812
|
const ms = st.birthtimeMs > 0 ? st.birthtimeMs : st.mtimeMs;
|
|
40643
40813
|
return new Date(ms).toISOString();
|
|
40644
40814
|
} catch {
|
|
@@ -40653,10 +40823,10 @@ async function listWorktrees(repoDir, inputs) {
|
|
|
40653
40823
|
const trash = resolve19(baseReal, ".worktrees", ".trash");
|
|
40654
40824
|
const sessions = new Map(inputs.sessions.map((s2) => [real(s2.cwd), { id: s2.id, specId: s2.specId }]));
|
|
40655
40825
|
const history = /* @__PURE__ */ new Map();
|
|
40656
|
-
for (const
|
|
40657
|
-
const path = real(
|
|
40826
|
+
for (const h2 of inputs.history ?? []) {
|
|
40827
|
+
const path = real(h2.cwd);
|
|
40658
40828
|
const previous = history.get(path);
|
|
40659
|
-
if (!previous ||
|
|
40829
|
+
if (!previous || h2.startedAt > previous.startedAt) history.set(path, h2);
|
|
40660
40830
|
}
|
|
40661
40831
|
const raw = parseWorktreePorcelain(text ?? "");
|
|
40662
40832
|
const mainPath = raw[0] ? resolve19(raw[0].path) : null;
|
|
@@ -41054,10 +41224,14 @@ function installSessionHistory() {
|
|
|
41054
41224
|
|
|
41055
41225
|
// src/services/worktree-project.ts
|
|
41056
41226
|
init_session_id();
|
|
41227
|
+
var toWorktreeSession = (s2) => ({ cwd: s2.cwd, id: s2.id, specId: s2.specId ?? null });
|
|
41057
41228
|
function worktreeSessions() {
|
|
41058
|
-
return listSessions().map(
|
|
41229
|
+
return listSessions().map(toWorktreeSession);
|
|
41230
|
+
}
|
|
41231
|
+
async function worktreeSessionsAsync() {
|
|
41232
|
+
return (await listSessionsAsync()).map(toWorktreeSession);
|
|
41059
41233
|
}
|
|
41060
|
-
async function projectWorktreeInputs(projectId, sessions =
|
|
41234
|
+
async function projectWorktreeInputs(projectId, sessions = worktreeSessionsAsync) {
|
|
41061
41235
|
const [specs, history] = await Promise.all([
|
|
41062
41236
|
prisma.spec.findMany({ where: { projectId }, select: { id: true, stage: true } }),
|
|
41063
41237
|
worktreeHistory(projectId)
|
|
@@ -41065,7 +41239,7 @@ async function projectWorktreeInputs(projectId, sessions = worktreeSessions) {
|
|
|
41065
41239
|
return {
|
|
41066
41240
|
specs: new Map(specs.map((s2) => [sessionIdForSpec(s2.id), s2])),
|
|
41067
41241
|
history,
|
|
41068
|
-
sessions: sessions()
|
|
41242
|
+
sessions: await sessions()
|
|
41069
41243
|
};
|
|
41070
41244
|
}
|
|
41071
41245
|
async function detectOrphanWorktrees(deps = { repos: prodReaperDeps.repos, inputs: projectWorktreeInputs }) {
|
|
@@ -41665,7 +41839,7 @@ async function finishGraphOp(reply, id2, repoDir, r, verb) {
|
|
|
41665
41839
|
}
|
|
41666
41840
|
|
|
41667
41841
|
// src/routes/fs.ts
|
|
41668
|
-
import { readdir as
|
|
41842
|
+
import { readdir as readdir6 } from "node:fs/promises";
|
|
41669
41843
|
import { homedir as homedir4 } from "node:os";
|
|
41670
41844
|
import { resolve as resolve21, dirname as dirname12, join as join23 } from "node:path";
|
|
41671
41845
|
async function fs_default(app3) {
|
|
@@ -41673,7 +41847,7 @@ async function fs_default(app3) {
|
|
|
41673
41847
|
const q = req.query.path;
|
|
41674
41848
|
const dir2 = q && q.trim() ? resolve21(q.trim()) : homedir4();
|
|
41675
41849
|
try {
|
|
41676
|
-
const ents = await
|
|
41850
|
+
const ents = await readdir6(dir2, { withFileTypes: true });
|
|
41677
41851
|
const entries3 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join23(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
|
|
41678
41852
|
const parent = dirname12(dir2);
|
|
41679
41853
|
return { path: dir2, parent: parent === dir2 ? null : parent, entries: entries3 };
|
|
@@ -41735,10 +41909,10 @@ function resumeState(repoDir, worktree, branchTo, headSha) {
|
|
|
41735
41909
|
return base2 ? { worktreeKept: false, base: base2 } : null;
|
|
41736
41910
|
}
|
|
41737
41911
|
function buildResumeCtx(repoDir, id2, flow2, worktreeKept) {
|
|
41738
|
-
const
|
|
41912
|
+
const phases2 = readPhases(phaseFilePath(repoDir, id2), flow2);
|
|
41739
41913
|
return {
|
|
41740
|
-
recorded:
|
|
41741
|
-
next:
|
|
41914
|
+
recorded: phases2.filter((p3) => p3.state === "done" || p3.state === "skipped").map((p3) => `${p3.name} ${p3.state}`),
|
|
41915
|
+
next: phases2.find((p3) => p3.state === "active")?.name,
|
|
41742
41916
|
worktreeKept
|
|
41743
41917
|
};
|
|
41744
41918
|
}
|
|
@@ -41834,8 +42008,8 @@ async function startSpecSession(spec, opts) {
|
|
|
41834
42008
|
const fullPlan = sessionPhasePlan(setting, opts.flow, agent, { model, effort: effort2 }, opts.phaseOverrides);
|
|
41835
42009
|
let plan = fullPlan;
|
|
41836
42010
|
if (fullPlan && isContinue && !isGoalFlow) {
|
|
41837
|
-
const
|
|
41838
|
-
plan =
|
|
42011
|
+
const phases2 = fullPlan.phases.filter((p3) => p3.phase === "Execute");
|
|
42012
|
+
plan = phases2.length ? { ...fullPlan, phases: phases2 } : null;
|
|
41839
42013
|
}
|
|
41840
42014
|
const buildPrompt = (p3) => {
|
|
41841
42015
|
if (isGoalFlow) {
|
|
@@ -41936,7 +42110,7 @@ function setBounded(map, key, value, maxKeys) {
|
|
|
41936
42110
|
// src/services/auth.ts
|
|
41937
42111
|
init_src2();
|
|
41938
42112
|
init_db();
|
|
41939
|
-
import { randomBytes as randomBytes3, scrypt as scryptCb, timingSafeEqual as timingSafeEqual3, createHash as
|
|
42113
|
+
import { randomBytes as randomBytes3, scrypt as scryptCb, timingSafeEqual as timingSafeEqual3, createHash as createHash9 } from "node:crypto";
|
|
41940
42114
|
import { promisify as promisify12 } from "node:util";
|
|
41941
42115
|
var scrypt = promisify12(scryptCb);
|
|
41942
42116
|
var COOKIE_NAME = "hn_session";
|
|
@@ -41954,7 +42128,7 @@ async function verifyPassword(pw, stored) {
|
|
|
41954
42128
|
return key.length === want.length && timingSafeEqual3(key, want);
|
|
41955
42129
|
}
|
|
41956
42130
|
var newSessionToken = () => randomBytes3(32).toString("base64url");
|
|
41957
|
-
var sessionId = (token) =>
|
|
42131
|
+
var sessionId = (token) => createHash9("sha256").update(token).digest("hex");
|
|
41958
42132
|
async function createSession2(userId) {
|
|
41959
42133
|
const token = newSessionToken();
|
|
41960
42134
|
await prisma.session.create({
|
|
@@ -42015,9 +42189,9 @@ function cookieOpts(req) {
|
|
|
42015
42189
|
|
|
42016
42190
|
// src/services/device-token.ts
|
|
42017
42191
|
init_db();
|
|
42018
|
-
import { randomBytes as randomBytes4, createHash as
|
|
42192
|
+
import { randomBytes as randomBytes4, createHash as createHash10 } from "node:crypto";
|
|
42019
42193
|
var newDeviceToken = () => randomBytes4(32).toString("base64url");
|
|
42020
|
-
var tokenHash = (token) =>
|
|
42194
|
+
var tokenHash = (token) => createHash10("sha256").update(token).digest("hex");
|
|
42021
42195
|
async function issueDeviceToken(userId, name2) {
|
|
42022
42196
|
const token = newDeviceToken();
|
|
42023
42197
|
const row = await prisma.deviceToken.create({ data: { userId, name: name2, tokenHash: tokenHash(token) } });
|
|
@@ -42253,12 +42427,77 @@ function appendDiag(home3, sessionId2, events) {
|
|
|
42253
42427
|
init_notifications2();
|
|
42254
42428
|
init_stage_machine();
|
|
42255
42429
|
init_pty();
|
|
42430
|
+
|
|
42431
|
+
// src/services/bounded-sender.ts
|
|
42432
|
+
var HIGH = 1024 * 1024;
|
|
42433
|
+
var CAP = 256 * 1024;
|
|
42434
|
+
var DRAIN_MS = 20;
|
|
42435
|
+
var isData = (frame2) => frame2.startsWith('{"t":"data"');
|
|
42436
|
+
function createBoundedSender(ws2, o = {}) {
|
|
42437
|
+
const high = o.high ?? HIGH;
|
|
42438
|
+
const cap = o.cap ?? CAP;
|
|
42439
|
+
const drainMs = o.drainMs ?? DRAIN_MS;
|
|
42440
|
+
let pending2 = [];
|
|
42441
|
+
let pendingBytes = 0;
|
|
42442
|
+
let timer10;
|
|
42443
|
+
let resyncNeeded = false;
|
|
42444
|
+
const trim = () => {
|
|
42445
|
+
for (let i = 0; i < pending2.length && pendingBytes > cap; ) {
|
|
42446
|
+
if (!isData(pending2[i])) {
|
|
42447
|
+
i++;
|
|
42448
|
+
continue;
|
|
42449
|
+
}
|
|
42450
|
+
pendingBytes -= pending2[i].length;
|
|
42451
|
+
pending2.splice(i, 1);
|
|
42452
|
+
resyncNeeded = true;
|
|
42453
|
+
}
|
|
42454
|
+
};
|
|
42455
|
+
const drain2 = () => {
|
|
42456
|
+
timer10 = void 0;
|
|
42457
|
+
if (ws2.bufferedAmount > high) {
|
|
42458
|
+
timer10 = setTimeout(drain2, drainMs);
|
|
42459
|
+
timer10.unref?.();
|
|
42460
|
+
return;
|
|
42461
|
+
}
|
|
42462
|
+
const out4 = pending2;
|
|
42463
|
+
pending2 = [];
|
|
42464
|
+
pendingBytes = 0;
|
|
42465
|
+
for (const f of out4) ws2.send(f);
|
|
42466
|
+
if (resyncNeeded) {
|
|
42467
|
+
resyncNeeded = false;
|
|
42468
|
+
o.onResync?.();
|
|
42469
|
+
}
|
|
42470
|
+
};
|
|
42471
|
+
return {
|
|
42472
|
+
send(frame2) {
|
|
42473
|
+
if (pending2.length === 0 && ws2.bufferedAmount <= high) {
|
|
42474
|
+
ws2.send(frame2);
|
|
42475
|
+
return;
|
|
42476
|
+
}
|
|
42477
|
+
pending2.push(frame2);
|
|
42478
|
+
pendingBytes += frame2.length;
|
|
42479
|
+
trim();
|
|
42480
|
+
if (!timer10) {
|
|
42481
|
+
timer10 = setTimeout(drain2, drainMs);
|
|
42482
|
+
timer10.unref?.();
|
|
42483
|
+
}
|
|
42484
|
+
},
|
|
42485
|
+
dispose() {
|
|
42486
|
+
if (timer10) clearTimeout(timer10);
|
|
42487
|
+
timer10 = void 0;
|
|
42488
|
+
pending2 = [];
|
|
42489
|
+
pendingBytes = 0;
|
|
42490
|
+
}
|
|
42491
|
+
};
|
|
42492
|
+
}
|
|
42493
|
+
|
|
42494
|
+
// src/routes/terminal.ts
|
|
42256
42495
|
init_uploads();
|
|
42257
42496
|
|
|
42258
42497
|
// src/services/agent-invocations.ts
|
|
42259
42498
|
init_src();
|
|
42260
42499
|
init_db();
|
|
42261
|
-
import { createHash as
|
|
42500
|
+
import { createHash as createHash11, randomUUID as randomUUID9 } from "node:crypto";
|
|
42262
42501
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
42263
42502
|
import { homedir as homedir5 } from "node:os";
|
|
42264
42503
|
import { readFileSync as readFileSync12, realpathSync as realpathSync7, statSync as statSync4 } from "node:fs";
|
|
@@ -42268,7 +42507,7 @@ import { isAbsolute as isAbsolute8, relative as relative4, resolve as resolve22
|
|
|
42268
42507
|
init_session_event_token();
|
|
42269
42508
|
init_session_event_spool();
|
|
42270
42509
|
import { constants as constants3 } from "node:fs";
|
|
42271
|
-
import { mkdir as mkdir6, open as open3, readdir as
|
|
42510
|
+
import { mkdir as mkdir6, open as open3, readdir as readdir7, rm as rm6 } from "node:fs/promises";
|
|
42272
42511
|
import { join as join25 } from "node:path";
|
|
42273
42512
|
var MAX_EVENT_BYTES = 1e6;
|
|
42274
42513
|
var MAX_FILES_PER_DRAIN = 1e3;
|
|
@@ -42314,12 +42553,12 @@ async function drainSpool(app3, root, observation) {
|
|
|
42314
42553
|
let delivered = 0;
|
|
42315
42554
|
let examined = 0;
|
|
42316
42555
|
let readBuffer;
|
|
42317
|
-
for (const session of await
|
|
42556
|
+
for (const session of await readdir7(root, { withFileTypes: true })) {
|
|
42318
42557
|
if (!session.isDirectory() || !SESSION_ID_RE.test(session.name)) continue;
|
|
42319
42558
|
const dir2 = join25(root, session.name);
|
|
42320
42559
|
let entries3;
|
|
42321
42560
|
try {
|
|
42322
|
-
entries3 = await
|
|
42561
|
+
entries3 = await readdir7(dir2, { withFileTypes: true });
|
|
42323
42562
|
} catch {
|
|
42324
42563
|
continue;
|
|
42325
42564
|
}
|
|
@@ -42331,8 +42570,8 @@ async function drainSpool(app3, root, observation) {
|
|
|
42331
42570
|
let handle;
|
|
42332
42571
|
try {
|
|
42333
42572
|
handle = await open3(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
42334
|
-
const
|
|
42335
|
-
if (!
|
|
42573
|
+
const stat7 = await handle.stat();
|
|
42574
|
+
if (!stat7.isFile() || stat7.size > MAX_EVENT_BYTES) throw new Error("payload terlalu besar");
|
|
42336
42575
|
readBuffer ??= Buffer.allocUnsafe(MAX_EVENT_BYTES + 1);
|
|
42337
42576
|
const { bytesRead } = await handle.read(readBuffer, 0, readBuffer.length, 0);
|
|
42338
42577
|
if (bytesRead > MAX_EVENT_BYTES) throw new Error("payload terlalu besar");
|
|
@@ -42405,7 +42644,7 @@ var MAX_TRANSCRIPT_BYTES2 = 10 * 1024 * 1024;
|
|
|
42405
42644
|
var ANSI2 = /[\u001b\u009b](?:\][^\u0007]*(?:\u0007|\u001b\\)|\[[0-?]*[ -/]*[@-~]|[@-_])/g;
|
|
42406
42645
|
var snapshotHashes = /* @__PURE__ */ new Map();
|
|
42407
42646
|
var keyOf = (x) => `${x.sessionId}\0${x.runtimeInvocationId}`;
|
|
42408
|
-
var hash3 = (value) =>
|
|
42647
|
+
var hash3 = (value) => createHash11("sha256").update(value).digest("hex");
|
|
42409
42648
|
var defaultGitStatus = (cwd) => {
|
|
42410
42649
|
try {
|
|
42411
42650
|
return execFileSync3("git", ["-C", cwd, "status", "--porcelain=v1", "-z"], {
|
|
@@ -42751,9 +42990,9 @@ async function refreshPhaseInvocations(sessionId2) {
|
|
|
42751
42990
|
// src/services/session-dialog.ts
|
|
42752
42991
|
init_tui_dialog();
|
|
42753
42992
|
init_pty();
|
|
42754
|
-
import { createHash as
|
|
42993
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
42755
42994
|
var DIALOG_CHUNK_MS = 50;
|
|
42756
|
-
var screenHashOf = (paneText2) =>
|
|
42995
|
+
var screenHashOf = (paneText2) => createHash12("sha256").update(dialogKey(paneText2)).digest("hex").slice(0, 16);
|
|
42757
42996
|
var answerable = (paneText2) => {
|
|
42758
42997
|
const s2 = readDialogScreen(paneText2);
|
|
42759
42998
|
if (s2?.kind !== "question") return null;
|
|
@@ -44363,9 +44602,9 @@ async function terminal_default(app3, opts) {
|
|
|
44363
44602
|
app3.get("/terminal/sessions/:id/phases", async (req, reply) => {
|
|
44364
44603
|
const { id: id2 } = req.params;
|
|
44365
44604
|
const s2 = getSession(id2);
|
|
44366
|
-
const
|
|
44367
|
-
if (!s2?.flow || !
|
|
44368
|
-
return { flow: s2.flow, phases };
|
|
44605
|
+
const phases2 = sessionPhases(id2);
|
|
44606
|
+
if (!s2?.flow || !phases2) return reply.code(404).send({ error: "not found" });
|
|
44607
|
+
return { flow: s2.flow, phases: phases2 };
|
|
44369
44608
|
});
|
|
44370
44609
|
app3.post("/terminal/sessions/:id/steer", async (req, reply) => {
|
|
44371
44610
|
const parsed = zTerminalSteerInput.safeParse(req.body);
|
|
@@ -44503,7 +44742,6 @@ async function terminal_default(app3, opts) {
|
|
|
44503
44742
|
}
|
|
44504
44743
|
}, (socket2, req) => {
|
|
44505
44744
|
const { id: id2 } = req.params;
|
|
44506
|
-
if (!getSession(id2)) return socket2.close(4004, "not found");
|
|
44507
44745
|
const isClientRemote = req.remote !== void 0;
|
|
44508
44746
|
const principal = isClientRemote ? { kind: "remote", id: `client:${req.remote.actor.hubOrigin}:${req.remote.actor.userId}` } : req.wsPrincipal;
|
|
44509
44747
|
let release;
|
|
@@ -44514,14 +44752,38 @@ async function terminal_default(app3, opts) {
|
|
|
44514
44752
|
return;
|
|
44515
44753
|
}
|
|
44516
44754
|
const guard = new WsMessageGuard({ perWindow: TERMINAL_WS_MESSAGES_PER_MINUTE });
|
|
44517
|
-
const
|
|
44518
|
-
|
|
44519
|
-
|
|
44755
|
+
const bounded = createBoundedSender(socket2, {
|
|
44756
|
+
onResync: () => {
|
|
44757
|
+
void screenResyncFrame(id2).then((f) => {
|
|
44758
|
+
if (f) bounded.send(f);
|
|
44759
|
+
});
|
|
44760
|
+
}
|
|
44761
|
+
});
|
|
44762
|
+
const client = { send: (m) => bounded.send(m), close: () => socket2.close() };
|
|
44763
|
+
let ready = false;
|
|
44764
|
+
let closed = false;
|
|
44765
|
+
const early = [];
|
|
44766
|
+
const attachReady = (async () => {
|
|
44767
|
+
const found = await getSessionAsync(id2);
|
|
44768
|
+
if (!found) {
|
|
44769
|
+
socket2.close(4004, "not found");
|
|
44770
|
+
return;
|
|
44771
|
+
}
|
|
44772
|
+
if (closed) return;
|
|
44773
|
+
await attachAsync(id2, client);
|
|
44774
|
+
if (closed) {
|
|
44775
|
+
detach(id2, client);
|
|
44776
|
+
return;
|
|
44777
|
+
}
|
|
44778
|
+
void refreshPhaseInvocations(id2);
|
|
44779
|
+
})().catch(() => {
|
|
44780
|
+
socket2.close(1011, "attach failed");
|
|
44781
|
+
});
|
|
44520
44782
|
const watch = isClientRemote ? null : createPrincipalWatch({
|
|
44521
44783
|
check: () => revalidateWsPrincipal(req, principal),
|
|
44522
44784
|
onRevoked: () => socket2.close(1008, "session revoked")
|
|
44523
44785
|
});
|
|
44524
|
-
|
|
44786
|
+
const onMessage = (raw) => {
|
|
44525
44787
|
const verdict = guard.accept(raw);
|
|
44526
44788
|
if (!verdict.ok) {
|
|
44527
44789
|
socket2.close(verdict.code, verdict.reason);
|
|
@@ -44544,10 +44806,20 @@ async function terminal_default(app3, opts) {
|
|
|
44544
44806
|
} catch {
|
|
44545
44807
|
}
|
|
44546
44808
|
}
|
|
44809
|
+
};
|
|
44810
|
+
socket2.on("message", (raw) => {
|
|
44811
|
+
if (ready) onMessage(raw);
|
|
44812
|
+
else early.push(raw);
|
|
44813
|
+
});
|
|
44814
|
+
void attachReady.then(() => {
|
|
44815
|
+
ready = true;
|
|
44816
|
+
for (const raw of early.splice(0)) onMessage(raw);
|
|
44547
44817
|
});
|
|
44548
44818
|
const revalidate = watch ? setInterval(() => watch.refresh(), 6e4) : void 0;
|
|
44549
44819
|
revalidate?.unref?.();
|
|
44550
44820
|
socket2.on("close", () => {
|
|
44821
|
+
closed = true;
|
|
44822
|
+
bounded.dispose();
|
|
44551
44823
|
if (revalidate) clearInterval(revalidate);
|
|
44552
44824
|
watch?.dispose();
|
|
44553
44825
|
release();
|
|
@@ -45037,13 +45309,13 @@ function mapToCatalog(checks) {
|
|
|
45037
45309
|
var isHardened = (checks) => CRITICAL.every((c) => checks.find((x) => x.check === c)?.status === "pass");
|
|
45038
45310
|
var HEALTH_CMD = `echo HEALTH uptime $(uptime -p 2>/dev/null || uptime); echo HEALTH disk $(df -P / | awk 'NR==2{print $5}'); echo HEALTH mem $(free -m 2>/dev/null | awk 'NR==2{printf "%d/%dMB", $3, $2}'); echo HEALTH load $(cut -d" " -f1-3 /proc/loadavg)`;
|
|
45039
45311
|
function parseHealth(out4) {
|
|
45040
|
-
const
|
|
45312
|
+
const h2 = {};
|
|
45041
45313
|
for (const line of out4.split("\n")) {
|
|
45042
45314
|
const m = line.match(/^HEALTH (\S+) ?(.*)$/);
|
|
45043
|
-
if (m)
|
|
45315
|
+
if (m) h2[m[1]] = m[2].trim();
|
|
45044
45316
|
}
|
|
45045
|
-
if (!
|
|
45046
|
-
return { uptime:
|
|
45317
|
+
if (!h2.uptime && !h2.disk) return null;
|
|
45318
|
+
return { uptime: h2.uptime ?? "", disk: h2.disk ?? "", mem: h2.mem ?? "", load: h2.load ?? "" };
|
|
45047
45319
|
}
|
|
45048
45320
|
var target = (v) => ({ host: v.host, port: v.port, user: v.user, keyPath: v.keyPath });
|
|
45049
45321
|
var packagedScript = (f) => fileURLToPath4(new URL(`../scripts/vps/${f}`, import.meta.url));
|
|
@@ -45625,9 +45897,9 @@ function readAccessToken() {
|
|
|
45625
45897
|
return null;
|
|
45626
45898
|
}
|
|
45627
45899
|
}
|
|
45628
|
-
function normalizeSeverity(s2,
|
|
45900
|
+
function normalizeSeverity(s2, pct3) {
|
|
45629
45901
|
if (s2 === "normal" || s2 === "warning" || s2 === "critical") return s2;
|
|
45630
|
-
return
|
|
45902
|
+
return pct3 >= 90 ? "critical" : pct3 >= 70 ? "warning" : "normal";
|
|
45631
45903
|
}
|
|
45632
45904
|
function mapWindows(json) {
|
|
45633
45905
|
const arr = json?.limits;
|
|
@@ -45676,7 +45948,7 @@ async function getLimits() {
|
|
|
45676
45948
|
}
|
|
45677
45949
|
|
|
45678
45950
|
// src/services/codex-limits.ts
|
|
45679
|
-
import { open as open4, readdir as
|
|
45951
|
+
import { open as open4, readdir as readdir8, stat as stat5 } from "node:fs/promises";
|
|
45680
45952
|
import { homedir as homedir9 } from "node:os";
|
|
45681
45953
|
import { join as join32 } from "node:path";
|
|
45682
45954
|
var TTL_MS3 = 3e4;
|
|
@@ -45694,7 +45966,7 @@ function windowLabel(minutes) {
|
|
|
45694
45966
|
if (minutes % 60 === 0) return `${minutes / 60} jam`;
|
|
45695
45967
|
return `${minutes} menit`;
|
|
45696
45968
|
}
|
|
45697
|
-
var severityOf = (
|
|
45969
|
+
var severityOf = (pct3) => pct3 >= 90 ? "critical" : pct3 >= 70 ? "warning" : "normal";
|
|
45698
45970
|
function toWindow(slot, raw, reached) {
|
|
45699
45971
|
const minutes = raw.window_minutes ?? 0;
|
|
45700
45972
|
const usedPct = Math.round(raw.used_percent ?? 0);
|
|
@@ -45713,14 +45985,14 @@ function toWindow(slot, raw, reached) {
|
|
|
45713
45985
|
async function recentRollouts(dir2) {
|
|
45714
45986
|
let entries3;
|
|
45715
45987
|
try {
|
|
45716
|
-
entries3 = (await
|
|
45988
|
+
entries3 = (await readdir8(dir2, { recursive: true })).filter((f) => f.endsWith(".jsonl"));
|
|
45717
45989
|
} catch {
|
|
45718
45990
|
return [];
|
|
45719
45991
|
}
|
|
45720
45992
|
const stamped = await Promise.all(entries3.map(async (rel) => {
|
|
45721
45993
|
const full = join32(dir2, rel);
|
|
45722
45994
|
try {
|
|
45723
|
-
return { full, mtime: (await
|
|
45995
|
+
return { full, mtime: (await stat5(full)).mtimeMs };
|
|
45724
45996
|
} catch {
|
|
45725
45997
|
return null;
|
|
45726
45998
|
}
|
|
@@ -45804,7 +46076,7 @@ async function codex(app3) {
|
|
|
45804
46076
|
// src/services/model-catalog.ts
|
|
45805
46077
|
init_src2();
|
|
45806
46078
|
init_src();
|
|
45807
|
-
import { readFile as
|
|
46079
|
+
import { readFile as readFile6, writeFile as writeFile5, rename as rename4, mkdir as mkdir7 } from "node:fs/promises";
|
|
45808
46080
|
import { dirname as dirname15, join as join34 } from "node:path";
|
|
45809
46081
|
|
|
45810
46082
|
// src/services/model-catalog-parser.ts
|
|
@@ -46059,7 +46331,7 @@ var cacheFile = () => join34(resolveHome(), "model-catalog.json");
|
|
|
46059
46331
|
var modelCatalogService = createModelCatalogService({
|
|
46060
46332
|
probe: probeModelCatalog,
|
|
46061
46333
|
read: async () => {
|
|
46062
|
-
const content = await
|
|
46334
|
+
const content = await readFile6(cacheFile(), "utf8");
|
|
46063
46335
|
if (Buffer.byteLength(content) > 4 * 1024 * 1024) throw new Error("oversized cache");
|
|
46064
46336
|
return JSON.parse(content);
|
|
46065
46337
|
},
|
|
@@ -46396,6 +46668,7 @@ async function buildTasksPage(f) {
|
|
|
46396
46668
|
// src/services/lead/views.ts
|
|
46397
46669
|
init_db();
|
|
46398
46670
|
init_pty();
|
|
46671
|
+
init_live_phases();
|
|
46399
46672
|
|
|
46400
46673
|
// src/services/lead/pulse.ts
|
|
46401
46674
|
init_db();
|
|
@@ -46576,13 +46849,7 @@ function findCollisions(areas) {
|
|
|
46576
46849
|
return out4;
|
|
46577
46850
|
}
|
|
46578
46851
|
var prodPulseDeps = {
|
|
46579
|
-
sessions: () =>
|
|
46580
|
-
try {
|
|
46581
|
-
return listSessions();
|
|
46582
|
-
} catch {
|
|
46583
|
-
return [];
|
|
46584
|
-
}
|
|
46585
|
-
},
|
|
46852
|
+
sessions: () => listSessionsAsync().catch(() => []),
|
|
46586
46853
|
areas: async (s2) => {
|
|
46587
46854
|
const repoDir = await resolveRepoDir(s2.projectId);
|
|
46588
46855
|
if (!repoDir) return [];
|
|
@@ -46595,14 +46862,8 @@ var prodPulseDeps = {
|
|
|
46595
46862
|
return [];
|
|
46596
46863
|
}
|
|
46597
46864
|
},
|
|
46598
|
-
planDone:
|
|
46599
|
-
finished: (id2) =>
|
|
46600
|
-
try {
|
|
46601
|
-
return sessionFinished(id2);
|
|
46602
|
-
} catch {
|
|
46603
|
-
return false;
|
|
46604
|
-
}
|
|
46605
|
-
},
|
|
46865
|
+
planDone: planCompleteAsync,
|
|
46866
|
+
finished: (id2) => sessionFinishedAsync(id2).catch(() => false),
|
|
46606
46867
|
decide,
|
|
46607
46868
|
decideDeps: prodDecideDeps,
|
|
46608
46869
|
apply: applyAction,
|
|
@@ -46648,10 +46909,10 @@ async function pulse(deps = prodPulseDeps) {
|
|
|
46648
46909
|
async function followUpFinished(cfg, optIn, deps) {
|
|
46649
46910
|
let n2 = 0;
|
|
46650
46911
|
const opt = new Set(optIn);
|
|
46651
|
-
for (const s2 of deps.sessions()) {
|
|
46912
|
+
for (const s2 of await deps.sessions()) {
|
|
46652
46913
|
if (!s2.exited || !s2.specId || !opt.has(s2.projectId)) continue;
|
|
46653
46914
|
const bad = (s2.exitCode ?? 0) !== 0;
|
|
46654
|
-
const unfinished = !deps.planDone(s2.cwd, s2.specId);
|
|
46915
|
+
const unfinished = !await deps.planDone(s2.cwd, s2.specId);
|
|
46655
46916
|
if (!bad && !unfinished) continue;
|
|
46656
46917
|
const mark = `Sesi ${s2.id} untuk backlog ${s2.specId}`;
|
|
46657
46918
|
const seen2 = await prisma.leadDecision.findFirst({
|
|
@@ -46690,10 +46951,10 @@ async function followUpFinished(cfg, optIn, deps) {
|
|
|
46690
46951
|
async function followUpComplete(optIn, deps) {
|
|
46691
46952
|
let n2 = 0;
|
|
46692
46953
|
const opt = new Set(optIn);
|
|
46693
|
-
for (const s2 of deps.sessions()) {
|
|
46954
|
+
for (const s2 of await deps.sessions()) {
|
|
46694
46955
|
if (!s2.specId || !opt.has(s2.projectId)) continue;
|
|
46695
46956
|
if ((s2.exitCode ?? 0) !== 0) continue;
|
|
46696
|
-
if (!deps.finished(s2.id)) continue;
|
|
46957
|
+
if (!await deps.finished(s2.id)) continue;
|
|
46697
46958
|
const mark = `Backlog ${s2.specId} sudah selesai di sesi ${s2.id}`;
|
|
46698
46959
|
const seen2 = await prisma.leadDecision.findFirst({
|
|
46699
46960
|
where: { sessionId: s2.id, gate: "pulse", question: { startsWith: mark } }
|
|
@@ -46732,7 +46993,7 @@ async function followUpComplete(optIn, deps) {
|
|
|
46732
46993
|
}
|
|
46733
46994
|
async function detectCollisions(optIn, deps) {
|
|
46734
46995
|
const opt = new Set(optIn);
|
|
46735
|
-
const live = deps.sessions().filter((s2) => !s2.exited && s2.specId && opt.has(s2.projectId));
|
|
46996
|
+
const live = (await deps.sessions()).filter((s2) => !s2.exited && s2.specId && opt.has(s2.projectId));
|
|
46736
46997
|
if (live.length < 2) return 0;
|
|
46737
46998
|
const areas = [];
|
|
46738
46999
|
for (const s2 of live) {
|
|
@@ -46827,7 +47088,7 @@ async function orderProject(projectId, deps) {
|
|
|
46827
47088
|
|
|
46828
47089
|
// src/services/lead/engine.ts
|
|
46829
47090
|
init_notifications2();
|
|
46830
|
-
|
|
47091
|
+
init_live_phases();
|
|
46831
47092
|
var HOUSEKEEPING_MS = 6e4;
|
|
46832
47093
|
var busyPulse = false;
|
|
46833
47094
|
var lastPulseAt = 0;
|
|
@@ -46862,9 +47123,9 @@ async function tick(now, deps = {}) {
|
|
|
46862
47123
|
console.error("lead expire:", e);
|
|
46863
47124
|
}));
|
|
46864
47125
|
jobs.push((async () => {
|
|
46865
|
-
const live = deps.live ??
|
|
47126
|
+
const live = deps.live ?? liveDecisionsAsync;
|
|
46866
47127
|
const legacy = deps.legacy ?? recordLegacySession;
|
|
46867
|
-
for (const s2 of live()) {
|
|
47128
|
+
for (const s2 of await live()) {
|
|
46868
47129
|
if (s2.eventHook || !s2.waiting) continue;
|
|
46869
47130
|
await legacy(s2.id, s2.projectId || null, s2.specId ?? null);
|
|
46870
47131
|
}
|
|
@@ -46909,7 +47170,7 @@ async function buildLeadStatus() {
|
|
|
46909
47170
|
}
|
|
46910
47171
|
let waiting2 = [];
|
|
46911
47172
|
try {
|
|
46912
|
-
waiting2 =
|
|
47173
|
+
waiting2 = (await liveDecisionsAsync()).filter((d) => d.waiting).map((d) => d.id);
|
|
46913
47174
|
} catch {
|
|
46914
47175
|
}
|
|
46915
47176
|
const rows = await Promise.all(projects.map(async (p3) => ({
|
|
@@ -46982,11 +47243,48 @@ function parseParams(topic, params) {
|
|
|
46982
47243
|
return r.success ? r.data : void 0;
|
|
46983
47244
|
}
|
|
46984
47245
|
|
|
47246
|
+
// src/services/events-profile.ts
|
|
47247
|
+
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
47248
|
+
var PROFILE = process.env.HANOMAN_EVENTS_PROFILE === "1";
|
|
47249
|
+
var acc = {};
|
|
47250
|
+
var h;
|
|
47251
|
+
var nowUs = () => Number(process.hrtime.bigint() / 1000n);
|
|
47252
|
+
var profStart = () => PROFILE ? nowUs() : 0;
|
|
47253
|
+
function profEnd(group2, t0, bytes, emitted) {
|
|
47254
|
+
if (!PROFILE) return;
|
|
47255
|
+
const a = acc[group2] ??= { builds: [], frames: 0, bytes: 0, ticks: 0 };
|
|
47256
|
+
a.builds.push((nowUs() - t0) / 1e3);
|
|
47257
|
+
a.ticks++;
|
|
47258
|
+
if (emitted) {
|
|
47259
|
+
a.frames++;
|
|
47260
|
+
a.bytes += bytes;
|
|
47261
|
+
}
|
|
47262
|
+
}
|
|
47263
|
+
var pct2 = (xs, p3) => {
|
|
47264
|
+
const s2 = [...xs].sort((a, b) => a - b);
|
|
47265
|
+
return s2[Math.min(s2.length - 1, Math.floor(s2.length * p3))] ?? 0;
|
|
47266
|
+
};
|
|
47267
|
+
function startLagMonitor() {
|
|
47268
|
+
if (!PROFILE || h) return;
|
|
47269
|
+
const mon = h = monitorEventLoopDelay({ resolution: 10 });
|
|
47270
|
+
mon.enable();
|
|
47271
|
+
setInterval(() => {
|
|
47272
|
+
const ms = (n2) => +(n2 / 1e6).toFixed(1);
|
|
47273
|
+
const rows = Object.entries(acc).map(([g, a]) => `${g}: build p95=${pct2(a.builds, 0.95).toFixed(1)}ms frames=${a.frames}/${a.ticks} bytes=${a.bytes}`);
|
|
47274
|
+
console.log(`[events-profile] loop p50=${ms(mon.percentile(50))} p99=${ms(mon.percentile(99))} max=${ms(mon.max)}ms | ${rows.join(" | ")}`);
|
|
47275
|
+
mon.reset();
|
|
47276
|
+
for (const k of Object.keys(acc)) delete acc[k];
|
|
47277
|
+
}, 1e4).unref();
|
|
47278
|
+
}
|
|
47279
|
+
|
|
46985
47280
|
// src/services/events.ts
|
|
46986
47281
|
var clients = /* @__PURE__ */ new Set();
|
|
46987
47282
|
var cookieClients = /* @__PURE__ */ new WeakSet();
|
|
46988
47283
|
var clientGroups = /* @__PURE__ */ new WeakMap();
|
|
46989
47284
|
var tickMs = () => effectiveInt("HANOMAN_EVENTS_TICK_MS") ?? 1e3;
|
|
47285
|
+
var presenceKey = (m) => JSON.stringify({ ...m, devices: m.devices.map(
|
|
47286
|
+
(d) => d.local ? { ...d, lastSeenAt: null } : d
|
|
47287
|
+
) });
|
|
46990
47288
|
var GROUPS = [
|
|
46991
47289
|
{ everyTicks: 3, cookieOnly: true, last: "", build: async () => ({
|
|
46992
47290
|
t: "models",
|
|
@@ -47002,7 +47300,16 @@ var GROUPS = [
|
|
|
47002
47300
|
// loop — termasuk frame ketikan terminal — selama spawn (terukur sampai 916 ms saat mesin sibuk).
|
|
47003
47301
|
sessions: (await listSessionsAsync()).map((s2) => isDeciding(s2.id) ? { ...s2, deciding: true } : s2)
|
|
47004
47302
|
}) },
|
|
47005
|
-
|
|
47303
|
+
// SPEC-1267 · frame ringkas (`SpecSlim`: tanpa payload/objective/sourceHistory) yang hanya dibangun
|
|
47304
|
+
// saat `specsDigest` berubah. Kemajuan stage dari fase sesi (`liveOverlayTick`) tetap dijalankan
|
|
47305
|
+
// tiap tick: ia hidup di berkas/tmux, bukan di DB, jadi digest DB tak melihatnya sebelum ditulis.
|
|
47306
|
+
{
|
|
47307
|
+
everyTicks: 1,
|
|
47308
|
+
last: "",
|
|
47309
|
+
pre: liveOverlayTick,
|
|
47310
|
+
gate: specsDigest,
|
|
47311
|
+
build: async () => ({ t: "specs", specs: await listSpecsSlim() })
|
|
47312
|
+
},
|
|
47006
47313
|
{ everyTicks: 3, last: "", build: async () => ({ t: "notifications", ...await notificationsFeed() }) },
|
|
47007
47314
|
// SPEC-742 · ADR-0116 · pembersihan worktree yang masih jalan. `listCleanups()` membaca peta di
|
|
47008
47315
|
// memori, bukan disk — nol I/O per tick, dan dedup signature membuat frame lahir hanya saat
|
|
@@ -47028,7 +47335,15 @@ var GROUPS = [
|
|
|
47028
47335
|
// dihitung). 3 dtk: presence berdenyut 30 dtk, jadi kadens lebih rapat hanya menambah build tanpa
|
|
47029
47336
|
// menambah informasi. `presenceView` menyegarkan sesi mesin ini sendiri di dalamnya — satu
|
|
47030
47337
|
// `tmux list-panes` asinkron, tak menahan event loop.
|
|
47031
|
-
|
|
47338
|
+
// SPEC-1267 · `lastSeenAt` device lokal = jam build ini, jadi berubah tiap build; kunci dedup
|
|
47339
|
+
// mengosongkannya supaya frame hanya lahir saat isi presence benar-benar berubah.
|
|
47340
|
+
{
|
|
47341
|
+
everyTicks: 3,
|
|
47342
|
+
cookieOnly: true,
|
|
47343
|
+
last: "",
|
|
47344
|
+
build: async () => ({ t: "presence", ...await presenceView() }),
|
|
47345
|
+
sig: presenceKey
|
|
47346
|
+
},
|
|
47032
47347
|
// SPEC-961 · grup GLOBAL ke-11 · angka "butuh pengajuan" untuk badge sidebar. 5 dtk: badge bukan
|
|
47033
47348
|
// board — yang dijanjikannya adalah "ada yang menunggu", bukan detik keberapa ia muncul — dan
|
|
47034
47349
|
// dedup signature membuat frame lahir hanya saat salah satu angka berubah (jarang). Empat angka,
|
|
@@ -47037,8 +47352,7 @@ var GROUPS = [
|
|
|
47037
47352
|
// id/judul, dan agent token sudah boleh membaca daftar sumbernya lewat capability masing-masing.
|
|
47038
47353
|
{ everyTicks: 5, last: "", build: async () => ({ t: "pending", counts: await pendingCounts() }) }
|
|
47039
47354
|
];
|
|
47040
|
-
function broadcast2(msg, cookieOnly = false) {
|
|
47041
|
-
const s2 = JSON.stringify(msg);
|
|
47355
|
+
function broadcast2(msg, s2, cookieOnly = false) {
|
|
47042
47356
|
for (const c of clients) {
|
|
47043
47357
|
if (cookieOnly && !cookieClients.has(c)) continue;
|
|
47044
47358
|
const groups = clientGroups.get(c);
|
|
@@ -47126,31 +47440,54 @@ function subscribeClient(c, subs) {
|
|
|
47126
47440
|
var tick2 = 0;
|
|
47127
47441
|
var busy = false;
|
|
47128
47442
|
var timer3;
|
|
47443
|
+
async function runGroup(g) {
|
|
47444
|
+
const t0 = profStart();
|
|
47445
|
+
let msg;
|
|
47446
|
+
try {
|
|
47447
|
+
await g.pre?.();
|
|
47448
|
+
let gate2;
|
|
47449
|
+
if (g.gate) {
|
|
47450
|
+
try {
|
|
47451
|
+
gate2 = await g.gate();
|
|
47452
|
+
} catch {
|
|
47453
|
+
gate2 = void 0;
|
|
47454
|
+
}
|
|
47455
|
+
if (gate2 !== void 0 && gate2 === g.lastGate) {
|
|
47456
|
+
profEnd("gate", t0, 0, false);
|
|
47457
|
+
return null;
|
|
47458
|
+
}
|
|
47459
|
+
}
|
|
47460
|
+
msg = await g.build();
|
|
47461
|
+
if (g.failing) {
|
|
47462
|
+
g.failing = false;
|
|
47463
|
+
console.log(`siar dashboard pulih: ${msg.t}`);
|
|
47464
|
+
}
|
|
47465
|
+
const json = JSON.stringify(msg);
|
|
47466
|
+
const key = g.sig ? g.sig(msg) : json;
|
|
47467
|
+
const emit = key !== g.lastSig;
|
|
47468
|
+
profEnd(msg.t, t0, json.length, emit);
|
|
47469
|
+
if (g.gate) g.lastGate = gate2;
|
|
47470
|
+
return emit ? { g, msg, json, key, gate: gate2 } : null;
|
|
47471
|
+
} catch (e) {
|
|
47472
|
+
if (!g.failing) {
|
|
47473
|
+
g.failing = true;
|
|
47474
|
+
console.error("siar dashboard gagal membangun frame:", e);
|
|
47475
|
+
}
|
|
47476
|
+
return null;
|
|
47477
|
+
}
|
|
47478
|
+
}
|
|
47129
47479
|
async function __tick() {
|
|
47130
47480
|
if (busy) return;
|
|
47131
47481
|
busy = true;
|
|
47132
47482
|
tick2++;
|
|
47133
47483
|
try {
|
|
47134
|
-
|
|
47135
|
-
|
|
47136
|
-
|
|
47137
|
-
|
|
47138
|
-
|
|
47139
|
-
|
|
47140
|
-
|
|
47141
|
-
g.failing = true;
|
|
47142
|
-
console.error("siar dashboard gagal membangun frame:", e);
|
|
47143
|
-
}
|
|
47144
|
-
continue;
|
|
47145
|
-
}
|
|
47146
|
-
if (g.failing) {
|
|
47147
|
-
g.failing = false;
|
|
47148
|
-
console.log(`siar dashboard pulih: ${msg.t}`);
|
|
47149
|
-
}
|
|
47150
|
-
const sig = JSON.stringify(msg);
|
|
47151
|
-
if (sig === g.last) continue;
|
|
47152
|
-
g.last = sig;
|
|
47153
|
-
broadcast2(msg, g.cookieOnly);
|
|
47484
|
+
const results = await Promise.all(GROUPS.filter((g) => tick2 % g.everyTicks === 0).map(runGroup));
|
|
47485
|
+
for (const r of results) {
|
|
47486
|
+
if (!r) continue;
|
|
47487
|
+
r.g.last = r.json;
|
|
47488
|
+
r.g.lastSig = r.key;
|
|
47489
|
+
r.g.lastT = r.msg.t;
|
|
47490
|
+
broadcast2(r.msg, r.json, r.g.cookieOnly);
|
|
47154
47491
|
}
|
|
47155
47492
|
for (const e of entries2.values()) {
|
|
47156
47493
|
e.tick++;
|
|
@@ -47163,6 +47500,7 @@ async function __tick() {
|
|
|
47163
47500
|
}
|
|
47164
47501
|
function startLoop() {
|
|
47165
47502
|
if (timer3) return;
|
|
47503
|
+
startLagMonitor();
|
|
47166
47504
|
timer3 = setInterval(() => {
|
|
47167
47505
|
void __tick();
|
|
47168
47506
|
}, tickMs());
|
|
@@ -47174,9 +47512,13 @@ function stopLoop() {
|
|
|
47174
47512
|
timer3 = void 0;
|
|
47175
47513
|
}
|
|
47176
47514
|
tick2 = 0;
|
|
47177
|
-
for (const g of GROUPS)
|
|
47515
|
+
for (const g of GROUPS) {
|
|
47516
|
+
g.last = "";
|
|
47517
|
+
g.lastSig = void 0;
|
|
47518
|
+
g.lastGate = void 0;
|
|
47519
|
+
}
|
|
47178
47520
|
}
|
|
47179
|
-
async function
|
|
47521
|
+
async function attach(c, o = {}) {
|
|
47180
47522
|
clients.add(c);
|
|
47181
47523
|
if (o.maySubscribe !== false) cookieClients.add(c);
|
|
47182
47524
|
if (o.groups) clientGroups.set(c, o.groups);
|
|
@@ -47187,17 +47529,20 @@ async function attach2(c, o = {}) {
|
|
|
47187
47529
|
} catch {
|
|
47188
47530
|
return;
|
|
47189
47531
|
}
|
|
47190
|
-
|
|
47191
|
-
|
|
47192
|
-
|
|
47532
|
+
const wanted = GROUPS.filter((g) => !(g.cookieOnly && !cookieClients.has(c)));
|
|
47533
|
+
const frames = await Promise.all(wanted.map(async (g) => {
|
|
47534
|
+
if (g.last && g.lastT) return { t: g.lastT, json: g.last };
|
|
47193
47535
|
try {
|
|
47194
|
-
msg = await g.build();
|
|
47536
|
+
const msg = await g.build();
|
|
47537
|
+
return { t: msg.t, json: JSON.stringify(msg) };
|
|
47195
47538
|
} catch {
|
|
47196
|
-
|
|
47539
|
+
return null;
|
|
47197
47540
|
}
|
|
47198
|
-
|
|
47541
|
+
}));
|
|
47542
|
+
for (const f of frames) {
|
|
47543
|
+
if (!f || o.groups && !o.groups.has(f.t)) continue;
|
|
47199
47544
|
try {
|
|
47200
|
-
c.send(
|
|
47545
|
+
c.send(f.json);
|
|
47201
47546
|
} catch {
|
|
47202
47547
|
return;
|
|
47203
47548
|
}
|
|
@@ -47231,7 +47576,7 @@ async function events_default(app3, opts) {
|
|
|
47231
47576
|
const client = { send: (m) => socket2.send(m), close: () => socket2.close() };
|
|
47232
47577
|
if (req.remote) {
|
|
47233
47578
|
const ideRead = req.remote.capabilities.includes("ide:read");
|
|
47234
|
-
void
|
|
47579
|
+
void attach(client, { maySubscribe: false, groups: remoteEventGroups(ideRead) });
|
|
47235
47580
|
socket2.on("close", () => detach2(client));
|
|
47236
47581
|
return;
|
|
47237
47582
|
}
|
|
@@ -47244,7 +47589,7 @@ async function events_default(app3, opts) {
|
|
|
47244
47589
|
return;
|
|
47245
47590
|
}
|
|
47246
47591
|
const maySubscribe = canSubscribeTopics(principal);
|
|
47247
|
-
void
|
|
47592
|
+
void attach(client, { maySubscribe });
|
|
47248
47593
|
const guard = new WsMessageGuard({ perWindow: SUB_FRAMES_PER_MINUTE });
|
|
47249
47594
|
socket2.on("message", (raw) => {
|
|
47250
47595
|
const verdict = guard.accept(raw);
|
|
@@ -47354,8 +47699,8 @@ import { join as join36 } from "node:path";
|
|
|
47354
47699
|
|
|
47355
47700
|
// src/services/device-auth.ts
|
|
47356
47701
|
function bearerToken2(req) {
|
|
47357
|
-
const
|
|
47358
|
-
return typeof
|
|
47702
|
+
const h2 = req.headers["authorization"];
|
|
47703
|
+
return typeof h2 === "string" && h2.startsWith("Bearer ") ? h2.slice(7) : void 0;
|
|
47359
47704
|
}
|
|
47360
47705
|
async function requireDeviceToken(req, reply) {
|
|
47361
47706
|
const token = bearerToken2(req);
|
|
@@ -47790,7 +48135,7 @@ async function session_history_default(app3) {
|
|
|
47790
48135
|
init_src();
|
|
47791
48136
|
init_session_event_token();
|
|
47792
48137
|
init_pty();
|
|
47793
|
-
var bearer = (
|
|
48138
|
+
var bearer = (h2) => /^Bearer (.+)$/.exec(h2 ?? "")?.[1] ?? "";
|
|
47794
48139
|
var recordOf = (value) => value && typeof value === "object" ? value : null;
|
|
47795
48140
|
var boundedString = (value, max) => typeof value === "string" && value.length > 0 && value.length <= max ? value : void 0;
|
|
47796
48141
|
async function session_events_default(app3) {
|
|
@@ -47938,9 +48283,9 @@ init_db();
|
|
|
47938
48283
|
init_db();
|
|
47939
48284
|
init_uploads();
|
|
47940
48285
|
init_src();
|
|
47941
|
-
import { createHash as
|
|
48286
|
+
import { createHash as createHash14, randomBytes as randomBytes7 } from "node:crypto";
|
|
47942
48287
|
function hashAccessKey(key) {
|
|
47943
|
-
return
|
|
48288
|
+
return createHash14("sha256").update(key).digest("hex");
|
|
47944
48289
|
}
|
|
47945
48290
|
function generateAccessKey() {
|
|
47946
48291
|
const key = "hnm_tkt_" + randomBytes7(24).toString("hex");
|
|
@@ -50842,8 +51187,8 @@ function systemPromptFor(type, nonce) {
|
|
|
50842
51187
|
${type === "brainstorm" ? BRAINSTORM : TANYA}`;
|
|
50843
51188
|
}
|
|
50844
51189
|
function renderTurnPrompt(o) {
|
|
50845
|
-
const riwayat = o.history.map((
|
|
50846
|
-
${
|
|
51190
|
+
const riwayat = o.history.map((h2) => h2.role === "klien" ? wrapClientMessage(h2.text, o.nonce) : `<jawaban-hanoman>
|
|
51191
|
+
${h2.text}
|
|
50847
51192
|
</jawaban-hanoman>`).join("\n\n");
|
|
50848
51193
|
const baru = wrapClientMessage(o.message, o.nonce);
|
|
50849
51194
|
return riwayat ? `Percakapan sejauh ini:
|
|
@@ -51463,8 +51808,8 @@ init_src();
|
|
|
51463
51808
|
init_db();
|
|
51464
51809
|
|
|
51465
51810
|
// src/services/bootstrap.ts
|
|
51466
|
-
import { createHash as
|
|
51467
|
-
import { chmod, lstat as lstat3, mkdir as mkdir10, readFile as
|
|
51811
|
+
import { createHash as createHash15, randomBytes as randomBytes11, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
|
|
51812
|
+
import { chmod, lstat as lstat3, mkdir as mkdir10, readFile as readFile8, unlink as unlink5, writeFile as writeFile7 } from "node:fs/promises";
|
|
51468
51813
|
import { join as join39 } from "node:path";
|
|
51469
51814
|
var TTL_MS5 = 15 * 6e4;
|
|
51470
51815
|
var SETUP_TOKEN_FILE = "setup.token";
|
|
@@ -51476,7 +51821,7 @@ async function readStored(home3) {
|
|
|
51476
51821
|
try {
|
|
51477
51822
|
const info = await lstat3(tokenPath(home3));
|
|
51478
51823
|
if (info.isSymbolicLink() || !info.isFile()) throw new BootstrapError("invalid setup proof");
|
|
51479
|
-
const [token, expires] = (await
|
|
51824
|
+
const [token, expires] = (await readFile8(tokenPath(home3), "utf8")).trim().split("\n");
|
|
51480
51825
|
const expiresAt = Date.parse(expires ?? "");
|
|
51481
51826
|
if (!token || !Number.isFinite(expiresAt)) throw new BootstrapError("invalid setup proof");
|
|
51482
51827
|
return { token, expiresAt };
|
|
@@ -51516,8 +51861,8 @@ ${new Date(expiresAt).toISOString()}
|
|
|
51516
51861
|
}
|
|
51517
51862
|
async function verifySetupToken(candidate, home3, now = Date.now()) {
|
|
51518
51863
|
const stored = await readStored(home3);
|
|
51519
|
-
const got =
|
|
51520
|
-
const want =
|
|
51864
|
+
const got = createHash15("sha256").update(candidate).digest();
|
|
51865
|
+
const want = createHash15("sha256").update(stored.token).digest();
|
|
51521
51866
|
if (stored.expiresAt <= now || !timingSafeEqual5(got, want)) throw new BootstrapError("invalid setup proof");
|
|
51522
51867
|
}
|
|
51523
51868
|
async function consumeSetupToken(home3) {
|
|
@@ -52012,7 +52357,7 @@ init_db();
|
|
|
52012
52357
|
init_event_log();
|
|
52013
52358
|
init_transcript_store();
|
|
52014
52359
|
init_src2();
|
|
52015
|
-
import { readFile as
|
|
52360
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
52016
52361
|
import { join as join40 } from "node:path";
|
|
52017
52362
|
function decodeCursor(c) {
|
|
52018
52363
|
if (!c) return null;
|
|
@@ -52053,7 +52398,7 @@ async function readRemoteTranscript(id2) {
|
|
|
52053
52398
|
if (row.deviceId === "local") return readTranscript(row.transcriptKey);
|
|
52054
52399
|
const path = join40(resolveDataDirs().home, "remote-transcripts", row.transcriptKey);
|
|
52055
52400
|
try {
|
|
52056
|
-
return await
|
|
52401
|
+
return await readFile9(path, "utf8");
|
|
52057
52402
|
} catch {
|
|
52058
52403
|
return null;
|
|
52059
52404
|
}
|
|
@@ -52093,8 +52438,8 @@ async function logs_default(app3) {
|
|
|
52093
52438
|
init_agent_token();
|
|
52094
52439
|
init_settings3();
|
|
52095
52440
|
function agentTokenFromReq(req) {
|
|
52096
|
-
const
|
|
52097
|
-
if (typeof
|
|
52441
|
+
const h2 = req.headers["authorization"];
|
|
52442
|
+
if (typeof h2 === "string" && h2.startsWith("Bearer ")) return h2.slice(7);
|
|
52098
52443
|
return void 0;
|
|
52099
52444
|
}
|
|
52100
52445
|
async function authenticateAgent(token) {
|
|
@@ -52415,11 +52760,11 @@ async function auditSweep(now = Date.now) {
|
|
|
52415
52760
|
var timers = [];
|
|
52416
52761
|
function startVpsMonitor() {
|
|
52417
52762
|
if (timers.length) return;
|
|
52418
|
-
const
|
|
52763
|
+
const h2 = setInterval(() => void healthSweep(), HEALTH_MS);
|
|
52419
52764
|
const a = setInterval(() => void auditSweep(), AUDIT_MS);
|
|
52420
|
-
|
|
52765
|
+
h2.unref();
|
|
52421
52766
|
a.unref();
|
|
52422
|
-
timers = [
|
|
52767
|
+
timers = [h2, a];
|
|
52423
52768
|
void healthSweep();
|
|
52424
52769
|
void auditSweep();
|
|
52425
52770
|
}
|
|
@@ -52589,10 +52934,10 @@ async function reconcile(deps) {
|
|
|
52589
52934
|
await markFailed(item.id, "spec hilang");
|
|
52590
52935
|
continue;
|
|
52591
52936
|
}
|
|
52592
|
-
const p3 = deps.pane(item.sessionId ?? "");
|
|
52937
|
+
const p3 = await deps.pane(item.sessionId ?? "");
|
|
52593
52938
|
let stage = spec.stage;
|
|
52594
52939
|
if (p3?.flow && p3.phaseFile) {
|
|
52595
|
-
const d = deps.deriveStage(p3.phaseFile, p3.flow, p3.cwd, item.specId);
|
|
52940
|
+
const d = await deps.deriveStage(p3.phaseFile, p3.flow, p3.cwd, item.specId);
|
|
52596
52941
|
if (d && STAGES.indexOf(d) > STAGES.indexOf(stage)) {
|
|
52597
52942
|
const { count } = await prisma.spec.updateMany({ where: { id: item.specId, stage }, data: { stage: d } });
|
|
52598
52943
|
if (count > 0) await notifySynced("spec", item.specId).catch(() => {
|
|
@@ -52625,11 +52970,11 @@ async function reconcile(deps) {
|
|
|
52625
52970
|
}
|
|
52626
52971
|
}
|
|
52627
52972
|
var reconcileProdDeps = {
|
|
52628
|
-
pane: (sessionId2) => {
|
|
52629
|
-
const s2 =
|
|
52973
|
+
pane: async (sessionId2) => {
|
|
52974
|
+
const s2 = await getSessionAsync(sessionId2);
|
|
52630
52975
|
return s2 ? { exited: s2.exited, flow: s2.flow, phaseFile: s2.phaseFile, cwd: s2.cwd } : void 0;
|
|
52631
52976
|
},
|
|
52632
|
-
deriveStage: (phaseFile, flow2, cwd, specId) =>
|
|
52977
|
+
deriveStage: async (phaseFile, flow2, cwd, specId) => stageForRunAsync(await readPhasesAsync(phaseFile, flow2), cwd, specId),
|
|
52633
52978
|
headSha: (wt) => {
|
|
52634
52979
|
try {
|
|
52635
52980
|
return realGit.headSha(wt);
|
|
@@ -53234,13 +53579,13 @@ async function pruneLogs(now, retention, opts = {}) {
|
|
|
53234
53579
|
return { logsPruned, logBytesFreed };
|
|
53235
53580
|
}
|
|
53236
53581
|
async function reconcileRemoteTranscripts() {
|
|
53237
|
-
const { readdir:
|
|
53582
|
+
const { readdir: readdir10, unlink: unlink6, stat: stat7 } = await import("node:fs/promises");
|
|
53238
53583
|
const { join: join41 } = await import("node:path");
|
|
53239
53584
|
const { resolveDataDirs: resolveDataDirs2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
|
|
53240
53585
|
const root = join41(resolveDataDirs2().home, "remote-transcripts");
|
|
53241
53586
|
let deviceDirs;
|
|
53242
53587
|
try {
|
|
53243
|
-
deviceDirs = await
|
|
53588
|
+
deviceDirs = await readdir10(root);
|
|
53244
53589
|
} catch {
|
|
53245
53590
|
return { orphans: 0 };
|
|
53246
53591
|
}
|
|
@@ -53251,14 +53596,14 @@ async function reconcileRemoteTranscripts() {
|
|
|
53251
53596
|
for (const dev of deviceDirs) {
|
|
53252
53597
|
let files;
|
|
53253
53598
|
try {
|
|
53254
|
-
files = await
|
|
53599
|
+
files = await readdir10(join41(root, dev));
|
|
53255
53600
|
} catch {
|
|
53256
53601
|
continue;
|
|
53257
53602
|
}
|
|
53258
53603
|
for (const f of files) {
|
|
53259
53604
|
const key = `${dev}/${f}`;
|
|
53260
53605
|
if (referenced.has(key)) continue;
|
|
53261
|
-
const st = await
|
|
53606
|
+
const st = await stat7(join41(root, dev, f)).catch(() => null);
|
|
53262
53607
|
if (!st || Date.now() - st.mtimeMs < gracePeriodMs) continue;
|
|
53263
53608
|
await unlink6(join41(root, dev, f)).catch(() => {
|
|
53264
53609
|
});
|