hanoman 0.1.13 → 0.1.14
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 +37 -14
- package/dist/server.js +274 -101
- package/docs/agent-integration.md +429 -0
- package/package.json +2 -1
- package/web/assets/{index-DRwKLKdp.js → index-CB1_4DSI.js} +1539 -1535
- package/web/index.html +1 -1
package/dist/server.js
CHANGED
|
@@ -5553,6 +5553,9 @@ var init_api = __esm({
|
|
|
5553
5553
|
limits: `${API}/limits`,
|
|
5554
5554
|
// SPEC-339 · versi codex CLI (peringatan lunak model GPT-5.6).
|
|
5555
5555
|
codexVersion: `${API}/codex/version`,
|
|
5556
|
+
// SPEC-489 · panduan AI agent — markdown MENTAH & PUBLIC (tanpa auth). Satu definisi URL untuk
|
|
5557
|
+
// klien web dan untuk tautan yang disalin operator ke agennya.
|
|
5558
|
+
agentDoc: `${API}/agent-integration.md`,
|
|
5556
5559
|
docs: (id) => `${API}/projects/${id}/docs`,
|
|
5557
5560
|
docFile: (id, path) => `${API}/projects/${id}/docs/${path}`,
|
|
5558
5561
|
// SPEC-210 · dokumen PRD project (freshest-wins: worktree sesi prd hidup > repoDir)
|
|
@@ -5879,6 +5882,7 @@ var init_config_registry = __esm({
|
|
|
5879
5882
|
kind: "url",
|
|
5880
5883
|
apply: "live",
|
|
5881
5884
|
category: "knob",
|
|
5885
|
+
example: "https://hanoman.nafanesia.id",
|
|
5882
5886
|
help: "Base URL hub tujuan sync (REST + WS). Kosong = instance ini murni HUB."
|
|
5883
5887
|
},
|
|
5884
5888
|
{
|
|
@@ -5888,6 +5892,7 @@ var init_config_registry = __esm({
|
|
|
5888
5892
|
kind: "secret",
|
|
5889
5893
|
apply: "live",
|
|
5890
5894
|
category: "credential",
|
|
5895
|
+
example: "43 karakter base64url dari tab Perangkat hub",
|
|
5891
5896
|
help: "Token yang diterbitkan hub (tab Perangkat di hub). Dikirim sebagai Bearer."
|
|
5892
5897
|
},
|
|
5893
5898
|
{
|
|
@@ -5897,6 +5902,7 @@ var init_config_registry = __esm({
|
|
|
5897
5902
|
kind: "int",
|
|
5898
5903
|
apply: "live",
|
|
5899
5904
|
category: "knob",
|
|
5905
|
+
example: "15000",
|
|
5900
5906
|
default: "15000",
|
|
5901
5907
|
min: 1e3
|
|
5902
5908
|
},
|
|
@@ -5908,13 +5914,14 @@ var init_config_registry = __esm({
|
|
|
5908
5914
|
kind: "secret",
|
|
5909
5915
|
apply: "new-session",
|
|
5910
5916
|
category: "credential",
|
|
5917
|
+
example: "sk-ant-oat01-\u2026",
|
|
5911
5918
|
inheritEnv: true,
|
|
5912
5919
|
help: "Token `claude setup-token`. Diwarisi proses claude yang di-spawn."
|
|
5913
5920
|
},
|
|
5914
|
-
{ key: "ANTHROPIC_API_KEY", group: "claude", label: "Anthropic API key", kind: "secret", apply: "new-session", category: "credential", inheritEnv: true },
|
|
5915
|
-
{ key: "HANOMAN_CLAUDE_BIN", group: "claude", label: "Biner claude", kind: "path", apply: "new-session", category: "knob", default: "claude" },
|
|
5921
|
+
{ key: "ANTHROPIC_API_KEY", group: "claude", label: "Anthropic API key", kind: "secret", apply: "new-session", category: "credential", example: "sk-ant-api03-\u2026", inheritEnv: true },
|
|
5922
|
+
{ key: "HANOMAN_CLAUDE_BIN", group: "claude", label: "Biner claude", kind: "path", apply: "new-session", category: "knob", example: "/opt/homebrew/bin/claude", default: "claude" },
|
|
5916
5923
|
// codex (SPEC-338 · ADR-0074)
|
|
5917
|
-
{ key: "HANOMAN_CODEX_BIN", group: "claude", label: "Biner codex", kind: "path", apply: "new-session", category: "knob", default: "codex" },
|
|
5924
|
+
{ key: "HANOMAN_CODEX_BIN", group: "claude", label: "Biner codex", kind: "path", apply: "new-session", category: "knob", example: "/opt/homebrew/bin/codex", default: "codex" },
|
|
5918
5925
|
{
|
|
5919
5926
|
key: "CLAUDE_CONFIG_DIR",
|
|
5920
5927
|
group: "claude",
|
|
@@ -5922,11 +5929,12 @@ var init_config_registry = __esm({
|
|
|
5922
5929
|
kind: "path",
|
|
5923
5930
|
apply: "new-session",
|
|
5924
5931
|
category: "knob",
|
|
5932
|
+
example: "~/.claude",
|
|
5925
5933
|
help: "Default ~/.claude. Sumber .credentials.json untuk panel usage/limit."
|
|
5926
5934
|
},
|
|
5927
5935
|
// vps
|
|
5928
|
-
{ key: "HANOMAN_SSH_KEY_DIR", group: "vps", label: "Dir key SSH", kind: "path", apply: "new-session", category: "knob", help: "Default ~/.hanoman." },
|
|
5929
|
-
{ key: "HANOMAN_SSH_BIN", group: "vps", label: "Biner ssh", kind: "path", apply: "new-session", category: "knob", default: "ssh" },
|
|
5936
|
+
{ key: "HANOMAN_SSH_KEY_DIR", group: "vps", label: "Dir key SSH", kind: "path", apply: "new-session", category: "knob", example: "~/.hanoman", help: "Default ~/.hanoman." },
|
|
5937
|
+
{ key: "HANOMAN_SSH_BIN", group: "vps", label: "Biner ssh", kind: "path", apply: "new-session", category: "knob", example: "/usr/bin/ssh", default: "ssh" },
|
|
5930
5938
|
// github (SPEC-471 · ADR-0095 · tarik issue → backlog). Dua mode auth, satu jalur kode:
|
|
5931
5939
|
// `gh` memakai keyring mesin; bila GITHUB_TOKEN diisi ia diteruskan sebagai GH_TOKEN ke
|
|
5932
5940
|
// proses gh DAN dipakai jalur REST (hub VPS yang tak punya keyring).
|
|
@@ -5937,6 +5945,7 @@ var init_config_registry = __esm({
|
|
|
5937
5945
|
kind: "secret",
|
|
5938
5946
|
apply: "live",
|
|
5939
5947
|
category: "credential",
|
|
5948
|
+
example: "ghp_\u2026",
|
|
5940
5949
|
help: "PAT scope `repo` (atau `public_repo`). Kosong = andalkan `gh auth login` di mesin ini."
|
|
5941
5950
|
},
|
|
5942
5951
|
{
|
|
@@ -5946,6 +5955,7 @@ var init_config_registry = __esm({
|
|
|
5946
5955
|
kind: "path",
|
|
5947
5956
|
apply: "live",
|
|
5948
5957
|
category: "knob",
|
|
5958
|
+
example: "/opt/homebrew/bin/gh",
|
|
5949
5959
|
default: "gh",
|
|
5950
5960
|
help: "Absen = tarik issue lewat HTTPS langsung ke api.github.com."
|
|
5951
5961
|
},
|
|
@@ -5958,6 +5968,7 @@ var init_config_registry = __esm({
|
|
|
5958
5968
|
kind: "secret",
|
|
5959
5969
|
apply: "live",
|
|
5960
5970
|
category: "credential",
|
|
5971
|
+
example: "123456789:AAE\u2026",
|
|
5961
5972
|
pattern: "^\\d{5,}:[A-Za-z0-9_-]{30,}$",
|
|
5962
5973
|
patternError: "format BotFather: <bot_id>:<secret>",
|
|
5963
5974
|
help: "Token dari BotFather. Disimpan terenkripsi; tak pernah dikembalikan utuh."
|
|
@@ -5969,6 +5980,7 @@ var init_config_registry = __esm({
|
|
|
5969
5980
|
kind: "secret",
|
|
5970
5981
|
apply: "live",
|
|
5971
5982
|
category: "credential",
|
|
5983
|
+
example: "hnm_agt_\u2026",
|
|
5972
5984
|
pattern: "^\\S{20,}$",
|
|
5973
5985
|
patternError: "plaintext AgentToken (hnm_agt_\u2026)",
|
|
5974
5986
|
help: "Plaintext AgentToken ber-capability Telegram. Dipakai session operator, bukan bot."
|
|
@@ -5980,6 +5992,7 @@ var init_config_registry = __esm({
|
|
|
5980
5992
|
kind: "string",
|
|
5981
5993
|
apply: "live",
|
|
5982
5994
|
category: "credential",
|
|
5995
|
+
example: "123456789, 987654321",
|
|
5983
5996
|
pattern: "^\\d+(?:[\\s,]+\\d+)*$",
|
|
5984
5997
|
patternError: "numeric user id, dipisah koma/spasi",
|
|
5985
5998
|
help: "Siapa yang boleh memerintah bot. Numeric Telegram user id, bukan username."
|
|
@@ -5991,12 +6004,13 @@ var init_config_registry = __esm({
|
|
|
5991
6004
|
kind: "string",
|
|
5992
6005
|
apply: "live",
|
|
5993
6006
|
category: "knob",
|
|
6007
|
+
example: "-1001234567890",
|
|
5994
6008
|
pattern: "^-?\\d+$",
|
|
5995
6009
|
patternError: "numeric chat id (channel/supergroup negatif)",
|
|
5996
6010
|
help: "Tujuan Test Connection. Kosong = pakai satu-satunya id di allowlist."
|
|
5997
6011
|
},
|
|
5998
6012
|
// runtime
|
|
5999
|
-
{ key: "HANOMAN_EVENTS_TICK_MS", group: "runtime", label: "Interval events (ms)", kind: "int", apply: "live", category: "knob", default: "1000", min: 100 },
|
|
6013
|
+
{ key: "HANOMAN_EVENTS_TICK_MS", group: "runtime", label: "Interval events (ms)", kind: "int", apply: "live", category: "knob", example: "1000", default: "1000", min: 100 },
|
|
6000
6014
|
{ key: "HANOMAN_UPDATE_FETCH", group: "runtime", label: "Deteksi update saat boot", kind: "bool", apply: "live", category: "knob", default: "1" },
|
|
6001
6015
|
// SPEC-398 · ADR-0087 · deteksi update kini membandingkan semver dengan registry npm, bukan
|
|
6002
6016
|
// menghitung commit di repo git (HANOMAN_REPO_ROOT dicabut bersama jalur git itu).
|
|
@@ -6007,6 +6021,7 @@ var init_config_registry = __esm({
|
|
|
6007
6021
|
kind: "string",
|
|
6008
6022
|
apply: "live",
|
|
6009
6023
|
category: "knob",
|
|
6024
|
+
example: "https://registry.npmjs.org",
|
|
6010
6025
|
default: "https://registry.npmjs.org",
|
|
6011
6026
|
help: "Sumber versi terbaru paket `hanoman`."
|
|
6012
6027
|
},
|
|
@@ -6017,15 +6032,16 @@ var init_config_registry = __esm({
|
|
|
6017
6032
|
kind: "string",
|
|
6018
6033
|
apply: "restart",
|
|
6019
6034
|
category: "knob",
|
|
6035
|
+
example: "hanoman",
|
|
6020
6036
|
default: "hanoman",
|
|
6021
6037
|
help: "Mengubah ini TIDAK memindahkan sesi tmux yang sudah hidup \u2014 berlaku setelah restart."
|
|
6022
6038
|
},
|
|
6023
6039
|
// gitGraph (SPEC-233 · preferensi tampilan git graph; semua live, dikonsumsi client)
|
|
6024
|
-
{ key: "gitGraph.style", group: "gitGraph", label: "Gaya graph", kind: "string", apply: "live", category: "knob", default: "rounded", help: "rounded | angular" },
|
|
6025
|
-
{ key: "gitGraph.colours", group: "gitGraph", label: "Warna lane (CSV)", kind: "string", apply: "live", category: "knob", help: "Daftar warna hex dipisah koma; kosong = palet bawaan." },
|
|
6026
|
-
{ key: "gitGraph.dateType", group: "gitGraph", label: "Jenis tanggal", kind: "string", apply: "live", category: "knob", default: "author", help: "author | commit" },
|
|
6027
|
-
{ key: "gitGraph.commitsInitialLoad", group: "gitGraph", label: "Muat awal", kind: "int", apply: "live", category: "knob", default: "200", min: 1 },
|
|
6028
|
-
{ key: "gitGraph.commitsLoadMore", group: "gitGraph", label: "Muat lagi", kind: "int", apply: "live", category: "knob", default: "100", min: 1 },
|
|
6040
|
+
{ key: "gitGraph.style", group: "gitGraph", label: "Gaya graph", kind: "string", apply: "live", category: "knob", example: "rounded", default: "rounded", help: "rounded | angular" },
|
|
6041
|
+
{ key: "gitGraph.colours", group: "gitGraph", label: "Warna lane (CSV)", kind: "string", apply: "live", category: "knob", example: "#c9a227, #6b8f71, #b4614e", help: "Daftar warna hex dipisah koma; kosong = palet bawaan." },
|
|
6042
|
+
{ key: "gitGraph.dateType", group: "gitGraph", label: "Jenis tanggal", kind: "string", apply: "live", category: "knob", example: "author", default: "author", help: "author | commit" },
|
|
6043
|
+
{ key: "gitGraph.commitsInitialLoad", group: "gitGraph", label: "Muat awal", kind: "int", apply: "live", category: "knob", example: "200", default: "200", min: 1 },
|
|
6044
|
+
{ key: "gitGraph.commitsLoadMore", group: "gitGraph", label: "Muat lagi", kind: "int", apply: "live", category: "knob", example: "100", default: "100", min: 1 },
|
|
6029
6045
|
{ key: "gitGraph.showRemoteBranches", group: "gitGraph", label: "Tampilkan remote", kind: "bool", apply: "live", category: "knob", default: "1" },
|
|
6030
6046
|
{ key: "gitGraph.showTags", group: "gitGraph", label: "Tampilkan tag", kind: "bool", apply: "live", category: "knob", default: "1" },
|
|
6031
6047
|
{ key: "gitGraph.showStashes", group: "gitGraph", label: "Tampilkan stash", kind: "bool", apply: "live", category: "knob", default: "1" },
|
|
@@ -6035,7 +6051,7 @@ var init_config_registry = __esm({
|
|
|
6035
6051
|
{ key: "gitGraph.combineLocalRemote", group: "gitGraph", label: "Gabung label local+remote", kind: "bool", apply: "live", category: "knob", default: "0" },
|
|
6036
6052
|
{ key: "gitGraph.markdown", group: "gitGraph", label: "Render markdown pesan", kind: "bool", apply: "live", category: "knob", default: "1" },
|
|
6037
6053
|
{ key: "gitGraph.emoji", group: "gitGraph", label: "Render emoji shortcode", kind: "bool", apply: "live", category: "knob", default: "1" },
|
|
6038
|
-
{ key: "gitGraph.issueLinkPattern", group: "gitGraph", label: "Pola link issue", kind: "string", apply: "live", category: "knob", help: "URL dengan $1 untuk nomor issue, mis. https://github.com/acme/app/issues/$1" },
|
|
6054
|
+
{ key: "gitGraph.issueLinkPattern", group: "gitGraph", label: "Pola link issue", kind: "string", apply: "live", category: "knob", example: "https://github.com/acme/app/issues/$1", help: "URL dengan $1 untuk nomor issue, mis. https://github.com/acme/app/issues/$1" },
|
|
6039
6055
|
// bootstrap (read-only)
|
|
6040
6056
|
{ key: "DATABASE_URL", group: "bootstrap", label: "DATABASE_URL", kind: "secret", apply: "restart", category: "bootstrap" },
|
|
6041
6057
|
{ key: "TEST_DATABASE_URL", group: "bootstrap", label: "TEST_DATABASE_URL", kind: "secret", apply: "restart", category: "bootstrap" },
|
|
@@ -7112,7 +7128,7 @@ var init_src = __esm({
|
|
|
7112
7128
|
|
|
7113
7129
|
// src/env.ts
|
|
7114
7130
|
import { readFileSync, existsSync } from "node:fs";
|
|
7115
|
-
import { dirname, resolve as
|
|
7131
|
+
import { dirname, resolve as resolve3 } from "node:path";
|
|
7116
7132
|
import { fileURLToPath } from "node:url";
|
|
7117
7133
|
var dir;
|
|
7118
7134
|
var init_env = __esm({
|
|
@@ -7120,7 +7136,7 @@ var init_env = __esm({
|
|
|
7120
7136
|
"use strict";
|
|
7121
7137
|
dir = dirname(fileURLToPath(import.meta.url));
|
|
7122
7138
|
for (let i = 0; i < 6; i++) {
|
|
7123
|
-
const file =
|
|
7139
|
+
const file = resolve3(dir, ".env");
|
|
7124
7140
|
if (existsSync(file)) {
|
|
7125
7141
|
for (const line of readFileSync(file, "utf8").split("\n")) {
|
|
7126
7142
|
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
@@ -7574,7 +7590,7 @@ ${lines2.join("\n")}` : "";
|
|
|
7574
7590
|
// ../runner/src/git.ts
|
|
7575
7591
|
import { spawnSync } from "node:child_process";
|
|
7576
7592
|
import { rmSync, mkdirSync, realpathSync } from "node:fs";
|
|
7577
|
-
import { isAbsolute, resolve as
|
|
7593
|
+
import { isAbsolute, resolve as resolve4 } from "node:path";
|
|
7578
7594
|
function git(cwd, args) {
|
|
7579
7595
|
const r = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
7580
7596
|
if (r.status !== 0) throw new Error(`git ${args.join(" ")} failed: ${r.stderr || r.stdout || r.error?.message || "gagal spawn"}`);
|
|
@@ -7609,7 +7625,7 @@ var init_git = __esm({
|
|
|
7609
7625
|
addWorktree: (repo, path, branchFrom) => {
|
|
7610
7626
|
tryGit(repo, ["worktree", "remove", "--force", path]);
|
|
7611
7627
|
tryGit(repo, ["worktree", "prune"]);
|
|
7612
|
-
rmSync(isAbsolute(path) ? path :
|
|
7628
|
+
rmSync(isAbsolute(path) ? path : resolve4(repo, path), { recursive: true, force: true });
|
|
7613
7629
|
const base2 = resolveCommit(repo, branchFrom);
|
|
7614
7630
|
git(repo, ["worktree", "add", "--detach", path, base2]);
|
|
7615
7631
|
return base2;
|
|
@@ -7619,8 +7635,8 @@ var init_git = __esm({
|
|
|
7619
7635
|
// `fatal: not a working tree` → membuat DELETE /terminal/sessions balas 500. remove+prune+rm
|
|
7620
7636
|
// semuanya toleran, jadi penutupan sesi selalu 204 dan registrasi worktree tak stale (SPEC-197).
|
|
7621
7637
|
removeWorktree: (repo, path) => {
|
|
7622
|
-
const target2 = isAbsolute(path) ?
|
|
7623
|
-
if (target2 ===
|
|
7638
|
+
const target2 = isAbsolute(path) ? resolve4(path) : resolve4(repo, path);
|
|
7639
|
+
if (target2 === resolve4(repo)) {
|
|
7624
7640
|
throw new Error(`removeWorktree menolak menghapus repo itu sendiri: ${target2}`);
|
|
7625
7641
|
}
|
|
7626
7642
|
tryGit(repo, ["worktree", "remove", "--force", target2]);
|
|
@@ -7936,7 +7952,7 @@ var init_custom_agents = __esm({
|
|
|
7936
7952
|
|
|
7937
7953
|
// ../runner/src/paths.ts
|
|
7938
7954
|
import { homedir } from "node:os";
|
|
7939
|
-
import { dirname as dirname2, isAbsolute as isAbsolute2, join, resolve as
|
|
7955
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join, resolve as resolve5 } from "node:path";
|
|
7940
7956
|
function resolveHome(env = process.env, home2 = homedir()) {
|
|
7941
7957
|
const v = env.HANOMAN_HOME?.trim();
|
|
7942
7958
|
return v ? v : join(home2, ".hanoman");
|
|
@@ -7961,7 +7977,7 @@ function resolveDbUrl(env, schemaDir2) {
|
|
|
7961
7977
|
function absoluteFileUrl(raw, schemaDir2) {
|
|
7962
7978
|
const p = raw.slice("file:".length);
|
|
7963
7979
|
if (p.startsWith(":")) return raw;
|
|
7964
|
-
return `file:${isAbsolute2(p) ? p :
|
|
7980
|
+
return `file:${isAbsolute2(p) ? p : resolve5(schemaDir2, p)}`;
|
|
7965
7981
|
}
|
|
7966
7982
|
function dbUrlNotice(env) {
|
|
7967
7983
|
if (env.HANOMAN_DATABASE_URL?.trim()) return null;
|
|
@@ -8197,7 +8213,7 @@ var init_tap = __esm({
|
|
|
8197
8213
|
|
|
8198
8214
|
// src/db.ts
|
|
8199
8215
|
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
8200
|
-
import { dirname as dirname3, resolve as
|
|
8216
|
+
import { dirname as dirname3, resolve as resolve6 } from "node:path";
|
|
8201
8217
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
8202
8218
|
import { PrismaClient } from "@prisma/client";
|
|
8203
8219
|
var schemaDir, notice, url, base, prisma;
|
|
@@ -8207,7 +8223,7 @@ var init_db = __esm({
|
|
|
8207
8223
|
init_env();
|
|
8208
8224
|
init_src2();
|
|
8209
8225
|
init_tap();
|
|
8210
|
-
schemaDir =
|
|
8226
|
+
schemaDir = resolve6(dirname3(fileURLToPath2(import.meta.url)), "../prisma");
|
|
8211
8227
|
notice = dbUrlNotice(process.env);
|
|
8212
8228
|
if (notice) console.warn(notice);
|
|
8213
8229
|
url = resolveDbUrl(process.env, schemaDir);
|
|
@@ -10024,13 +10040,15 @@ var init_runtime = __esm({
|
|
|
10024
10040
|
|
|
10025
10041
|
// src/services/telegram/gateway.ts
|
|
10026
10042
|
import { createHash as createHash3 } from "node:crypto";
|
|
10027
|
-
var digestRaw, TelegramGateway;
|
|
10043
|
+
var GATEWAY_PROGRESS_KIND, GATEWAY_FAILURE_KIND, digestRaw, TelegramGateway;
|
|
10028
10044
|
var init_gateway = __esm({
|
|
10029
10045
|
"src/services/telegram/gateway.ts"() {
|
|
10030
10046
|
"use strict";
|
|
10031
10047
|
init_client();
|
|
10032
10048
|
init_protocol();
|
|
10033
10049
|
init_runtime();
|
|
10050
|
+
GATEWAY_PROGRESS_KIND = "gateway-progress";
|
|
10051
|
+
GATEWAY_FAILURE_KIND = "gateway-failure";
|
|
10034
10052
|
digestRaw = (update2) => createHash3("sha256").update(JSON.stringify(update2)).digest("hex");
|
|
10035
10053
|
TelegramGateway = class {
|
|
10036
10054
|
constructor(deps) {
|
|
@@ -10116,8 +10134,18 @@ var init_gateway = __esm({
|
|
|
10116
10134
|
outcome: target2.created ? "session-created" : "session-reused",
|
|
10117
10135
|
correlationId: `tg:${input.updateId}`
|
|
10118
10136
|
});
|
|
10137
|
+
if (this.deps.progress) {
|
|
10138
|
+
await this.deps.store.enqueueReply({
|
|
10139
|
+
chatId: input.chatId,
|
|
10140
|
+
updateId: input.updateId,
|
|
10141
|
+
kind: GATEWAY_PROGRESS_KIND,
|
|
10142
|
+
text: target2.created ? "Diterima. Sesi operator Hanoman untuk chat ini sedang dijalankan \u2014 jawabannya menyusul." : "Diterima. Diteruskan ke sesi operator."
|
|
10143
|
+
}).catch(() => {
|
|
10144
|
+
});
|
|
10145
|
+
}
|
|
10119
10146
|
updateTelegramRuntimeStatus({ lastUpdateAt: (/* @__PURE__ */ new Date()).toISOString(), lastError: null });
|
|
10120
10147
|
} catch (error) {
|
|
10148
|
+
const reason = sanitizeTelegramOutput(error.message, this.deps.exactSecrets).slice(0, 200);
|
|
10121
10149
|
await this.deps.store.markUpdateUncertain(input.updateId, "dispatch-failed");
|
|
10122
10150
|
await this.deps.store.audit({
|
|
10123
10151
|
chatId: input.chatId,
|
|
@@ -10127,7 +10155,14 @@ var init_gateway = __esm({
|
|
|
10127
10155
|
outcome: "uncertain",
|
|
10128
10156
|
correlationId: `tg:${input.updateId}`
|
|
10129
10157
|
});
|
|
10130
|
-
|
|
10158
|
+
await this.deps.store.enqueueReply({
|
|
10159
|
+
chatId: input.chatId,
|
|
10160
|
+
updateId: input.updateId,
|
|
10161
|
+
kind: GATEWAY_FAILURE_KIND,
|
|
10162
|
+
text: `Pesan Anda tertangkap tapi gagal diteruskan ke sesi operator: ${reason || "sebab tak diketahui"}. Kirim ulang setelah masalahnya beres \u2014 hanoman tidak pernah mengulanginya sendiri (at-most-once).`
|
|
10163
|
+
}).catch(() => {
|
|
10164
|
+
});
|
|
10165
|
+
updateTelegramRuntimeStatus({ lastError: reason });
|
|
10131
10166
|
}
|
|
10132
10167
|
}
|
|
10133
10168
|
async flushOutbox() {
|
|
@@ -10186,14 +10221,20 @@ var init_gateway = __esm({
|
|
|
10186
10221
|
timeout: this.deps.pollTimeout ?? 25,
|
|
10187
10222
|
signal
|
|
10188
10223
|
});
|
|
10189
|
-
|
|
10190
|
-
|
|
10224
|
+
if (telegramRuntimeStatus().readiness === "error") {
|
|
10225
|
+
updateTelegramRuntimeStatus({ running: true, readiness: "running", lastError: null });
|
|
10226
|
+
}
|
|
10227
|
+
try {
|
|
10228
|
+
await this.processUpdates(updates);
|
|
10229
|
+
} finally {
|
|
10230
|
+
await this.flushOutbox();
|
|
10231
|
+
}
|
|
10191
10232
|
} catch (error) {
|
|
10192
10233
|
if (signal.aborted) return;
|
|
10193
10234
|
const message = sanitizeTelegramOutput(error.message, this.deps.exactSecrets).slice(0, 500);
|
|
10194
10235
|
updateTelegramRuntimeStatus({ running: false, readiness: "error", lastError: message });
|
|
10195
10236
|
if (error instanceof TelegramApiError && error.code === 409) return;
|
|
10196
|
-
await new Promise((
|
|
10237
|
+
await new Promise((resolve15) => setTimeout(resolve15, 1e3));
|
|
10197
10238
|
}
|
|
10198
10239
|
}
|
|
10199
10240
|
}
|
|
@@ -17222,17 +17263,17 @@ var require_stream_wormhole = __commonJS({
|
|
|
17222
17263
|
"../node_modules/.pnpm/stream-wormhole@1.1.0/node_modules/stream-wormhole/index.js"(exports, module) {
|
|
17223
17264
|
"use strict";
|
|
17224
17265
|
module.exports = (stream, throwError) => {
|
|
17225
|
-
return new Promise((
|
|
17266
|
+
return new Promise((resolve15, reject2) => {
|
|
17226
17267
|
if (typeof stream.resume !== "function") {
|
|
17227
|
-
return
|
|
17268
|
+
return resolve15();
|
|
17228
17269
|
}
|
|
17229
17270
|
stream.unpipe && stream.unpipe();
|
|
17230
17271
|
stream.resume();
|
|
17231
17272
|
if (stream._readableState && stream._readableState.ended) {
|
|
17232
|
-
return
|
|
17273
|
+
return resolve15();
|
|
17233
17274
|
}
|
|
17234
17275
|
if (!stream.readable || stream.destroyed) {
|
|
17235
|
-
return
|
|
17276
|
+
return resolve15();
|
|
17236
17277
|
}
|
|
17237
17278
|
function cleanup() {
|
|
17238
17279
|
stream.removeListener("end", onEnd);
|
|
@@ -17241,14 +17282,14 @@ var require_stream_wormhole = __commonJS({
|
|
|
17241
17282
|
}
|
|
17242
17283
|
function onEnd() {
|
|
17243
17284
|
cleanup();
|
|
17244
|
-
|
|
17285
|
+
resolve15();
|
|
17245
17286
|
}
|
|
17246
17287
|
function onError(err) {
|
|
17247
17288
|
cleanup();
|
|
17248
17289
|
if (throwError) {
|
|
17249
17290
|
reject2(err);
|
|
17250
17291
|
} else {
|
|
17251
|
-
|
|
17292
|
+
resolve15();
|
|
17252
17293
|
}
|
|
17253
17294
|
}
|
|
17254
17295
|
stream.on("end", onEnd);
|
|
@@ -17683,16 +17724,16 @@ var require_multipart2 = __commonJS({
|
|
|
17683
17724
|
}
|
|
17684
17725
|
};
|
|
17685
17726
|
const parts = () => {
|
|
17686
|
-
return new Promise((
|
|
17727
|
+
return new Promise((resolve15, reject2) => {
|
|
17687
17728
|
handle((val) => {
|
|
17688
17729
|
if (val instanceof Error) {
|
|
17689
17730
|
if (val.message === "Unexpected end of multipart data") {
|
|
17690
|
-
|
|
17731
|
+
resolve15(null);
|
|
17691
17732
|
} else {
|
|
17692
17733
|
reject2(val);
|
|
17693
17734
|
}
|
|
17694
17735
|
} else {
|
|
17695
|
-
|
|
17736
|
+
resolve15(val);
|
|
17696
17737
|
}
|
|
17697
17738
|
});
|
|
17698
17739
|
});
|
|
@@ -17975,11 +18016,39 @@ function pickWebDir(distDir, env, exists) {
|
|
|
17975
18016
|
return null;
|
|
17976
18017
|
}
|
|
17977
18018
|
|
|
18019
|
+
// src/guide-file.ts
|
|
18020
|
+
import { resolve as resolve2 } from "node:path";
|
|
18021
|
+
var AGENT_DOC_REL = "docs/agent-integration.md";
|
|
18022
|
+
function pickGuideFile(distDir, env, exists) {
|
|
18023
|
+
const forced = env.HANOMAN_AGENT_DOC?.trim();
|
|
18024
|
+
if (forced) {
|
|
18025
|
+
if (!exists(forced)) throw new Error(`HANOMAN_AGENT_DOC menunjuk berkas yang tak ada: ${forced}`);
|
|
18026
|
+
return forced;
|
|
18027
|
+
}
|
|
18028
|
+
for (const c of [resolve2(distDir, "..", AGENT_DOC_REL), resolve2(distDir, "../..", AGENT_DOC_REL)]) {
|
|
18029
|
+
if (exists(c)) return c;
|
|
18030
|
+
}
|
|
18031
|
+
return null;
|
|
18032
|
+
}
|
|
18033
|
+
|
|
17978
18034
|
// src/routes/health.ts
|
|
17979
18035
|
async function health_default(app2) {
|
|
17980
18036
|
app2.get("/health", async () => ({ ok: true }));
|
|
17981
18037
|
}
|
|
17982
18038
|
|
|
18039
|
+
// src/routes/agent-doc.ts
|
|
18040
|
+
import { readFile } from "node:fs/promises";
|
|
18041
|
+
async function agent_doc_default(app2, opts) {
|
|
18042
|
+
app2.get("/agent-integration.md", async (_req, reply) => {
|
|
18043
|
+
if (!opts.file)
|
|
18044
|
+
return reply.code(404).send({
|
|
18045
|
+
error: `dokumen panduan tak ada di instalasi ini (${AGENT_DOC_REL}) \u2014 pasang ulang paket hanoman atau set HANOMAN_AGENT_DOC`
|
|
18046
|
+
});
|
|
18047
|
+
const text = await readFile(opts.file, "utf8");
|
|
18048
|
+
return reply.type("text/markdown; charset=utf-8").send(text);
|
|
18049
|
+
});
|
|
18050
|
+
}
|
|
18051
|
+
|
|
17983
18052
|
// src/routes/projects.ts
|
|
17984
18053
|
init_src();
|
|
17985
18054
|
init_db();
|
|
@@ -17994,7 +18063,7 @@ init_src();
|
|
|
17994
18063
|
import { execFile } from "node:child_process";
|
|
17995
18064
|
import { promisify } from "node:util";
|
|
17996
18065
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, rmSync as rmSync2, mkdirSync as mkdirSync5 } from "node:fs";
|
|
17997
|
-
import { resolve as
|
|
18066
|
+
import { resolve as resolve7, dirname as dirname6, sep } from "node:path";
|
|
17998
18067
|
var exec = promisify(execFile);
|
|
17999
18068
|
async function listRepoDocs(repoDir) {
|
|
18000
18069
|
try {
|
|
@@ -18010,7 +18079,7 @@ async function listRepoDocs(repoDir) {
|
|
|
18010
18079
|
}
|
|
18011
18080
|
function docsDirOf(repoDir) {
|
|
18012
18081
|
try {
|
|
18013
|
-
const raw = readFileSync4(
|
|
18082
|
+
const raw = readFileSync4(resolve7(repoDir, "hanoman.config.json"), "utf8");
|
|
18014
18083
|
return zHanomanConfig.parse(JSON.parse(raw)).docsDir;
|
|
18015
18084
|
} catch {
|
|
18016
18085
|
return zHanomanConfig.parse({}).docsDir;
|
|
@@ -18018,7 +18087,7 @@ function docsDirOf(repoDir) {
|
|
|
18018
18087
|
}
|
|
18019
18088
|
function resolveIndex(repoDir, docsDir) {
|
|
18020
18089
|
const rel = `${docsDir}/README.md`;
|
|
18021
|
-
return existsSync3(
|
|
18090
|
+
return existsSync3(resolve7(repoDir, rel)) ? rel : "";
|
|
18022
18091
|
}
|
|
18023
18092
|
var catOf = (rel) => rel.includes("/") ? rel.slice(0, rel.lastIndexOf("/")) : ".";
|
|
18024
18093
|
var nameOf = (rel) => rel.includes("/") ? rel.slice(rel.lastIndexOf("/") + 1) : rel;
|
|
@@ -18029,7 +18098,7 @@ async function scanRepoDocs(repoDir) {
|
|
|
18029
18098
|
const index = resolveIndex(repoDir, docsDir);
|
|
18030
18099
|
const read = (rel) => {
|
|
18031
18100
|
try {
|
|
18032
|
-
return readFileSync4(
|
|
18101
|
+
return readFileSync4(resolve7(repoDir, rel), "utf8");
|
|
18033
18102
|
} catch {
|
|
18034
18103
|
return null;
|
|
18035
18104
|
}
|
|
@@ -18052,7 +18121,7 @@ async function scanRepoDocs(repoDir) {
|
|
|
18052
18121
|
function docAbsPath(repoDir, rel) {
|
|
18053
18122
|
if (!rel.endsWith(".md")) throw new Error("hanya file .md yang diizinkan");
|
|
18054
18123
|
if (rel.split("/").includes(".git")) throw new Error("tidak boleh menyentuh .git");
|
|
18055
|
-
const abs =
|
|
18124
|
+
const abs = resolve7(repoDir, rel);
|
|
18056
18125
|
if (abs !== repoDir && !abs.startsWith(repoDir + sep)) throw new Error("path keluar dari repo");
|
|
18057
18126
|
return abs;
|
|
18058
18127
|
}
|
|
@@ -18552,9 +18621,9 @@ init_db();
|
|
|
18552
18621
|
// src/services/spec-review.ts
|
|
18553
18622
|
import { execFile as execFile4 } from "node:child_process";
|
|
18554
18623
|
import { promisify as promisify4 } from "node:util";
|
|
18555
|
-
import { readFile, mkdtemp, copyFile, rm } from "node:fs/promises";
|
|
18624
|
+
import { readFile as readFile2, mkdtemp, copyFile, rm } from "node:fs/promises";
|
|
18556
18625
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
18557
|
-
import { join as join4, resolve as
|
|
18626
|
+
import { join as join4, resolve as resolve8 } from "node:path";
|
|
18558
18627
|
var exec4 = promisify4(execFile4);
|
|
18559
18628
|
var GIT3 = { maxBuffer: 1 << 24 };
|
|
18560
18629
|
var MAX = 256 * 1024;
|
|
@@ -18563,7 +18632,7 @@ async function withTempIndex(wt, fn) {
|
|
|
18563
18632
|
const idx = (await exec4("git", ["rev-parse", "--git-path", "index"], { cwd: wt, ...GIT3 })).stdout.trim();
|
|
18564
18633
|
const dir2 = await mkdtemp(join4(tmpdir2(), "hanoman-idx-"));
|
|
18565
18634
|
const tmp = join4(dir2, "index");
|
|
18566
|
-
await copyFile(
|
|
18635
|
+
await copyFile(resolve8(wt, idx), tmp);
|
|
18567
18636
|
const env = { ...process.env, GIT_INDEX_FILE: tmp };
|
|
18568
18637
|
try {
|
|
18569
18638
|
await exec4("git", ["add", "-A", "-N"], { cwd: wt, env, ...GIT3 });
|
|
@@ -18687,7 +18756,7 @@ async function reviewFile(repoDir, specId, baseSha, branchFrom, path) {
|
|
|
18687
18756
|
let contentRaw = null;
|
|
18688
18757
|
if (status !== "D") {
|
|
18689
18758
|
try {
|
|
18690
|
-
contentRaw = await
|
|
18759
|
+
contentRaw = await readFile2(join4(wt, path), "utf8");
|
|
18691
18760
|
} catch {
|
|
18692
18761
|
contentRaw = null;
|
|
18693
18762
|
}
|
|
@@ -21300,7 +21369,7 @@ function renderDocPdf(text, name2, meta) {
|
|
|
21300
21369
|
});
|
|
21301
21370
|
const chunks = [];
|
|
21302
21371
|
doc.on("data", (c) => chunks.push(c));
|
|
21303
|
-
const done = new Promise((
|
|
21372
|
+
const done = new Promise((resolve15) => doc.on("end", () => resolve15(Buffer.concat(chunks))));
|
|
21304
21373
|
doc.font("Helvetica-Bold").fontSize(8).fillColor(BRASS).text(toWinAnsi(meta.eyebrow.toUpperCase()), { characterSpacing: 0.8 });
|
|
21305
21374
|
doc.moveDown(0.25);
|
|
21306
21375
|
doc.font("Helvetica-Bold").fontSize(17).fillColor(STRONG).text(toWinAnsi(name2.split("/").pop() ?? name2));
|
|
@@ -22009,16 +22078,16 @@ import { promisify as promisify7 } from "node:util";
|
|
|
22009
22078
|
// src/services/git-ide.ts
|
|
22010
22079
|
import { execFile as execFile6 } from "node:child_process";
|
|
22011
22080
|
import { promisify as promisify6 } from "node:util";
|
|
22012
|
-
import { readFile as
|
|
22081
|
+
import { readFile as readFile3, writeFile, mkdir } from "node:fs/promises";
|
|
22013
22082
|
import { existsSync as existsSync6 } from "node:fs";
|
|
22014
|
-
import { resolve as
|
|
22083
|
+
import { resolve as resolve9, sep as sep2, dirname as dirname8 } from "node:path";
|
|
22015
22084
|
var exec6 = promisify6(execFile6);
|
|
22016
22085
|
var GIT5 = { maxBuffer: 1 << 24 };
|
|
22017
22086
|
var MAX2 = 256 * 1024;
|
|
22018
22087
|
var splitZ2 = (s2) => s2.split("\0").filter(Boolean);
|
|
22019
22088
|
function repoAbsPath(repoDir, rel) {
|
|
22020
22089
|
if (rel.split(/[\\/]/).includes(".git")) throw new Error("tidak boleh menyentuh .git");
|
|
22021
|
-
const abs =
|
|
22090
|
+
const abs = resolve9(repoDir, rel);
|
|
22022
22091
|
if (abs !== repoDir && !abs.startsWith(repoDir + sep2)) throw new Error("path keluar dari repo");
|
|
22023
22092
|
return abs;
|
|
22024
22093
|
}
|
|
@@ -22036,7 +22105,7 @@ async function readRepoFile(repoDir, rel, ref = "") {
|
|
|
22036
22105
|
repoAbsPath(repoDir, rel);
|
|
22037
22106
|
let raw;
|
|
22038
22107
|
try {
|
|
22039
|
-
raw = ref ? (await exec6("git", ["show", `${ref}:${rel}`], { cwd: repoDir, ...GIT5 })).stdout : await
|
|
22108
|
+
raw = ref ? (await exec6("git", ["show", `${ref}:${rel}`], { cwd: repoDir, ...GIT5 })).stdout : await readFile3(repoAbsPath(repoDir, rel), "utf8");
|
|
22040
22109
|
} catch {
|
|
22041
22110
|
return null;
|
|
22042
22111
|
}
|
|
@@ -22437,7 +22506,7 @@ async function workingFileDiff(repoDir, path, staged) {
|
|
|
22437
22506
|
let contentRaw = null;
|
|
22438
22507
|
if (cf.status !== "D") {
|
|
22439
22508
|
try {
|
|
22440
|
-
contentRaw = staged ? (await exec6("git", ["show", `:${path}`], { cwd: repoDir, ...GIT5 })).stdout : await
|
|
22509
|
+
contentRaw = staged ? (await exec6("git", ["show", `:${path}`], { cwd: repoDir, ...GIT5 })).stdout : await readFile3(repoAbsPath(repoDir, path), "utf8");
|
|
22441
22510
|
} catch {
|
|
22442
22511
|
contentRaw = null;
|
|
22443
22512
|
}
|
|
@@ -22903,11 +22972,11 @@ async function finishGraphOp(reply, id, repoDir, r, verb) {
|
|
|
22903
22972
|
// src/routes/fs.ts
|
|
22904
22973
|
import { readdir } from "node:fs/promises";
|
|
22905
22974
|
import { homedir as homedir3 } from "node:os";
|
|
22906
|
-
import { resolve as
|
|
22975
|
+
import { resolve as resolve10, dirname as dirname9, join as join5 } from "node:path";
|
|
22907
22976
|
async function fs_default(app2) {
|
|
22908
22977
|
app2.get("/fs/browse", async (req, reply) => {
|
|
22909
22978
|
const q = req.query.path;
|
|
22910
|
-
const dir2 = q && q.trim() ?
|
|
22979
|
+
const dir2 = q && q.trim() ? resolve10(q.trim()) : homedir3();
|
|
22911
22980
|
try {
|
|
22912
22981
|
const ents = await readdir(dir2, { withFileTypes: true });
|
|
22913
22982
|
const entries = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join5(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -23043,11 +23112,11 @@ async function startSpecSession(spec, opts) {
|
|
|
23043
23112
|
}
|
|
23044
23113
|
|
|
23045
23114
|
// src/services/session-worktree.ts
|
|
23046
|
-
import { resolve as
|
|
23115
|
+
import { resolve as resolve11, sep as sep3 } from "node:path";
|
|
23047
23116
|
function ownsWorktree(repoDir, cwd) {
|
|
23048
|
-
const base2 =
|
|
23049
|
-
const dir2 =
|
|
23050
|
-
const container =
|
|
23117
|
+
const base2 = resolve11(repoDir);
|
|
23118
|
+
const dir2 = resolve11(cwd);
|
|
23119
|
+
const container = resolve11(base2, ".worktrees");
|
|
23051
23120
|
return dir2 !== base2 && dir2 !== container && dir2.startsWith(container + sep3);
|
|
23052
23121
|
}
|
|
23053
23122
|
|
|
@@ -23548,14 +23617,14 @@ function sshExec(t, remoteCmd, opts = {}) {
|
|
|
23548
23617
|
SSH_ASKPASS_REQUIRE: "force",
|
|
23549
23618
|
HANOMAN_SSH_PASSWORD: opts.password
|
|
23550
23619
|
} : process.env;
|
|
23551
|
-
return new Promise((
|
|
23620
|
+
return new Promise((resolve15) => {
|
|
23552
23621
|
const p = spawn3(sshBin(), args, { stdio: ["pipe", "pipe", "pipe"], env });
|
|
23553
23622
|
let out3 = "";
|
|
23554
23623
|
const timer7 = setTimeout(() => p.kill("SIGKILL"), opts.timeoutMs ?? 6e4);
|
|
23555
23624
|
const done = (r) => {
|
|
23556
23625
|
clearTimeout(timer7);
|
|
23557
23626
|
if (askpass) rmSync4(dirname10(askpass), { recursive: true, force: true });
|
|
23558
|
-
|
|
23627
|
+
resolve15(r);
|
|
23559
23628
|
};
|
|
23560
23629
|
p.stdout.on("data", (d) => {
|
|
23561
23630
|
out3 += d;
|
|
@@ -24332,7 +24401,7 @@ init_src();
|
|
|
24332
24401
|
init_src();
|
|
24333
24402
|
init_config2();
|
|
24334
24403
|
import { readFileSync as readFileSync11 } from "node:fs";
|
|
24335
|
-
import { resolve as
|
|
24404
|
+
import { resolve as resolve12, dirname as dirname12 } from "node:path";
|
|
24336
24405
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
24337
24406
|
var UPDATE_COMMAND = "npm i -g hanoman@latest";
|
|
24338
24407
|
function composeUpdate(x) {
|
|
@@ -24358,7 +24427,7 @@ var lastLatest = null;
|
|
|
24358
24427
|
var lastStatus = "unavailable";
|
|
24359
24428
|
function runningVersion() {
|
|
24360
24429
|
const here = dirname12(fileURLToPath3(import.meta.url));
|
|
24361
|
-
for (const p of [
|
|
24430
|
+
for (const p of [resolve12(here, "build-info.json"), resolve12(here, "../package.json")]) {
|
|
24362
24431
|
try {
|
|
24363
24432
|
const v = JSON.parse(readFileSync11(p, "utf8")).version;
|
|
24364
24433
|
if (typeof v === "string" && v) return v;
|
|
@@ -24707,8 +24776,8 @@ init_conflicts();
|
|
|
24707
24776
|
// src/services/uploads.ts
|
|
24708
24777
|
init_config2();
|
|
24709
24778
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
24710
|
-
import { mkdir as mkdir2, writeFile as writeFile2, readFile as
|
|
24711
|
-
import { join as join12, resolve as
|
|
24779
|
+
import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile4, unlink } from "node:fs/promises";
|
|
24780
|
+
import { join as join12, resolve as resolve13 } from "node:path";
|
|
24712
24781
|
var EXT = {
|
|
24713
24782
|
"image/png": ".png",
|
|
24714
24783
|
"image/jpeg": ".jpg",
|
|
@@ -24718,7 +24787,7 @@ function extFor(mimeType) {
|
|
|
24718
24787
|
return EXT[mimeType] ?? ".bin";
|
|
24719
24788
|
}
|
|
24720
24789
|
function uploadDir() {
|
|
24721
|
-
return
|
|
24790
|
+
return resolve13(effectiveStr("HANOMAN_UPLOAD_DIR") ?? join12(process.cwd(), "data", "uploads"));
|
|
24722
24791
|
}
|
|
24723
24792
|
async function saveUpload(buf, mimeType) {
|
|
24724
24793
|
const dir2 = uploadDir();
|
|
@@ -24729,13 +24798,13 @@ async function saveUpload(buf, mimeType) {
|
|
|
24729
24798
|
}
|
|
24730
24799
|
async function readUpload(storageKey) {
|
|
24731
24800
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
24732
|
-
return
|
|
24801
|
+
return readFile4(join12(uploadDir(), safe));
|
|
24733
24802
|
}
|
|
24734
24803
|
async function readUploadOrFetch(storageKey) {
|
|
24735
24804
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
24736
24805
|
const target2 = join12(uploadDir(), safe);
|
|
24737
24806
|
try {
|
|
24738
|
-
return await
|
|
24807
|
+
return await readFile4(target2);
|
|
24739
24808
|
} catch {
|
|
24740
24809
|
const base2 = effectiveStr("SYNC_SERVER_URL");
|
|
24741
24810
|
const token = effectiveStr("SYNC_DEVICE_TOKEN");
|
|
@@ -24882,11 +24951,11 @@ import { randomUUID as randomUUID5 } from "node:crypto";
|
|
|
24882
24951
|
// src/services/transcript-store.ts
|
|
24883
24952
|
init_config2();
|
|
24884
24953
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
24885
|
-
import { mkdir as mkdir3, writeFile as writeFile3, readFile as
|
|
24886
|
-
import { join as join13, resolve as
|
|
24954
|
+
import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile5, unlink as unlink2 } from "node:fs/promises";
|
|
24955
|
+
import { join as join13, resolve as resolve14, basename as basename2 } from "node:path";
|
|
24887
24956
|
var MAX_TRANSCRIPT_BYTES = 1024 * 1024;
|
|
24888
24957
|
function transcriptDir() {
|
|
24889
|
-
return
|
|
24958
|
+
return resolve14(effectiveStr("HANOMAN_TRANSCRIPT_DIR") ?? join13(process.cwd(), "data", "transcripts"));
|
|
24890
24959
|
}
|
|
24891
24960
|
function clamp(text) {
|
|
24892
24961
|
const buf = Buffer.from(text, "utf8");
|
|
@@ -24910,7 +24979,7 @@ async function saveTranscript(text) {
|
|
|
24910
24979
|
async function readTranscript(key) {
|
|
24911
24980
|
if (!key) return null;
|
|
24912
24981
|
try {
|
|
24913
|
-
return await
|
|
24982
|
+
return await readFile5(join13(transcriptDir(), basename2(key)), "utf8");
|
|
24914
24983
|
} catch {
|
|
24915
24984
|
return null;
|
|
24916
24985
|
}
|
|
@@ -25938,7 +26007,7 @@ function leadFailureReason(agent, timeoutMs, err, stdout, stderr) {
|
|
|
25938
26007
|
function think(prompt, o) {
|
|
25939
26008
|
const bin = binFor(o.agent);
|
|
25940
26009
|
const args = leadArgv({ agent: o.agent, model: o.model, effort: o.effort, prompt });
|
|
25941
|
-
return new Promise((
|
|
26010
|
+
return new Promise((resolve15, reject2) => {
|
|
25942
26011
|
const child = execFile9(bin, args, {
|
|
25943
26012
|
cwd: o.cwd,
|
|
25944
26013
|
timeout: o.timeoutMs,
|
|
@@ -25951,7 +26020,7 @@ function think(prompt, o) {
|
|
|
25951
26020
|
reject2(new Error(leadFailureReason(o.agent, o.timeoutMs, err, stdout, stderr)));
|
|
25952
26021
|
return;
|
|
25953
26022
|
}
|
|
25954
|
-
|
|
26023
|
+
resolve15(stdout);
|
|
25955
26024
|
});
|
|
25956
26025
|
child.stdin?.end();
|
|
25957
26026
|
});
|
|
@@ -25988,9 +26057,9 @@ function acquire(cap, waitMs) {
|
|
|
25988
26057
|
return Promise.resolve();
|
|
25989
26058
|
}
|
|
25990
26059
|
const startedAt = Date.now();
|
|
25991
|
-
return new Promise((
|
|
26060
|
+
return new Promise((resolve15, reject2) => {
|
|
25992
26061
|
const w = {
|
|
25993
|
-
grant:
|
|
26062
|
+
grant: resolve15,
|
|
25994
26063
|
deny: reject2,
|
|
25995
26064
|
timer: setTimeout(() => {
|
|
25996
26065
|
const i = queue.indexOf(w);
|
|
@@ -26507,6 +26576,7 @@ var CODEX_FINISHED = [
|
|
|
26507
26576
|
/\btokens used\b/i,
|
|
26508
26577
|
/\bTo continue this session\b/i
|
|
26509
26578
|
];
|
|
26579
|
+
var AGENT_TURN_LINE = /(?:\bfor\s+\d+\s*[hms]\b|\(\s*\d+\s*[hms][^)]*·)/i;
|
|
26510
26580
|
var ASK_SIGNALS = [
|
|
26511
26581
|
/\?\s*$/m,
|
|
26512
26582
|
/^\s*\[?\d+[).\]]\s+\S/m,
|
|
@@ -26515,9 +26585,12 @@ var ASK_SIGNALS = [
|
|
|
26515
26585
|
function readPaneQuestion(text, agent) {
|
|
26516
26586
|
const lines2 = tail2(text, 40);
|
|
26517
26587
|
const body = lines2.join("\n").trim();
|
|
26518
|
-
const
|
|
26588
|
+
const dialog = readChoiceDialog(text);
|
|
26589
|
+
const choices = dialog?.options ?? [];
|
|
26519
26590
|
if (!body) return { asking: false, question: "", reason: "layar kosong", choices };
|
|
26520
26591
|
const question = tail2(text, 25).join("\n").trim();
|
|
26592
|
+
if (!dialog && AGENT_TURN_LINE.test(body))
|
|
26593
|
+
return { asking: false, question, reason: "giliran agen, bukan pertanyaan (baris giliran claude di layar)", choices };
|
|
26521
26594
|
if (agent === "codex") {
|
|
26522
26595
|
const finished = CODEX_FINISHED.find((re) => re.test(body));
|
|
26523
26596
|
if (finished) return { asking: false, question, reason: "sesi codex selesai wajar (ADR-0074)", choices };
|
|
@@ -26532,14 +26605,20 @@ var answers = /* @__PURE__ */ new Map();
|
|
|
26532
26605
|
var capped = /* @__PURE__ */ new Set();
|
|
26533
26606
|
var failures = /* @__PURE__ */ new Map();
|
|
26534
26607
|
var failCapped = /* @__PURE__ */ new Set();
|
|
26608
|
+
var failedAt = /* @__PURE__ */ new Map();
|
|
26609
|
+
var FAIL_COOLDOWN_MS = 15 * 6e4;
|
|
26610
|
+
var chainFlows = /* @__PURE__ */ new Map();
|
|
26535
26611
|
var MAX_CHAIN_STEPS = 6;
|
|
26536
26612
|
var CHAIN_POLL_MS = 300;
|
|
26537
26613
|
var CHAIN_POLL_TRIES = 20;
|
|
26614
|
+
var CHAIN_END_TRIES = 5;
|
|
26538
26615
|
function resetSession(sessionId2) {
|
|
26539
26616
|
answers.delete(sessionId2);
|
|
26540
26617
|
capped.delete(sessionId2);
|
|
26541
26618
|
failures.delete(sessionId2);
|
|
26542
26619
|
failCapped.delete(sessionId2);
|
|
26620
|
+
failedAt.delete(sessionId2);
|
|
26621
|
+
chainFlows.delete(sessionId2);
|
|
26543
26622
|
}
|
|
26544
26623
|
var prodDetectDeps = {
|
|
26545
26624
|
live: () => {
|
|
@@ -26577,6 +26656,12 @@ var prodDetectDeps = {
|
|
|
26577
26656
|
},
|
|
26578
26657
|
submit: (id) => submitPaneDialog(id),
|
|
26579
26658
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
26659
|
+
// Alur yang gagal ditutup bukan alasan menggagalkan rantai yang sudah berhasil: penyapu TTL
|
|
26660
|
+
// (ADR-0102) tetap menutupnya.
|
|
26661
|
+
closeChain: async (flowId) => {
|
|
26662
|
+
await closeFlow(flowId, "submit").catch(() => null);
|
|
26663
|
+
},
|
|
26664
|
+
now: Date.now,
|
|
26580
26665
|
decide,
|
|
26581
26666
|
decideDeps: prodDecideDeps,
|
|
26582
26667
|
delivery: takeDelivery,
|
|
@@ -26602,7 +26687,7 @@ async function scanAndAnswer(deps = prodDetectDeps) {
|
|
|
26602
26687
|
skip("lead dijeda untuk project ini");
|
|
26603
26688
|
continue;
|
|
26604
26689
|
}
|
|
26605
|
-
if (!deps.filled(s2.decisionFile)) continue;
|
|
26690
|
+
if (!deps.filled(s2.decisionFile) && !(!deps.exited(s2.id) && readDialogScreen(deps.pane(s2.id)))) continue;
|
|
26606
26691
|
if (deps.exited(s2.id)) {
|
|
26607
26692
|
skip("pane mati");
|
|
26608
26693
|
continue;
|
|
@@ -26635,6 +26720,12 @@ async function scanAndAnswer(deps = prodDetectDeps) {
|
|
|
26635
26720
|
skip("batas jawaban otomatis tercapai");
|
|
26636
26721
|
continue;
|
|
26637
26722
|
}
|
|
26723
|
+
const lastFail = failedAt.get(s2.id);
|
|
26724
|
+
if (lastFail !== void 0 && deps.now() - lastFail > FAIL_COOLDOWN_MS) {
|
|
26725
|
+
failures.delete(s2.id);
|
|
26726
|
+
failCapped.delete(s2.id);
|
|
26727
|
+
failedAt.delete(s2.id);
|
|
26728
|
+
}
|
|
26638
26729
|
if ((failures.get(s2.id) ?? 0) >= cfg.maxAutoAnswers) {
|
|
26639
26730
|
if (!failCapped.has(s2.id)) {
|
|
26640
26731
|
failCapped.add(s2.id);
|
|
@@ -26680,15 +26771,27 @@ async function scanAndAnswer(deps = prodDetectDeps) {
|
|
|
26680
26771
|
out3.answered.push(s2.id);
|
|
26681
26772
|
return;
|
|
26682
26773
|
}
|
|
26683
|
-
if (chain.failed)
|
|
26774
|
+
if (chain.failed) {
|
|
26775
|
+
failures.set(s2.id, (failures.get(s2.id) ?? 0) + 1);
|
|
26776
|
+
failedAt.set(s2.id, deps.now());
|
|
26777
|
+
}
|
|
26684
26778
|
out3.skipped.push({ id: s2.id, reason: chain.reason });
|
|
26685
26779
|
});
|
|
26686
26780
|
return out3;
|
|
26687
26781
|
}
|
|
26688
26782
|
async function runChain(s2, agent, deps) {
|
|
26689
26783
|
let acted = false;
|
|
26784
|
+
const done = async (r) => {
|
|
26785
|
+
const flowId = chainFlows.get(s2.id);
|
|
26786
|
+
if (flowId) {
|
|
26787
|
+
chainFlows.delete(s2.id);
|
|
26788
|
+
await deps.closeChain(flowId).catch(() => {
|
|
26789
|
+
});
|
|
26790
|
+
}
|
|
26791
|
+
return r;
|
|
26792
|
+
};
|
|
26690
26793
|
for (let step = 0; step < MAX_CHAIN_STEPS; step++) {
|
|
26691
|
-
const text = deps.pane(s2.id);
|
|
26794
|
+
const text = step === 0 ? deps.pane(s2.id) : await settledPane(s2.id, deps);
|
|
26692
26795
|
const screen = readDialogScreen(text);
|
|
26693
26796
|
if (screen?.kind === "review") {
|
|
26694
26797
|
if (!await deps.submit(s2.id))
|
|
@@ -26696,10 +26799,10 @@ async function runChain(s2, agent, deps) {
|
|
|
26696
26799
|
acted = true;
|
|
26697
26800
|
continue;
|
|
26698
26801
|
}
|
|
26699
|
-
if (step > 0 && !screen) return { acted, done: true, failed: false, reason: "" };
|
|
26802
|
+
if (step > 0 && !screen) return done({ acted, done: true, failed: false, reason: "" });
|
|
26700
26803
|
const read = readPaneQuestion(text, agent);
|
|
26701
26804
|
if (!read.asking) {
|
|
26702
|
-
return step === 0 ? { acted, done: false, failed: false, reason: read.reason } : { acted, done: true, failed: false, reason: "" };
|
|
26805
|
+
return step === 0 ? { acted, done: false, failed: false, reason: read.reason } : done({ acted, done: true, failed: false, reason: "" });
|
|
26703
26806
|
}
|
|
26704
26807
|
const notes = [`Sesi ini menunggu di terminal; teks di bawah adalah layar terakhirnya. Jawablah sebagai masukan yang bisa langsung diketik ke terminal itu (isi \`reply\`).`];
|
|
26705
26808
|
if (read.choices.length) {
|
|
@@ -26711,22 +26814,32 @@ async function runChain(s2, agent, deps) {
|
|
|
26711
26814
|
`Dialog ini BERANTAI: ${screen.tabs.length} pertanyaan dalam satu tanya (${screen.tabs.map((t) => `${t.answered ? "sudah" : "belum"}: ${t.header}`).join(", ")}). Yang sedang tampil pertanyaan ke-${at + 1}; jawab HANYA pertanyaan itu \u2014 sisanya akan ditanyakan sesudah ini.`
|
|
26712
26815
|
);
|
|
26713
26816
|
}
|
|
26817
|
+
const ask = {
|
|
26818
|
+
projectId: s2.projectId,
|
|
26819
|
+
specId: s2.specId,
|
|
26820
|
+
sessionId: s2.id,
|
|
26821
|
+
gate: "detected",
|
|
26822
|
+
kind: "answer",
|
|
26823
|
+
question: read.question,
|
|
26824
|
+
options: read.choices.length ? read.choices : void 0,
|
|
26825
|
+
notes,
|
|
26826
|
+
chain: true
|
|
26827
|
+
};
|
|
26714
26828
|
let row;
|
|
26715
26829
|
try {
|
|
26716
|
-
row = await deps.decide({
|
|
26717
|
-
projectId: s2.projectId,
|
|
26718
|
-
specId: s2.specId,
|
|
26719
|
-
sessionId: s2.id,
|
|
26720
|
-
gate: "detected",
|
|
26721
|
-
kind: "answer",
|
|
26722
|
-
question: read.question,
|
|
26723
|
-
options: read.choices.length ? read.choices : void 0,
|
|
26724
|
-
notes
|
|
26725
|
-
}, deps.decideDeps);
|
|
26830
|
+
row = await deps.decide({ ...ask, flowId: chainFlows.get(s2.id) ?? null }, deps.decideDeps);
|
|
26726
26831
|
} catch (e) {
|
|
26727
26832
|
if (e instanceof LeadBusyError) return { acted, done: false, failed: false, reason: `lead penuh \u2014 ${e.message}` };
|
|
26728
|
-
throw e;
|
|
26833
|
+
if (!(e instanceof LeadFlowClosedError)) throw e;
|
|
26834
|
+
chainFlows.delete(s2.id);
|
|
26835
|
+
try {
|
|
26836
|
+
row = await deps.decide({ ...ask, flowId: null }, deps.decideDeps);
|
|
26837
|
+
} catch (e2) {
|
|
26838
|
+
if (e2 instanceof LeadBusyError) return { acted, done: false, failed: false, reason: `lead penuh \u2014 ${e2.message}` };
|
|
26839
|
+
throw e2;
|
|
26840
|
+
}
|
|
26729
26841
|
}
|
|
26842
|
+
if (row?.flowId) chainFlows.set(s2.id, row.flowId);
|
|
26730
26843
|
if (!row) return { acted, done: false, failed: false, reason: "lead tak menghasilkan keputusan yang berlaku" };
|
|
26731
26844
|
if (row.status !== "berlaku")
|
|
26732
26845
|
return { acted, done: false, failed: true, reason: "lead tak menghasilkan keputusan yang berlaku" };
|
|
@@ -26736,12 +26849,20 @@ async function runChain(s2, agent, deps) {
|
|
|
26736
26849
|
if (!await deps.send(s2.id, reply, picked))
|
|
26737
26850
|
return { acted, done: false, failed: true, reason: "gagal mengetik ke pane" };
|
|
26738
26851
|
acted = true;
|
|
26739
|
-
if (!screen) return { acted, done: true, failed: false, reason: "" };
|
|
26852
|
+
if (!screen) return done({ acted, done: true, failed: false, reason: "" });
|
|
26740
26853
|
if (!await waitScreenChange(s2.id, dialogKey(text), deps))
|
|
26741
26854
|
return { acted, done: false, failed: true, reason: "layar dialog tak berubah sesudah dijawab" };
|
|
26742
26855
|
}
|
|
26743
26856
|
return { acted, done: false, failed: true, reason: "batas langkah rantai dialog tercapai" };
|
|
26744
26857
|
}
|
|
26858
|
+
async function settledPane(id, deps) {
|
|
26859
|
+
let text = deps.pane(id);
|
|
26860
|
+
for (let i = 0; i < CHAIN_END_TRIES && !readDialogScreen(text); i++) {
|
|
26861
|
+
await deps.sleep(CHAIN_POLL_MS);
|
|
26862
|
+
text = deps.pane(id);
|
|
26863
|
+
}
|
|
26864
|
+
return text;
|
|
26865
|
+
}
|
|
26745
26866
|
async function waitScreenChange(id, before, deps) {
|
|
26746
26867
|
for (let i = 0; i < CHAIN_POLL_TRIES; i++) {
|
|
26747
26868
|
await deps.sleep(CHAIN_POLL_MS);
|
|
@@ -26755,6 +26876,8 @@ function sweep(liveIds) {
|
|
|
26755
26876
|
for (const id of [...capped]) if (!live.has(id)) capped.delete(id);
|
|
26756
26877
|
for (const id of [...failures.keys()]) if (!live.has(id)) failures.delete(id);
|
|
26757
26878
|
for (const id of [...failCapped]) if (!live.has(id)) failCapped.delete(id);
|
|
26879
|
+
for (const id of [...failedAt.keys()]) if (!live.has(id)) failedAt.delete(id);
|
|
26880
|
+
for (const id of [...chainFlows.keys()]) if (!live.has(id)) chainFlows.delete(id);
|
|
26758
26881
|
}
|
|
26759
26882
|
|
|
26760
26883
|
// src/services/lead/pulse.ts
|
|
@@ -27662,7 +27785,7 @@ function issuesFromRest(raw) {
|
|
|
27662
27785
|
}
|
|
27663
27786
|
var GH_FIELDS = "number,title,body,author,labels,url,state,createdAt,updatedAt";
|
|
27664
27787
|
var API2 = "https://api.github.com";
|
|
27665
|
-
var defaultRunGh = (args, env) => new Promise((
|
|
27788
|
+
var defaultRunGh = (args, env) => new Promise((resolve15, reject2) => {
|
|
27666
27789
|
execFile10(
|
|
27667
27790
|
args[0],
|
|
27668
27791
|
args.slice(1),
|
|
@@ -27670,7 +27793,7 @@ var defaultRunGh = (args, env) => new Promise((resolve14, reject2) => {
|
|
|
27670
27793
|
(err, stdout, stderr) => {
|
|
27671
27794
|
const e = err;
|
|
27672
27795
|
if (e && (e.code === "ENOENT" || e.code === "EACCES")) return reject2(e);
|
|
27673
|
-
|
|
27796
|
+
resolve15({ code: err ? Number(err.code ?? 1) : 0, stdout, stderr });
|
|
27674
27797
|
}
|
|
27675
27798
|
);
|
|
27676
27799
|
});
|
|
@@ -27989,8 +28112,42 @@ import { randomBytes as randomBytes5 } from "node:crypto";
|
|
|
27989
28112
|
// src/services/telegram/credentials.ts
|
|
27990
28113
|
init_src();
|
|
27991
28114
|
init_config2();
|
|
28115
|
+
init_agent_token();
|
|
28116
|
+
init_settings2();
|
|
28117
|
+
init_bootstrap();
|
|
27992
28118
|
init_client();
|
|
27993
28119
|
init_protocol();
|
|
28120
|
+
init_runtime();
|
|
28121
|
+
async function verifyTelegramAgentToken(token, deps = {}) {
|
|
28122
|
+
const agent = await (deps.verify ?? verifyAgentToken)(token);
|
|
28123
|
+
if (!agent) {
|
|
28124
|
+
return {
|
|
28125
|
+
ok: false,
|
|
28126
|
+
missing: [...TELEGRAM_REQUIRED_CAPABILITIES],
|
|
28127
|
+
reason: "AgentToken tidak dikenal atau sudah dicabut \u2014 salin plaintext `hnm_agt_\u2026` dari Akses AI Agent, bukan hash-nya."
|
|
28128
|
+
};
|
|
28129
|
+
}
|
|
28130
|
+
const missing = TELEGRAM_REQUIRED_CAPABILITIES.filter((c) => !agent.capabilities.includes(c));
|
|
28131
|
+
return missing.length ? { ok: false, missing, reason: `AgentToken kurang ${missing.length} capability.` } : { ok: true };
|
|
28132
|
+
}
|
|
28133
|
+
async function telegramInboundReadiness(deps = {}) {
|
|
28134
|
+
const polling = deps.polling ?? telegramRuntimeStatus().running;
|
|
28135
|
+
const empty = [];
|
|
28136
|
+
const token = deps.agentToken ?? effectiveStr("HANOMAN_TELEGRAM_AGENT_TOKEN")?.trim();
|
|
28137
|
+
if (!token) {
|
|
28138
|
+
return { ok: false, reason: "AgentToken gateway belum diisi.", missingCapabilities: [...TELEGRAM_REQUIRED_CAPABILITIES], polling };
|
|
28139
|
+
}
|
|
28140
|
+
const setting = await (deps.getSetting ?? getSetting)();
|
|
28141
|
+
if (!setting.agentAccessEnabled) {
|
|
28142
|
+
return { ok: false, reason: "Akses agent mati \u2014 nyalakan master switch di Akses AI Agent.", missingCapabilities: empty, polling };
|
|
28143
|
+
}
|
|
28144
|
+
const gate = await verifyTelegramAgentToken(token, deps);
|
|
28145
|
+
if (!gate.ok) return { ok: false, reason: gate.reason, missingCapabilities: gate.missing, polling };
|
|
28146
|
+
if (!polling) {
|
|
28147
|
+
return { ok: false, reason: "Kredensial sudah sah tapi gateway belum polling \u2014 nyalakan \u201CGateway aktif\u201D.", missingCapabilities: empty, polling };
|
|
28148
|
+
}
|
|
28149
|
+
return { ok: true, reason: null, missingCapabilities: empty, polling };
|
|
28150
|
+
}
|
|
27994
28151
|
var KEYS = TELEGRAM_CONFIG_KEYS;
|
|
27995
28152
|
function telegramCredentialView() {
|
|
27996
28153
|
return {
|
|
@@ -28008,7 +28165,7 @@ function telegramCredentialView() {
|
|
|
28008
28165
|
})
|
|
28009
28166
|
};
|
|
28010
28167
|
}
|
|
28011
|
-
async function saveTelegramCredentials(patch) {
|
|
28168
|
+
async function saveTelegramCredentials(patch, deps = {}) {
|
|
28012
28169
|
const writes = [];
|
|
28013
28170
|
for (const [key, raw] of Object.entries(patch)) {
|
|
28014
28171
|
if (!KEYS.includes(key)) return { ok: false, key, error: "key bukan kredensial Telegram" };
|
|
@@ -28022,6 +28179,14 @@ async function saveTelegramCredentials(patch) {
|
|
|
28022
28179
|
if (!parsed.ok) return { ok: false, key, error: parsed.error };
|
|
28023
28180
|
writes.push([key, parsed.value]);
|
|
28024
28181
|
}
|
|
28182
|
+
const agentToken = writes.find(([key]) => key === "HANOMAN_TELEGRAM_AGENT_TOKEN")?.[1];
|
|
28183
|
+
if (agentToken !== void 0) {
|
|
28184
|
+
const gate = await verifyTelegramAgentToken(agentToken, deps);
|
|
28185
|
+
if (!gate.ok) {
|
|
28186
|
+
const detail = gate.missing.length < TELEGRAM_REQUIRED_CAPABILITIES.length ? `${gate.reason} Kurang: ${gate.missing.join(", ")}.` : gate.reason;
|
|
28187
|
+
return { ok: false, key: "HANOMAN_TELEGRAM_AGENT_TOKEN", error: detail };
|
|
28188
|
+
}
|
|
28189
|
+
}
|
|
28025
28190
|
for (const [key, value] of writes) await setConfig(key, value);
|
|
28026
28191
|
return { ok: true };
|
|
28027
28192
|
}
|
|
@@ -28036,11 +28201,12 @@ async function clearTelegramCredentials() {
|
|
|
28036
28201
|
return { cleared, envFallback: KEYS.filter((k) => sourceOf(k) === "env") };
|
|
28037
28202
|
}
|
|
28038
28203
|
async function testTelegramConnection(deps = {}) {
|
|
28204
|
+
const inbound = await telegramInboundReadiness(deps);
|
|
28039
28205
|
const botToken = deps.botToken ?? effectiveStr("HANOMAN_TELEGRAM_BOT_TOKEN");
|
|
28040
|
-
if (!botToken) return { ok: false, error: "Bot token belum diisi." };
|
|
28206
|
+
if (!botToken) return { ok: false, error: "Bot token belum diisi.", inbound };
|
|
28041
28207
|
const chatId = deps.chatId ?? resolveTestChatId();
|
|
28042
28208
|
if (!chatId) {
|
|
28043
|
-
return { ok: false, error: "Isi Chat / Channel ID target \u2014 atau isi allowlist dengan tepat satu user id." };
|
|
28209
|
+
return { ok: false, inbound, error: "Isi Chat / Channel ID target \u2014 atau isi allowlist dengan tepat satu user id." };
|
|
28044
28210
|
}
|
|
28045
28211
|
const timeoutMs = deps.timeoutMs ?? 1e4;
|
|
28046
28212
|
const controller = new AbortController();
|
|
@@ -28052,11 +28218,11 @@ async function testTelegramConnection(deps = {}) {
|
|
|
28052
28218
|
const me = await client.getMe();
|
|
28053
28219
|
const stamp2 = (deps.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
28054
28220
|
await client.sendMessage({ chatId, text: `hanoman: uji koneksi Telegram berhasil (${stamp2}).` });
|
|
28055
|
-
return { ok: true, botUsername: me.username ?? null, chatId };
|
|
28221
|
+
return { ok: true, botUsername: me.username ?? null, chatId, inbound };
|
|
28056
28222
|
} catch (error) {
|
|
28057
28223
|
const raw = error.message || "gagal menghubungi Telegram";
|
|
28058
28224
|
const message = controller.signal.aborted ? `Timeout ${timeoutMs} ms \u2014 Telegram tidak menjawab.` : raw;
|
|
28059
|
-
return { ok: false, error: sanitizeTelegramOutput(message, [botToken]).slice(0, 500) };
|
|
28225
|
+
return { ok: false, inbound, error: sanitizeTelegramOutput(message, [botToken]).slice(0, 500) };
|
|
28060
28226
|
} finally {
|
|
28061
28227
|
clearTimeout(timer7);
|
|
28062
28228
|
}
|
|
@@ -28961,9 +29127,15 @@ var PUBLIC = /* @__PURE__ */ new Set([
|
|
|
28961
29127
|
"GET /api/health",
|
|
28962
29128
|
"GET /api/auth/status",
|
|
28963
29129
|
"POST /api/auth/login",
|
|
28964
|
-
"POST /api/auth/setup"
|
|
29130
|
+
"POST /api/auth/setup",
|
|
29131
|
+
// SPEC-489 · panduan AI agent. Sengaja tanpa auth: byte-nya sudah publik di GitHub, dan
|
|
29132
|
+
// "cukup diberi link + token" hanya benar bila link-nya terbaca SEBELUM token disetel —
|
|
29133
|
+
// menggerbanginya berarti agen yang capability-nya kurang menerima 403 pada dokumen yang
|
|
29134
|
+
// justru menjelaskan arti 403 itu.
|
|
29135
|
+
"GET /api/agent-integration.md"
|
|
28965
29136
|
]);
|
|
28966
|
-
function buildApp({ requireAuth = true } = {}) {
|
|
29137
|
+
function buildApp({ requireAuth = true, agentDocFile } = {}) {
|
|
29138
|
+
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname13(fileURLToPath4(import.meta.url)), process.env, existsSync12);
|
|
28967
29139
|
const app2 = Fastify({ logger: false, trustProxy: true });
|
|
28968
29140
|
app2.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => {
|
|
28969
29141
|
if (!body) return done(null, void 0);
|
|
@@ -29017,6 +29189,7 @@ function buildApp({ requireAuth = true } = {}) {
|
|
|
29017
29189
|
api.addHook("onResponse", auditTelegramGatewayResponse);
|
|
29018
29190
|
await api.register(auth_default);
|
|
29019
29191
|
await api.register(health_default);
|
|
29192
|
+
await api.register(agent_doc_default, { file: docFile });
|
|
29020
29193
|
await api.register(projects_default);
|
|
29021
29194
|
await api.register(specs_default);
|
|
29022
29195
|
await api.register(notifications_default);
|