hanoman 0.7.1 → 0.9.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 +18 -12
- package/dist/server.js +822 -448
- package/package.json +1 -1
- package/web/assets/{ChangelogScreen-D6brfukI.js → ChangelogScreen-DMP7CLV9.js} +1 -1
- package/web/assets/{DalangHanomanScreen-C8GIJv8X.js → DalangHanomanScreen-Cfm4VZcu.js} +1 -1
- package/web/assets/{DocPreviewModal-4m5Afjx2.js → DocPreviewModal--5Zt1KqB.js} +1 -1
- package/web/assets/{DocsWorkspace-8U-UYo4-.js → DocsWorkspace-DPbik78Y.js} +1 -1
- package/web/assets/{IdeScreen-8QAVPotm.js → IdeScreen-MDeF9i8w.js} +1 -1
- package/web/assets/{LeadScreen-CRs2p9QY.js → LeadScreen-DDw5WUIQ.js} +1 -1
- package/web/assets/{ReviewScreen-CBmaRAHG.js → ReviewScreen-BfAOioO4.js} +1 -1
- package/web/assets/{SchedulerScreen-C1dPfo1w.js → SchedulerScreen-CX3Y_AsX.js} +1 -1
- package/web/assets/{SettingsScreen-rE_jbjMw.js → SettingsScreen-Dei1_F25.js} +2 -2
- package/web/assets/{TeamScreen-Dz0rA4Tk.js → TeamScreen-CWVRHKQZ.js} +1 -1
- package/web/assets/TerminalScreen-Bn_jJ-Mm.js +2 -0
- package/web/assets/{TriageScreen-BZQvccje.js → TriageScreen-Dk50d9JW.js} +1 -1
- package/web/assets/{VpsScreen-CPlPXExd.js → VpsScreen-Dk6FGek6.js} +1 -1
- package/web/assets/index-DqFaLJVc.js +1005 -0
- package/web/assets/{live-BWD2sDTC.js → live-B1kEuxRj.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
|
}
|
|
@@ -6787,7 +6792,7 @@ function subKey(topic, params) {
|
|
|
6787
6792
|
for (const k of Object.keys(params).sort()) if (params[k] !== void 0) canon[k] = params[k];
|
|
6788
6793
|
return `${topic}|${JSON.stringify(canon)}`;
|
|
6789
6794
|
}
|
|
6790
|
-
var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, zMarkSpecDone, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerQueueCounts, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, zFlow, isGoalShapedFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, USER_ROLES, zUserRole, zLogin, zSignup, zSetup, zSetupApply, zChangePassword, zCreateClientAccount, zUpdateClientAccount, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, PROVISION_PROFILES, COMPONENT_IDS, zProvision, UPDATE_RESTART_EXIT, CONFIG_RESTART_EXIT, zUpdateApplyBody, MAX_SUBS, zSubPage, zSubLimit, zTopicParams, zEventsClientMsg, zTicketView, zTicketAttachmentView, zSpecAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus, zCronExpr, zCreateCron, zPatchCron, zSchedulerCron, zSchedulerCronRun;
|
|
6795
|
+
var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, zMarkSpecDone, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerQueueCounts, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, zFlow, isGoalShapedFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, USER_ROLES, zUserRole, zLogin, zSignup, zSetup, zSetupApply, zChangePassword, zCreateClientAccount, zUpdateClientAccount, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, PROVISION_PROFILES, COMPONENT_IDS, zProvision, UPDATE_RESTART_EXIT, CONFIG_RESTART_EXIT, MANUAL_RESTART_EXIT, zUpdateApplyBody, MAX_SUBS, zSubPage, zSubLimit, zTopicParams, zEventsClientMsg, zTicketView, zTicketAttachmentView, zSpecAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus, zCronExpr, zCreateCron, zPatchCron, zSchedulerCron, zSchedulerCronRun;
|
|
6791
6796
|
var init_dto = __esm({
|
|
6792
6797
|
"../shared/src/dto.ts"() {
|
|
6793
6798
|
"use strict";
|
|
@@ -7309,6 +7314,7 @@ var init_dto = __esm({
|
|
|
7309
7314
|
});
|
|
7310
7315
|
UPDATE_RESTART_EXIT = 75;
|
|
7311
7316
|
CONFIG_RESTART_EXIT = 76;
|
|
7317
|
+
MANUAL_RESTART_EXIT = 77;
|
|
7312
7318
|
zUpdateApplyBody = external_exports.object({ confirm: external_exports.boolean().optional() });
|
|
7313
7319
|
MAX_SUBS = 16;
|
|
7314
7320
|
zSubPage = external_exports.number().int().min(1).max(1e4);
|
|
@@ -7760,8 +7766,8 @@ function coverageOf(docs) {
|
|
|
7760
7766
|
const linked = [...byCat.values()].filter(Boolean).length;
|
|
7761
7767
|
return Math.round(linked / byCat.size * 100);
|
|
7762
7768
|
}
|
|
7763
|
-
function docStatusFor(
|
|
7764
|
-
return
|
|
7769
|
+
function docStatusFor(pct3) {
|
|
7770
|
+
return pct3 >= 90 ? "ok" : pct3 >= 60 ? "drift" : "broken";
|
|
7765
7771
|
}
|
|
7766
7772
|
function isExternalLink(target3) {
|
|
7767
7773
|
return !target3 || /^(https?:|#|mailto:)/.test(target3);
|
|
@@ -9252,17 +9258,12 @@ var init_backlog = __esm({
|
|
|
9252
9258
|
inputSchema: obj({ properties: { spec: str(ID_HINT) }, required: ["spec"] }),
|
|
9253
9259
|
mode: "read",
|
|
9254
9260
|
capability: "backlog:read",
|
|
9255
|
-
samplePath: "/specs",
|
|
9261
|
+
samplePath: "/specs/SPEC-1",
|
|
9256
9262
|
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
|
-
}
|
|
9263
|
+
// SPEC-1267 · `GET /specs` tak lagi membawa `payload`/`sourceHistory`, jadi isi penuh HANYA ada di
|
|
9264
|
+
// `GET /specs/:id`. Pencocokan id persis kini urusan server (404 bila tak ada).
|
|
9265
|
+
build: (a) => ({ method: "GET", path: `/specs/${enc(String(a.spec).trim())}` }),
|
|
9266
|
+
shape: (raw) => shapeSpecDetail(raw)
|
|
9266
9267
|
},
|
|
9267
9268
|
{
|
|
9268
9269
|
name: "hanoman_backlog_docs_list",
|
|
@@ -13042,8 +13043,8 @@ function guideLine(guide, phase) {
|
|
|
13042
13043
|
}
|
|
13043
13044
|
function phaseSkillsFor(flow2, phase, method) {
|
|
13044
13045
|
const own = method.phaseSkills[phase] ?? [];
|
|
13045
|
-
const
|
|
13046
|
-
return writesCode(flow2) && phase ===
|
|
13046
|
+
const phases2 = PIPELINES[flow2];
|
|
13047
|
+
return writesCode(flow2) && phase === phases2[phases2.length - 1] ? [.../* @__PURE__ */ new Set([...own, ...method.exitSkills])] : [...own];
|
|
13047
13048
|
}
|
|
13048
13049
|
function orchestratorClause(plan, o = {}) {
|
|
13049
13050
|
const codex2 = plan.runtime === "codex";
|
|
@@ -13320,15 +13321,15 @@ var init_prompt = __esm({
|
|
|
13320
13321
|
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
13322
|
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
13323
|
autonomyClause = (mode) => mode === "full-control" ? AUTONOMY_CLAUSE_FULL : AUTONOMY_CLAUSE;
|
|
13323
|
-
phaseInstruction = (
|
|
13324
|
-
const base2 = `Kerjakan fase berurutan: ${
|
|
13324
|
+
phaseInstruction = (phases2, method) => {
|
|
13325
|
+
const base2 = `Kerjakan fase berurutan: ${phases2.join(" \u2192 ")}.
|
|
13325
13326
|
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 (!
|
|
13327
|
+
if (!phases2.includes("Plan") || !phases2.includes("Execute")) return base2;
|
|
13327
13328
|
return base2 + `
|
|
13328
13329
|
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
13330
|
};
|
|
13330
|
-
skillInstruction = (flow2,
|
|
13331
|
-
const lines2 =
|
|
13331
|
+
skillInstruction = (flow2, phases2, method) => {
|
|
13332
|
+
const lines2 = phases2.map((p3) => {
|
|
13332
13333
|
const skills = phaseSkillsFor(flow2, p3, method);
|
|
13333
13334
|
return skills.length ? `- ${p3}: ${skills.join(", ")}` : "";
|
|
13334
13335
|
}).filter(Boolean);
|
|
@@ -13645,6 +13646,20 @@ var init_settings2 = __esm({
|
|
|
13645
13646
|
}];
|
|
13646
13647
|
hooks2.SubagentStart = [{ hooks: [{ type: "command", command: EVENT_HOOK_COMMAND }] }];
|
|
13647
13648
|
hooks2.SubagentStop = [{ hooks: [{ type: "command", command: EVENT_HOOK_COMMAND }] }];
|
|
13649
|
+
if (decisionFile) {
|
|
13650
|
+
const sub = `'${decisionFile.split("'").join("'\\''")}.sub'`;
|
|
13651
|
+
const f = `'${decisionFile.split("'").join("'\\''")}'`;
|
|
13652
|
+
hooks2.SubagentStart.push({ hooks: [{ type: "command", command: `echo 1 >> ${sub}; exit 0` }] });
|
|
13653
|
+
hooks2.SubagentStop.push({ hooks: [{
|
|
13654
|
+
type: "command",
|
|
13655
|
+
command: `[ -s ${sub} ] && { sed '$d' ${sub} > ${sub}.t; mv ${sub}.t ${sub}; }; exit 0`
|
|
13656
|
+
}] });
|
|
13657
|
+
hooks2.Notification = [{ hooks: [{
|
|
13658
|
+
type: "command",
|
|
13659
|
+
command: `m=$(cat); if [ -s ${sub} ]; then echo "$m" | grep -qiE 'permission|needs.?input'; else echo "$m" | grep -qiE 'idle|permission|waiting for|needs.?input'; fi && { [ -s ${f} ] || date +%s > ${f}; } || true`
|
|
13660
|
+
}] }];
|
|
13661
|
+
hooks2.UserPromptSubmit = [{ hooks: [{ type: "command", command: `: > ${f}; : > ${sub}` }] }];
|
|
13662
|
+
}
|
|
13648
13663
|
}
|
|
13649
13664
|
if (goal) hooks2.Stop = [{ hooks: [{ type: "prompt", prompt: goal }] }];
|
|
13650
13665
|
return {
|
|
@@ -13671,10 +13686,10 @@ function goalFlowCondition(flow2, specId, branchTo, spec) {
|
|
|
13671
13686
|
}
|
|
13672
13687
|
function defaultGoalCondition({ flow: flow2, specId, branchTo, spec }) {
|
|
13673
13688
|
if (isGoalShapedFlow(flow2)) return goalFlowCondition(flow2, specId, branchTo, spec);
|
|
13674
|
-
const
|
|
13675
|
-
const planGate =
|
|
13689
|
+
const phases2 = PIPELINES[flow2];
|
|
13690
|
+
const planGate = phases2.includes("Plan") && phases2.includes("Execute");
|
|
13676
13691
|
const clauses = [
|
|
13677
|
-
`1. output \`cat "$HANOMAN_PHASE_FILE"\` yang memuat satu baris untuk SETIAP fase ${
|
|
13692
|
+
`1. output \`cat "$HANOMAN_PHASE_FILE"\` yang memuat satu baris untuk SETIAP fase ${phases2.join(" \u2192 ")}, masing-masing berakhiran \`done\` atau \`skipped\`;`
|
|
13678
13693
|
];
|
|
13679
13694
|
if (planGate) {
|
|
13680
13695
|
const dirs = PLAN_DIRS.map((d) => `${d}/`).join(" ");
|
|
@@ -13734,8 +13749,8 @@ function codexHookArgs(o) {
|
|
|
13734
13749
|
return args;
|
|
13735
13750
|
}
|
|
13736
13751
|
function codexGoalScript(o) {
|
|
13737
|
-
const
|
|
13738
|
-
const planGate =
|
|
13752
|
+
const phases2 = PIPELINES[o.flow];
|
|
13753
|
+
const planGate = phases2.includes("Plan") && phases2.includes("Execute");
|
|
13739
13754
|
const max = o.maxBlocks ?? GOAL_MAX_BLOCKS;
|
|
13740
13755
|
const lines2 = [
|
|
13741
13756
|
"#!/bin/sh",
|
|
@@ -13745,7 +13760,7 @@ function codexGoalScript(o) {
|
|
|
13745
13760
|
`ST=${shq(o.stateFile)}`,
|
|
13746
13761
|
"missing=''"
|
|
13747
13762
|
];
|
|
13748
|
-
for (const p3 of
|
|
13763
|
+
for (const p3 of phases2) {
|
|
13749
13764
|
lines2.push(
|
|
13750
13765
|
`grep -qE ${shq(`^${p3} (done|skipped)[[:space:]]*$`)} "$PF" 2>/dev/null || missing="$missing\\n- fase ${p3} belum tercatat di \\$HANOMAN_PHASE_FILE"`
|
|
13751
13766
|
);
|
|
@@ -14494,8 +14509,8 @@ var init_custom_agent_eval_evidence = __esm({
|
|
|
14494
14509
|
};
|
|
14495
14510
|
textFile = (root, path) => {
|
|
14496
14511
|
const target3 = evidencePath(root, path);
|
|
14497
|
-
const
|
|
14498
|
-
if (!
|
|
14512
|
+
const stat7 = lstatSync(target3);
|
|
14513
|
+
if (!stat7.isFile() || stat7.size > 1024 * 1024) throw new Error("Evidence must be a bounded regular file");
|
|
14499
14514
|
return readFileSync2(target3, "utf8");
|
|
14500
14515
|
};
|
|
14501
14516
|
snapshotEvalEvidence = (context) => {
|
|
@@ -14867,9 +14882,9 @@ var init_custom_agent_eval = __esm({
|
|
|
14867
14882
|
hash4.update(`missing:${rel}\0`);
|
|
14868
14883
|
continue;
|
|
14869
14884
|
}
|
|
14870
|
-
const
|
|
14871
|
-
if (
|
|
14872
|
-
else hash4.update(`f:${
|
|
14885
|
+
const stat7 = lstatSync2(path);
|
|
14886
|
+
if (stat7.isSymbolicLink()) hash4.update(`l:${rel}\0${readlinkSync(path)}\0`);
|
|
14887
|
+
else hash4.update(`f:${stat7.mode}:${rel}\0`).update(readFileSync3(path));
|
|
14873
14888
|
}
|
|
14874
14889
|
return hash4.digest("hex");
|
|
14875
14890
|
};
|
|
@@ -15022,8 +15037,8 @@ ${result.error.message}` : result.stderr ?? ""
|
|
|
15022
15037
|
for (const name2 of readdirSync2(eventDir).filter((entry) => entry.endsWith(".json")).sort()) {
|
|
15023
15038
|
try {
|
|
15024
15039
|
const path = evidencePath(eventDir, name2);
|
|
15025
|
-
const
|
|
15026
|
-
if (!
|
|
15040
|
+
const stat7 = lstatSync2(path);
|
|
15041
|
+
if (!stat7.isFile() || stat7.size > 1024 * 1024) continue;
|
|
15027
15042
|
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
15028
15043
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
15029
15044
|
events.push(parsed);
|
|
@@ -15986,14 +16001,9 @@ var init_stage_machine = __esm({
|
|
|
15986
16001
|
|
|
15987
16002
|
// src/services/session-phases.ts
|
|
15988
16003
|
import { readFileSync as readFileSync6, readdirSync as readdirSync5 } from "node:fs";
|
|
15989
|
-
|
|
16004
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
16005
|
+
function parseRecorded(raw) {
|
|
15990
16006
|
const out4 = /* @__PURE__ */ new Map();
|
|
15991
|
-
let raw;
|
|
15992
|
-
try {
|
|
15993
|
-
raw = readFileSync6(file, "utf8");
|
|
15994
|
-
} catch {
|
|
15995
|
-
return out4;
|
|
15996
|
-
}
|
|
15997
16007
|
for (const line of raw.split("\n")) {
|
|
15998
16008
|
const trimmed = line.trimEnd();
|
|
15999
16009
|
const i = trimmed.lastIndexOf(" ");
|
|
@@ -16004,19 +16014,40 @@ function recorded(file) {
|
|
|
16004
16014
|
}
|
|
16005
16015
|
return out4;
|
|
16006
16016
|
}
|
|
16007
|
-
function
|
|
16008
|
-
|
|
16009
|
-
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
|
|
16015
|
-
return { name: name2, state: "active" };
|
|
16016
|
-
});
|
|
16017
|
+
function recorded(file) {
|
|
16018
|
+
let raw;
|
|
16019
|
+
try {
|
|
16020
|
+
raw = readFileSync6(file, "utf8");
|
|
16021
|
+
} catch {
|
|
16022
|
+
return /* @__PURE__ */ new Map();
|
|
16023
|
+
}
|
|
16024
|
+
return parseRecorded(raw);
|
|
16017
16025
|
}
|
|
16018
|
-
function
|
|
16019
|
-
|
|
16026
|
+
async function recordedAsync(file) {
|
|
16027
|
+
let st;
|
|
16028
|
+
try {
|
|
16029
|
+
st = await stat(file);
|
|
16030
|
+
} catch {
|
|
16031
|
+
phaseFileMemo.delete(file);
|
|
16032
|
+
return /* @__PURE__ */ new Map();
|
|
16033
|
+
}
|
|
16034
|
+
const hit = phaseFileMemo.get(file);
|
|
16035
|
+
if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) return hit.seen;
|
|
16036
|
+
let raw;
|
|
16037
|
+
try {
|
|
16038
|
+
raw = await readFile(file, "utf8");
|
|
16039
|
+
} catch {
|
|
16040
|
+
return /* @__PURE__ */ new Map();
|
|
16041
|
+
}
|
|
16042
|
+
const seen2 = parseRecorded(raw);
|
|
16043
|
+
phaseFileMemo.set(file, { mtimeMs: st.mtimeMs, size: st.size, seen: seen2 });
|
|
16044
|
+
return seen2;
|
|
16045
|
+
}
|
|
16046
|
+
async function readPhasesAsync(file, flow2) {
|
|
16047
|
+
return derivePhases(await recordedAsync(file), flow2);
|
|
16048
|
+
}
|
|
16049
|
+
function enrichPhases(phases2, roster, invocations, doneSeenAt, now, bornAt2, doneAtBirth = /* @__PURE__ */ new Set()) {
|
|
16050
|
+
return phases2.map((p3) => {
|
|
16020
16051
|
const r = roster.find((entry) => entry.phase === p3.name);
|
|
16021
16052
|
if (!r) return p3;
|
|
16022
16053
|
const all = invocations.filter((i) => i.phase === p3.name);
|
|
@@ -16046,23 +16077,23 @@ function enrichPhases(phases, roster, invocations, doneSeenAt, now, bornAt2, don
|
|
|
16046
16077
|
};
|
|
16047
16078
|
});
|
|
16048
16079
|
}
|
|
16049
|
-
function trackDoneSeen(
|
|
16050
|
-
const byName = new Map(
|
|
16080
|
+
function trackDoneSeen(phases2, doneSeenAt, now) {
|
|
16081
|
+
const byName = new Map(phases2.map((p3) => [p3.name, p3.state]));
|
|
16051
16082
|
for (const name2 of [...doneSeenAt.keys()])
|
|
16052
16083
|
if (byName.get(name2) !== "done") doneSeenAt.delete(name2);
|
|
16053
|
-
for (const p3 of
|
|
16084
|
+
for (const p3 of phases2)
|
|
16054
16085
|
if (p3.state === "done" && !doneSeenAt.has(p3.name)) doneSeenAt.set(p3.name, now);
|
|
16055
16086
|
}
|
|
16056
|
-
function stageFor(
|
|
16087
|
+
function stageFor(phases2) {
|
|
16057
16088
|
let best = -1;
|
|
16058
|
-
for (const p3 of
|
|
16089
|
+
for (const p3 of phases2) {
|
|
16059
16090
|
if (WORK_PHASES.includes(p3.name) && p3.state === "active")
|
|
16060
16091
|
best = Math.max(best, STAGES.indexOf("executing"));
|
|
16061
16092
|
if (p3.state !== "done" && p3.state !== "skipped") continue;
|
|
16062
16093
|
const s2 = REACHED[p3.name];
|
|
16063
16094
|
if (s2) best = Math.max(best, STAGES.indexOf(s2));
|
|
16064
16095
|
}
|
|
16065
|
-
if (
|
|
16096
|
+
if (phases2[0]?.state === "active") best = Math.max(best, STAGES.indexOf("brainstorming"));
|
|
16066
16097
|
return best < 0 ? null : STAGES[best];
|
|
16067
16098
|
}
|
|
16068
16099
|
function planComplete(worktree, specId) {
|
|
@@ -16085,16 +16116,45 @@ function planComplete(worktree, specId) {
|
|
|
16085
16116
|
}
|
|
16086
16117
|
return true;
|
|
16087
16118
|
}
|
|
16088
|
-
function
|
|
16089
|
-
|
|
16119
|
+
async function planCompleteAsync(worktree, specId) {
|
|
16120
|
+
const re = new RegExp(`(^|[^a-z0-9])${specId.toLowerCase()}([^0-9]|$)`);
|
|
16121
|
+
for (const rel of PLAN_DIRS) {
|
|
16122
|
+
const dir2 = `${worktree}/${rel}`;
|
|
16123
|
+
let names;
|
|
16124
|
+
try {
|
|
16125
|
+
names = await readdir(dir2);
|
|
16126
|
+
} catch {
|
|
16127
|
+
continue;
|
|
16128
|
+
}
|
|
16129
|
+
for (const n2 of names) {
|
|
16130
|
+
if (!re.test(n2.toLowerCase())) continue;
|
|
16131
|
+
try {
|
|
16132
|
+
if (/^[ \t]*- \[ \]/m.test(await readFile(`${dir2}/${n2}`, "utf8"))) return false;
|
|
16133
|
+
} catch {
|
|
16134
|
+
}
|
|
16135
|
+
}
|
|
16136
|
+
}
|
|
16137
|
+
return true;
|
|
16138
|
+
}
|
|
16139
|
+
function sessionComplete(phases2, worktree, specId) {
|
|
16140
|
+
if (!phasesComplete(phases2)) return false;
|
|
16090
16141
|
return specId ? planComplete(worktree, specId) : true;
|
|
16091
16142
|
}
|
|
16092
|
-
function stageForRun(
|
|
16093
|
-
const s2 = stageFor(
|
|
16143
|
+
function stageForRun(phases2, worktree, specId) {
|
|
16144
|
+
const s2 = stageFor(phases2);
|
|
16094
16145
|
if (s2 === "done" && !planComplete(worktree, specId)) return "executing";
|
|
16095
16146
|
return s2;
|
|
16096
16147
|
}
|
|
16097
|
-
|
|
16148
|
+
async function stageForRunAsync(phases2, worktree, specId) {
|
|
16149
|
+
const s2 = stageFor(phases2);
|
|
16150
|
+
if (s2 === "done" && !await planCompleteAsync(worktree, specId)) return "executing";
|
|
16151
|
+
return s2;
|
|
16152
|
+
}
|
|
16153
|
+
async function sessionCompleteAsync(phases2, worktree, specId) {
|
|
16154
|
+
if (!phasesComplete(phases2)) return false;
|
|
16155
|
+
return specId ? planCompleteAsync(worktree, specId) : true;
|
|
16156
|
+
}
|
|
16157
|
+
var phaseFilePath, decisionFilePath, phaseFileMemo, derivePhases, readPhases, REACHED, phasesComplete;
|
|
16098
16158
|
var init_session_phases = __esm({
|
|
16099
16159
|
"src/services/session-phases.ts"() {
|
|
16100
16160
|
"use strict";
|
|
@@ -16103,6 +16163,18 @@ var init_session_phases = __esm({
|
|
|
16103
16163
|
init_stage_machine();
|
|
16104
16164
|
phaseFilePath = (repoDir, sessionId2) => `${repoDir}/.worktrees/.phases/${sessionId2}`;
|
|
16105
16165
|
decisionFilePath = (repoDir, sessionId2) => `${repoDir}/.worktrees/.decisions/${sessionId2}`;
|
|
16166
|
+
phaseFileMemo = /* @__PURE__ */ new Map();
|
|
16167
|
+
derivePhases = (seen2, flow2) => {
|
|
16168
|
+
let activeTaken = false;
|
|
16169
|
+
return PIPELINES[flow2].map((name2) => {
|
|
16170
|
+
const state = seen2.get(name2);
|
|
16171
|
+
if (state) return { name: name2, state };
|
|
16172
|
+
if (activeTaken) return { name: name2, state: "pending" };
|
|
16173
|
+
activeTaken = true;
|
|
16174
|
+
return { name: name2, state: "active" };
|
|
16175
|
+
});
|
|
16176
|
+
};
|
|
16177
|
+
readPhases = (file, flow2) => derivePhases(recorded(file), flow2);
|
|
16106
16178
|
REACHED = {
|
|
16107
16179
|
Objective: "objective",
|
|
16108
16180
|
Audit: "objective",
|
|
@@ -16118,7 +16190,7 @@ var init_session_phases = __esm({
|
|
|
16118
16190
|
// mencapai `done` — tak ada fase verifikasi untuk menutupnya.
|
|
16119
16191
|
Kerjakan: "done"
|
|
16120
16192
|
};
|
|
16121
|
-
phasesComplete = (
|
|
16193
|
+
phasesComplete = (phases2) => phases2.length > 0 && phases2.every((p3) => p3.state === "done" || p3.state === "skipped");
|
|
16122
16194
|
}
|
|
16123
16195
|
});
|
|
16124
16196
|
|
|
@@ -16467,7 +16539,7 @@ var init_safe_outbound_request = __esm({
|
|
|
16467
16539
|
|
|
16468
16540
|
// src/services/uploads.ts
|
|
16469
16541
|
import { randomUUID } from "node:crypto";
|
|
16470
|
-
import { mkdir, writeFile, readFile, unlink, rm } from "node:fs/promises";
|
|
16542
|
+
import { mkdir, writeFile, readFile as readFile2, unlink, rm } from "node:fs/promises";
|
|
16471
16543
|
import { join as join11, resolve as resolve8 } from "node:path";
|
|
16472
16544
|
function extFor(mimeType) {
|
|
16473
16545
|
return EXT[mimeType] ?? ".bin";
|
|
@@ -16498,13 +16570,13 @@ async function dropSessionUploads(sessionId2) {
|
|
|
16498
16570
|
}
|
|
16499
16571
|
async function readUpload(storageKey) {
|
|
16500
16572
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
16501
|
-
return
|
|
16573
|
+
return readFile2(join11(uploadDir(), safe));
|
|
16502
16574
|
}
|
|
16503
16575
|
async function readUploadOrFetch(storageKey) {
|
|
16504
16576
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
16505
16577
|
const target3 = join11(uploadDir(), safe);
|
|
16506
16578
|
try {
|
|
16507
|
-
return await
|
|
16579
|
+
return await readFile2(target3);
|
|
16508
16580
|
} catch {
|
|
16509
16581
|
const base2 = effectiveStr("SYNC_SERVER_URL");
|
|
16510
16582
|
const token = effectiveStr("SYNC_DEVICE_TOKEN");
|
|
@@ -16976,22 +17048,22 @@ import { execFile, execFileSync as execFileSync2 } from "node:child_process";
|
|
|
16976
17048
|
import { createRequire } from "node:module";
|
|
16977
17049
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
16978
17050
|
import { chmodSync as chmodSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync8, rmSync as rmSync4, statSync as statSync2, writeFileSync as writeFileSync8 } from "node:fs";
|
|
17051
|
+
import { stat as stat2 } from "node:fs/promises";
|
|
16979
17052
|
import { dirname as dirname8 } from "node:path";
|
|
16980
17053
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
16981
|
-
function phaseView(p3, a) {
|
|
16982
|
-
const phases = readPhases(p3.phaseFile, p3.flow);
|
|
17054
|
+
function phaseView(p3, a, phases2) {
|
|
16983
17055
|
const now = Date.now();
|
|
16984
|
-
trackDoneSeen(
|
|
17056
|
+
trackDoneSeen(phases2, a.doneSeenAt, now);
|
|
16985
17057
|
const roster = (p3.agentRoster ?? []).flatMap((r) => r.phase ? [{ name: r.name, phase: r.phase, model: r.model, effort: r.effort }] : []);
|
|
16986
17058
|
return roster.length ? enrichPhases(
|
|
16987
|
-
|
|
17059
|
+
phases2,
|
|
16988
17060
|
roster,
|
|
16989
17061
|
phaseInvocations.get(p3.id) ?? [],
|
|
16990
17062
|
a.doneSeenAt,
|
|
16991
17063
|
now,
|
|
16992
17064
|
p3.startedAt * 1e3,
|
|
16993
17065
|
new Set(p3.doneAtBirth ?? [])
|
|
16994
|
-
) :
|
|
17066
|
+
) : phases2;
|
|
16995
17067
|
}
|
|
16996
17068
|
function noTtyPromptEnv() {
|
|
16997
17069
|
const path = askpassDenyPath();
|
|
@@ -17164,10 +17236,10 @@ function sessionEventEnv(sessionId2, env = process.env) {
|
|
|
17164
17236
|
...host2 ? { HANOMAN_EVENT_HOST: host2 } : {}
|
|
17165
17237
|
};
|
|
17166
17238
|
}
|
|
17167
|
-
function registerSessionHooks(
|
|
17168
|
-
hooks.add(
|
|
17239
|
+
function registerSessionHooks(h2) {
|
|
17240
|
+
hooks.add(h2);
|
|
17169
17241
|
return () => {
|
|
17170
|
-
hooks.delete(
|
|
17242
|
+
hooks.delete(h2);
|
|
17171
17243
|
};
|
|
17172
17244
|
}
|
|
17173
17245
|
function registerCustomAgentSource(fn) {
|
|
@@ -17601,20 +17673,6 @@ function sessionPhases(id2) {
|
|
|
17601
17673
|
if (!p3?.flow || !p3.phaseFile) return null;
|
|
17602
17674
|
return readPhases(p3.phaseFile, p3.flow);
|
|
17603
17675
|
}
|
|
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
17676
|
function broadcast(a, f) {
|
|
17619
17677
|
const msg = frame(f);
|
|
17620
17678
|
for (const c of a.clients) c.send(msg);
|
|
@@ -17669,14 +17727,16 @@ function end(id2, code) {
|
|
|
17669
17727
|
broadcast(a, { t: "exit", code });
|
|
17670
17728
|
drop(id2);
|
|
17671
17729
|
}
|
|
17672
|
-
function pollPhases(p3, a) {
|
|
17730
|
+
async function pollPhases(p3, a) {
|
|
17673
17731
|
if (!p3.flow || !p3.phaseFile) return;
|
|
17674
|
-
const
|
|
17675
|
-
const complete =
|
|
17676
|
-
|
|
17732
|
+
const raw = await readPhasesAsync(p3.phaseFile, p3.flow);
|
|
17733
|
+
const complete = await sessionCompleteAsync(raw, p3.cwd, p3.specId);
|
|
17734
|
+
if (attached.get(p3.id) !== a) return;
|
|
17735
|
+
const phases2 = phaseView(p3, a, raw);
|
|
17736
|
+
const json = phaseKey(phases2, complete);
|
|
17677
17737
|
if (json === a.lastPhases) return;
|
|
17678
17738
|
a.lastPhases = json;
|
|
17679
|
-
broadcast(a, { t: "phase", phases, complete });
|
|
17739
|
+
broadcast(a, { t: "phase", phases: phases2, complete });
|
|
17680
17740
|
}
|
|
17681
17741
|
function setPhaseInvocations(sessionId2, rows) {
|
|
17682
17742
|
if (attached.has(sessionId2)) phaseInvocations.set(sessionId2, rows);
|
|
@@ -17692,7 +17752,7 @@ function startPoll() {
|
|
|
17692
17752
|
if (polling) return;
|
|
17693
17753
|
polling = true;
|
|
17694
17754
|
const snapshot3 = [...attached.entries()];
|
|
17695
|
-
listPanesAsync().then((panes) => {
|
|
17755
|
+
listPanesAsync().then(async (panes) => {
|
|
17696
17756
|
const live = new Map(panes.map((p3) => [p3.id, p3]));
|
|
17697
17757
|
for (const [id2, a] of snapshot3) {
|
|
17698
17758
|
if (attached.get(id2) !== a) continue;
|
|
@@ -17700,7 +17760,8 @@ function startPoll() {
|
|
|
17700
17760
|
if (!p3) end(id2, 0);
|
|
17701
17761
|
else if (p3.exited) end(id2, p3.code);
|
|
17702
17762
|
else {
|
|
17703
|
-
pollPhases(p3, a)
|
|
17763
|
+
await pollPhases(p3, a).catch(() => {
|
|
17764
|
+
});
|
|
17704
17765
|
pollAlt(p3, a);
|
|
17705
17766
|
}
|
|
17706
17767
|
}
|
|
@@ -17715,31 +17776,43 @@ function startPoll() {
|
|
|
17715
17776
|
}, POLL_MS);
|
|
17716
17777
|
poll.unref();
|
|
17717
17778
|
}
|
|
17718
|
-
function
|
|
17719
|
-
|
|
17779
|
+
function replayExited(c, p3, screen) {
|
|
17780
|
+
if (screen.trim()) c.send(frame({ t: "data", d: stripTerminalQueries(screen.replace(/\n/g, "\r\n")) }));
|
|
17781
|
+
c.send(frame({ t: "exit", code: p3.code }));
|
|
17782
|
+
c.close();
|
|
17783
|
+
}
|
|
17784
|
+
async function screenResyncFrame(id2) {
|
|
17785
|
+
try {
|
|
17786
|
+
const screen = await tmuxAsync("capture-pane", "-p", "-e", "-J", "-t", name(id2));
|
|
17787
|
+
return frame({ t: "data", d: `\x1B[2J\x1B[H${stripTerminalQueries(screen.replace(/\n/g, "\r\n"))}` });
|
|
17788
|
+
} catch {
|
|
17789
|
+
return null;
|
|
17790
|
+
}
|
|
17791
|
+
}
|
|
17792
|
+
async function attachAsync(id2, c) {
|
|
17793
|
+
const p3 = await getSessionAsync(id2);
|
|
17720
17794
|
if (!p3) {
|
|
17721
17795
|
c.close();
|
|
17722
17796
|
return;
|
|
17723
17797
|
}
|
|
17724
17798
|
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();
|
|
17799
|
+
replayExited(c, p3, await tmuxAsync(...CAPTURE_ARGS(id2)));
|
|
17731
17800
|
return;
|
|
17732
17801
|
}
|
|
17802
|
+
attachLive(id2, c, p3);
|
|
17803
|
+
}
|
|
17804
|
+
function attachLive(id2, c, p3) {
|
|
17733
17805
|
const a = attached.get(id2) ?? open(id2);
|
|
17734
17806
|
a.clients.add(c);
|
|
17735
17807
|
if (a.scrollback) c.send(frame({ t: "data", d: stripTerminalQueries(a.scrollback) }));
|
|
17736
17808
|
a.lastAlt = p3.altScreen;
|
|
17737
17809
|
c.send(frame({ t: "alt", on: p3.altScreen }));
|
|
17738
17810
|
if (p3.flow && p3.phaseFile) {
|
|
17739
|
-
const
|
|
17740
|
-
const
|
|
17741
|
-
|
|
17742
|
-
|
|
17811
|
+
const raw = readPhases(p3.phaseFile, p3.flow);
|
|
17812
|
+
const phases2 = phaseView(p3, a, raw);
|
|
17813
|
+
const complete = sessionComplete(raw, p3.cwd, p3.specId);
|
|
17814
|
+
a.lastPhases = phaseKey(phases2, complete);
|
|
17815
|
+
c.send(frame({ t: "phase", phases: phases2, complete }));
|
|
17743
17816
|
}
|
|
17744
17817
|
}
|
|
17745
17818
|
function writeTo(id2, d, from) {
|
|
@@ -17791,7 +17864,7 @@ function spawnPty(...args) {
|
|
|
17791
17864
|
);
|
|
17792
17865
|
}
|
|
17793
17866
|
}
|
|
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,
|
|
17867
|
+
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
17868
|
var init_pty = __esm({
|
|
17796
17869
|
"src/services/pty.ts"() {
|
|
17797
17870
|
"use strict";
|
|
@@ -17820,6 +17893,13 @@ var init_pty = __esm({
|
|
|
17820
17893
|
return false;
|
|
17821
17894
|
}
|
|
17822
17895
|
};
|
|
17896
|
+
markerFilledAsync = async (f) => {
|
|
17897
|
+
try {
|
|
17898
|
+
return (await stat2(f)).size > 0;
|
|
17899
|
+
} catch {
|
|
17900
|
+
return false;
|
|
17901
|
+
}
|
|
17902
|
+
};
|
|
17823
17903
|
clearMarker = (f) => {
|
|
17824
17904
|
try {
|
|
17825
17905
|
writeFileSync8(f, "");
|
|
@@ -17937,7 +18017,7 @@ exit 1
|
|
|
17937
18017
|
});
|
|
17938
18018
|
listSessions = () => listPanes().map(toSessionInfo);
|
|
17939
18019
|
listSessionsAsync = async () => (await listPanesAsync()).map(toSessionInfo);
|
|
17940
|
-
|
|
18020
|
+
decisionsOf = (panes) => panes.filter((p3) => !p3.exited && p3.decisionFile).map((p3) => ({
|
|
17941
18021
|
id: p3.id,
|
|
17942
18022
|
specId: p3.specId,
|
|
17943
18023
|
projectId: p3.projectId,
|
|
@@ -17951,17 +18031,17 @@ exit 1
|
|
|
17951
18031
|
getSessionAsync = async (id2) => (await listPanesAsync()).find((p3) => p3.id === id2);
|
|
17952
18032
|
hooks = /* @__PURE__ */ new Set();
|
|
17953
18033
|
emitBirth = (b) => {
|
|
17954
|
-
for (const
|
|
18034
|
+
for (const h2 of hooks) {
|
|
17955
18035
|
try {
|
|
17956
|
-
|
|
18036
|
+
h2.onBirth?.(b);
|
|
17957
18037
|
} catch {
|
|
17958
18038
|
}
|
|
17959
18039
|
}
|
|
17960
18040
|
};
|
|
17961
18041
|
emitDeath = (d) => {
|
|
17962
|
-
for (const
|
|
18042
|
+
for (const h2 of hooks) {
|
|
17963
18043
|
try {
|
|
17964
|
-
|
|
18044
|
+
h2.onDeath?.(d);
|
|
17965
18045
|
} catch {
|
|
17966
18046
|
}
|
|
17967
18047
|
}
|
|
@@ -18017,11 +18097,10 @@ exit 1
|
|
|
18017
18097
|
COALESCE_MS = 16;
|
|
18018
18098
|
COALESCE_MAX_BYTES = 64 * 1024;
|
|
18019
18099
|
trimScrollback = (s2) => s2.length > MAX_SCROLLBACK + SCROLLBACK_SLACK ? s2.slice(-MAX_SCROLLBACK) : s2;
|
|
18020
|
-
phaseKey = (
|
|
18021
|
-
|
|
18022
|
-
|
|
18023
|
-
|
|
18024
|
-
return !!p3 && paneComplete(p3);
|
|
18100
|
+
phaseKey = (phases2, complete) => JSON.stringify({ phases: phases2, complete });
|
|
18101
|
+
sessionFinishedAsync = async (id2) => {
|
|
18102
|
+
const p3 = await getSessionAsync(id2);
|
|
18103
|
+
return !!p3 && !!p3.flow && !!p3.phaseFile && sessionCompleteAsync(await readPhasesAsync(p3.phaseFile, p3.flow), p3.cwd, p3.specId);
|
|
18025
18104
|
};
|
|
18026
18105
|
polling = false;
|
|
18027
18106
|
TERMINAL_QUERY = new RegExp([
|
|
@@ -18041,6 +18120,7 @@ exit 1
|
|
|
18041
18120
|
// XTWINOPS laporan — berparameter TUNGGAL
|
|
18042
18121
|
].join("|"), "g");
|
|
18043
18122
|
stripTerminalQueries = (s2) => s2.replace(TERMINAL_QUERY, "");
|
|
18123
|
+
CAPTURE_ARGS = (id2) => ["capture-pane", "-p", "-e", "-J", "-S", "-2000", "-t", name(id2)];
|
|
18044
18124
|
detach = (id2, c) => {
|
|
18045
18125
|
attached.get(id2)?.clients.delete(c);
|
|
18046
18126
|
};
|
|
@@ -18066,7 +18146,7 @@ var init_redact_known = __esm({
|
|
|
18066
18146
|
|
|
18067
18147
|
// src/services/transcript-store.ts
|
|
18068
18148
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
18069
|
-
import { mkdir as mkdir2, writeFile as writeFile2, readFile as
|
|
18149
|
+
import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile3, unlink as unlink2, readdir as readdir2, stat as stat3 } from "node:fs/promises";
|
|
18070
18150
|
import { join as join13, resolve as resolve9, basename as basename4 } from "node:path";
|
|
18071
18151
|
function transcriptDir() {
|
|
18072
18152
|
return resolve9(effectiveStr("HANOMAN_TRANSCRIPT_DIR")?.trim() || resolveDataDirs().transcripts);
|
|
@@ -18093,7 +18173,7 @@ async function saveTranscript(text) {
|
|
|
18093
18173
|
async function readTranscript(key) {
|
|
18094
18174
|
if (!key) return null;
|
|
18095
18175
|
try {
|
|
18096
|
-
return await
|
|
18176
|
+
return await readFile3(join13(transcriptDir(), basename4(key)), "utf8");
|
|
18097
18177
|
} catch {
|
|
18098
18178
|
return null;
|
|
18099
18179
|
}
|
|
@@ -18110,7 +18190,7 @@ async function listTranscripts() {
|
|
|
18110
18190
|
const dir2 = transcriptDir();
|
|
18111
18191
|
let names;
|
|
18112
18192
|
try {
|
|
18113
|
-
names = await
|
|
18193
|
+
names = await readdir2(dir2);
|
|
18114
18194
|
} catch (e) {
|
|
18115
18195
|
if (e.code === "ENOENT") return [];
|
|
18116
18196
|
throw e;
|
|
@@ -18119,7 +18199,7 @@ async function listTranscripts() {
|
|
|
18119
18199
|
for (const name2 of names) {
|
|
18120
18200
|
if (!name2.endsWith(".log")) continue;
|
|
18121
18201
|
try {
|
|
18122
|
-
rows.push({ key: name2, mtimeMs: (await
|
|
18202
|
+
rows.push({ key: name2, mtimeMs: (await stat3(join13(dir2, name2))).mtimeMs });
|
|
18123
18203
|
} catch {
|
|
18124
18204
|
}
|
|
18125
18205
|
}
|
|
@@ -19181,6 +19261,118 @@ var init_codex_trust = __esm({
|
|
|
19181
19261
|
}
|
|
19182
19262
|
});
|
|
19183
19263
|
|
|
19264
|
+
// src/services/logs/phase-tap.ts
|
|
19265
|
+
function observePhases(rows) {
|
|
19266
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
19267
|
+
for (const r of rows) {
|
|
19268
|
+
seen2.add(r.sessionId);
|
|
19269
|
+
const prev = known.get(r.sessionId);
|
|
19270
|
+
if (!known.has(r.sessionId)) {
|
|
19271
|
+
known.set(r.sessionId, r.phase);
|
|
19272
|
+
continue;
|
|
19273
|
+
}
|
|
19274
|
+
if (prev !== r.phase) {
|
|
19275
|
+
void appendEvent({
|
|
19276
|
+
kind: "session.phase",
|
|
19277
|
+
msg: `sesi ${r.sessionId} fase ${prev ?? "?"} \u2192 ${r.phase ?? "?"}`,
|
|
19278
|
+
projectId: r.projectId,
|
|
19279
|
+
specId: r.specId ?? null,
|
|
19280
|
+
sessionId: r.sessionId,
|
|
19281
|
+
data: { from: prev, to: r.phase }
|
|
19282
|
+
});
|
|
19283
|
+
known.set(r.sessionId, r.phase);
|
|
19284
|
+
}
|
|
19285
|
+
}
|
|
19286
|
+
for (const id2 of [...known.keys()]) if (!seen2.has(id2)) known.delete(id2);
|
|
19287
|
+
}
|
|
19288
|
+
var known;
|
|
19289
|
+
var init_phase_tap = __esm({
|
|
19290
|
+
"src/services/logs/phase-tap.ts"() {
|
|
19291
|
+
"use strict";
|
|
19292
|
+
init_event_log();
|
|
19293
|
+
known = /* @__PURE__ */ new Map();
|
|
19294
|
+
}
|
|
19295
|
+
});
|
|
19296
|
+
|
|
19297
|
+
// src/services/presence/snapshot.ts
|
|
19298
|
+
function paneToPresence(p3, phase) {
|
|
19299
|
+
return {
|
|
19300
|
+
sessionId: p3.id,
|
|
19301
|
+
projectId: p3.projectId,
|
|
19302
|
+
...p3.specId ? { specId: p3.specId } : {},
|
|
19303
|
+
...p3.flow ? { flow: p3.flow } : {},
|
|
19304
|
+
...phase ? { phase } : {},
|
|
19305
|
+
agent: p3.agent,
|
|
19306
|
+
// Presedensi: pane mati sudah berakhir apa pun isi markernya.
|
|
19307
|
+
status: p3.exited ? "exited" : p3.decision ? "waiting" : "working",
|
|
19308
|
+
startedAt: isoFromEpochSeconds(p3.startedAt)
|
|
19309
|
+
};
|
|
19310
|
+
}
|
|
19311
|
+
function activePhase(p3) {
|
|
19312
|
+
if (!p3.flow || !p3.phaseFile) return void 0;
|
|
19313
|
+
return readPhases(p3.phaseFile, p3.flow).find((f) => f.state === "active")?.name;
|
|
19314
|
+
}
|
|
19315
|
+
function listPanesShared(now = Date.now()) {
|
|
19316
|
+
if (panesMemo && now - panesMemo.at < PANES_MEMO_MS) return panesMemo.value;
|
|
19317
|
+
const value = listPanesAsync();
|
|
19318
|
+
panesMemo = { at: now, value };
|
|
19319
|
+
value.catch(() => {
|
|
19320
|
+
if (panesMemo?.value === value) panesMemo = null;
|
|
19321
|
+
});
|
|
19322
|
+
return value;
|
|
19323
|
+
}
|
|
19324
|
+
async function buildLocalPresence() {
|
|
19325
|
+
const panes = await listPanesShared();
|
|
19326
|
+
const rows = panes.slice(0, MAX_PRESENCE_SESSIONS).map((p3) => paneToPresence(p3, activePhase(p3)));
|
|
19327
|
+
observePhases(rows.map((r) => ({ sessionId: r.sessionId, projectId: r.projectId, specId: r.specId, phase: r.phase })));
|
|
19328
|
+
return rows;
|
|
19329
|
+
}
|
|
19330
|
+
async function buildLocalCapacity() {
|
|
19331
|
+
return currentLaunchStatus(await listPanesShared(), await getScheduler());
|
|
19332
|
+
}
|
|
19333
|
+
var isoFromEpochSeconds, PANES_MEMO_MS, panesMemo;
|
|
19334
|
+
var init_snapshot = __esm({
|
|
19335
|
+
"src/services/presence/snapshot.ts"() {
|
|
19336
|
+
"use strict";
|
|
19337
|
+
init_src();
|
|
19338
|
+
init_pty();
|
|
19339
|
+
init_session_phases();
|
|
19340
|
+
init_config3();
|
|
19341
|
+
init_session_launch_gate();
|
|
19342
|
+
init_phase_tap();
|
|
19343
|
+
isoFromEpochSeconds = (s2) => new Date((s2 || 0) * 1e3).toISOString();
|
|
19344
|
+
PANES_MEMO_MS = 1e3;
|
|
19345
|
+
panesMemo = null;
|
|
19346
|
+
}
|
|
19347
|
+
});
|
|
19348
|
+
|
|
19349
|
+
// src/services/live-phases.ts
|
|
19350
|
+
async function sessionPhasesBySpecAsync() {
|
|
19351
|
+
const out4 = /* @__PURE__ */ new Map();
|
|
19352
|
+
let panes;
|
|
19353
|
+
try {
|
|
19354
|
+
panes = await listPanesShared();
|
|
19355
|
+
} catch {
|
|
19356
|
+
return out4;
|
|
19357
|
+
}
|
|
19358
|
+
await Promise.all(panes.map(async (p3) => {
|
|
19359
|
+
if (!p3.specId || !p3.flow || !p3.phaseFile) return;
|
|
19360
|
+
out4.set(p3.specId, { phases: await readPhasesAsync(p3.phaseFile, p3.flow), cwd: p3.cwd });
|
|
19361
|
+
}));
|
|
19362
|
+
return out4;
|
|
19363
|
+
}
|
|
19364
|
+
async function liveDecisionsAsync() {
|
|
19365
|
+
return decisionsOf(await listPanesShared());
|
|
19366
|
+
}
|
|
19367
|
+
var init_live_phases = __esm({
|
|
19368
|
+
"src/services/live-phases.ts"() {
|
|
19369
|
+
"use strict";
|
|
19370
|
+
init_session_phases();
|
|
19371
|
+
init_snapshot();
|
|
19372
|
+
init_pty();
|
|
19373
|
+
}
|
|
19374
|
+
});
|
|
19375
|
+
|
|
19184
19376
|
// src/services/notifications.ts
|
|
19185
19377
|
async function recordDrift(vpsId, vpsName, drift, snapshotId) {
|
|
19186
19378
|
if (drift.length === 0) return;
|
|
@@ -19282,11 +19474,11 @@ async function recordCronRun(cronId, cronName, projectId, dueAt, status2, note)
|
|
|
19282
19474
|
}).catch(() => {
|
|
19283
19475
|
});
|
|
19284
19476
|
}
|
|
19285
|
-
async function scanDecisions(read2 =
|
|
19477
|
+
async function scanDecisions(read2 = liveDecisionsAsync) {
|
|
19286
19478
|
const next = /* @__PURE__ */ new Set();
|
|
19287
19479
|
const fresh = [];
|
|
19288
|
-
for (const s2 of read2()) {
|
|
19289
|
-
if (!
|
|
19480
|
+
for (const s2 of await read2()) {
|
|
19481
|
+
if (!await markerFilledAsync(s2.decisionFile)) continue;
|
|
19290
19482
|
if (awaiting.has(s2.id)) {
|
|
19291
19483
|
next.add(s2.id);
|
|
19292
19484
|
continue;
|
|
@@ -19322,96 +19514,12 @@ var init_notifications2 = __esm({
|
|
|
19322
19514
|
"use strict";
|
|
19323
19515
|
init_db();
|
|
19324
19516
|
init_pty();
|
|
19517
|
+
init_live_phases();
|
|
19325
19518
|
awaiting = /* @__PURE__ */ new Set();
|
|
19326
19519
|
DEFAULT_FEED_TAKE = 50;
|
|
19327
19520
|
}
|
|
19328
19521
|
});
|
|
19329
19522
|
|
|
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
19523
|
// src/services/device-sockets.ts
|
|
19416
19524
|
function registerDeviceSocket(deviceId, kind, socket2) {
|
|
19417
19525
|
const entry = { kind, socket: socket2 };
|
|
@@ -19916,8 +20024,8 @@ var require_ms = __commonJS({
|
|
|
19916
20024
|
"../node_modules/.pnpm/ms@2.1.3/node_modules/ms/index.js"(exports, module) {
|
|
19917
20025
|
var s2 = 1e3;
|
|
19918
20026
|
var m = s2 * 60;
|
|
19919
|
-
var
|
|
19920
|
-
var d =
|
|
20027
|
+
var h2 = m * 60;
|
|
20028
|
+
var d = h2 * 24;
|
|
19921
20029
|
var w = d * 7;
|
|
19922
20030
|
var y = d * 365.25;
|
|
19923
20031
|
module.exports = function(val, options2) {
|
|
@@ -19965,7 +20073,7 @@ var require_ms = __commonJS({
|
|
|
19965
20073
|
case "hrs":
|
|
19966
20074
|
case "hr":
|
|
19967
20075
|
case "h":
|
|
19968
|
-
return n2 *
|
|
20076
|
+
return n2 * h2;
|
|
19969
20077
|
case "minutes":
|
|
19970
20078
|
case "minute":
|
|
19971
20079
|
case "mins":
|
|
@@ -19993,8 +20101,8 @@ var require_ms = __commonJS({
|
|
|
19993
20101
|
if (msAbs >= d) {
|
|
19994
20102
|
return Math.round(ms / d) + "d";
|
|
19995
20103
|
}
|
|
19996
|
-
if (msAbs >=
|
|
19997
|
-
return Math.round(ms /
|
|
20104
|
+
if (msAbs >= h2) {
|
|
20105
|
+
return Math.round(ms / h2) + "h";
|
|
19998
20106
|
}
|
|
19999
20107
|
if (msAbs >= m) {
|
|
20000
20108
|
return Math.round(ms / m) + "m";
|
|
@@ -20009,8 +20117,8 @@ var require_ms = __commonJS({
|
|
|
20009
20117
|
if (msAbs >= d) {
|
|
20010
20118
|
return plural(ms, msAbs, d, "day");
|
|
20011
20119
|
}
|
|
20012
|
-
if (msAbs >=
|
|
20013
|
-
return plural(ms, msAbs,
|
|
20120
|
+
if (msAbs >= h2) {
|
|
20121
|
+
return plural(ms, msAbs, h2, "hour");
|
|
20014
20122
|
}
|
|
20015
20123
|
if (msAbs >= m) {
|
|
20016
20124
|
return plural(ms, msAbs, m, "minute");
|
|
@@ -20726,7 +20834,7 @@ var init_runtime_defaults2 = __esm({
|
|
|
20726
20834
|
});
|
|
20727
20835
|
|
|
20728
20836
|
// src/services/agent-token.ts
|
|
20729
|
-
import { randomBytes as randomBytes2, createHash as
|
|
20837
|
+
import { randomBytes as randomBytes2, createHash as createHash5, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
20730
20838
|
function toAgentTokenView(t) {
|
|
20731
20839
|
return {
|
|
20732
20840
|
id: t.id,
|
|
@@ -20792,7 +20900,7 @@ var init_agent_token = __esm({
|
|
|
20792
20900
|
"src/services/agent-token.ts"() {
|
|
20793
20901
|
"use strict";
|
|
20794
20902
|
init_db();
|
|
20795
|
-
hash2 = (token) =>
|
|
20903
|
+
hash2 = (token) => createHash5("sha256").update(token).digest("hex");
|
|
20796
20904
|
}
|
|
20797
20905
|
});
|
|
20798
20906
|
|
|
@@ -20932,7 +21040,7 @@ var init_config4 = __esm({
|
|
|
20932
21040
|
});
|
|
20933
21041
|
|
|
20934
21042
|
// src/services/telegram/protocol.ts
|
|
20935
|
-
import { createHash as
|
|
21043
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
20936
21044
|
function normalizeTelegramCommand(text) {
|
|
20937
21045
|
const clean2 = text.trim();
|
|
20938
21046
|
const match = clean2.match(/^\/([a-z0-9_]+)(?:@[a-z0-9_]+)?(?:\s+([\s\S]*))?$/i);
|
|
@@ -20999,7 +21107,7 @@ function parseTelegramUpdate(update2, allowlist, maxText = 12e3) {
|
|
|
20999
21107
|
};
|
|
21000
21108
|
}
|
|
21001
21109
|
function inboundDigest(input) {
|
|
21002
|
-
return
|
|
21110
|
+
return createHash6("sha256").update(`${input.updateId}\0${input.chatId}\0${input.userId}\0${input.kind}\0${input.text}`, "utf8").digest("hex");
|
|
21003
21111
|
}
|
|
21004
21112
|
function sanitizeTelegramOutput(text, exactSecrets = []) {
|
|
21005
21113
|
let clean2 = text.replace(ANSI, "").replace(CONTROLS, "");
|
|
@@ -21166,7 +21274,7 @@ var init_typing = __esm({
|
|
|
21166
21274
|
});
|
|
21167
21275
|
|
|
21168
21276
|
// src/services/telegram/gateway.ts
|
|
21169
|
-
import { createHash as
|
|
21277
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
21170
21278
|
var digestRaw, TelegramGateway;
|
|
21171
21279
|
var init_gateway = __esm({
|
|
21172
21280
|
"src/services/telegram/gateway.ts"() {
|
|
@@ -21175,7 +21283,7 @@ var init_gateway = __esm({
|
|
|
21175
21283
|
init_protocol();
|
|
21176
21284
|
init_runtime();
|
|
21177
21285
|
init_typing();
|
|
21178
|
-
digestRaw = (update2) =>
|
|
21286
|
+
digestRaw = (update2) => createHash7("sha256").update(JSON.stringify(update2)).digest("hex");
|
|
21179
21287
|
TelegramGateway = class {
|
|
21180
21288
|
constructor(deps) {
|
|
21181
21289
|
this.deps = deps;
|
|
@@ -21482,14 +21590,14 @@ var init_engine_command = __esm({
|
|
|
21482
21590
|
});
|
|
21483
21591
|
|
|
21484
21592
|
// src/services/telegram/session.ts
|
|
21485
|
-
import { createHash as
|
|
21593
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
21486
21594
|
var chatHash, telegramOperatorSessionId, formatTelegramTurn, TelegramSessionCoordinator;
|
|
21487
21595
|
var init_session = __esm({
|
|
21488
21596
|
"src/services/telegram/session.ts"() {
|
|
21489
21597
|
"use strict";
|
|
21490
21598
|
init_src2();
|
|
21491
21599
|
init_engine_command();
|
|
21492
|
-
chatHash = (chatId) =>
|
|
21600
|
+
chatHash = (chatId) => createHash8("sha256").update(chatId).digest("hex").slice(0, 16);
|
|
21493
21601
|
telegramOperatorSessionId = (chatId) => `telegram-${chatHash(chatId)}`;
|
|
21494
21602
|
formatTelegramTurn = (input) => `[Telegram update ${input.updateId} \xB7 chat ${input.chatId} \xB7 kind ${input.kind}]
|
|
21495
21603
|
${input.text}`;
|
|
@@ -24949,7 +25057,7 @@ var require_websocket = __commonJS({
|
|
|
24949
25057
|
var http = __require("http");
|
|
24950
25058
|
var net = __require("net");
|
|
24951
25059
|
var tls = __require("tls");
|
|
24952
|
-
var { randomBytes: randomBytes12, createHash:
|
|
25060
|
+
var { randomBytes: randomBytes12, createHash: createHash16 } = __require("crypto");
|
|
24953
25061
|
var { Duplex, Readable } = __require("stream");
|
|
24954
25062
|
var { URL: URL2 } = __require("url");
|
|
24955
25063
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -25617,7 +25725,7 @@ var require_websocket = __commonJS({
|
|
|
25617
25725
|
abortHandshake(websocket2, socket2, "Invalid Upgrade header");
|
|
25618
25726
|
return;
|
|
25619
25727
|
}
|
|
25620
|
-
const digest2 =
|
|
25728
|
+
const digest2 = createHash16("sha1").update(key + GUID).digest("base64");
|
|
25621
25729
|
if (res.headers["sec-websocket-accept"] !== digest2) {
|
|
25622
25730
|
abortHandshake(websocket2, socket2, "Invalid Sec-WebSocket-Accept header");
|
|
25623
25731
|
return;
|
|
@@ -25986,7 +26094,7 @@ var require_websocket_server = __commonJS({
|
|
|
25986
26094
|
var EventEmitter = __require("events");
|
|
25987
26095
|
var http = __require("http");
|
|
25988
26096
|
var { Duplex } = __require("stream");
|
|
25989
|
-
var { createHash:
|
|
26097
|
+
var { createHash: createHash16 } = __require("crypto");
|
|
25990
26098
|
var extension2 = require_extension();
|
|
25991
26099
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
25992
26100
|
var subprotocol2 = require_subprotocol();
|
|
@@ -26293,7 +26401,7 @@ var require_websocket_server = __commonJS({
|
|
|
26293
26401
|
);
|
|
26294
26402
|
}
|
|
26295
26403
|
if (this._state > RUNNING) return abortHandshake(socket2, 503);
|
|
26296
|
-
const digest2 =
|
|
26404
|
+
const digest2 = createHash16("sha1").update(key + GUID).digest("base64");
|
|
26297
26405
|
const headers = [
|
|
26298
26406
|
"HTTP/1.1 101 Switching Protocols",
|
|
26299
26407
|
"Upgrade: websocket",
|
|
@@ -26365,7 +26473,7 @@ var require_websocket_server = __commonJS({
|
|
|
26365
26473
|
socket2.once("finish", socket2.destroy);
|
|
26366
26474
|
socket2.end(
|
|
26367
26475
|
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r
|
|
26368
|
-
` + Object.keys(headers).map((
|
|
26476
|
+
` + Object.keys(headers).map((h2) => `${h2}: ${headers[h2]}`).join("\r\n") + "\r\n\r\n" + message
|
|
26369
26477
|
);
|
|
26370
26478
|
}
|
|
26371
26479
|
function abortHandshakeOrEmitwsClientError(server, req, socket2, code, message, headers) {
|
|
@@ -26589,7 +26697,7 @@ var init_client2 = __esm({
|
|
|
26589
26697
|
});
|
|
26590
26698
|
|
|
26591
26699
|
// src/services/logs/spool.ts
|
|
26592
|
-
import { appendFile, mkdir as mkdir8, readFile as
|
|
26700
|
+
import { appendFile, mkdir as mkdir8, readFile as readFile7, readdir as readdir9, rm as rm8, stat as stat6 } from "node:fs/promises";
|
|
26593
26701
|
import { join as join35 } from "node:path";
|
|
26594
26702
|
function spoolDir(lane) {
|
|
26595
26703
|
return join35(resolveDataDirs().home, "log-spool", lane);
|
|
@@ -26613,7 +26721,7 @@ async function readSpoolSegments(lane) {
|
|
|
26613
26721
|
const dir2 = spoolDir(lane);
|
|
26614
26722
|
let names;
|
|
26615
26723
|
try {
|
|
26616
|
-
names = await
|
|
26724
|
+
names = await readdir9(dir2);
|
|
26617
26725
|
} catch {
|
|
26618
26726
|
return [];
|
|
26619
26727
|
}
|
|
@@ -26621,7 +26729,7 @@ async function readSpoolSegments(lane) {
|
|
|
26621
26729
|
const out4 = [];
|
|
26622
26730
|
for (const name2 of files) {
|
|
26623
26731
|
const file = join35(dir2, name2);
|
|
26624
|
-
const raw = await
|
|
26732
|
+
const raw = await readFile7(file, "utf8").catch(() => "");
|
|
26625
26733
|
const entries3 = raw.split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
26626
26734
|
out4.push({ file, entries: entries3 });
|
|
26627
26735
|
}
|
|
@@ -27229,7 +27337,7 @@ var init_agent_tool_catalog = __esm({
|
|
|
27229
27337
|
});
|
|
27230
27338
|
|
|
27231
27339
|
// src/services/builtin-agents.ts
|
|
27232
|
-
import { createHash as
|
|
27340
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
27233
27341
|
async function seedBuiltinAgents() {
|
|
27234
27342
|
try {
|
|
27235
27343
|
const setting = await getSetting();
|
|
@@ -27319,7 +27427,7 @@ var init_builtin_agents2 = __esm({
|
|
|
27319
27427
|
init_settings3();
|
|
27320
27428
|
init_tombstone();
|
|
27321
27429
|
init_sync_notify();
|
|
27322
|
-
digest = (parts) =>
|
|
27430
|
+
digest = (parts) => createHash13("sha256").update(parts.join(" ")).digest("hex").slice(0, 16);
|
|
27323
27431
|
legacyFingerprint = (name2, description, instructions, tools) => digest([name2, description, instructions, [...tools].join(",")]);
|
|
27324
27432
|
fingerprint = (a) => digest([
|
|
27325
27433
|
a.name,
|
|
@@ -27825,14 +27933,14 @@ var require_HeaderParser = __commonJS({
|
|
|
27825
27933
|
}
|
|
27826
27934
|
const lines2 = this.buffer.split(RE_CRLF);
|
|
27827
27935
|
const len = lines2.length;
|
|
27828
|
-
let m,
|
|
27936
|
+
let m, h2;
|
|
27829
27937
|
for (var i = 0; i < len; ++i) {
|
|
27830
27938
|
if (lines2[i].length === 0) {
|
|
27831
27939
|
continue;
|
|
27832
27940
|
}
|
|
27833
27941
|
if (lines2[i][0] === " " || lines2[i][0] === " ") {
|
|
27834
|
-
if (
|
|
27835
|
-
this.header[
|
|
27942
|
+
if (h2) {
|
|
27943
|
+
this.header[h2][this.header[h2].length - 1] += lines2[i];
|
|
27836
27944
|
continue;
|
|
27837
27945
|
}
|
|
27838
27946
|
}
|
|
@@ -27841,9 +27949,9 @@ var require_HeaderParser = __commonJS({
|
|
|
27841
27949
|
return;
|
|
27842
27950
|
}
|
|
27843
27951
|
m = RE_HDR.exec(lines2[i]);
|
|
27844
|
-
|
|
27845
|
-
this.header[
|
|
27846
|
-
this.header[
|
|
27952
|
+
h2 = m[1].toLowerCase();
|
|
27953
|
+
this.header[h2] = this.header[h2] || [];
|
|
27954
|
+
this.header[h2].push(m[2] || "");
|
|
27847
27955
|
if (++this.npairs === this.maxHeaderPairs) {
|
|
27848
27956
|
break;
|
|
27849
27957
|
}
|
|
@@ -30835,14 +30943,14 @@ async function health_default(app3) {
|
|
|
30835
30943
|
}
|
|
30836
30944
|
|
|
30837
30945
|
// src/routes/agent-doc.ts
|
|
30838
|
-
import { readFile as
|
|
30946
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
30839
30947
|
async function agent_doc_default(app3, opts) {
|
|
30840
30948
|
app3.get("/agent-integration.md", async (_req, reply) => {
|
|
30841
30949
|
if (!opts.file)
|
|
30842
30950
|
return reply.code(404).send({
|
|
30843
30951
|
error: `dokumen panduan tak ada di instalasi ini (${AGENT_DOC_REL}) \u2014 pasang ulang paket hanoman atau set HANOMAN_AGENT_DOC`
|
|
30844
30952
|
});
|
|
30845
|
-
const text = await
|
|
30953
|
+
const text = await readFile4(opts.file, "utf8");
|
|
30846
30954
|
return reply.type("text/markdown; charset=utf-8").send(text);
|
|
30847
30955
|
});
|
|
30848
30956
|
}
|
|
@@ -30883,8 +30991,8 @@ function components(rel) {
|
|
|
30883
30991
|
}
|
|
30884
30992
|
async function canonicalRoot(root) {
|
|
30885
30993
|
const out4 = await realpath(root).catch(() => denied("root tidak ada"));
|
|
30886
|
-
const
|
|
30887
|
-
if (!
|
|
30994
|
+
const stat7 = await lstat(out4);
|
|
30995
|
+
if (!stat7.isDirectory() || stat7.isSymbolicLink()) denied("root bukan direktori regular");
|
|
30888
30996
|
return out4;
|
|
30889
30997
|
}
|
|
30890
30998
|
function beneath(root, candidate) {
|
|
@@ -30902,8 +31010,8 @@ async function ensureRepoParents(root, rel) {
|
|
|
30902
31010
|
} catch (error) {
|
|
30903
31011
|
if (error.code !== "EEXIST") throw error;
|
|
30904
31012
|
}
|
|
30905
|
-
const
|
|
30906
|
-
if (
|
|
31013
|
+
const stat7 = await lstat(current);
|
|
31014
|
+
if (stat7.isSymbolicLink() || !stat7.isDirectory()) denied("parent tidak aman");
|
|
30907
31015
|
const canonical = await realpath(current);
|
|
30908
31016
|
if (!beneath(base2, canonical) || canonical !== current) denied("parent keluar root");
|
|
30909
31017
|
}
|
|
@@ -30919,8 +31027,8 @@ function ensureRepoParentsSync(root, rel) {
|
|
|
30919
31027
|
} catch (error) {
|
|
30920
31028
|
if (error.code !== "EEXIST") throw error;
|
|
30921
31029
|
}
|
|
30922
|
-
const
|
|
30923
|
-
if (
|
|
31030
|
+
const stat7 = lstatSync3(current);
|
|
31031
|
+
if (stat7.isSymbolicLink() || !stat7.isDirectory()) denied("parent tidak aman");
|
|
30924
31032
|
if (realpathSync4(current) !== current || !beneath(base2, current)) denied("parent keluar root");
|
|
30925
31033
|
}
|
|
30926
31034
|
}
|
|
@@ -30930,14 +31038,14 @@ async function resolveRepoEntry(root, rel, opts = {}) {
|
|
|
30930
31038
|
let current = base2;
|
|
30931
31039
|
for (let i = 0; i < parts.length; i++) {
|
|
30932
31040
|
current = join14(current, parts[i]);
|
|
30933
|
-
const
|
|
31041
|
+
const stat7 = await lstat(current).catch((error) => {
|
|
30934
31042
|
if (error.code === "ENOENT" && opts.allowMissingTail) return null;
|
|
30935
31043
|
if (error.code === "ENOENT" && opts.allowMissingFinal && i === parts.length - 1) return null;
|
|
30936
31044
|
return denied("komponen tidak ada");
|
|
30937
31045
|
});
|
|
30938
|
-
if (!
|
|
30939
|
-
if (
|
|
30940
|
-
if (i < parts.length - 1 && !
|
|
31046
|
+
if (!stat7) break;
|
|
31047
|
+
if (stat7.isSymbolicLink()) denied("symlink");
|
|
31048
|
+
if (i < parts.length - 1 && !stat7.isDirectory()) denied("parent bukan direktori");
|
|
30941
31049
|
}
|
|
30942
31050
|
if (!beneath(base2, resolve12(current))) denied("keluar root");
|
|
30943
31051
|
const parent = resolve12(current, "..");
|
|
@@ -30952,9 +31060,9 @@ function assertSafeRepoPathSync(root, rel, allowMissingFinal = false, allowMissi
|
|
|
30952
31060
|
for (let i = 0; i < parts.length; i++) {
|
|
30953
31061
|
current = join14(current, parts[i]);
|
|
30954
31062
|
try {
|
|
30955
|
-
const
|
|
30956
|
-
if (
|
|
30957
|
-
if (i < parts.length - 1 && !
|
|
31063
|
+
const stat7 = lstatSync3(current);
|
|
31064
|
+
if (stat7.isSymbolicLink()) denied("symlink");
|
|
31065
|
+
if (i < parts.length - 1 && !stat7.isDirectory()) denied("parent bukan direktori");
|
|
30958
31066
|
} catch (error) {
|
|
30959
31067
|
if (allowMissingTail && error.code === "ENOENT") break;
|
|
30960
31068
|
if (allowMissingFinal && error.code === "ENOENT" && i === parts.length - 1) break;
|
|
@@ -31009,8 +31117,8 @@ async function readRepoFile(root, rel) {
|
|
|
31009
31117
|
const entry = await resolveRepoEntry(root, rel);
|
|
31010
31118
|
const handle = await open2(entry.path, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0)).catch(() => denied("final component tidak aman"));
|
|
31011
31119
|
try {
|
|
31012
|
-
const
|
|
31013
|
-
if (!
|
|
31120
|
+
const stat7 = await handle.stat();
|
|
31121
|
+
if (!stat7.isFile()) denied("bukan file regular");
|
|
31014
31122
|
return await handle.readFile();
|
|
31015
31123
|
} finally {
|
|
31016
31124
|
await handle.close();
|
|
@@ -31338,7 +31446,7 @@ async function projects_default(app3) {
|
|
|
31338
31446
|
const needle = (q ?? "").trim().toLowerCase();
|
|
31339
31447
|
const filtered = needle ? views.filter((v) => `${v.name} ${v.desc} ${v.stack}`.toLowerCase().includes(needle)) : views;
|
|
31340
31448
|
const device = (handledBy ?? "").trim();
|
|
31341
|
-
const scoped = device ? filtered.filter((v) => v.handledBy.some((
|
|
31449
|
+
const scoped = device ? filtered.filter((v) => v.handledBy.some((h2) => h2.deviceId === device)) : filtered;
|
|
31342
31450
|
return paginate(scoped, page, limit);
|
|
31343
31451
|
});
|
|
31344
31452
|
app3.get("/projects/:id", async (req, reply) => {
|
|
@@ -31916,7 +32024,7 @@ async function deleteDoc(projectId, path) {
|
|
|
31916
32024
|
init_src2();
|
|
31917
32025
|
init_db();
|
|
31918
32026
|
import { execFile as execFile6 } from "node:child_process";
|
|
31919
|
-
import { readdir as
|
|
32027
|
+
import { readdir as readdir3, rm as rm3 } from "node:fs/promises";
|
|
31920
32028
|
import { promisify as promisify5 } from "node:util";
|
|
31921
32029
|
import { join as join17, resolve as resolve15 } from "node:path";
|
|
31922
32030
|
init_notifications2();
|
|
@@ -31974,7 +32082,7 @@ async function sweepRepo(repoDir, projectId, deps = prodReaperDeps) {
|
|
|
31974
32082
|
const dir2 = trashDirOf(repoDir);
|
|
31975
32083
|
let entries3;
|
|
31976
32084
|
try {
|
|
31977
|
-
entries3 = await
|
|
32085
|
+
entries3 = await readdir3(dir2);
|
|
31978
32086
|
} catch {
|
|
31979
32087
|
return 0;
|
|
31980
32088
|
}
|
|
@@ -32033,7 +32141,7 @@ function ownsWorktree(repoDir, cwd) {
|
|
|
32033
32141
|
import { execFile as execFile7 } from "node:child_process";
|
|
32034
32142
|
import { promisify as promisify6 } from "node:util";
|
|
32035
32143
|
import { existsSync as existsSync8 } from "node:fs";
|
|
32036
|
-
import { readdir as
|
|
32144
|
+
import { readdir as readdir4 } from "node:fs/promises";
|
|
32037
32145
|
import { resolve as resolve17 } from "node:path";
|
|
32038
32146
|
var exec6 = promisify6(execFile7);
|
|
32039
32147
|
var GIT4 = { maxBuffer: 1 << 24 };
|
|
@@ -32079,7 +32187,7 @@ async function listDirEntries(repoDir, rel) {
|
|
|
32079
32187
|
const abs = repoAbsPath(repoDir, rel);
|
|
32080
32188
|
let raw;
|
|
32081
32189
|
try {
|
|
32082
|
-
raw = await
|
|
32190
|
+
raw = await readdir4(abs, { withFileTypes: true });
|
|
32083
32191
|
} catch {
|
|
32084
32192
|
return empty;
|
|
32085
32193
|
}
|
|
@@ -32245,12 +32353,12 @@ async function searchCommits(repoDir, q, by = "all") {
|
|
|
32245
32353
|
};
|
|
32246
32354
|
const byMessage = () => run4(["-i", "-F", `--grep=${q}`]);
|
|
32247
32355
|
const byAuthor = () => run4(["-i", `--author=${q}`]);
|
|
32248
|
-
const byHash = async () => (await run4([])).filter((
|
|
32356
|
+
const byHash = async () => (await run4([])).filter((h3) => h3.toLowerCase().startsWith(q.toLowerCase()));
|
|
32249
32357
|
if (by === "message") return byMessage();
|
|
32250
32358
|
if (by === "author") return byAuthor();
|
|
32251
32359
|
if (by === "hash") return byHash();
|
|
32252
|
-
const [m, a,
|
|
32253
|
-
return [.../* @__PURE__ */ new Set([...m, ...a, ...
|
|
32360
|
+
const [m, a, h2] = await Promise.all([byMessage(), byAuthor(), byHash()]);
|
|
32361
|
+
return [.../* @__PURE__ */ new Set([...m, ...a, ...h2])];
|
|
32254
32362
|
}
|
|
32255
32363
|
async function compareFile(repoDir, from, to, rel) {
|
|
32256
32364
|
if (!repoDir) return null;
|
|
@@ -32271,9 +32379,9 @@ async function commitDetail(repoDir, sha) {
|
|
|
32271
32379
|
try {
|
|
32272
32380
|
const fmt = ["%H", "%P", "%an", "%aI", "%s", "%G?", "%cn", "%cI", "%ae", "%b"].join(US);
|
|
32273
32381
|
const parts = (await exec6("git", ["show", "-s", `--pretty=format:${fmt}`, sha], { cwd: repoDir, ...GIT4 })).stdout.split(US);
|
|
32274
|
-
const [
|
|
32382
|
+
const [h2, parents, author, at, subject, gsig, committer, committedAt, authorEmail] = parts;
|
|
32275
32383
|
return {
|
|
32276
|
-
sha:
|
|
32384
|
+
sha: h2,
|
|
32277
32385
|
parents: parents ? parents.split(" ") : [],
|
|
32278
32386
|
author: author ?? "",
|
|
32279
32387
|
at: at ?? "",
|
|
@@ -33328,8 +33436,8 @@ var FileTokenizer = class _FileTokenizer extends AbstractTokenizer {
|
|
|
33328
33436
|
*/
|
|
33329
33437
|
static async fromFile(sourceFilePath) {
|
|
33330
33438
|
const fileHandle = await fsOpen(sourceFilePath, "r");
|
|
33331
|
-
const
|
|
33332
|
-
return new _FileTokenizer(fileHandle, { fileInfo: { path: sourceFilePath, size:
|
|
33439
|
+
const stat7 = await fileHandle.stat();
|
|
33440
|
+
return new _FileTokenizer(fileHandle, { fileInfo: { path: sourceFilePath, size: stat7.size } });
|
|
33333
33441
|
}
|
|
33334
33442
|
constructor(fileHandle, options2) {
|
|
33335
33443
|
super(options2);
|
|
@@ -33979,7 +34087,7 @@ function indexOf(buffer, portion) {
|
|
|
33979
34087
|
return -1;
|
|
33980
34088
|
}
|
|
33981
34089
|
function mergeArrays(chunks) {
|
|
33982
|
-
const totalLength = chunks.reduce((
|
|
34090
|
+
const totalLength = chunks.reduce((acc2, curr) => acc2 + curr.length, 0);
|
|
33983
34091
|
const mergedArray = new Uint8Array(totalLength);
|
|
33984
34092
|
let offset = 0;
|
|
33985
34093
|
for (const chunk of chunks) {
|
|
@@ -36939,7 +37047,7 @@ async function dropSpecAttachments(specId) {
|
|
|
36939
37047
|
|
|
36940
37048
|
// src/services/spec-attachment-dir.ts
|
|
36941
37049
|
init_db();
|
|
36942
|
-
import { mkdir as mkdir5, readdir as
|
|
37050
|
+
import { mkdir as mkdir5, readdir as readdir5, readFile as readFile5, rm as rm4, writeFile as writeFile4 } from "node:fs/promises";
|
|
36943
37051
|
import { join as join20 } from "node:path";
|
|
36944
37052
|
init_session_id();
|
|
36945
37053
|
init_uploads();
|
|
@@ -36993,7 +37101,7 @@ async function syncSpecAttachmentsDir(specId, projectId) {
|
|
|
36993
37101
|
for (const a of rows) {
|
|
36994
37102
|
let bytes;
|
|
36995
37103
|
try {
|
|
36996
|
-
bytes = await
|
|
37104
|
+
bytes = await readFile5(join20(uploadDir(), a.storageKey));
|
|
36997
37105
|
} catch {
|
|
36998
37106
|
continue;
|
|
36999
37107
|
}
|
|
@@ -37004,7 +37112,7 @@ async function syncSpecAttachmentsDir(specId, projectId) {
|
|
|
37004
37112
|
}
|
|
37005
37113
|
await writeFile4(join20(dir2, INDEX), renderIndex(specId, items), { mode: 384 });
|
|
37006
37114
|
const keep = /* @__PURE__ */ new Set([INDEX, ...items.map((a) => a.filename)]);
|
|
37007
|
-
for (const name2 of await
|
|
37115
|
+
for (const name2 of await readdir5(dir2)) {
|
|
37008
37116
|
if (!keep.has(name2)) await rm4(join20(dir2, name2), { recursive: true, force: true }).catch(() => {
|
|
37009
37117
|
});
|
|
37010
37118
|
}
|
|
@@ -39439,12 +39547,12 @@ function drawCode(doc, text) {
|
|
|
39439
39547
|
used += hl;
|
|
39440
39548
|
}
|
|
39441
39549
|
const body = seg.join("\n");
|
|
39442
|
-
const
|
|
39550
|
+
const h2 = doc.heightOfString(body, opts) + 12;
|
|
39443
39551
|
const top = doc.y;
|
|
39444
|
-
doc.save().rect(left, top, right - left,
|
|
39552
|
+
doc.save().rect(left, top, right - left, h2).fill(PANEL).restore();
|
|
39445
39553
|
doc.fillColor(INK).text(body, left + 8, top + 6, opts);
|
|
39446
39554
|
doc.x = left;
|
|
39447
|
-
doc.y = top +
|
|
39555
|
+
doc.y = top + h2;
|
|
39448
39556
|
if (i < lines2.length) doc.addPage();
|
|
39449
39557
|
}
|
|
39450
39558
|
doc.moveDown(0.5);
|
|
@@ -39478,12 +39586,12 @@ function drawTable(doc, t) {
|
|
|
39478
39586
|
const row = (cells, bold) => {
|
|
39479
39587
|
const texts = cells.map((c) => toWinAnsi(flatten(c.tokens).map((r) => r.text).join("") || c.text || ""));
|
|
39480
39588
|
doc.font(bold ? "Helvetica-Bold" : "Helvetica").fontSize(9.2).fillColor(bold ? STRONG : INK);
|
|
39481
|
-
const
|
|
39482
|
-
if (doc.y +
|
|
39589
|
+
const h2 = Math.max(...texts.map((s2) => doc.heightOfString(s2, { width: w - 10 })), 0) + 7;
|
|
39590
|
+
if (doc.y + h2 > doc.page.height - doc.page.margins.bottom) doc.addPage();
|
|
39483
39591
|
const y0 = doc.y;
|
|
39484
39592
|
texts.forEach((s2, i) => doc.text(s2, left + i * w + 5, y0 + 3, { width: w - 10 }));
|
|
39485
39593
|
doc.x = left;
|
|
39486
|
-
doc.y = y0 +
|
|
39594
|
+
doc.y = y0 + h2;
|
|
39487
39595
|
rule(doc, HAIR, 2);
|
|
39488
39596
|
};
|
|
39489
39597
|
rule(doc, HAIR, 2);
|
|
@@ -39632,12 +39740,14 @@ function inDayRange(at, from, to) {
|
|
|
39632
39740
|
}
|
|
39633
39741
|
|
|
39634
39742
|
// src/services/live-specs.ts
|
|
39743
|
+
init_src();
|
|
39635
39744
|
init_db();
|
|
39636
|
-
|
|
39745
|
+
init_live_phases();
|
|
39637
39746
|
init_session_phases();
|
|
39638
39747
|
init_stage_machine();
|
|
39639
39748
|
init_notifications2();
|
|
39640
39749
|
init_sync_notify();
|
|
39750
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
39641
39751
|
|
|
39642
39752
|
// src/services/spec-head.ts
|
|
39643
39753
|
init_db();
|
|
@@ -39659,34 +39769,102 @@ async function recordHeadSha(specId, worktree, read2 = readHead) {
|
|
|
39659
39769
|
|
|
39660
39770
|
// src/services/live-specs.ts
|
|
39661
39771
|
var specNum = (id2) => Number.parseInt(id2.match(/\d+/)?.[0] ?? "0", 10);
|
|
39662
|
-
|
|
39663
|
-
|
|
39664
|
-
|
|
39665
|
-
|
|
39666
|
-
|
|
39667
|
-
|
|
39668
|
-
|
|
39772
|
+
var selectOf = (shape) => Object.fromEntries(
|
|
39773
|
+
Object.keys(shape).filter((k) => k !== "blockedBy").map((k) => [k, true])
|
|
39774
|
+
);
|
|
39775
|
+
var SLIM_SELECT = selectOf(zSpecSlim.shape);
|
|
39776
|
+
var LIST_SELECT = selectOf(zSpecListItem.shape);
|
|
39777
|
+
var phases = () => sessionPhasesBySpecAsync().catch(() => /* @__PURE__ */ new Map());
|
|
39778
|
+
var sortByNumber = (rows) => {
|
|
39779
|
+
const n2 = new Map(rows.map((r) => [r.id, specNum(r.id)]));
|
|
39780
|
+
return rows.sort((a, b) => n2.get(b.id) - n2.get(a.id));
|
|
39781
|
+
};
|
|
39782
|
+
async function applyOverlay(specs, live) {
|
|
39783
|
+
if (live.size === 0) return { out: specs, advanced: [], doneNow: [] };
|
|
39669
39784
|
const advanced = [];
|
|
39670
39785
|
const doneNow = [];
|
|
39671
|
-
const out4 = specs.map((s2) => {
|
|
39786
|
+
const out4 = await Promise.all(specs.map(async (s2) => {
|
|
39672
39787
|
const entry = live.get(s2.id);
|
|
39673
39788
|
if (!entry) return s2;
|
|
39674
|
-
const next =
|
|
39789
|
+
const next = await stageForRunAsync(entry.phases, entry.cwd, s2.id);
|
|
39675
39790
|
if (!next || STAGES.indexOf(next) <= STAGES.indexOf(s2.stage)) return s2;
|
|
39676
39791
|
advanced.push({ id: s2.id, from: s2.stage, stage: next, cwd: entry.cwd });
|
|
39677
39792
|
if (next === "done") doneNow.push({ specId: s2.id, title: s2.title, projectId: s2.projectId });
|
|
39678
39793
|
return { ...s2, stage: next };
|
|
39679
|
-
});
|
|
39680
|
-
|
|
39681
|
-
|
|
39682
|
-
|
|
39683
|
-
|
|
39684
|
-
|
|
39685
|
-
|
|
39686
|
-
}
|
|
39794
|
+
}));
|
|
39795
|
+
return { out: out4, advanced, doneNow };
|
|
39796
|
+
}
|
|
39797
|
+
async function liveOverlayTick() {
|
|
39798
|
+
const live = await phases();
|
|
39799
|
+
if (live.size === 0) return;
|
|
39800
|
+
const specs = await prisma.spec.findMany({
|
|
39801
|
+
where: { id: { in: [...live.keys()] } },
|
|
39802
|
+
select: { id: true, stage: true, title: true, projectId: true }
|
|
39803
|
+
});
|
|
39804
|
+
const { advanced, doneNow } = await applyOverlay(specs, live);
|
|
39805
|
+
await Promise.all(advanced.map(async (a) => {
|
|
39806
|
+
const res = await prisma.spec.updateMany({ where: { id: a.id, stage: a.from }, data: { stage: a.stage } }).catch(() => ({ count: 0 }));
|
|
39807
|
+
if (res.count === 0) return;
|
|
39808
|
+
if (a.stage === "done") await recordHeadSha(a.id, a.cwd);
|
|
39809
|
+
await notifySynced("spec", a.id);
|
|
39810
|
+
}));
|
|
39687
39811
|
await Promise.all(doneNow.map((d) => recordCompletion(d.specId, d.title, d.projectId)));
|
|
39812
|
+
}
|
|
39813
|
+
async function listSpecsLive(filter = {}) {
|
|
39814
|
+
const specs = sortByNumber(await prisma.spec.findMany({
|
|
39815
|
+
where: { projectId: filter.project, source: filter.source },
|
|
39816
|
+
orderBy: { id: "desc" }
|
|
39817
|
+
}));
|
|
39818
|
+
return decorateBlocked((await applyOverlay(specs, await phases())).out);
|
|
39819
|
+
}
|
|
39820
|
+
async function listSelected(select, where = {}) {
|
|
39821
|
+
const rows = sortByNumber(await prisma.spec.findMany({ where, select, orderBy: { id: "desc" } }));
|
|
39822
|
+
const { out: out4 } = await applyOverlay(rows, await phases());
|
|
39688
39823
|
return decorateBlocked(out4);
|
|
39689
39824
|
}
|
|
39825
|
+
var slimCache = null;
|
|
39826
|
+
async function slimRows() {
|
|
39827
|
+
const count = await prisma.spec.count();
|
|
39828
|
+
if (slimCache) {
|
|
39829
|
+
const changed = await prisma.spec.findMany({
|
|
39830
|
+
where: { updatedAt: { gte: slimCache.max } },
|
|
39831
|
+
select: SLIM_SELECT
|
|
39832
|
+
});
|
|
39833
|
+
for (const r of changed) {
|
|
39834
|
+
slimCache.rows.set(r.id, r);
|
|
39835
|
+
if (r.updatedAt > slimCache.max) slimCache.max = r.updatedAt;
|
|
39836
|
+
}
|
|
39837
|
+
if (slimCache.rows.size === count) return [...slimCache.rows.values()];
|
|
39838
|
+
}
|
|
39839
|
+
const rows = await prisma.spec.findMany({ select: SLIM_SELECT });
|
|
39840
|
+
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)) };
|
|
39841
|
+
return rows;
|
|
39842
|
+
}
|
|
39843
|
+
async function listSpecsSlim() {
|
|
39844
|
+
const { out: out4 } = await applyOverlay(sortByNumber(await slimRows()), await phases());
|
|
39845
|
+
return await decorateBlocked(out4);
|
|
39846
|
+
}
|
|
39847
|
+
var listSpecsItems = (filter = {}) => listSelected(LIST_SELECT, { projectId: filter.project, source: filter.source });
|
|
39848
|
+
async function overlayOne(spec) {
|
|
39849
|
+
const live = await phases();
|
|
39850
|
+
return (await applyOverlay([spec], live)).out[0];
|
|
39851
|
+
}
|
|
39852
|
+
async function liveSignature() {
|
|
39853
|
+
const live = await phases();
|
|
39854
|
+
const parts = await Promise.all([...live.entries()].sort(([a], [b]) => a.localeCompare(b)).map(
|
|
39855
|
+
async ([id2, e]) => `${id2}:${e.phases.map((p3) => p3.state[0]).join("")}${phasesComplete(e.phases) ? `:${await planCompleteAsync(e.cwd, id2)}` : ""}`
|
|
39856
|
+
));
|
|
39857
|
+
return parts.join("|");
|
|
39858
|
+
}
|
|
39859
|
+
async function specsDigest() {
|
|
39860
|
+
const agg = await prisma.spec.aggregate({ _count: { _all: true }, _max: { updatedAt: true }, _sum: { version: true } });
|
|
39861
|
+
const db = `${agg._count._all}:${agg._max.updatedAt?.getTime() ?? 0}:${agg._sum.version ?? 0}`;
|
|
39862
|
+
return createHash4("sha1").update(`${db}|${await liveSignature()}`).digest("hex");
|
|
39863
|
+
}
|
|
39864
|
+
async function liveSpecs(filter = {}) {
|
|
39865
|
+
await liveOverlayTick();
|
|
39866
|
+
return listSpecsLive(filter);
|
|
39867
|
+
}
|
|
39690
39868
|
|
|
39691
39869
|
// src/services/launch-authority.ts
|
|
39692
39870
|
init_src();
|
|
@@ -39721,8 +39899,15 @@ function filterSpecs(specs, f) {
|
|
|
39721
39899
|
async function specs_default(app3) {
|
|
39722
39900
|
app3.get("/specs", async (req) => {
|
|
39723
39901
|
const { project, source, q, stage, priority, startable, dateField, from, to, page, limit } = req.query;
|
|
39724
|
-
|
|
39725
|
-
|
|
39902
|
+
await liveOverlayTick();
|
|
39903
|
+
const items = await listSpecsItems({ project, source });
|
|
39904
|
+
return paginate(filterSpecs(items, { q, stage, priority, startable, dateField, from, to }), page, limit);
|
|
39905
|
+
});
|
|
39906
|
+
app3.get("/specs/:id", async (req, reply) => {
|
|
39907
|
+
const { id: id2 } = req.params;
|
|
39908
|
+
const row = await prisma.spec.findUnique({ where: { id: id2 } });
|
|
39909
|
+
if (!row) return reply.code(404).send({ error: "spec tak ditemukan" });
|
|
39910
|
+
return (await decorateBlocked([await overlayOne(row)]))[0];
|
|
39726
39911
|
});
|
|
39727
39912
|
app3.post("/specs", async (req, reply) => {
|
|
39728
39913
|
const parsed = zCreateSpec.safeParse(req.body);
|
|
@@ -40583,7 +40768,7 @@ async function deleteBranches(repoDir, names, opts) {
|
|
|
40583
40768
|
// src/services/worktree-list.ts
|
|
40584
40769
|
import { execFile as execFile11 } from "node:child_process";
|
|
40585
40770
|
import { realpathSync as realpathSync6 } from "node:fs";
|
|
40586
|
-
import { stat as
|
|
40771
|
+
import { stat as stat4 } from "node:fs/promises";
|
|
40587
40772
|
import { basename as basename6, dirname as dirname11, join as join21, resolve as resolve19, sep as sep6 } from "node:path";
|
|
40588
40773
|
import { promisify as promisify10 } from "node:util";
|
|
40589
40774
|
var exec10 = promisify10(execFile11);
|
|
@@ -40638,7 +40823,7 @@ function hasWorktreeSession(path, sessionId2, sessions) {
|
|
|
40638
40823
|
}
|
|
40639
40824
|
async function bornAt(path) {
|
|
40640
40825
|
try {
|
|
40641
|
-
const st = await
|
|
40826
|
+
const st = await stat4(join21(path, ".git"));
|
|
40642
40827
|
const ms = st.birthtimeMs > 0 ? st.birthtimeMs : st.mtimeMs;
|
|
40643
40828
|
return new Date(ms).toISOString();
|
|
40644
40829
|
} catch {
|
|
@@ -40653,10 +40838,10 @@ async function listWorktrees(repoDir, inputs) {
|
|
|
40653
40838
|
const trash = resolve19(baseReal, ".worktrees", ".trash");
|
|
40654
40839
|
const sessions = new Map(inputs.sessions.map((s2) => [real(s2.cwd), { id: s2.id, specId: s2.specId }]));
|
|
40655
40840
|
const history = /* @__PURE__ */ new Map();
|
|
40656
|
-
for (const
|
|
40657
|
-
const path = real(
|
|
40841
|
+
for (const h2 of inputs.history ?? []) {
|
|
40842
|
+
const path = real(h2.cwd);
|
|
40658
40843
|
const previous = history.get(path);
|
|
40659
|
-
if (!previous ||
|
|
40844
|
+
if (!previous || h2.startedAt > previous.startedAt) history.set(path, h2);
|
|
40660
40845
|
}
|
|
40661
40846
|
const raw = parseWorktreePorcelain(text ?? "");
|
|
40662
40847
|
const mainPath = raw[0] ? resolve19(raw[0].path) : null;
|
|
@@ -41054,10 +41239,14 @@ function installSessionHistory() {
|
|
|
41054
41239
|
|
|
41055
41240
|
// src/services/worktree-project.ts
|
|
41056
41241
|
init_session_id();
|
|
41242
|
+
var toWorktreeSession = (s2) => ({ cwd: s2.cwd, id: s2.id, specId: s2.specId ?? null });
|
|
41057
41243
|
function worktreeSessions() {
|
|
41058
|
-
return listSessions().map(
|
|
41244
|
+
return listSessions().map(toWorktreeSession);
|
|
41245
|
+
}
|
|
41246
|
+
async function worktreeSessionsAsync() {
|
|
41247
|
+
return (await listSessionsAsync()).map(toWorktreeSession);
|
|
41059
41248
|
}
|
|
41060
|
-
async function projectWorktreeInputs(projectId, sessions =
|
|
41249
|
+
async function projectWorktreeInputs(projectId, sessions = worktreeSessionsAsync) {
|
|
41061
41250
|
const [specs, history] = await Promise.all([
|
|
41062
41251
|
prisma.spec.findMany({ where: { projectId }, select: { id: true, stage: true } }),
|
|
41063
41252
|
worktreeHistory(projectId)
|
|
@@ -41065,7 +41254,7 @@ async function projectWorktreeInputs(projectId, sessions = worktreeSessions) {
|
|
|
41065
41254
|
return {
|
|
41066
41255
|
specs: new Map(specs.map((s2) => [sessionIdForSpec(s2.id), s2])),
|
|
41067
41256
|
history,
|
|
41068
|
-
sessions: sessions()
|
|
41257
|
+
sessions: await sessions()
|
|
41069
41258
|
};
|
|
41070
41259
|
}
|
|
41071
41260
|
async function detectOrphanWorktrees(deps = { repos: prodReaperDeps.repos, inputs: projectWorktreeInputs }) {
|
|
@@ -41665,7 +41854,7 @@ async function finishGraphOp(reply, id2, repoDir, r, verb) {
|
|
|
41665
41854
|
}
|
|
41666
41855
|
|
|
41667
41856
|
// src/routes/fs.ts
|
|
41668
|
-
import { readdir as
|
|
41857
|
+
import { readdir as readdir6 } from "node:fs/promises";
|
|
41669
41858
|
import { homedir as homedir4 } from "node:os";
|
|
41670
41859
|
import { resolve as resolve21, dirname as dirname12, join as join23 } from "node:path";
|
|
41671
41860
|
async function fs_default(app3) {
|
|
@@ -41673,7 +41862,7 @@ async function fs_default(app3) {
|
|
|
41673
41862
|
const q = req.query.path;
|
|
41674
41863
|
const dir2 = q && q.trim() ? resolve21(q.trim()) : homedir4();
|
|
41675
41864
|
try {
|
|
41676
|
-
const ents = await
|
|
41865
|
+
const ents = await readdir6(dir2, { withFileTypes: true });
|
|
41677
41866
|
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
41867
|
const parent = dirname12(dir2);
|
|
41679
41868
|
return { path: dir2, parent: parent === dir2 ? null : parent, entries: entries3 };
|
|
@@ -41735,10 +41924,10 @@ function resumeState(repoDir, worktree, branchTo, headSha) {
|
|
|
41735
41924
|
return base2 ? { worktreeKept: false, base: base2 } : null;
|
|
41736
41925
|
}
|
|
41737
41926
|
function buildResumeCtx(repoDir, id2, flow2, worktreeKept) {
|
|
41738
|
-
const
|
|
41927
|
+
const phases2 = readPhases(phaseFilePath(repoDir, id2), flow2);
|
|
41739
41928
|
return {
|
|
41740
|
-
recorded:
|
|
41741
|
-
next:
|
|
41929
|
+
recorded: phases2.filter((p3) => p3.state === "done" || p3.state === "skipped").map((p3) => `${p3.name} ${p3.state}`),
|
|
41930
|
+
next: phases2.find((p3) => p3.state === "active")?.name,
|
|
41742
41931
|
worktreeKept
|
|
41743
41932
|
};
|
|
41744
41933
|
}
|
|
@@ -41834,8 +42023,8 @@ async function startSpecSession(spec, opts) {
|
|
|
41834
42023
|
const fullPlan = sessionPhasePlan(setting, opts.flow, agent, { model, effort: effort2 }, opts.phaseOverrides);
|
|
41835
42024
|
let plan = fullPlan;
|
|
41836
42025
|
if (fullPlan && isContinue && !isGoalFlow) {
|
|
41837
|
-
const
|
|
41838
|
-
plan =
|
|
42026
|
+
const phases2 = fullPlan.phases.filter((p3) => p3.phase === "Execute");
|
|
42027
|
+
plan = phases2.length ? { ...fullPlan, phases: phases2 } : null;
|
|
41839
42028
|
}
|
|
41840
42029
|
const buildPrompt = (p3) => {
|
|
41841
42030
|
if (isGoalFlow) {
|
|
@@ -41936,7 +42125,7 @@ function setBounded(map, key, value, maxKeys) {
|
|
|
41936
42125
|
// src/services/auth.ts
|
|
41937
42126
|
init_src2();
|
|
41938
42127
|
init_db();
|
|
41939
|
-
import { randomBytes as randomBytes3, scrypt as scryptCb, timingSafeEqual as timingSafeEqual3, createHash as
|
|
42128
|
+
import { randomBytes as randomBytes3, scrypt as scryptCb, timingSafeEqual as timingSafeEqual3, createHash as createHash9 } from "node:crypto";
|
|
41940
42129
|
import { promisify as promisify12 } from "node:util";
|
|
41941
42130
|
var scrypt = promisify12(scryptCb);
|
|
41942
42131
|
var COOKIE_NAME = "hn_session";
|
|
@@ -41954,7 +42143,7 @@ async function verifyPassword(pw, stored) {
|
|
|
41954
42143
|
return key.length === want.length && timingSafeEqual3(key, want);
|
|
41955
42144
|
}
|
|
41956
42145
|
var newSessionToken = () => randomBytes3(32).toString("base64url");
|
|
41957
|
-
var sessionId = (token) =>
|
|
42146
|
+
var sessionId = (token) => createHash9("sha256").update(token).digest("hex");
|
|
41958
42147
|
async function createSession2(userId) {
|
|
41959
42148
|
const token = newSessionToken();
|
|
41960
42149
|
await prisma.session.create({
|
|
@@ -42015,9 +42204,9 @@ function cookieOpts(req) {
|
|
|
42015
42204
|
|
|
42016
42205
|
// src/services/device-token.ts
|
|
42017
42206
|
init_db();
|
|
42018
|
-
import { randomBytes as randomBytes4, createHash as
|
|
42207
|
+
import { randomBytes as randomBytes4, createHash as createHash10 } from "node:crypto";
|
|
42019
42208
|
var newDeviceToken = () => randomBytes4(32).toString("base64url");
|
|
42020
|
-
var tokenHash = (token) =>
|
|
42209
|
+
var tokenHash = (token) => createHash10("sha256").update(token).digest("hex");
|
|
42021
42210
|
async function issueDeviceToken(userId, name2) {
|
|
42022
42211
|
const token = newDeviceToken();
|
|
42023
42212
|
const row = await prisma.deviceToken.create({ data: { userId, name: name2, tokenHash: tokenHash(token) } });
|
|
@@ -42253,12 +42442,77 @@ function appendDiag(home3, sessionId2, events) {
|
|
|
42253
42442
|
init_notifications2();
|
|
42254
42443
|
init_stage_machine();
|
|
42255
42444
|
init_pty();
|
|
42445
|
+
|
|
42446
|
+
// src/services/bounded-sender.ts
|
|
42447
|
+
var HIGH = 1024 * 1024;
|
|
42448
|
+
var CAP = 256 * 1024;
|
|
42449
|
+
var DRAIN_MS = 20;
|
|
42450
|
+
var isData = (frame2) => frame2.startsWith('{"t":"data"');
|
|
42451
|
+
function createBoundedSender(ws2, o = {}) {
|
|
42452
|
+
const high = o.high ?? HIGH;
|
|
42453
|
+
const cap = o.cap ?? CAP;
|
|
42454
|
+
const drainMs = o.drainMs ?? DRAIN_MS;
|
|
42455
|
+
let pending2 = [];
|
|
42456
|
+
let pendingBytes = 0;
|
|
42457
|
+
let timer10;
|
|
42458
|
+
let resyncNeeded = false;
|
|
42459
|
+
const trim = () => {
|
|
42460
|
+
for (let i = 0; i < pending2.length && pendingBytes > cap; ) {
|
|
42461
|
+
if (!isData(pending2[i])) {
|
|
42462
|
+
i++;
|
|
42463
|
+
continue;
|
|
42464
|
+
}
|
|
42465
|
+
pendingBytes -= pending2[i].length;
|
|
42466
|
+
pending2.splice(i, 1);
|
|
42467
|
+
resyncNeeded = true;
|
|
42468
|
+
}
|
|
42469
|
+
};
|
|
42470
|
+
const drain2 = () => {
|
|
42471
|
+
timer10 = void 0;
|
|
42472
|
+
if (ws2.bufferedAmount > high) {
|
|
42473
|
+
timer10 = setTimeout(drain2, drainMs);
|
|
42474
|
+
timer10.unref?.();
|
|
42475
|
+
return;
|
|
42476
|
+
}
|
|
42477
|
+
const out4 = pending2;
|
|
42478
|
+
pending2 = [];
|
|
42479
|
+
pendingBytes = 0;
|
|
42480
|
+
for (const f of out4) ws2.send(f);
|
|
42481
|
+
if (resyncNeeded) {
|
|
42482
|
+
resyncNeeded = false;
|
|
42483
|
+
o.onResync?.();
|
|
42484
|
+
}
|
|
42485
|
+
};
|
|
42486
|
+
return {
|
|
42487
|
+
send(frame2) {
|
|
42488
|
+
if (pending2.length === 0 && ws2.bufferedAmount <= high) {
|
|
42489
|
+
ws2.send(frame2);
|
|
42490
|
+
return;
|
|
42491
|
+
}
|
|
42492
|
+
pending2.push(frame2);
|
|
42493
|
+
pendingBytes += frame2.length;
|
|
42494
|
+
trim();
|
|
42495
|
+
if (!timer10) {
|
|
42496
|
+
timer10 = setTimeout(drain2, drainMs);
|
|
42497
|
+
timer10.unref?.();
|
|
42498
|
+
}
|
|
42499
|
+
},
|
|
42500
|
+
dispose() {
|
|
42501
|
+
if (timer10) clearTimeout(timer10);
|
|
42502
|
+
timer10 = void 0;
|
|
42503
|
+
pending2 = [];
|
|
42504
|
+
pendingBytes = 0;
|
|
42505
|
+
}
|
|
42506
|
+
};
|
|
42507
|
+
}
|
|
42508
|
+
|
|
42509
|
+
// src/routes/terminal.ts
|
|
42256
42510
|
init_uploads();
|
|
42257
42511
|
|
|
42258
42512
|
// src/services/agent-invocations.ts
|
|
42259
42513
|
init_src();
|
|
42260
42514
|
init_db();
|
|
42261
|
-
import { createHash as
|
|
42515
|
+
import { createHash as createHash11, randomUUID as randomUUID9 } from "node:crypto";
|
|
42262
42516
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
42263
42517
|
import { homedir as homedir5 } from "node:os";
|
|
42264
42518
|
import { readFileSync as readFileSync12, realpathSync as realpathSync7, statSync as statSync4 } from "node:fs";
|
|
@@ -42268,7 +42522,7 @@ import { isAbsolute as isAbsolute8, relative as relative4, resolve as resolve22
|
|
|
42268
42522
|
init_session_event_token();
|
|
42269
42523
|
init_session_event_spool();
|
|
42270
42524
|
import { constants as constants3 } from "node:fs";
|
|
42271
|
-
import { mkdir as mkdir6, open as open3, readdir as
|
|
42525
|
+
import { mkdir as mkdir6, open as open3, readdir as readdir7, rm as rm6 } from "node:fs/promises";
|
|
42272
42526
|
import { join as join25 } from "node:path";
|
|
42273
42527
|
var MAX_EVENT_BYTES = 1e6;
|
|
42274
42528
|
var MAX_FILES_PER_DRAIN = 1e3;
|
|
@@ -42314,12 +42568,12 @@ async function drainSpool(app3, root, observation) {
|
|
|
42314
42568
|
let delivered = 0;
|
|
42315
42569
|
let examined = 0;
|
|
42316
42570
|
let readBuffer;
|
|
42317
|
-
for (const session of await
|
|
42571
|
+
for (const session of await readdir7(root, { withFileTypes: true })) {
|
|
42318
42572
|
if (!session.isDirectory() || !SESSION_ID_RE.test(session.name)) continue;
|
|
42319
42573
|
const dir2 = join25(root, session.name);
|
|
42320
42574
|
let entries3;
|
|
42321
42575
|
try {
|
|
42322
|
-
entries3 = await
|
|
42576
|
+
entries3 = await readdir7(dir2, { withFileTypes: true });
|
|
42323
42577
|
} catch {
|
|
42324
42578
|
continue;
|
|
42325
42579
|
}
|
|
@@ -42331,8 +42585,8 @@ async function drainSpool(app3, root, observation) {
|
|
|
42331
42585
|
let handle;
|
|
42332
42586
|
try {
|
|
42333
42587
|
handle = await open3(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
42334
|
-
const
|
|
42335
|
-
if (!
|
|
42588
|
+
const stat7 = await handle.stat();
|
|
42589
|
+
if (!stat7.isFile() || stat7.size > MAX_EVENT_BYTES) throw new Error("payload terlalu besar");
|
|
42336
42590
|
readBuffer ??= Buffer.allocUnsafe(MAX_EVENT_BYTES + 1);
|
|
42337
42591
|
const { bytesRead } = await handle.read(readBuffer, 0, readBuffer.length, 0);
|
|
42338
42592
|
if (bytesRead > MAX_EVENT_BYTES) throw new Error("payload terlalu besar");
|
|
@@ -42405,7 +42659,7 @@ var MAX_TRANSCRIPT_BYTES2 = 10 * 1024 * 1024;
|
|
|
42405
42659
|
var ANSI2 = /[\u001b\u009b](?:\][^\u0007]*(?:\u0007|\u001b\\)|\[[0-?]*[ -/]*[@-~]|[@-_])/g;
|
|
42406
42660
|
var snapshotHashes = /* @__PURE__ */ new Map();
|
|
42407
42661
|
var keyOf = (x) => `${x.sessionId}\0${x.runtimeInvocationId}`;
|
|
42408
|
-
var hash3 = (value) =>
|
|
42662
|
+
var hash3 = (value) => createHash11("sha256").update(value).digest("hex");
|
|
42409
42663
|
var defaultGitStatus = (cwd) => {
|
|
42410
42664
|
try {
|
|
42411
42665
|
return execFileSync3("git", ["-C", cwd, "status", "--porcelain=v1", "-z"], {
|
|
@@ -42751,9 +43005,9 @@ async function refreshPhaseInvocations(sessionId2) {
|
|
|
42751
43005
|
// src/services/session-dialog.ts
|
|
42752
43006
|
init_tui_dialog();
|
|
42753
43007
|
init_pty();
|
|
42754
|
-
import { createHash as
|
|
43008
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
42755
43009
|
var DIALOG_CHUNK_MS = 50;
|
|
42756
|
-
var screenHashOf = (paneText2) =>
|
|
43010
|
+
var screenHashOf = (paneText2) => createHash12("sha256").update(dialogKey(paneText2)).digest("hex").slice(0, 16);
|
|
42757
43011
|
var answerable = (paneText2) => {
|
|
42758
43012
|
const s2 = readDialogScreen(paneText2);
|
|
42759
43013
|
if (s2?.kind !== "question") return null;
|
|
@@ -44363,9 +44617,9 @@ async function terminal_default(app3, opts) {
|
|
|
44363
44617
|
app3.get("/terminal/sessions/:id/phases", async (req, reply) => {
|
|
44364
44618
|
const { id: id2 } = req.params;
|
|
44365
44619
|
const s2 = getSession(id2);
|
|
44366
|
-
const
|
|
44367
|
-
if (!s2?.flow || !
|
|
44368
|
-
return { flow: s2.flow, phases };
|
|
44620
|
+
const phases2 = sessionPhases(id2);
|
|
44621
|
+
if (!s2?.flow || !phases2) return reply.code(404).send({ error: "not found" });
|
|
44622
|
+
return { flow: s2.flow, phases: phases2 };
|
|
44369
44623
|
});
|
|
44370
44624
|
app3.post("/terminal/sessions/:id/steer", async (req, reply) => {
|
|
44371
44625
|
const parsed = zTerminalSteerInput.safeParse(req.body);
|
|
@@ -44503,7 +44757,6 @@ async function terminal_default(app3, opts) {
|
|
|
44503
44757
|
}
|
|
44504
44758
|
}, (socket2, req) => {
|
|
44505
44759
|
const { id: id2 } = req.params;
|
|
44506
|
-
if (!getSession(id2)) return socket2.close(4004, "not found");
|
|
44507
44760
|
const isClientRemote = req.remote !== void 0;
|
|
44508
44761
|
const principal = isClientRemote ? { kind: "remote", id: `client:${req.remote.actor.hubOrigin}:${req.remote.actor.userId}` } : req.wsPrincipal;
|
|
44509
44762
|
let release;
|
|
@@ -44514,14 +44767,38 @@ async function terminal_default(app3, opts) {
|
|
|
44514
44767
|
return;
|
|
44515
44768
|
}
|
|
44516
44769
|
const guard = new WsMessageGuard({ perWindow: TERMINAL_WS_MESSAGES_PER_MINUTE });
|
|
44517
|
-
const
|
|
44518
|
-
|
|
44519
|
-
|
|
44770
|
+
const bounded = createBoundedSender(socket2, {
|
|
44771
|
+
onResync: () => {
|
|
44772
|
+
void screenResyncFrame(id2).then((f) => {
|
|
44773
|
+
if (f) bounded.send(f);
|
|
44774
|
+
});
|
|
44775
|
+
}
|
|
44776
|
+
});
|
|
44777
|
+
const client = { send: (m) => bounded.send(m), close: () => socket2.close() };
|
|
44778
|
+
let ready = false;
|
|
44779
|
+
let closed = false;
|
|
44780
|
+
const early = [];
|
|
44781
|
+
const attachReady = (async () => {
|
|
44782
|
+
const found = await getSessionAsync(id2);
|
|
44783
|
+
if (!found) {
|
|
44784
|
+
socket2.close(4004, "not found");
|
|
44785
|
+
return;
|
|
44786
|
+
}
|
|
44787
|
+
if (closed) return;
|
|
44788
|
+
await attachAsync(id2, client);
|
|
44789
|
+
if (closed) {
|
|
44790
|
+
detach(id2, client);
|
|
44791
|
+
return;
|
|
44792
|
+
}
|
|
44793
|
+
void refreshPhaseInvocations(id2);
|
|
44794
|
+
})().catch(() => {
|
|
44795
|
+
socket2.close(1011, "attach failed");
|
|
44796
|
+
});
|
|
44520
44797
|
const watch = isClientRemote ? null : createPrincipalWatch({
|
|
44521
44798
|
check: () => revalidateWsPrincipal(req, principal),
|
|
44522
44799
|
onRevoked: () => socket2.close(1008, "session revoked")
|
|
44523
44800
|
});
|
|
44524
|
-
|
|
44801
|
+
const onMessage = (raw) => {
|
|
44525
44802
|
const verdict = guard.accept(raw);
|
|
44526
44803
|
if (!verdict.ok) {
|
|
44527
44804
|
socket2.close(verdict.code, verdict.reason);
|
|
@@ -44544,10 +44821,20 @@ async function terminal_default(app3, opts) {
|
|
|
44544
44821
|
} catch {
|
|
44545
44822
|
}
|
|
44546
44823
|
}
|
|
44824
|
+
};
|
|
44825
|
+
socket2.on("message", (raw) => {
|
|
44826
|
+
if (ready) onMessage(raw);
|
|
44827
|
+
else early.push(raw);
|
|
44828
|
+
});
|
|
44829
|
+
void attachReady.then(() => {
|
|
44830
|
+
ready = true;
|
|
44831
|
+
for (const raw of early.splice(0)) onMessage(raw);
|
|
44547
44832
|
});
|
|
44548
44833
|
const revalidate = watch ? setInterval(() => watch.refresh(), 6e4) : void 0;
|
|
44549
44834
|
revalidate?.unref?.();
|
|
44550
44835
|
socket2.on("close", () => {
|
|
44836
|
+
closed = true;
|
|
44837
|
+
bounded.dispose();
|
|
44551
44838
|
if (revalidate) clearInterval(revalidate);
|
|
44552
44839
|
watch?.dispose();
|
|
44553
44840
|
release();
|
|
@@ -45037,13 +45324,13 @@ function mapToCatalog(checks) {
|
|
|
45037
45324
|
var isHardened = (checks) => CRITICAL.every((c) => checks.find((x) => x.check === c)?.status === "pass");
|
|
45038
45325
|
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
45326
|
function parseHealth(out4) {
|
|
45040
|
-
const
|
|
45327
|
+
const h2 = {};
|
|
45041
45328
|
for (const line of out4.split("\n")) {
|
|
45042
45329
|
const m = line.match(/^HEALTH (\S+) ?(.*)$/);
|
|
45043
|
-
if (m)
|
|
45330
|
+
if (m) h2[m[1]] = m[2].trim();
|
|
45044
45331
|
}
|
|
45045
|
-
if (!
|
|
45046
|
-
return { uptime:
|
|
45332
|
+
if (!h2.uptime && !h2.disk) return null;
|
|
45333
|
+
return { uptime: h2.uptime ?? "", disk: h2.disk ?? "", mem: h2.mem ?? "", load: h2.load ?? "" };
|
|
45047
45334
|
}
|
|
45048
45335
|
var target = (v) => ({ host: v.host, port: v.port, user: v.user, keyPath: v.keyPath });
|
|
45049
45336
|
var packagedScript = (f) => fileURLToPath4(new URL(`../scripts/vps/${f}`, import.meta.url));
|
|
@@ -45625,9 +45912,9 @@ function readAccessToken() {
|
|
|
45625
45912
|
return null;
|
|
45626
45913
|
}
|
|
45627
45914
|
}
|
|
45628
|
-
function normalizeSeverity(s2,
|
|
45915
|
+
function normalizeSeverity(s2, pct3) {
|
|
45629
45916
|
if (s2 === "normal" || s2 === "warning" || s2 === "critical") return s2;
|
|
45630
|
-
return
|
|
45917
|
+
return pct3 >= 90 ? "critical" : pct3 >= 70 ? "warning" : "normal";
|
|
45631
45918
|
}
|
|
45632
45919
|
function mapWindows(json) {
|
|
45633
45920
|
const arr = json?.limits;
|
|
@@ -45676,7 +45963,7 @@ async function getLimits() {
|
|
|
45676
45963
|
}
|
|
45677
45964
|
|
|
45678
45965
|
// src/services/codex-limits.ts
|
|
45679
|
-
import { open as open4, readdir as
|
|
45966
|
+
import { open as open4, readdir as readdir8, stat as stat5 } from "node:fs/promises";
|
|
45680
45967
|
import { homedir as homedir9 } from "node:os";
|
|
45681
45968
|
import { join as join32 } from "node:path";
|
|
45682
45969
|
var TTL_MS3 = 3e4;
|
|
@@ -45694,7 +45981,7 @@ function windowLabel(minutes) {
|
|
|
45694
45981
|
if (minutes % 60 === 0) return `${minutes / 60} jam`;
|
|
45695
45982
|
return `${minutes} menit`;
|
|
45696
45983
|
}
|
|
45697
|
-
var severityOf = (
|
|
45984
|
+
var severityOf = (pct3) => pct3 >= 90 ? "critical" : pct3 >= 70 ? "warning" : "normal";
|
|
45698
45985
|
function toWindow(slot, raw, reached) {
|
|
45699
45986
|
const minutes = raw.window_minutes ?? 0;
|
|
45700
45987
|
const usedPct = Math.round(raw.used_percent ?? 0);
|
|
@@ -45713,14 +46000,14 @@ function toWindow(slot, raw, reached) {
|
|
|
45713
46000
|
async function recentRollouts(dir2) {
|
|
45714
46001
|
let entries3;
|
|
45715
46002
|
try {
|
|
45716
|
-
entries3 = (await
|
|
46003
|
+
entries3 = (await readdir8(dir2, { recursive: true })).filter((f) => f.endsWith(".jsonl"));
|
|
45717
46004
|
} catch {
|
|
45718
46005
|
return [];
|
|
45719
46006
|
}
|
|
45720
46007
|
const stamped = await Promise.all(entries3.map(async (rel) => {
|
|
45721
46008
|
const full = join32(dir2, rel);
|
|
45722
46009
|
try {
|
|
45723
|
-
return { full, mtime: (await
|
|
46010
|
+
return { full, mtime: (await stat5(full)).mtimeMs };
|
|
45724
46011
|
} catch {
|
|
45725
46012
|
return null;
|
|
45726
46013
|
}
|
|
@@ -45804,7 +46091,7 @@ async function codex(app3) {
|
|
|
45804
46091
|
// src/services/model-catalog.ts
|
|
45805
46092
|
init_src2();
|
|
45806
46093
|
init_src();
|
|
45807
|
-
import { readFile as
|
|
46094
|
+
import { readFile as readFile6, writeFile as writeFile5, rename as rename4, mkdir as mkdir7 } from "node:fs/promises";
|
|
45808
46095
|
import { dirname as dirname15, join as join34 } from "node:path";
|
|
45809
46096
|
|
|
45810
46097
|
// src/services/model-catalog-parser.ts
|
|
@@ -46059,7 +46346,7 @@ var cacheFile = () => join34(resolveHome(), "model-catalog.json");
|
|
|
46059
46346
|
var modelCatalogService = createModelCatalogService({
|
|
46060
46347
|
probe: probeModelCatalog,
|
|
46061
46348
|
read: async () => {
|
|
46062
|
-
const content = await
|
|
46349
|
+
const content = await readFile6(cacheFile(), "utf8");
|
|
46063
46350
|
if (Buffer.byteLength(content) > 4 * 1024 * 1024) throw new Error("oversized cache");
|
|
46064
46351
|
return JSON.parse(content);
|
|
46065
46352
|
},
|
|
@@ -46096,6 +46383,17 @@ async function methods(app3) {
|
|
|
46096
46383
|
init_src();
|
|
46097
46384
|
init_update();
|
|
46098
46385
|
init_pty();
|
|
46386
|
+
|
|
46387
|
+
// src/services/restart.ts
|
|
46388
|
+
init_src();
|
|
46389
|
+
function requestConfigRestart(delayMs = 50) {
|
|
46390
|
+
setTimeout(() => process.exit(CONFIG_RESTART_EXIT), delayMs);
|
|
46391
|
+
}
|
|
46392
|
+
function requestManualRestart(delayMs = 250) {
|
|
46393
|
+
setTimeout(() => process.exit(MANUAL_RESTART_EXIT), delayMs);
|
|
46394
|
+
}
|
|
46395
|
+
|
|
46396
|
+
// src/routes/update.ts
|
|
46099
46397
|
async function update(app3) {
|
|
46100
46398
|
app3.get("/update", async () => getUpdateStatus());
|
|
46101
46399
|
app3.post("/update/apply", async (req, reply) => {
|
|
@@ -46111,6 +46409,15 @@ async function update(app3) {
|
|
|
46111
46409
|
requestRestartForUpdate();
|
|
46112
46410
|
return reply.code(202).send({ accepted: true, from, to, liveSessions });
|
|
46113
46411
|
});
|
|
46412
|
+
app3.post("/restart", async (req, reply) => {
|
|
46413
|
+
const parsed = zUpdateApplyBody.safeParse(req.body ?? {});
|
|
46414
|
+
if (!parsed.success) return reply.code(400).send({ error: "bad-body" });
|
|
46415
|
+
if (!supervised()) return reply.code(409).send({ error: "unsupervised" });
|
|
46416
|
+
const liveSessions = listSessions().filter((s2) => !s2.exited).length;
|
|
46417
|
+
if (!parsed.data.confirm) return reply.code(409).send({ error: "confirm-required", liveSessions });
|
|
46418
|
+
requestManualRestart();
|
|
46419
|
+
return reply.code(202).send({ accepted: true, liveSessions });
|
|
46420
|
+
});
|
|
46114
46421
|
}
|
|
46115
46422
|
|
|
46116
46423
|
// src/services/events.ts
|
|
@@ -46396,6 +46703,7 @@ async function buildTasksPage(f) {
|
|
|
46396
46703
|
// src/services/lead/views.ts
|
|
46397
46704
|
init_db();
|
|
46398
46705
|
init_pty();
|
|
46706
|
+
init_live_phases();
|
|
46399
46707
|
|
|
46400
46708
|
// src/services/lead/pulse.ts
|
|
46401
46709
|
init_db();
|
|
@@ -46576,13 +46884,7 @@ function findCollisions(areas) {
|
|
|
46576
46884
|
return out4;
|
|
46577
46885
|
}
|
|
46578
46886
|
var prodPulseDeps = {
|
|
46579
|
-
sessions: () =>
|
|
46580
|
-
try {
|
|
46581
|
-
return listSessions();
|
|
46582
|
-
} catch {
|
|
46583
|
-
return [];
|
|
46584
|
-
}
|
|
46585
|
-
},
|
|
46887
|
+
sessions: () => listSessionsAsync().catch(() => []),
|
|
46586
46888
|
areas: async (s2) => {
|
|
46587
46889
|
const repoDir = await resolveRepoDir(s2.projectId);
|
|
46588
46890
|
if (!repoDir) return [];
|
|
@@ -46595,14 +46897,8 @@ var prodPulseDeps = {
|
|
|
46595
46897
|
return [];
|
|
46596
46898
|
}
|
|
46597
46899
|
},
|
|
46598
|
-
planDone:
|
|
46599
|
-
finished: (id2) =>
|
|
46600
|
-
try {
|
|
46601
|
-
return sessionFinished(id2);
|
|
46602
|
-
} catch {
|
|
46603
|
-
return false;
|
|
46604
|
-
}
|
|
46605
|
-
},
|
|
46900
|
+
planDone: planCompleteAsync,
|
|
46901
|
+
finished: (id2) => sessionFinishedAsync(id2).catch(() => false),
|
|
46606
46902
|
decide,
|
|
46607
46903
|
decideDeps: prodDecideDeps,
|
|
46608
46904
|
apply: applyAction,
|
|
@@ -46648,10 +46944,10 @@ async function pulse(deps = prodPulseDeps) {
|
|
|
46648
46944
|
async function followUpFinished(cfg, optIn, deps) {
|
|
46649
46945
|
let n2 = 0;
|
|
46650
46946
|
const opt = new Set(optIn);
|
|
46651
|
-
for (const s2 of deps.sessions()) {
|
|
46947
|
+
for (const s2 of await deps.sessions()) {
|
|
46652
46948
|
if (!s2.exited || !s2.specId || !opt.has(s2.projectId)) continue;
|
|
46653
46949
|
const bad = (s2.exitCode ?? 0) !== 0;
|
|
46654
|
-
const unfinished = !deps.planDone(s2.cwd, s2.specId);
|
|
46950
|
+
const unfinished = !await deps.planDone(s2.cwd, s2.specId);
|
|
46655
46951
|
if (!bad && !unfinished) continue;
|
|
46656
46952
|
const mark = `Sesi ${s2.id} untuk backlog ${s2.specId}`;
|
|
46657
46953
|
const seen2 = await prisma.leadDecision.findFirst({
|
|
@@ -46690,10 +46986,10 @@ async function followUpFinished(cfg, optIn, deps) {
|
|
|
46690
46986
|
async function followUpComplete(optIn, deps) {
|
|
46691
46987
|
let n2 = 0;
|
|
46692
46988
|
const opt = new Set(optIn);
|
|
46693
|
-
for (const s2 of deps.sessions()) {
|
|
46989
|
+
for (const s2 of await deps.sessions()) {
|
|
46694
46990
|
if (!s2.specId || !opt.has(s2.projectId)) continue;
|
|
46695
46991
|
if ((s2.exitCode ?? 0) !== 0) continue;
|
|
46696
|
-
if (!deps.finished(s2.id)) continue;
|
|
46992
|
+
if (!await deps.finished(s2.id)) continue;
|
|
46697
46993
|
const mark = `Backlog ${s2.specId} sudah selesai di sesi ${s2.id}`;
|
|
46698
46994
|
const seen2 = await prisma.leadDecision.findFirst({
|
|
46699
46995
|
where: { sessionId: s2.id, gate: "pulse", question: { startsWith: mark } }
|
|
@@ -46732,7 +47028,7 @@ async function followUpComplete(optIn, deps) {
|
|
|
46732
47028
|
}
|
|
46733
47029
|
async function detectCollisions(optIn, deps) {
|
|
46734
47030
|
const opt = new Set(optIn);
|
|
46735
|
-
const live = deps.sessions().filter((s2) => !s2.exited && s2.specId && opt.has(s2.projectId));
|
|
47031
|
+
const live = (await deps.sessions()).filter((s2) => !s2.exited && s2.specId && opt.has(s2.projectId));
|
|
46736
47032
|
if (live.length < 2) return 0;
|
|
46737
47033
|
const areas = [];
|
|
46738
47034
|
for (const s2 of live) {
|
|
@@ -46827,7 +47123,7 @@ async function orderProject(projectId, deps) {
|
|
|
46827
47123
|
|
|
46828
47124
|
// src/services/lead/engine.ts
|
|
46829
47125
|
init_notifications2();
|
|
46830
|
-
|
|
47126
|
+
init_live_phases();
|
|
46831
47127
|
var HOUSEKEEPING_MS = 6e4;
|
|
46832
47128
|
var busyPulse = false;
|
|
46833
47129
|
var lastPulseAt = 0;
|
|
@@ -46862,9 +47158,9 @@ async function tick(now, deps = {}) {
|
|
|
46862
47158
|
console.error("lead expire:", e);
|
|
46863
47159
|
}));
|
|
46864
47160
|
jobs.push((async () => {
|
|
46865
|
-
const live = deps.live ??
|
|
47161
|
+
const live = deps.live ?? liveDecisionsAsync;
|
|
46866
47162
|
const legacy = deps.legacy ?? recordLegacySession;
|
|
46867
|
-
for (const s2 of live()) {
|
|
47163
|
+
for (const s2 of await live()) {
|
|
46868
47164
|
if (s2.eventHook || !s2.waiting) continue;
|
|
46869
47165
|
await legacy(s2.id, s2.projectId || null, s2.specId ?? null);
|
|
46870
47166
|
}
|
|
@@ -46909,7 +47205,7 @@ async function buildLeadStatus() {
|
|
|
46909
47205
|
}
|
|
46910
47206
|
let waiting2 = [];
|
|
46911
47207
|
try {
|
|
46912
|
-
waiting2 =
|
|
47208
|
+
waiting2 = (await liveDecisionsAsync()).filter((d) => d.waiting).map((d) => d.id);
|
|
46913
47209
|
} catch {
|
|
46914
47210
|
}
|
|
46915
47211
|
const rows = await Promise.all(projects.map(async (p3) => ({
|
|
@@ -46982,11 +47278,48 @@ function parseParams(topic, params) {
|
|
|
46982
47278
|
return r.success ? r.data : void 0;
|
|
46983
47279
|
}
|
|
46984
47280
|
|
|
47281
|
+
// src/services/events-profile.ts
|
|
47282
|
+
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
47283
|
+
var PROFILE = process.env.HANOMAN_EVENTS_PROFILE === "1";
|
|
47284
|
+
var acc = {};
|
|
47285
|
+
var h;
|
|
47286
|
+
var nowUs = () => Number(process.hrtime.bigint() / 1000n);
|
|
47287
|
+
var profStart = () => PROFILE ? nowUs() : 0;
|
|
47288
|
+
function profEnd(group2, t0, bytes, emitted) {
|
|
47289
|
+
if (!PROFILE) return;
|
|
47290
|
+
const a = acc[group2] ??= { builds: [], frames: 0, bytes: 0, ticks: 0 };
|
|
47291
|
+
a.builds.push((nowUs() - t0) / 1e3);
|
|
47292
|
+
a.ticks++;
|
|
47293
|
+
if (emitted) {
|
|
47294
|
+
a.frames++;
|
|
47295
|
+
a.bytes += bytes;
|
|
47296
|
+
}
|
|
47297
|
+
}
|
|
47298
|
+
var pct2 = (xs, p3) => {
|
|
47299
|
+
const s2 = [...xs].sort((a, b) => a - b);
|
|
47300
|
+
return s2[Math.min(s2.length - 1, Math.floor(s2.length * p3))] ?? 0;
|
|
47301
|
+
};
|
|
47302
|
+
function startLagMonitor() {
|
|
47303
|
+
if (!PROFILE || h) return;
|
|
47304
|
+
const mon = h = monitorEventLoopDelay({ resolution: 10 });
|
|
47305
|
+
mon.enable();
|
|
47306
|
+
setInterval(() => {
|
|
47307
|
+
const ms = (n2) => +(n2 / 1e6).toFixed(1);
|
|
47308
|
+
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}`);
|
|
47309
|
+
console.log(`[events-profile] loop p50=${ms(mon.percentile(50))} p99=${ms(mon.percentile(99))} max=${ms(mon.max)}ms | ${rows.join(" | ")}`);
|
|
47310
|
+
mon.reset();
|
|
47311
|
+
for (const k of Object.keys(acc)) delete acc[k];
|
|
47312
|
+
}, 1e4).unref();
|
|
47313
|
+
}
|
|
47314
|
+
|
|
46985
47315
|
// src/services/events.ts
|
|
46986
47316
|
var clients = /* @__PURE__ */ new Set();
|
|
46987
47317
|
var cookieClients = /* @__PURE__ */ new WeakSet();
|
|
46988
47318
|
var clientGroups = /* @__PURE__ */ new WeakMap();
|
|
46989
47319
|
var tickMs = () => effectiveInt("HANOMAN_EVENTS_TICK_MS") ?? 1e3;
|
|
47320
|
+
var presenceKey = (m) => JSON.stringify({ ...m, devices: m.devices.map(
|
|
47321
|
+
(d) => d.local ? { ...d, lastSeenAt: null } : d
|
|
47322
|
+
) });
|
|
46990
47323
|
var GROUPS = [
|
|
46991
47324
|
{ everyTicks: 3, cookieOnly: true, last: "", build: async () => ({
|
|
46992
47325
|
t: "models",
|
|
@@ -47002,7 +47335,16 @@ var GROUPS = [
|
|
|
47002
47335
|
// loop — termasuk frame ketikan terminal — selama spawn (terukur sampai 916 ms saat mesin sibuk).
|
|
47003
47336
|
sessions: (await listSessionsAsync()).map((s2) => isDeciding(s2.id) ? { ...s2, deciding: true } : s2)
|
|
47004
47337
|
}) },
|
|
47005
|
-
|
|
47338
|
+
// SPEC-1267 · frame ringkas (`SpecSlim`: tanpa payload/objective/sourceHistory) yang hanya dibangun
|
|
47339
|
+
// saat `specsDigest` berubah. Kemajuan stage dari fase sesi (`liveOverlayTick`) tetap dijalankan
|
|
47340
|
+
// tiap tick: ia hidup di berkas/tmux, bukan di DB, jadi digest DB tak melihatnya sebelum ditulis.
|
|
47341
|
+
{
|
|
47342
|
+
everyTicks: 1,
|
|
47343
|
+
last: "",
|
|
47344
|
+
pre: liveOverlayTick,
|
|
47345
|
+
gate: specsDigest,
|
|
47346
|
+
build: async () => ({ t: "specs", specs: await listSpecsSlim() })
|
|
47347
|
+
},
|
|
47006
47348
|
{ everyTicks: 3, last: "", build: async () => ({ t: "notifications", ...await notificationsFeed() }) },
|
|
47007
47349
|
// SPEC-742 · ADR-0116 · pembersihan worktree yang masih jalan. `listCleanups()` membaca peta di
|
|
47008
47350
|
// memori, bukan disk — nol I/O per tick, dan dedup signature membuat frame lahir hanya saat
|
|
@@ -47028,7 +47370,15 @@ var GROUPS = [
|
|
|
47028
47370
|
// dihitung). 3 dtk: presence berdenyut 30 dtk, jadi kadens lebih rapat hanya menambah build tanpa
|
|
47029
47371
|
// menambah informasi. `presenceView` menyegarkan sesi mesin ini sendiri di dalamnya — satu
|
|
47030
47372
|
// `tmux list-panes` asinkron, tak menahan event loop.
|
|
47031
|
-
|
|
47373
|
+
// SPEC-1267 · `lastSeenAt` device lokal = jam build ini, jadi berubah tiap build; kunci dedup
|
|
47374
|
+
// mengosongkannya supaya frame hanya lahir saat isi presence benar-benar berubah.
|
|
47375
|
+
{
|
|
47376
|
+
everyTicks: 3,
|
|
47377
|
+
cookieOnly: true,
|
|
47378
|
+
last: "",
|
|
47379
|
+
build: async () => ({ t: "presence", ...await presenceView() }),
|
|
47380
|
+
sig: presenceKey
|
|
47381
|
+
},
|
|
47032
47382
|
// SPEC-961 · grup GLOBAL ke-11 · angka "butuh pengajuan" untuk badge sidebar. 5 dtk: badge bukan
|
|
47033
47383
|
// board — yang dijanjikannya adalah "ada yang menunggu", bukan detik keberapa ia muncul — dan
|
|
47034
47384
|
// dedup signature membuat frame lahir hanya saat salah satu angka berubah (jarang). Empat angka,
|
|
@@ -47037,8 +47387,7 @@ var GROUPS = [
|
|
|
47037
47387
|
// id/judul, dan agent token sudah boleh membaca daftar sumbernya lewat capability masing-masing.
|
|
47038
47388
|
{ everyTicks: 5, last: "", build: async () => ({ t: "pending", counts: await pendingCounts() }) }
|
|
47039
47389
|
];
|
|
47040
|
-
function broadcast2(msg, cookieOnly = false) {
|
|
47041
|
-
const s2 = JSON.stringify(msg);
|
|
47390
|
+
function broadcast2(msg, s2, cookieOnly = false) {
|
|
47042
47391
|
for (const c of clients) {
|
|
47043
47392
|
if (cookieOnly && !cookieClients.has(c)) continue;
|
|
47044
47393
|
const groups = clientGroups.get(c);
|
|
@@ -47126,31 +47475,54 @@ function subscribeClient(c, subs) {
|
|
|
47126
47475
|
var tick2 = 0;
|
|
47127
47476
|
var busy = false;
|
|
47128
47477
|
var timer3;
|
|
47478
|
+
async function runGroup(g) {
|
|
47479
|
+
const t0 = profStart();
|
|
47480
|
+
let msg;
|
|
47481
|
+
try {
|
|
47482
|
+
await g.pre?.();
|
|
47483
|
+
let gate2;
|
|
47484
|
+
if (g.gate) {
|
|
47485
|
+
try {
|
|
47486
|
+
gate2 = await g.gate();
|
|
47487
|
+
} catch {
|
|
47488
|
+
gate2 = void 0;
|
|
47489
|
+
}
|
|
47490
|
+
if (gate2 !== void 0 && gate2 === g.lastGate) {
|
|
47491
|
+
profEnd("gate", t0, 0, false);
|
|
47492
|
+
return null;
|
|
47493
|
+
}
|
|
47494
|
+
}
|
|
47495
|
+
msg = await g.build();
|
|
47496
|
+
if (g.failing) {
|
|
47497
|
+
g.failing = false;
|
|
47498
|
+
console.log(`siar dashboard pulih: ${msg.t}`);
|
|
47499
|
+
}
|
|
47500
|
+
const json = JSON.stringify(msg);
|
|
47501
|
+
const key = g.sig ? g.sig(msg) : json;
|
|
47502
|
+
const emit = key !== g.lastSig;
|
|
47503
|
+
profEnd(msg.t, t0, json.length, emit);
|
|
47504
|
+
if (g.gate) g.lastGate = gate2;
|
|
47505
|
+
return emit ? { g, msg, json, key, gate: gate2 } : null;
|
|
47506
|
+
} catch (e) {
|
|
47507
|
+
if (!g.failing) {
|
|
47508
|
+
g.failing = true;
|
|
47509
|
+
console.error("siar dashboard gagal membangun frame:", e);
|
|
47510
|
+
}
|
|
47511
|
+
return null;
|
|
47512
|
+
}
|
|
47513
|
+
}
|
|
47129
47514
|
async function __tick() {
|
|
47130
47515
|
if (busy) return;
|
|
47131
47516
|
busy = true;
|
|
47132
47517
|
tick2++;
|
|
47133
47518
|
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);
|
|
47519
|
+
const results = await Promise.all(GROUPS.filter((g) => tick2 % g.everyTicks === 0).map(runGroup));
|
|
47520
|
+
for (const r of results) {
|
|
47521
|
+
if (!r) continue;
|
|
47522
|
+
r.g.last = r.json;
|
|
47523
|
+
r.g.lastSig = r.key;
|
|
47524
|
+
r.g.lastT = r.msg.t;
|
|
47525
|
+
broadcast2(r.msg, r.json, r.g.cookieOnly);
|
|
47154
47526
|
}
|
|
47155
47527
|
for (const e of entries2.values()) {
|
|
47156
47528
|
e.tick++;
|
|
@@ -47163,6 +47535,7 @@ async function __tick() {
|
|
|
47163
47535
|
}
|
|
47164
47536
|
function startLoop() {
|
|
47165
47537
|
if (timer3) return;
|
|
47538
|
+
startLagMonitor();
|
|
47166
47539
|
timer3 = setInterval(() => {
|
|
47167
47540
|
void __tick();
|
|
47168
47541
|
}, tickMs());
|
|
@@ -47174,9 +47547,13 @@ function stopLoop() {
|
|
|
47174
47547
|
timer3 = void 0;
|
|
47175
47548
|
}
|
|
47176
47549
|
tick2 = 0;
|
|
47177
|
-
for (const g of GROUPS)
|
|
47550
|
+
for (const g of GROUPS) {
|
|
47551
|
+
g.last = "";
|
|
47552
|
+
g.lastSig = void 0;
|
|
47553
|
+
g.lastGate = void 0;
|
|
47554
|
+
}
|
|
47178
47555
|
}
|
|
47179
|
-
async function
|
|
47556
|
+
async function attach(c, o = {}) {
|
|
47180
47557
|
clients.add(c);
|
|
47181
47558
|
if (o.maySubscribe !== false) cookieClients.add(c);
|
|
47182
47559
|
if (o.groups) clientGroups.set(c, o.groups);
|
|
@@ -47187,17 +47564,20 @@ async function attach2(c, o = {}) {
|
|
|
47187
47564
|
} catch {
|
|
47188
47565
|
return;
|
|
47189
47566
|
}
|
|
47190
|
-
|
|
47191
|
-
|
|
47192
|
-
|
|
47567
|
+
const wanted = GROUPS.filter((g) => !(g.cookieOnly && !cookieClients.has(c)));
|
|
47568
|
+
const frames = await Promise.all(wanted.map(async (g) => {
|
|
47569
|
+
if (g.last && g.lastT) return { t: g.lastT, json: g.last };
|
|
47193
47570
|
try {
|
|
47194
|
-
msg = await g.build();
|
|
47571
|
+
const msg = await g.build();
|
|
47572
|
+
return { t: msg.t, json: JSON.stringify(msg) };
|
|
47195
47573
|
} catch {
|
|
47196
|
-
|
|
47574
|
+
return null;
|
|
47197
47575
|
}
|
|
47198
|
-
|
|
47576
|
+
}));
|
|
47577
|
+
for (const f of frames) {
|
|
47578
|
+
if (!f || o.groups && !o.groups.has(f.t)) continue;
|
|
47199
47579
|
try {
|
|
47200
|
-
c.send(
|
|
47580
|
+
c.send(f.json);
|
|
47201
47581
|
} catch {
|
|
47202
47582
|
return;
|
|
47203
47583
|
}
|
|
@@ -47231,7 +47611,7 @@ async function events_default(app3, opts) {
|
|
|
47231
47611
|
const client = { send: (m) => socket2.send(m), close: () => socket2.close() };
|
|
47232
47612
|
if (req.remote) {
|
|
47233
47613
|
const ideRead = req.remote.capabilities.includes("ide:read");
|
|
47234
|
-
void
|
|
47614
|
+
void attach(client, { maySubscribe: false, groups: remoteEventGroups(ideRead) });
|
|
47235
47615
|
socket2.on("close", () => detach2(client));
|
|
47236
47616
|
return;
|
|
47237
47617
|
}
|
|
@@ -47244,7 +47624,7 @@ async function events_default(app3, opts) {
|
|
|
47244
47624
|
return;
|
|
47245
47625
|
}
|
|
47246
47626
|
const maySubscribe = canSubscribeTopics(principal);
|
|
47247
|
-
void
|
|
47627
|
+
void attach(client, { maySubscribe });
|
|
47248
47628
|
const guard = new WsMessageGuard({ perWindow: SUB_FRAMES_PER_MINUTE });
|
|
47249
47629
|
socket2.on("message", (raw) => {
|
|
47250
47630
|
const verdict = guard.accept(raw);
|
|
@@ -47354,8 +47734,8 @@ import { join as join36 } from "node:path";
|
|
|
47354
47734
|
|
|
47355
47735
|
// src/services/device-auth.ts
|
|
47356
47736
|
function bearerToken2(req) {
|
|
47357
|
-
const
|
|
47358
|
-
return typeof
|
|
47737
|
+
const h2 = req.headers["authorization"];
|
|
47738
|
+
return typeof h2 === "string" && h2.startsWith("Bearer ") ? h2.slice(7) : void 0;
|
|
47359
47739
|
}
|
|
47360
47740
|
async function requireDeviceToken(req, reply) {
|
|
47361
47741
|
const token = bearerToken2(req);
|
|
@@ -47790,7 +48170,7 @@ async function session_history_default(app3) {
|
|
|
47790
48170
|
init_src();
|
|
47791
48171
|
init_session_event_token();
|
|
47792
48172
|
init_pty();
|
|
47793
|
-
var bearer = (
|
|
48173
|
+
var bearer = (h2) => /^Bearer (.+)$/.exec(h2 ?? "")?.[1] ?? "";
|
|
47794
48174
|
var recordOf = (value) => value && typeof value === "object" ? value : null;
|
|
47795
48175
|
var boundedString = (value, max) => typeof value === "string" && value.length > 0 && value.length <= max ? value : void 0;
|
|
47796
48176
|
async function session_events_default(app3) {
|
|
@@ -47938,9 +48318,9 @@ init_db();
|
|
|
47938
48318
|
init_db();
|
|
47939
48319
|
init_uploads();
|
|
47940
48320
|
init_src();
|
|
47941
|
-
import { createHash as
|
|
48321
|
+
import { createHash as createHash14, randomBytes as randomBytes7 } from "node:crypto";
|
|
47942
48322
|
function hashAccessKey(key) {
|
|
47943
|
-
return
|
|
48323
|
+
return createHash14("sha256").update(key).digest("hex");
|
|
47944
48324
|
}
|
|
47945
48325
|
function generateAccessKey() {
|
|
47946
48326
|
const key = "hnm_tkt_" + randomBytes7(24).toString("hex");
|
|
@@ -50842,8 +51222,8 @@ function systemPromptFor(type, nonce) {
|
|
|
50842
51222
|
${type === "brainstorm" ? BRAINSTORM : TANYA}`;
|
|
50843
51223
|
}
|
|
50844
51224
|
function renderTurnPrompt(o) {
|
|
50845
|
-
const riwayat = o.history.map((
|
|
50846
|
-
${
|
|
51225
|
+
const riwayat = o.history.map((h2) => h2.role === "klien" ? wrapClientMessage(h2.text, o.nonce) : `<jawaban-hanoman>
|
|
51226
|
+
${h2.text}
|
|
50847
51227
|
</jawaban-hanoman>`).join("\n\n");
|
|
50848
51228
|
const baru = wrapClientMessage(o.message, o.nonce);
|
|
50849
51229
|
return riwayat ? `Percakapan sejauh ini:
|
|
@@ -51463,8 +51843,8 @@ init_src();
|
|
|
51463
51843
|
init_db();
|
|
51464
51844
|
|
|
51465
51845
|
// src/services/bootstrap.ts
|
|
51466
|
-
import { createHash as
|
|
51467
|
-
import { chmod, lstat as lstat3, mkdir as mkdir10, readFile as
|
|
51846
|
+
import { createHash as createHash15, randomBytes as randomBytes11, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
|
|
51847
|
+
import { chmod, lstat as lstat3, mkdir as mkdir10, readFile as readFile8, unlink as unlink5, writeFile as writeFile7 } from "node:fs/promises";
|
|
51468
51848
|
import { join as join39 } from "node:path";
|
|
51469
51849
|
var TTL_MS5 = 15 * 6e4;
|
|
51470
51850
|
var SETUP_TOKEN_FILE = "setup.token";
|
|
@@ -51476,7 +51856,7 @@ async function readStored(home3) {
|
|
|
51476
51856
|
try {
|
|
51477
51857
|
const info = await lstat3(tokenPath(home3));
|
|
51478
51858
|
if (info.isSymbolicLink() || !info.isFile()) throw new BootstrapError("invalid setup proof");
|
|
51479
|
-
const [token, expires] = (await
|
|
51859
|
+
const [token, expires] = (await readFile8(tokenPath(home3), "utf8")).trim().split("\n");
|
|
51480
51860
|
const expiresAt = Date.parse(expires ?? "");
|
|
51481
51861
|
if (!token || !Number.isFinite(expiresAt)) throw new BootstrapError("invalid setup proof");
|
|
51482
51862
|
return { token, expiresAt };
|
|
@@ -51516,8 +51896,8 @@ ${new Date(expiresAt).toISOString()}
|
|
|
51516
51896
|
}
|
|
51517
51897
|
async function verifySetupToken(candidate, home3, now = Date.now()) {
|
|
51518
51898
|
const stored = await readStored(home3);
|
|
51519
|
-
const got =
|
|
51520
|
-
const want =
|
|
51899
|
+
const got = createHash15("sha256").update(candidate).digest();
|
|
51900
|
+
const want = createHash15("sha256").update(stored.token).digest();
|
|
51521
51901
|
if (stored.expiresAt <= now || !timingSafeEqual5(got, want)) throw new BootstrapError("invalid setup proof");
|
|
51522
51902
|
}
|
|
51523
51903
|
async function consumeSetupToken(home3) {
|
|
@@ -51666,12 +52046,6 @@ function applySetup(home3, input) {
|
|
|
51666
52046
|
return next;
|
|
51667
52047
|
}
|
|
51668
52048
|
|
|
51669
|
-
// src/services/restart.ts
|
|
51670
|
-
init_src();
|
|
51671
|
-
function requestConfigRestart(delayMs = 50) {
|
|
51672
|
-
setTimeout(() => process.exit(CONFIG_RESTART_EXIT), delayMs);
|
|
51673
|
-
}
|
|
51674
|
-
|
|
51675
52049
|
// src/routes/setup.ts
|
|
51676
52050
|
async function setup_default(app3, opts) {
|
|
51677
52051
|
const attempts = new BoundedRateLimiter({ windowMs: 6e4, limit: 10, maxKeys: 4096 });
|
|
@@ -52012,7 +52386,7 @@ init_db();
|
|
|
52012
52386
|
init_event_log();
|
|
52013
52387
|
init_transcript_store();
|
|
52014
52388
|
init_src2();
|
|
52015
|
-
import { readFile as
|
|
52389
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
52016
52390
|
import { join as join40 } from "node:path";
|
|
52017
52391
|
function decodeCursor(c) {
|
|
52018
52392
|
if (!c) return null;
|
|
@@ -52053,7 +52427,7 @@ async function readRemoteTranscript(id2) {
|
|
|
52053
52427
|
if (row.deviceId === "local") return readTranscript(row.transcriptKey);
|
|
52054
52428
|
const path = join40(resolveDataDirs().home, "remote-transcripts", row.transcriptKey);
|
|
52055
52429
|
try {
|
|
52056
|
-
return await
|
|
52430
|
+
return await readFile9(path, "utf8");
|
|
52057
52431
|
} catch {
|
|
52058
52432
|
return null;
|
|
52059
52433
|
}
|
|
@@ -52093,8 +52467,8 @@ async function logs_default(app3) {
|
|
|
52093
52467
|
init_agent_token();
|
|
52094
52468
|
init_settings3();
|
|
52095
52469
|
function agentTokenFromReq(req) {
|
|
52096
|
-
const
|
|
52097
|
-
if (typeof
|
|
52470
|
+
const h2 = req.headers["authorization"];
|
|
52471
|
+
if (typeof h2 === "string" && h2.startsWith("Bearer ")) return h2.slice(7);
|
|
52098
52472
|
return void 0;
|
|
52099
52473
|
}
|
|
52100
52474
|
async function authenticateAgent(token) {
|
|
@@ -52415,11 +52789,11 @@ async function auditSweep(now = Date.now) {
|
|
|
52415
52789
|
var timers = [];
|
|
52416
52790
|
function startVpsMonitor() {
|
|
52417
52791
|
if (timers.length) return;
|
|
52418
|
-
const
|
|
52792
|
+
const h2 = setInterval(() => void healthSweep(), HEALTH_MS);
|
|
52419
52793
|
const a = setInterval(() => void auditSweep(), AUDIT_MS);
|
|
52420
|
-
|
|
52794
|
+
h2.unref();
|
|
52421
52795
|
a.unref();
|
|
52422
|
-
timers = [
|
|
52796
|
+
timers = [h2, a];
|
|
52423
52797
|
void healthSweep();
|
|
52424
52798
|
void auditSweep();
|
|
52425
52799
|
}
|
|
@@ -52589,10 +52963,10 @@ async function reconcile(deps) {
|
|
|
52589
52963
|
await markFailed(item.id, "spec hilang");
|
|
52590
52964
|
continue;
|
|
52591
52965
|
}
|
|
52592
|
-
const p3 = deps.pane(item.sessionId ?? "");
|
|
52966
|
+
const p3 = await deps.pane(item.sessionId ?? "");
|
|
52593
52967
|
let stage = spec.stage;
|
|
52594
52968
|
if (p3?.flow && p3.phaseFile) {
|
|
52595
|
-
const d = deps.deriveStage(p3.phaseFile, p3.flow, p3.cwd, item.specId);
|
|
52969
|
+
const d = await deps.deriveStage(p3.phaseFile, p3.flow, p3.cwd, item.specId);
|
|
52596
52970
|
if (d && STAGES.indexOf(d) > STAGES.indexOf(stage)) {
|
|
52597
52971
|
const { count } = await prisma.spec.updateMany({ where: { id: item.specId, stage }, data: { stage: d } });
|
|
52598
52972
|
if (count > 0) await notifySynced("spec", item.specId).catch(() => {
|
|
@@ -52625,11 +52999,11 @@ async function reconcile(deps) {
|
|
|
52625
52999
|
}
|
|
52626
53000
|
}
|
|
52627
53001
|
var reconcileProdDeps = {
|
|
52628
|
-
pane: (sessionId2) => {
|
|
52629
|
-
const s2 =
|
|
53002
|
+
pane: async (sessionId2) => {
|
|
53003
|
+
const s2 = await getSessionAsync(sessionId2);
|
|
52630
53004
|
return s2 ? { exited: s2.exited, flow: s2.flow, phaseFile: s2.phaseFile, cwd: s2.cwd } : void 0;
|
|
52631
53005
|
},
|
|
52632
|
-
deriveStage: (phaseFile, flow2, cwd, specId) =>
|
|
53006
|
+
deriveStage: async (phaseFile, flow2, cwd, specId) => stageForRunAsync(await readPhasesAsync(phaseFile, flow2), cwd, specId),
|
|
52633
53007
|
headSha: (wt) => {
|
|
52634
53008
|
try {
|
|
52635
53009
|
return realGit.headSha(wt);
|
|
@@ -53234,13 +53608,13 @@ async function pruneLogs(now, retention, opts = {}) {
|
|
|
53234
53608
|
return { logsPruned, logBytesFreed };
|
|
53235
53609
|
}
|
|
53236
53610
|
async function reconcileRemoteTranscripts() {
|
|
53237
|
-
const { readdir:
|
|
53611
|
+
const { readdir: readdir10, unlink: unlink6, stat: stat7 } = await import("node:fs/promises");
|
|
53238
53612
|
const { join: join41 } = await import("node:path");
|
|
53239
53613
|
const { resolveDataDirs: resolveDataDirs2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
|
|
53240
53614
|
const root = join41(resolveDataDirs2().home, "remote-transcripts");
|
|
53241
53615
|
let deviceDirs;
|
|
53242
53616
|
try {
|
|
53243
|
-
deviceDirs = await
|
|
53617
|
+
deviceDirs = await readdir10(root);
|
|
53244
53618
|
} catch {
|
|
53245
53619
|
return { orphans: 0 };
|
|
53246
53620
|
}
|
|
@@ -53251,14 +53625,14 @@ async function reconcileRemoteTranscripts() {
|
|
|
53251
53625
|
for (const dev of deviceDirs) {
|
|
53252
53626
|
let files;
|
|
53253
53627
|
try {
|
|
53254
|
-
files = await
|
|
53628
|
+
files = await readdir10(join41(root, dev));
|
|
53255
53629
|
} catch {
|
|
53256
53630
|
continue;
|
|
53257
53631
|
}
|
|
53258
53632
|
for (const f of files) {
|
|
53259
53633
|
const key = `${dev}/${f}`;
|
|
53260
53634
|
if (referenced.has(key)) continue;
|
|
53261
|
-
const st = await
|
|
53635
|
+
const st = await stat7(join41(root, dev, f)).catch(() => null);
|
|
53262
53636
|
if (!st || Date.now() - st.mtimeMs < gracePeriodMs) continue;
|
|
53263
53637
|
await unlink6(join41(root, dev, f)).catch(() => {
|
|
53264
53638
|
});
|