hanoman 0.9.18 → 0.9.20
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 +63 -167
- package/dist/server.js +418 -639
- package/docs/agent-integration.md +9 -53
- package/package.json +1 -1
- package/prisma/migrations/20261007120000_project_memory/migration.sql +54 -0
- package/prisma/migrations/20261007130000_memory_sync/migration.sql +32 -0
- package/prisma/migrations/20261010070000_remove_memory_changelog/migration.sql +40 -0
- package/prisma/schema.prisma +3 -25
- package/web/assets/{DalangHanomanScreen-C1Ivdj4r.js → DalangHanomanScreen-DqzYitwv.js} +1 -1
- package/web/assets/{DocPreviewModal-Cw69hQk1.js → DocPreviewModal-Chk-jQ51.js} +1 -1
- package/web/assets/{DocsWorkspace--9rvckps.js → DocsWorkspace-DuNVIjTg.js} +1 -1
- package/web/assets/{IdeScreen-DxNG9eLb.js → IdeScreen-BZGVyFT5.js} +1 -1
- package/web/assets/{LeadScreen-CV3Lgbyd.js → LeadScreen-35VgddUo.js} +1 -1
- package/web/assets/{ReviewScreen-JLPOS52f.js → ReviewScreen-EkG8ywTU.js} +1 -1
- package/web/assets/{SchedulerScreen-UhxWcCtY.js → SchedulerScreen-ClaLLB8Y.js} +1 -1
- package/web/assets/SettingsScreen-liOlVCeT.js +62 -0
- package/web/assets/{TeamScreen-D24DsEwn.js → TeamScreen-Dzl6Ruju.js} +1 -1
- package/web/assets/{TerminalScreen-3nSnkwTy.js → TerminalScreen-CK3SzzgP.js} +1 -1
- package/web/assets/{TriageScreen-Dw6EqWar.js → TriageScreen-EtBRNg-t.js} +1 -1
- package/web/assets/{VpsScreen-C_rNhMi5.js → VpsScreen-9qeTHo1w.js} +1 -1
- package/web/assets/index-D5w059cP.js +1063 -0
- package/web/assets/{live-DHIFZVo0.js → live-Bz-jmiWI.js} +1 -1
- package/web/index.html +1 -1
- package/web/assets/ChangelogScreen-Bj1KreGh.js +0 -1
- package/web/assets/SettingsScreen-D3nVVnsZ.js +0 -62
- package/web/assets/index-DHVdBKua.js +0 -1053
package/dist/server.js
CHANGED
|
@@ -4273,13 +4273,14 @@ function stampSpecMethod(payload, methodId) {
|
|
|
4273
4273
|
if (typeof payload !== "object" || Array.isArray(payload)) return null;
|
|
4274
4274
|
return { ...payload, method: methodId };
|
|
4275
4275
|
}
|
|
4276
|
-
var DEFAULT_METHOD, VERIFICATION_GATE, MATT_CLAUSE, METHODS, METHOD_IDS, uniq, PLAN_DIRS, SPEC_DIRS;
|
|
4276
|
+
var DEFAULT_METHOD, VERIFICATION_GATE, MATT_CLAUSE, AGENT_SKILLS_CLAUSE, METHODS, METHOD_IDS, uniq, PLAN_DIRS, SPEC_DIRS;
|
|
4277
4277
|
var init_method_catalog = __esm({
|
|
4278
4278
|
"../shared/src/method-catalog.ts"() {
|
|
4279
4279
|
"use strict";
|
|
4280
4280
|
DEFAULT_METHOD = "superpowers";
|
|
4281
4281
|
VERIFICATION_GATE = "superpowers:verification-before-completion";
|
|
4282
4282
|
MATT_CLAUSE = "Skill mattpocock yang kontraknya mewawancarai manusia (`/grill-me`, `/to-spec`, `/triage`) JANGAN dipakai: pakai primitif model-invoked-nya untuk menguji rancanganmu sendiri, bukan untuk mewawancarai siapa pun. Hal yang masih ambigu dan akan mempengaruhi hasil tetap JANGAN diputuskan sendiri \u2014 bila kamu agen fase, daftarkan di `Keputusan terbuka:` laporanmu; bila tidak, ajukan lewat jalur tanya di klausa otonomi prompt ini. `to-tickets` di fase Plan dipakai HANYA sebagai penghasil berkas plan berkotak `- [ ]` di `docs/matt/plans/`, BUKAN penerbit tiket \u2014 hanoman sendiri adalah issue tracker-nya, jadi jangan menulis ke tracker eksternal mana pun.";
|
|
4283
|
+
AGENT_SKILLS_CLAUSE = "Pakai skill Agent Skills langsung sesuai fase, bukan slash-command atau meta-router `using-agent-skills`. Kontrak fase, instruksi project/pengguna, dan jalur keputusan/review hanoman tetap berlaku: `spec-driven-development` hanya menyusun spec pada fase Spec, jangan menjalankan Plan atau Execute dari fase itu. Simpan spec di `docs/agent-skills/specs/` dan plan beserta SELURUH checklist task `- [ ]` di `docs/agent-skills/plans/`, dengan id backlog pada nama berkas; ini menggantikan default `SPEC.md`, `tasks/plan.md`, dan `tasks/todo.md` upstream. Jangan menimpa plan lain yang belum tuntas atau membuat tiket di tracker eksternal: hanoman adalah issue tracker-nya. Centang task di berkas plan yang sama setelah implementasi dan verifikasi. Hal ambigu yang mempengaruhi hasil mengikuti jalur tanya prompt ini; bila kamu agen fase, daftarkan di `Keputusan terbuka:` laporanmu.";
|
|
4283
4284
|
METHODS = {
|
|
4284
4285
|
// Isi `PHASE_SKILLS` (SPEC-166) dipindah APA ADANYA. Objective & Spec adalah keluaran skill
|
|
4285
4286
|
// brainstorming yang di-invoke di fase Brainstorm — sengaja tak punya entri sendiri. Fase reverse
|
|
@@ -4360,6 +4361,44 @@ var init_method_catalog = __esm({
|
|
|
4360
4361
|
"codex plugin add superpowers@openai-curated"
|
|
4361
4362
|
]
|
|
4362
4363
|
}
|
|
4364
|
+
},
|
|
4365
|
+
"agent-skills": {
|
|
4366
|
+
id: "agent-skills",
|
|
4367
|
+
label: "Agent Skills (Addy Osmani)",
|
|
4368
|
+
planDir: "docs/agent-skills/plans",
|
|
4369
|
+
specDir: "docs/agent-skills/specs",
|
|
4370
|
+
phaseSkills: {
|
|
4371
|
+
Brainstorm: ["agent-skills:idea-refine"],
|
|
4372
|
+
Spec: ["agent-skills:spec-driven-development"],
|
|
4373
|
+
Audit: [
|
|
4374
|
+
"agent-skills:debugging-and-error-recovery",
|
|
4375
|
+
"agent-skills:code-review-and-quality"
|
|
4376
|
+
],
|
|
4377
|
+
Plan: ["agent-skills:planning-and-task-breakdown"],
|
|
4378
|
+
Execute: [
|
|
4379
|
+
"agent-skills:incremental-implementation",
|
|
4380
|
+
"agent-skills:test-driven-development",
|
|
4381
|
+
"agent-skills:code-review-and-quality"
|
|
4382
|
+
],
|
|
4383
|
+
Verifikasi: [VERIFICATION_GATE]
|
|
4384
|
+
},
|
|
4385
|
+
exitSkills: [VERIFICATION_GATE],
|
|
4386
|
+
extraClause: AGENT_SKILLS_CLAUSE,
|
|
4387
|
+
requires: ["agent-skills", "superpowers"],
|
|
4388
|
+
// Manifest upstream: marketplace Claude = addy-agent-skills, Codex = agent-skills.
|
|
4389
|
+
install: {
|
|
4390
|
+
claude: [
|
|
4391
|
+
"claude plugin marketplace add addyosmani/agent-skills",
|
|
4392
|
+
"claude plugin install agent-skills@addy-agent-skills",
|
|
4393
|
+
"claude plugin marketplace add obra/superpowers-marketplace",
|
|
4394
|
+
"claude plugin install superpowers@superpowers-marketplace"
|
|
4395
|
+
],
|
|
4396
|
+
codex: [
|
|
4397
|
+
"codex plugin marketplace add addyosmani/agent-skills",
|
|
4398
|
+
"codex plugin add agent-skills@agent-skills",
|
|
4399
|
+
"codex plugin add superpowers@openai-curated"
|
|
4400
|
+
]
|
|
4401
|
+
}
|
|
4363
4402
|
}
|
|
4364
4403
|
};
|
|
4365
4404
|
METHOD_IDS = Object.keys(METHODS);
|
|
@@ -5055,7 +5094,7 @@ function cmpVersion(a, b) {
|
|
|
5055
5094
|
}
|
|
5056
5095
|
return 0;
|
|
5057
5096
|
}
|
|
5058
|
-
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, zManualDone, zSpec, zSpecListItem, zSpecSlim, NOTIFY_SOUNDS, EFFORTS, MODELS, CLAUDE_DEFAULT_ALIAS, 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,
|
|
5097
|
+
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, zManualDone, zSpec, zSpecListItem, zSpecSlim, NOTIFY_SOUNDS, EFFORTS, MODELS, CLAUDE_DEFAULT_ALIAS, 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, zLeadEngine, zLead, LEAD_DEFAULTS, zPortalChat, PORTAL_CHAT_DEFAULTS, zPhaseCell, PHASE_OVERRIDE_LIMITS, zPhaseOverride, zPhaseOverrides, zFlowOrchestration, zOrchestration, ORCHESTRATION_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
|
|
5059
5098
|
var init_entities = __esm({
|
|
5060
5099
|
"../shared/src/entities.ts"() {
|
|
5061
5100
|
"use strict";
|
|
@@ -5247,7 +5286,6 @@ var init_entities = __esm({
|
|
|
5247
5286
|
effort: external_exports.string().default("xhigh")
|
|
5248
5287
|
});
|
|
5249
5288
|
CONFLICT_DEFAULTS = zConflict.parse({});
|
|
5250
|
-
CHANGELOG_ENGINE_DEFAULTS = zAgentEngine.parse({});
|
|
5251
5289
|
zLeadEngine = zAgentEngine;
|
|
5252
5290
|
zLead = external_exports.object({
|
|
5253
5291
|
enabled: external_exports.boolean().default(false),
|
|
@@ -5368,8 +5406,6 @@ var init_entities = __esm({
|
|
|
5368
5406
|
// SPEC-409 · ADR-0091 · hanoman-lead (default mati)
|
|
5369
5407
|
telegram: zTelegramSettings.default(TELEGRAM_DEFAULTS),
|
|
5370
5408
|
// SPEC-476 · ADR-0096 · gateway Telegram (default mati)
|
|
5371
|
-
changelog: zAgentEngine.default(CHANGELOG_ENGINE_DEFAULTS),
|
|
5372
|
-
// SPEC-518 · agen pembuat changelog (opt-in, mati)
|
|
5373
5409
|
portalChat: zPortalChat.default(PORTAL_CHAT_DEFAULTS),
|
|
5374
5410
|
// SPEC-854 · ADR-0130 · chat portal klien (opt-in, mati)
|
|
5375
5411
|
orchestration: zOrchestration.default(ORCHESTRATION_DEFAULTS),
|
|
@@ -8191,6 +8227,7 @@ var init_api = __esm({
|
|
|
8191
8227
|
qaAttachments: (pid, rid) => `${API}/projects/${encodeURIComponent(pid)}/qa/reports/${encodeURIComponent(rid)}/attachments`,
|
|
8192
8228
|
qaAttachment: (pid, rid, aid) => `${API}/projects/${encodeURIComponent(pid)}/qa/reports/${encodeURIComponent(rid)}/attachments/${encodeURIComponent(aid)}`,
|
|
8193
8229
|
qaFindingBacklog: (pid, rid, fid) => `${API}/projects/${encodeURIComponent(pid)}/qa/reports/${encodeURIComponent(rid)}/findings/${encodeURIComponent(fid)}/backlog`,
|
|
8230
|
+
qaFindingSession: (pid, rid, fid) => `${API}/projects/${encodeURIComponent(pid)}/qa/reports/${encodeURIComponent(rid)}/findings/${encodeURIComponent(fid)}/session`,
|
|
8194
8231
|
qaReportBacklog: (pid, rid) => `${API}/projects/${encodeURIComponent(pid)}/qa/reports/${encodeURIComponent(rid)}/backlog`,
|
|
8195
8232
|
qaCasesImport: (pid, rid) => `${API}/projects/${encodeURIComponent(pid)}/qa/reports/${encodeURIComponent(rid)}/cases/import`,
|
|
8196
8233
|
qaImport: (pid) => `${API}/projects/${encodeURIComponent(pid)}/qa/import`,
|
|
@@ -8241,10 +8278,6 @@ var init_api = __esm({
|
|
|
8241
8278
|
// SPEC-471 · ADR-0095 · tarik & triase issue GitHub (di belakang gate cookie, capability `support`).
|
|
8242
8279
|
// hanoman TIDAK PERNAH menulis ke GitHub — tak ada path komentar/close di sini, dan itu disengaja.
|
|
8243
8280
|
githubPull: (id2) => `${API}/projects/${encodeURIComponent(id2)}/github/pull`,
|
|
8244
|
-
// SPEC-516 · ADR-0105 · changelog per project
|
|
8245
|
-
changelog: (id2) => `${API}/projects/${encodeURIComponent(id2)}/changelog`,
|
|
8246
|
-
changelogSources: (id2) => `${API}/projects/${encodeURIComponent(id2)}/changelog/sources`,
|
|
8247
|
-
changelogItem: (id2, cid) => `${API}/projects/${encodeURIComponent(id2)}/changelog/${encodeURIComponent(cid)}`,
|
|
8248
8281
|
githubIssues: (id2) => `${API}/projects/${encodeURIComponent(id2)}/github/issues`,
|
|
8249
8282
|
githubIssuesAccept: `${API}/github-issues/accept`,
|
|
8250
8283
|
githubIssueAccept: (id2) => `${API}/github-issues/${encodeURIComponent(id2)}/accept`,
|
|
@@ -10819,7 +10852,7 @@ var init_docs = __esm({
|
|
|
10819
10852
|
required: ["project", "prd"]
|
|
10820
10853
|
}),
|
|
10821
10854
|
// `breakdown` hidup di berkas route docs.ts, TAPI `capabilityForRoute` memetakan sub-path
|
|
10822
|
-
// `projects/:id/*` yang tak terdaftar ke `projects:*` — hanya `docs
|
|
10855
|
+
// `projects/:id/*` yang tak terdaftar ke `projects:*` — hanya `docs` dan `prds`
|
|
10823
10856
|
// yang dipetakan ke `docs:*`. Menuliskannya `docs:read` membuat uji kontrak merah, dan itulah
|
|
10824
10857
|
// yang terjadi saat tool ini pertama ditulis.
|
|
10825
10858
|
mode: "read",
|
|
@@ -10832,116 +10865,6 @@ var init_docs = __esm({
|
|
|
10832
10865
|
query: query({ prd: s(a.prd) })
|
|
10833
10866
|
}),
|
|
10834
10867
|
shape: (raw) => raw
|
|
10835
|
-
},
|
|
10836
|
-
{
|
|
10837
|
-
name: "hanoman_changelog_sources",
|
|
10838
|
-
title: "Sumber changelog yang tersedia",
|
|
10839
|
-
description: "Bahan mentah yang bisa dipakai membuat changelog: daftar tag git, HEAD, dan rentang backlog yang sudah selesai. Panggil ini SEBELUM hanoman_changelog_create \u2014 ia memberitahu mode mana yang mungkin (`hasRepo: false` berarti mode commit & version tak akan jalan) dan `defaultRange` yang masuk akal.",
|
|
10840
|
-
inputSchema: obj({ properties: { project: PROJECT }, required: ["project"] }),
|
|
10841
|
-
mode: "read",
|
|
10842
|
-
capability: "docs:read",
|
|
10843
|
-
samplePath: "/projects/hanoman/changelog/sources",
|
|
10844
|
-
sampleMethod: "GET",
|
|
10845
|
-
build: (a) => ({ method: "GET", path: `/projects/${enc(String(a.project))}/changelog/sources` }),
|
|
10846
|
-
shape: (raw) => raw
|
|
10847
|
-
},
|
|
10848
|
-
{
|
|
10849
|
-
name: "hanoman_changelog_list",
|
|
10850
|
-
title: "Daftar changelog project",
|
|
10851
|
-
description: "Changelog yang sudah dibuat untuk sebuah project, terbaru dulu, berhalaman. `q` menyaring judul & isi.",
|
|
10852
|
-
inputSchema: obj({
|
|
10853
|
-
properties: {
|
|
10854
|
-
project: PROJECT,
|
|
10855
|
-
q: str("Kata kunci penyaring judul & isi changelog. Kosongkan untuk semua."),
|
|
10856
|
-
page: int("Halaman, mulai 1.", { minimum: 1 }),
|
|
10857
|
-
limit: int("Item per halaman.", { minimum: 1, maximum: 200 })
|
|
10858
|
-
},
|
|
10859
|
-
required: ["project"]
|
|
10860
|
-
}),
|
|
10861
|
-
mode: "read",
|
|
10862
|
-
capability: "docs:read",
|
|
10863
|
-
samplePath: "/projects/hanoman/changelog",
|
|
10864
|
-
sampleMethod: "GET",
|
|
10865
|
-
build: (a) => ({
|
|
10866
|
-
method: "GET",
|
|
10867
|
-
path: `/projects/${enc(String(a.project))}/changelog`,
|
|
10868
|
-
query: query({ q: s(a.q), page: n(a.page)?.toString(), limit: n(a.limit)?.toString() })
|
|
10869
|
-
}),
|
|
10870
|
-
shape: (raw) => raw
|
|
10871
|
-
},
|
|
10872
|
-
{
|
|
10873
|
-
name: "hanoman_changelog_get",
|
|
10874
|
-
title: "Baca satu changelog",
|
|
10875
|
-
description: "Satu entri changelog beserta isinya, dicari menurut id di dalam project tersebut.",
|
|
10876
|
-
inputSchema: obj({
|
|
10877
|
-
properties: { project: PROJECT, changelog: str("Id changelog, dari hanoman_changelog_list.") },
|
|
10878
|
-
required: ["project", "changelog"]
|
|
10879
|
-
}),
|
|
10880
|
-
mode: "read",
|
|
10881
|
-
capability: "docs:read",
|
|
10882
|
-
samplePath: "/projects/hanoman/changelog/c1",
|
|
10883
|
-
sampleMethod: "GET",
|
|
10884
|
-
build: (a) => ({ method: "GET", path: `/projects/${enc(String(a.project))}/changelog/${enc(String(a.changelog))}` }),
|
|
10885
|
-
shape: (raw) => raw
|
|
10886
|
-
},
|
|
10887
|
-
{
|
|
10888
|
-
name: "hanoman_changelog_create",
|
|
10889
|
-
title: "Buat changelog",
|
|
10890
|
-
description: "Membuat changelog baru dari salah satu dari tiga sumber. `mode` menentukan field mana yang WAJIB menyertainya: `backlog` memakai rentang tanggal (`from`/`to`, boleh kosong = seluruhnya), `commit` memakai dua sha, `version` memakai tag. Server menjawab 422 bila sumbernya tak tersedia \u2014 panggil hanoman_changelog_sources lebih dulu.",
|
|
10891
|
-
inputSchema: obj({
|
|
10892
|
-
properties: {
|
|
10893
|
-
project: PROJECT,
|
|
10894
|
-
mode: enumStr(["backlog", "commit", "version"], "Sumber changelog. Field yang menyertainya ditentukan nilai ini."),
|
|
10895
|
-
from: str("Mode `backlog`: tanggal awal `YYYY-MM-DD`. Kosongkan untuk tanpa batas awal."),
|
|
10896
|
-
to: str("Mode `backlog`: tanggal akhir `YYYY-MM-DD`. Harus >= `from`."),
|
|
10897
|
-
fromSha: str("Mode `commit`: sha awal, minimal 4 karakter."),
|
|
10898
|
-
toSha: str("Mode `commit`: sha akhir, minimal 4 karakter."),
|
|
10899
|
-
fromTag: str("Mode `version`: tag awal. Kosongkan untuk mulai dari tag terawal."),
|
|
10900
|
-
toTag: str("Mode `version`: tag akhir. WAJIB.")
|
|
10901
|
-
},
|
|
10902
|
-
required: ["project", "mode"],
|
|
10903
|
-
// Skema ketat DI KLIEN: validator SDK menolak pasangan yang salah sebelum permintaan lahir,
|
|
10904
|
-
// jadi agen dibimbing ke panggilan yang sah alih-alih menemukannya lewat 400 (ADR-0099 #2).
|
|
10905
|
-
allOf: [
|
|
10906
|
-
{
|
|
10907
|
-
if: { properties: { mode: { const: "backlog" } }, required: ["mode"] },
|
|
10908
|
-
then: { properties: { from: { type: "string" }, to: { type: "string" } } }
|
|
10909
|
-
},
|
|
10910
|
-
{
|
|
10911
|
-
if: { properties: { mode: { const: "commit" } }, required: ["mode"] },
|
|
10912
|
-
then: { required: ["fromSha", "toSha"] }
|
|
10913
|
-
},
|
|
10914
|
-
{
|
|
10915
|
-
if: { properties: { mode: { const: "version" } }, required: ["mode"] },
|
|
10916
|
-
then: { required: ["toTag"] }
|
|
10917
|
-
}
|
|
10918
|
-
]
|
|
10919
|
-
}),
|
|
10920
|
-
mode: "write",
|
|
10921
|
-
capability: "docs:write",
|
|
10922
|
-
samplePath: "/projects/hanoman/changelog",
|
|
10923
|
-
sampleMethod: "POST",
|
|
10924
|
-
build: (a) => {
|
|
10925
|
-
const mode = String(a.mode);
|
|
10926
|
-
const body = mode === "commit" ? { mode, fromSha: String(a.fromSha), toSha: String(a.toSha) } : mode === "version" ? { mode, ...s(a.fromTag) ? { fromTag: s(a.fromTag) } : {}, toTag: String(a.toTag) } : { mode, ...s(a.from) ? { from: s(a.from) } : {}, ...s(a.to) ? { to: s(a.to) } : {} };
|
|
10927
|
-
return { method: "POST", path: `/projects/${enc(String(a.project))}/changelog`, body };
|
|
10928
|
-
},
|
|
10929
|
-
shape: (raw) => raw
|
|
10930
|
-
},
|
|
10931
|
-
{
|
|
10932
|
-
name: "hanoman_changelog_delete",
|
|
10933
|
-
title: "Hapus changelog (BERBAHAYA)",
|
|
10934
|
-
description: "BERBAHAYA \u2014 menghapus satu entri changelog secara permanen. Isinya tak tersimpan di tempat lain: changelog lahir dari generator dan tak bisa dipulihkan tanpa membuatnya ulang. Hanya muncul saat tingkat `--danger` menyala.",
|
|
10935
|
-
inputSchema: obj({
|
|
10936
|
-
properties: { project: PROJECT, changelog: str("Id changelog yang akan dihapus.") },
|
|
10937
|
-
required: ["project", "changelog"]
|
|
10938
|
-
}),
|
|
10939
|
-
mode: "danger",
|
|
10940
|
-
capability: "docs:write",
|
|
10941
|
-
samplePath: "/projects/hanoman/changelog/c1",
|
|
10942
|
-
sampleMethod: "DELETE",
|
|
10943
|
-
build: (a) => ({ method: "DELETE", path: `/projects/${enc(String(a.project))}/changelog/${enc(String(a.changelog))}` }),
|
|
10944
|
-
shape: (raw) => raw
|
|
10945
10868
|
}
|
|
10946
10869
|
];
|
|
10947
10870
|
}
|
|
@@ -13679,6 +13602,18 @@ var init_qa2 = __esm({
|
|
|
13679
13602
|
build: (a) => ({ method: "PATCH", path: `${one(a)}/findings/${enc(String(a.finding))}`, body: findingBody(a) }),
|
|
13680
13603
|
shape: (raw) => raw
|
|
13681
13604
|
},
|
|
13605
|
+
{
|
|
13606
|
+
name: "hanoman_qa_finding_start_session",
|
|
13607
|
+
title: "Kerjakan temuan QA langsung",
|
|
13608
|
+
description: "Membuka sesi agen terisolasi dari temuan QA tanpa membuat backlog, membawa langkah uji dan lampiran. Memerlukan qa:write DAN sessions:write. Klik ulang memakai sesi aktif yang sama. Temuan wontfix atau sudah terkait backlog ditolak; laporan closed boleh.",
|
|
13609
|
+
inputSchema: obj({ properties: { project: PROJECT3, report: REPORT, finding: FINDING }, required: ["project", "report", "finding"] }),
|
|
13610
|
+
mode: "write",
|
|
13611
|
+
capability: "qa:write",
|
|
13612
|
+
samplePath: "/projects/p1/qa/reports/r1/findings/f1/session",
|
|
13613
|
+
sampleMethod: "POST",
|
|
13614
|
+
build: (a) => ({ method: "POST", path: `${one(a)}/findings/${enc(String(a.finding))}/session`, body: {} }),
|
|
13615
|
+
shape: (raw) => raw
|
|
13616
|
+
},
|
|
13682
13617
|
{
|
|
13683
13618
|
name: "hanoman_qa_finding_to_backlog",
|
|
13684
13619
|
title: "Kirim temuan QA ke backlog",
|
|
@@ -13839,7 +13774,7 @@ var init_mcp = __esm({
|
|
|
13839
13774
|
init_mcp_shape();
|
|
13840
13775
|
init_mcp_catalog();
|
|
13841
13776
|
init_mcp_catalog();
|
|
13842
|
-
MCP_TOOL_SCHEMA_VERSION =
|
|
13777
|
+
MCP_TOOL_SCHEMA_VERSION = 2;
|
|
13843
13778
|
MCP_INSTRUCTIONS = [
|
|
13844
13779
|
`hanoman \u2014 orchestrator backlog + dashboard. Skema tool versi ${MCP_TOOL_SCHEMA_VERSION}.`,
|
|
13845
13780
|
"",
|
|
@@ -13852,56 +13787,6 @@ var init_mcp = __esm({
|
|
|
13852
13787
|
}
|
|
13853
13788
|
});
|
|
13854
13789
|
|
|
13855
|
-
// ../shared/src/changelog.ts
|
|
13856
|
-
function dayString(d) {
|
|
13857
|
-
const p3 = (n2) => String(n2).padStart(2, "0");
|
|
13858
|
-
return `${d.getFullYear()}-${p3(d.getMonth() + 1)}-${p3(d.getDate())}`;
|
|
13859
|
-
}
|
|
13860
|
-
function defaultRange(today) {
|
|
13861
|
-
const from = new Date(today.getFullYear(), today.getMonth(), today.getDate() - (DEFAULT_RANGE_DAYS - 1));
|
|
13862
|
-
return { from: dayString(from), to: dayString(today) };
|
|
13863
|
-
}
|
|
13864
|
-
function changelogMatches(row, q2) {
|
|
13865
|
-
const needle = (q2 ?? "").trim().toLowerCase();
|
|
13866
|
-
if (!needle) return true;
|
|
13867
|
-
return row.title.toLowerCase().includes(needle) || row.body.toLowerCase().includes(needle) || row.mode.toLowerCase().includes(needle);
|
|
13868
|
-
}
|
|
13869
|
-
var CHANGELOG_MODES, zChangelogMode, zDay, zChangelogRequest, zChangelogView, DEFAULT_RANGE_DAYS;
|
|
13870
|
-
var init_changelog = __esm({
|
|
13871
|
-
"../shared/src/changelog.ts"() {
|
|
13872
|
-
"use strict";
|
|
13873
|
-
init_zod();
|
|
13874
|
-
CHANGELOG_MODES = ["backlog", "commit", "version"];
|
|
13875
|
-
zChangelogMode = external_exports.enum(CHANGELOG_MODES);
|
|
13876
|
-
zDay = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/, "format tanggal harus YYYY-MM-DD");
|
|
13877
|
-
zChangelogRequest = external_exports.discriminatedUnion("mode", [
|
|
13878
|
-
external_exports.object({ mode: external_exports.literal("backlog"), from: zDay.optional(), to: zDay.optional() }),
|
|
13879
|
-
external_exports.object({ mode: external_exports.literal("commit"), fromSha: external_exports.string().trim().min(4), toSha: external_exports.string().trim().min(4) }),
|
|
13880
|
-
external_exports.object({ mode: external_exports.literal("version"), fromTag: external_exports.string().trim().min(1).optional(), toTag: external_exports.string().trim().min(1) })
|
|
13881
|
-
]).superRefine((v, ctx) => {
|
|
13882
|
-
if (v.mode === "backlog" && v.from && v.to && v.from > v.to)
|
|
13883
|
-
ctx.addIssue({
|
|
13884
|
-
code: external_exports.ZodIssueCode.custom,
|
|
13885
|
-
path: ["from"],
|
|
13886
|
-
message: "rentang tanggal terbalik \u2014 `from` harus lebih awal atau sama dengan `to`"
|
|
13887
|
-
});
|
|
13888
|
-
});
|
|
13889
|
-
zChangelogView = external_exports.object({
|
|
13890
|
-
id: external_exports.string(),
|
|
13891
|
-
projectId: external_exports.string(),
|
|
13892
|
-
mode: zChangelogMode,
|
|
13893
|
-
title: external_exports.string(),
|
|
13894
|
-
params: external_exports.unknown(),
|
|
13895
|
-
body: external_exports.string(),
|
|
13896
|
-
generator: external_exports.enum(["agent", "fallback"]),
|
|
13897
|
-
warning: external_exports.string().nullable(),
|
|
13898
|
-
itemCount: external_exports.number().int(),
|
|
13899
|
-
createdAt: external_exports.string()
|
|
13900
|
-
});
|
|
13901
|
-
DEFAULT_RANGE_DAYS = 30;
|
|
13902
|
-
}
|
|
13903
|
-
});
|
|
13904
|
-
|
|
13905
13790
|
// ../shared/src/terminal-workspace.ts
|
|
13906
13791
|
var zSessionId, zTrackSizes, zTerminalGroup, zTerminalWorkspaceV1, zTerminalWorkspaceWrite, zTerminalWorkspaceSnapshot;
|
|
13907
13792
|
var init_terminal_workspace = __esm({
|
|
@@ -14350,7 +14235,6 @@ var init_src = __esm({
|
|
|
14350
14235
|
init_mcp();
|
|
14351
14236
|
init_auto_merge();
|
|
14352
14237
|
init_handled_by();
|
|
14353
|
-
init_changelog();
|
|
14354
14238
|
init_cron_expr();
|
|
14355
14239
|
init_terminal_workspace();
|
|
14356
14240
|
init_terminal_io();
|
|
@@ -23445,8 +23329,8 @@ var require_webidl = __commonJS({
|
|
|
23445
23329
|
return new Uint8Array(0);
|
|
23446
23330
|
}
|
|
23447
23331
|
const bytes = new Uint8Array(length);
|
|
23448
|
-
const
|
|
23449
|
-
bytes.set(
|
|
23332
|
+
const view12 = new Uint8Array(jsArrayBuffer, offset, length);
|
|
23333
|
+
bytes.set(view12);
|
|
23450
23334
|
return bytes;
|
|
23451
23335
|
};
|
|
23452
23336
|
webidl.converters.DOMString = function(V, prefix, argument, flags) {
|
|
@@ -31855,7 +31739,7 @@ var require_mock_interceptor = __commonJS({
|
|
|
31855
31739
|
var require_mock_client = __commonJS({
|
|
31856
31740
|
"../node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/mock/mock-client.js"(exports, module) {
|
|
31857
31741
|
"use strict";
|
|
31858
|
-
var { promisify:
|
|
31742
|
+
var { promisify: promisify17 } = __require("node:util");
|
|
31859
31743
|
var Client = require_client();
|
|
31860
31744
|
var { buildMockDispatch } = require_mock_utils();
|
|
31861
31745
|
var {
|
|
@@ -31903,7 +31787,7 @@ var require_mock_client = __commonJS({
|
|
|
31903
31787
|
this[kDispatches] = [];
|
|
31904
31788
|
}
|
|
31905
31789
|
async [kClose]() {
|
|
31906
|
-
await
|
|
31790
|
+
await promisify17(this[kOriginalClose])();
|
|
31907
31791
|
this[kConnected] = 0;
|
|
31908
31792
|
this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
|
|
31909
31793
|
}
|
|
@@ -32116,7 +32000,7 @@ var require_mock_call_history = __commonJS({
|
|
|
32116
32000
|
var require_mock_pool = __commonJS({
|
|
32117
32001
|
"../node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/mock/mock-pool.js"(exports, module) {
|
|
32118
32002
|
"use strict";
|
|
32119
|
-
var { promisify:
|
|
32003
|
+
var { promisify: promisify17 } = __require("node:util");
|
|
32120
32004
|
var Pool = require_pool();
|
|
32121
32005
|
var { buildMockDispatch } = require_mock_utils();
|
|
32122
32006
|
var {
|
|
@@ -32164,7 +32048,7 @@ var require_mock_pool = __commonJS({
|
|
|
32164
32048
|
this[kDispatches] = [];
|
|
32165
32049
|
}
|
|
32166
32050
|
async [kClose]() {
|
|
32167
|
-
await
|
|
32051
|
+
await promisify17(this[kOriginalClose])();
|
|
32168
32052
|
this[kConnected] = 0;
|
|
32169
32053
|
this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
|
|
32170
32054
|
}
|
|
@@ -48932,25 +48816,25 @@ var init_lib4 = __esm({
|
|
|
48932
48816
|
});
|
|
48933
48817
|
|
|
48934
48818
|
// ../node_modules/.pnpm/uint8array-extras@1.5.0/node_modules/uint8array-extras/index.js
|
|
48935
|
-
function getUintBE(
|
|
48936
|
-
const { byteLength } =
|
|
48819
|
+
function getUintBE(view12) {
|
|
48820
|
+
const { byteLength } = view12;
|
|
48937
48821
|
if (byteLength === 6) {
|
|
48938
|
-
return
|
|
48822
|
+
return view12.getUint16(0) * 2 ** 32 + view12.getUint32(2);
|
|
48939
48823
|
}
|
|
48940
48824
|
if (byteLength === 5) {
|
|
48941
|
-
return
|
|
48825
|
+
return view12.getUint8(0) * 2 ** 32 + view12.getUint32(1);
|
|
48942
48826
|
}
|
|
48943
48827
|
if (byteLength === 4) {
|
|
48944
|
-
return
|
|
48828
|
+
return view12.getUint32(0);
|
|
48945
48829
|
}
|
|
48946
48830
|
if (byteLength === 3) {
|
|
48947
|
-
return
|
|
48831
|
+
return view12.getUint8(0) * 2 ** 16 + view12.getUint16(1);
|
|
48948
48832
|
}
|
|
48949
48833
|
if (byteLength === 2) {
|
|
48950
|
-
return
|
|
48834
|
+
return view12.getUint16(0);
|
|
48951
48835
|
}
|
|
48952
48836
|
if (byteLength === 1) {
|
|
48953
|
-
return
|
|
48837
|
+
return view12.getUint8(0);
|
|
48954
48838
|
}
|
|
48955
48839
|
}
|
|
48956
48840
|
var cachedDecoders, cachedEncoder, byteToHexLookupTable;
|
|
@@ -51784,6 +51668,7 @@ var init_qa_attachment_sync = __esm({
|
|
|
51784
51668
|
var sync_exports = {};
|
|
51785
51669
|
__export(sync_exports, {
|
|
51786
51670
|
BOOTSTRAP_ORDER: () => BOOTSTRAP_ORDER,
|
|
51671
|
+
OPTIONAL_ENTITIES: () => OPTIONAL_ENTITIES,
|
|
51787
51672
|
PARENTS: () => PARENTS,
|
|
51788
51673
|
PULL_MAX_BYTES: () => PULL_MAX_BYTES,
|
|
51789
51674
|
QA_SYNC_MIMES: () => QA_SYNC_MIMES,
|
|
@@ -51792,12 +51677,14 @@ __export(sync_exports, {
|
|
|
51792
51677
|
__FIELDS: () => __FIELDS,
|
|
51793
51678
|
__FIELDS_FOR_TEST: () => __FIELDS_FOR_TEST,
|
|
51794
51679
|
__JSON_FIELDS: () => __JSON_FIELDS,
|
|
51680
|
+
acceptedOptional: () => acceptedOptional,
|
|
51795
51681
|
applyPush: () => applyPush,
|
|
51796
51682
|
backfillFeed: () => backfillFeed,
|
|
51797
51683
|
bootstrapSnapshot: () => bootstrapSnapshot,
|
|
51798
51684
|
consumeTombstoneOnRecreate: () => consumeTombstoneOnRecreate,
|
|
51799
51685
|
deleteRow: () => deleteRow,
|
|
51800
51686
|
isEntity: () => isEntity,
|
|
51687
|
+
isOptionalEntity: () => isOptionalEntity,
|
|
51801
51688
|
publishDelete: () => publishDelete,
|
|
51802
51689
|
publishLocal: () => publishLocal,
|
|
51803
51690
|
pull: () => pull,
|
|
@@ -51807,6 +51694,10 @@ __export(sync_exports, {
|
|
|
51807
51694
|
upsertLocal: () => upsertLocal,
|
|
51808
51695
|
validateSyncData: () => validateSyncData
|
|
51809
51696
|
});
|
|
51697
|
+
function acceptedOptional(raw) {
|
|
51698
|
+
const want = typeof raw === "string" ? raw.split(",").map((x) => x.trim()) : [];
|
|
51699
|
+
return new Set(OPTIONAL_ENTITIES.filter((e) => want.includes(e)));
|
|
51700
|
+
}
|
|
51810
51701
|
function validateSyncData(entity, data, options2 = {}) {
|
|
51811
51702
|
const allowed = new Set(FIELDS[entity]);
|
|
51812
51703
|
if (entity === "project" && options2.allowProjectRename) allowed.add("renamedFrom");
|
|
@@ -51953,17 +51844,23 @@ async function applyPush(entity, id2, baseVersion, data, deviceId, op = "upsert"
|
|
|
51953
51844
|
function recordBytes(rec) {
|
|
51954
51845
|
return Buffer.byteLength(JSON.stringify(rec));
|
|
51955
51846
|
}
|
|
51956
|
-
async function pull(sinceCursor, limit = 500, maxBytes = PULL_MAX_BYTES) {
|
|
51847
|
+
async function pull(sinceCursor, limit = 500, maxBytes = PULL_MAX_BYTES, opts2 = {}) {
|
|
51957
51848
|
const since = Number(sinceCursor || "0");
|
|
51958
51849
|
const rows = await prisma.syncLog.findMany({
|
|
51959
51850
|
where: { seq: { gt: since } },
|
|
51960
51851
|
orderBy: { seq: "asc" },
|
|
51961
51852
|
take: limit
|
|
51962
51853
|
});
|
|
51854
|
+
const accept = opts2.accept ?? /* @__PURE__ */ new Set();
|
|
51963
51855
|
const records = [];
|
|
51964
51856
|
let bytes = 0;
|
|
51965
51857
|
let trimmed = false;
|
|
51858
|
+
let consumed = null;
|
|
51966
51859
|
for (const r of rows) {
|
|
51860
|
+
if (isOptionalEntity(r.entity) && !accept.has(r.entity)) {
|
|
51861
|
+
consumed = r.seq;
|
|
51862
|
+
continue;
|
|
51863
|
+
}
|
|
51967
51864
|
const rec = {
|
|
51968
51865
|
entity: r.entity,
|
|
51969
51866
|
recordId: r.recordId,
|
|
@@ -51978,11 +51875,12 @@ async function pull(sinceCursor, limit = 500, maxBytes = PULL_MAX_BYTES) {
|
|
|
51978
51875
|
}
|
|
51979
51876
|
bytes += size;
|
|
51980
51877
|
records.push(rec);
|
|
51878
|
+
consumed = r.seq;
|
|
51981
51879
|
}
|
|
51982
|
-
const cursor =
|
|
51983
|
-
return { cursor, records, hasMore: trimmed || rows.length === limit };
|
|
51880
|
+
const cursor = consumed !== null ? String(consumed) : sinceCursor || "0";
|
|
51881
|
+
return { cursor, records, hasMore: trimmed || rows.length === limit, entities: [...OPTIONAL_ENTITIES] };
|
|
51984
51882
|
}
|
|
51985
|
-
async function bootstrapSnapshot(after, maxBytes = PULL_MAX_BYTES) {
|
|
51883
|
+
async function bootstrapSnapshot(after, maxBytes = PULL_MAX_BYTES, opts2 = {}) {
|
|
51986
51884
|
const tip = await prisma.syncLog.findFirst({ orderBy: { seq: "desc" }, select: { seq: true } });
|
|
51987
51885
|
const cursor = tip ? String(tip.seq) : "0";
|
|
51988
51886
|
const sep9 = after ? after.indexOf(":") : -1;
|
|
@@ -51995,6 +51893,8 @@ async function bootstrapSnapshot(after, maxBytes = PULL_MAX_BYTES) {
|
|
|
51995
51893
|
let last2 = null;
|
|
51996
51894
|
for (let i = startAt; i < BOOTSTRAP_ORDER.length; i++) {
|
|
51997
51895
|
const entity = BOOTSTRAP_ORDER[i];
|
|
51896
|
+
if (isOptionalEntity(entity) && !(opts2.accept ?? /* @__PURE__ */ new Set()).has(entity)) continue;
|
|
51897
|
+
if (opts2.only && !opts2.only.has(entity)) continue;
|
|
51998
51898
|
const gt = i === startAt && afterId ? afterId : null;
|
|
51999
51899
|
const rows = await DELEGATE[entity].findMany({
|
|
52000
51900
|
...gt ? { where: { id: { gt } } } : {},
|
|
@@ -52013,14 +51913,14 @@ async function bootstrapSnapshot(after, maxBytes = PULL_MAX_BYTES) {
|
|
|
52013
51913
|
};
|
|
52014
51914
|
const size = recordBytes(rec);
|
|
52015
51915
|
if (records.length && bytes + size > maxBytes) {
|
|
52016
|
-
return { cursor, records, hasMore: true, next: last2 };
|
|
51916
|
+
return { cursor, records, hasMore: true, next: last2, entities: [...OPTIONAL_ENTITIES] };
|
|
52017
51917
|
}
|
|
52018
51918
|
bytes += size;
|
|
52019
51919
|
records.push(rec);
|
|
52020
51920
|
last2 = `${entity}:${row.id}`;
|
|
52021
51921
|
}
|
|
52022
51922
|
}
|
|
52023
|
-
return { cursor, records, hasMore: false, next: null };
|
|
51923
|
+
return { cursor, records, hasMore: false, next: null, entities: [...OPTIONAL_ENTITIES] };
|
|
52024
51924
|
}
|
|
52025
51925
|
async function publishLocal(entity, id2) {
|
|
52026
51926
|
const snap = await snapshot(entity, id2);
|
|
@@ -52114,7 +52014,7 @@ async function upsertLocal(entity, id2, version, data) {
|
|
|
52114
52014
|
function setAcceptedHook(hook) {
|
|
52115
52015
|
onAccepted = hook;
|
|
52116
52016
|
}
|
|
52117
|
-
var SYNCED, DELEGATE, FIELDS, DATE_FIELDS, PARENTS, __FIELDS, __DATE_FIELDS, NUMBER_FIELDS, NULLABLE_NUMBER_FIELDS, FLOAT_FIELDS, BOOLEAN_FIELDS, JSON_FIELDS, __JSON_FIELDS, QA_SYNC_MIMES, PULL_MAX_BYTES, BOOTSTRAP_ORDER, onAccepted, __FIELDS_FOR_TEST;
|
|
52017
|
+
var SYNCED, OPTIONAL_ENTITIES, isOptionalEntity, DELEGATE, FIELDS, DATE_FIELDS, PARENTS, __FIELDS, __DATE_FIELDS, NUMBER_FIELDS, NULLABLE_NUMBER_FIELDS, FLOAT_FIELDS, BOOLEAN_FIELDS, JSON_FIELDS, __JSON_FIELDS, QA_SYNC_MIMES, PULL_MAX_BYTES, BOOTSTRAP_ORDER, onAccepted, __FIELDS_FOR_TEST;
|
|
52118
52018
|
var init_sync = __esm({
|
|
52119
52019
|
"src/services/sync.ts"() {
|
|
52120
52020
|
"use strict";
|
|
@@ -52123,6 +52023,8 @@ var init_sync = __esm({
|
|
|
52123
52023
|
init_tombstone();
|
|
52124
52024
|
init_qa_attachment_sync();
|
|
52125
52025
|
SYNCED = ["project", "spec", "vps", "sessionResult", "ticket", "ticketAttachment", "customAgent", "githubIssue", "member", "task", "qaReport", "qaCase", "qaFinding", "qaAttachment"];
|
|
52026
|
+
OPTIONAL_ENTITIES = [];
|
|
52027
|
+
isOptionalEntity = (e) => OPTIONAL_ENTITIES.includes(e);
|
|
52126
52028
|
DELEGATE = {
|
|
52127
52029
|
project: prisma.project,
|
|
52128
52030
|
spec: prisma.spec,
|
|
@@ -52151,7 +52053,7 @@ var init_sync = __esm({
|
|
|
52151
52053
|
// SPEC-447 · ADR-0093 · dependsOn ikut juga: tanpa itu client tak tahu urutannya dan akan
|
|
52152
52054
|
// meluncurkan pekerjaan yang di hub terblokir. Bukan DATE_FIELDS — nilainya array string.
|
|
52153
52055
|
// SPEC-516 · ADR-0105 · doneAt ikut menyeberang — cermin createdAt/startedAt. Tanpa ini spec
|
|
52154
|
-
// asal-hub mendarat di tiap client dengan doneAt null tanpa satu pun error, dan
|
|
52056
|
+
// asal-hub mendarat di tiap client dengan doneAt null tanpa satu pun error, dan riwayat selesai
|
|
52155
52057
|
// mode backlog di client itu selamanya kosong.
|
|
52156
52058
|
// SPEC-546 · ADR-0109 · sourceHistory ikut menyeberang: jejak konversi type adalah bagian
|
|
52157
52059
|
// keadaan yang harus dilihat sama oleh semua mesin. `upsert` yang tak menyebut sebuah kolom
|
|
@@ -52467,8 +52369,6 @@ var init_settings3 = __esm({
|
|
|
52467
52369
|
// SPEC-409 · ADR-0091 · hanoman-lead (master switch mati)
|
|
52468
52370
|
telegram: TELEGRAM_DEFAULTS,
|
|
52469
52371
|
// SPEC-476 · ADR-0096 · gateway Telegram opt-in
|
|
52470
|
-
changelog: CHANGELOG_ENGINE_DEFAULTS,
|
|
52471
|
-
// SPEC-518 · agen pembuat changelog (opt-in, mati)
|
|
52472
52372
|
portalChat: PORTAL_CHAT_DEFAULTS,
|
|
52473
52373
|
// SPEC-854 · ADR-0130 · chat portal klien (opt-in, mati)
|
|
52474
52374
|
orchestration: ORCHESTRATION_DEFAULTS,
|
|
@@ -53494,7 +53394,10 @@ async function verifyAgentToken(token) {
|
|
|
53494
53394
|
if (a.length !== b.length || !timingSafeEqual2(a, b)) return null;
|
|
53495
53395
|
await prisma.agentToken.update({ where: { id: row.id }, data: { lastUsedAt: /* @__PURE__ */ new Date() } }).catch(() => {
|
|
53496
53396
|
});
|
|
53497
|
-
return {
|
|
53397
|
+
return {
|
|
53398
|
+
id: row.id,
|
|
53399
|
+
capabilities: Array.isArray(row.capabilities) ? row.capabilities : []
|
|
53400
|
+
};
|
|
53498
53401
|
}
|
|
53499
53402
|
async function listAgentTokens() {
|
|
53500
53403
|
const rows = await prisma.agentToken.findMany({ orderBy: { createdAt: "desc" } });
|
|
@@ -53651,8 +53554,8 @@ async function telegramEngineContext() {
|
|
|
53651
53554
|
};
|
|
53652
53555
|
}
|
|
53653
53556
|
function telegramReloadNeeded(before2, after) {
|
|
53654
|
-
const
|
|
53655
|
-
return JSON.stringify(
|
|
53557
|
+
const strip = ({ engine: _engine, ...rest }) => rest;
|
|
53558
|
+
return JSON.stringify(strip(before2)) !== JSON.stringify(strip(after));
|
|
53656
53559
|
}
|
|
53657
53560
|
var init_config4 = __esm({
|
|
53658
53561
|
"src/services/telegram/config.ts"() {
|
|
@@ -54907,8 +54810,18 @@ async function uploadPendingQaBytes() {
|
|
|
54907
54810
|
const hub = hubOf();
|
|
54908
54811
|
if (!hub) return out4;
|
|
54909
54812
|
const queued3 = new Set((await listOutbox()).filter((o) => o.entity === "qaAttachment").map((o) => o.recordId));
|
|
54910
|
-
const
|
|
54813
|
+
const pendingRows = (after) => prisma.qaAttachment.findMany({
|
|
54814
|
+
where: { syncState: "local-only", id: { gt: after } },
|
|
54815
|
+
orderBy: { id: "asc" },
|
|
54816
|
+
take: UPLOAD_BATCH + queued3.size
|
|
54817
|
+
});
|
|
54818
|
+
let rows = await pendingRows(uploadCursor);
|
|
54819
|
+
if (!rows.length && uploadCursor) {
|
|
54820
|
+
uploadCursor = "";
|
|
54821
|
+
rows = await pendingRows("");
|
|
54822
|
+
}
|
|
54911
54823
|
for (const a of rows.filter((r) => !queued3.has(r.id)).slice(0, UPLOAD_BATCH)) {
|
|
54824
|
+
uploadCursor = a.id;
|
|
54912
54825
|
const bytes = QA_STORAGE_KEY.test(a.storageKey) ? await readUpload(a.storageKey).catch(() => null) : null;
|
|
54913
54826
|
if (!bytes) {
|
|
54914
54827
|
await prisma.qaAttachment.update({ where: { id: a.id }, data: { syncState: "failed" } });
|
|
@@ -54939,7 +54852,28 @@ async function uploadPendingQaBytes() {
|
|
|
54939
54852
|
}
|
|
54940
54853
|
return out4;
|
|
54941
54854
|
}
|
|
54942
|
-
|
|
54855
|
+
async function downloadPendingQaBytes() {
|
|
54856
|
+
if (!hubOf()) return { downloaded: 0, pending: 0 };
|
|
54857
|
+
let rows = await prisma.qaAttachment.findMany({
|
|
54858
|
+
where: { syncState: "remote", id: { gt: downloadCursor } },
|
|
54859
|
+
orderBy: { id: "asc" },
|
|
54860
|
+
take: 5
|
|
54861
|
+
});
|
|
54862
|
+
if (!rows.length && downloadCursor) {
|
|
54863
|
+
downloadCursor = "";
|
|
54864
|
+
rows = await prisma.qaAttachment.findMany({ where: { syncState: "remote" }, orderBy: { id: "asc" }, take: 5 });
|
|
54865
|
+
}
|
|
54866
|
+
let downloaded = 0;
|
|
54867
|
+
for (const row of rows) {
|
|
54868
|
+
downloadCursor = row.id;
|
|
54869
|
+
if (await readQaAttachmentBytes(row.id)) {
|
|
54870
|
+
await prisma.qaAttachment.updateMany({ where: { id: row.id, syncState: "remote" }, data: { syncState: "available" } });
|
|
54871
|
+
downloaded++;
|
|
54872
|
+
}
|
|
54873
|
+
}
|
|
54874
|
+
return { downloaded, pending: await prisma.qaAttachment.count({ where: { syncState: "remote" } }) };
|
|
54875
|
+
}
|
|
54876
|
+
var hubOf, endpoint, isLoopback, inflight2, UPLOAD_BATCH, uploadCursor, downloadCursor;
|
|
54943
54877
|
var init_qa_attachment_transfer = __esm({
|
|
54944
54878
|
"src/services/qa-attachment-transfer.ts"() {
|
|
54945
54879
|
"use strict";
|
|
@@ -54958,6 +54892,8 @@ var init_qa_attachment_transfer = __esm({
|
|
|
54958
54892
|
isLoopback = (u) => ["localhost", "127.0.0.1", "::1"].includes(u.hostname);
|
|
54959
54893
|
inflight2 = /* @__PURE__ */ new Map();
|
|
54960
54894
|
UPLOAD_BATCH = 5;
|
|
54895
|
+
uploadCursor = "";
|
|
54896
|
+
downloadCursor = "";
|
|
54961
54897
|
}
|
|
54962
54898
|
});
|
|
54963
54899
|
|
|
@@ -55165,7 +55101,7 @@ function capabilityForRoute(method, path) {
|
|
|
55165
55101
|
if (top === "projects") {
|
|
55166
55102
|
const sub = seg[2];
|
|
55167
55103
|
if (sub === "qa") return rw("qa");
|
|
55168
|
-
if (sub === "docs" || sub === "prds"
|
|
55104
|
+
if (sub === "docs" || sub === "prds") return rw("docs");
|
|
55169
55105
|
if (sub === "github") return rw("support");
|
|
55170
55106
|
if (!read2 && sub === "git") return "ide:git";
|
|
55171
55107
|
if (!read2 && (sub === "branches" || sub === "worktrees") && seg[3] === "delete") return "ide:git";
|
|
@@ -55690,7 +55626,7 @@ var require_permessage_deflate2 = __commonJS({
|
|
|
55690
55626
|
var Limiter = require_limiter();
|
|
55691
55627
|
var { kStatusCode } = require_constants6();
|
|
55692
55628
|
var FastBuffer = Buffer[Symbol.species];
|
|
55693
|
-
var
|
|
55629
|
+
var TRAILER = Buffer.from([0, 0, 255, 255]);
|
|
55694
55630
|
var kPerMessageDeflate = Symbol("permessage-deflate");
|
|
55695
55631
|
var kTotalLength = Symbol("total-length");
|
|
55696
55632
|
var kCallback = Symbol("callback");
|
|
@@ -55965,7 +55901,7 @@ var require_permessage_deflate2 = __commonJS({
|
|
|
55965
55901
|
}
|
|
55966
55902
|
this._inflate[kCallback] = callback;
|
|
55967
55903
|
this._inflate.write(data);
|
|
55968
|
-
if (fin) this._inflate.write(
|
|
55904
|
+
if (fin) this._inflate.write(TRAILER);
|
|
55969
55905
|
this._inflate.flush(() => {
|
|
55970
55906
|
const err = this._inflate[kError];
|
|
55971
55907
|
if (err) {
|
|
@@ -57781,7 +57717,7 @@ var require_websocket2 = __commonJS({
|
|
|
57781
57717
|
var http = __require("http");
|
|
57782
57718
|
var net = __require("net");
|
|
57783
57719
|
var tls = __require("tls");
|
|
57784
|
-
var { randomBytes: randomBytes12, createHash:
|
|
57720
|
+
var { randomBytes: randomBytes12, createHash: createHash20 } = __require("crypto");
|
|
57785
57721
|
var { Duplex, Readable } = __require("stream");
|
|
57786
57722
|
var { URL: URL2 } = __require("url");
|
|
57787
57723
|
var PerMessageDeflate2 = require_permessage_deflate2();
|
|
@@ -58449,7 +58385,7 @@ var require_websocket2 = __commonJS({
|
|
|
58449
58385
|
abortHandshake(websocket2, socket2, "Invalid Upgrade header");
|
|
58450
58386
|
return;
|
|
58451
58387
|
}
|
|
58452
|
-
const digest2 =
|
|
58388
|
+
const digest2 = createHash20("sha1").update(key + GUID).digest("base64");
|
|
58453
58389
|
if (res.headers["sec-websocket-accept"] !== digest2) {
|
|
58454
58390
|
abortHandshake(websocket2, socket2, "Invalid Sec-WebSocket-Accept header");
|
|
58455
58391
|
return;
|
|
@@ -58818,7 +58754,7 @@ var require_websocket_server = __commonJS({
|
|
|
58818
58754
|
var EventEmitter = __require("events");
|
|
58819
58755
|
var http = __require("http");
|
|
58820
58756
|
var { Duplex } = __require("stream");
|
|
58821
|
-
var { createHash:
|
|
58757
|
+
var { createHash: createHash20 } = __require("crypto");
|
|
58822
58758
|
var extension2 = require_extension();
|
|
58823
58759
|
var PerMessageDeflate2 = require_permessage_deflate2();
|
|
58824
58760
|
var subprotocol2 = require_subprotocol();
|
|
@@ -59125,7 +59061,7 @@ var require_websocket_server = __commonJS({
|
|
|
59125
59061
|
);
|
|
59126
59062
|
}
|
|
59127
59063
|
if (this._state > RUNNING) return abortHandshake(socket2, 503);
|
|
59128
|
-
const digest2 =
|
|
59064
|
+
const digest2 = createHash20("sha1").update(key + GUID).digest("base64");
|
|
59129
59065
|
const headers = [
|
|
59130
59066
|
"HTTP/1.1 101 Switching Protocols",
|
|
59131
59067
|
"Upgrade: websocket",
|
|
@@ -59571,7 +59507,7 @@ var init_shipper = __esm({
|
|
|
59571
59507
|
function validateIncomingRecord(input) {
|
|
59572
59508
|
if (!input || typeof input !== "object") throw new Error("sync record harus object");
|
|
59573
59509
|
const row = input;
|
|
59574
|
-
if (typeof row.entity !== "string" || !isEntity(row.entity)) throw new
|
|
59510
|
+
if (typeof row.entity !== "string" || !isEntity(row.entity)) throw new UnknownEntityError(row.entity);
|
|
59575
59511
|
if (typeof row.recordId !== "string" || !row.recordId || row.recordId.length > 256) throw new Error("sync recordId invalid");
|
|
59576
59512
|
if (!Number.isSafeInteger(row.version) || Number(row.version) < 0) throw new Error("sync version invalid");
|
|
59577
59513
|
if (!row.data || typeof row.data !== "object" || Array.isArray(row.data)) throw new Error("sync data invalid");
|
|
@@ -59639,7 +59575,11 @@ async function applyFeedFrame(msg) {
|
|
|
59639
59575
|
try {
|
|
59640
59576
|
const record2 = validateIncomingRecord({ ...msg, version: Number(msg.version ?? 0), data: msg.data ?? {} });
|
|
59641
59577
|
if (record2.op) await applyRemote(record2.entity, record2.recordId, record2.version, record2.data, record2.op);
|
|
59642
|
-
} catch {
|
|
59578
|
+
} catch (e) {
|
|
59579
|
+
if (e instanceof UnknownEntityError) {
|
|
59580
|
+
if (msg.seq && !feedHole) await setCursor(String(msg.seq));
|
|
59581
|
+
return true;
|
|
59582
|
+
}
|
|
59643
59583
|
feedHole = true;
|
|
59644
59584
|
return false;
|
|
59645
59585
|
}
|
|
@@ -59671,11 +59611,18 @@ async function bootstrapOnce(transport) {
|
|
|
59671
59611
|
let cursor = "0";
|
|
59672
59612
|
let applied = 0;
|
|
59673
59613
|
for (let page = 0; page < MAX_DRAIN_PAGES; page++) {
|
|
59674
|
-
const q2 = after ?
|
|
59614
|
+
const q2 = `?${OPTIONAL_QUERY}${after ? `&after=${encodeURIComponent(after)}` : ""}`;
|
|
59675
59615
|
const res = await transport("GET", `/api/sync/bootstrap${q2}`);
|
|
59676
59616
|
if (res.status !== 200 || !Array.isArray(res.body?.records)) return null;
|
|
59617
|
+
if (page === 0) hubOptional = Array.isArray(res.body.entities) ? new Set(res.body.entities) : null;
|
|
59677
59618
|
for (const raw of res.body.records) {
|
|
59678
|
-
|
|
59619
|
+
let rec;
|
|
59620
|
+
try {
|
|
59621
|
+
rec = validateIncomingRecord(raw);
|
|
59622
|
+
} catch (e) {
|
|
59623
|
+
if (e instanceof UnknownEntityError) continue;
|
|
59624
|
+
throw e;
|
|
59625
|
+
}
|
|
59679
59626
|
if (!rec.op) continue;
|
|
59680
59627
|
await applyRemote(rec.entity, rec.recordId, rec.version, rec.data, rec.op);
|
|
59681
59628
|
applied++;
|
|
@@ -59686,6 +59633,46 @@ async function bootstrapOnce(transport) {
|
|
|
59686
59633
|
after = next;
|
|
59687
59634
|
}
|
|
59688
59635
|
await setCursor(cursor);
|
|
59636
|
+
if (hubOptional) await setOptionalMarker([...hubOptional]);
|
|
59637
|
+
return applied;
|
|
59638
|
+
}
|
|
59639
|
+
async function getOptionalMarker() {
|
|
59640
|
+
const st = await prisma.syncState.findUnique({ where: { id: 1 } });
|
|
59641
|
+
return new Set((st?.entities ?? "").split(",").filter(Boolean));
|
|
59642
|
+
}
|
|
59643
|
+
async function setOptionalMarker(entities) {
|
|
59644
|
+
const have = await getOptionalMarker();
|
|
59645
|
+
for (const e of entities) if (isOptionalEntity(String(e))) have.add(String(e));
|
|
59646
|
+
const value = OPTIONAL_ENTITIES.filter((e) => have.has(e)).join(",");
|
|
59647
|
+
await prisma.syncState.upsert({ where: { id: 1 }, create: { id: 1, entities: value }, update: { entities: value } });
|
|
59648
|
+
}
|
|
59649
|
+
async function catchUpOptional(transport) {
|
|
59650
|
+
const have = await getOptionalMarker();
|
|
59651
|
+
const missing = OPTIONAL_ENTITIES.filter((e) => !have.has(e) && hubOptional?.has(e));
|
|
59652
|
+
if (!missing.length) return 0;
|
|
59653
|
+
if (await getCursor() === "0") {
|
|
59654
|
+
await setOptionalMarker(missing);
|
|
59655
|
+
return 0;
|
|
59656
|
+
}
|
|
59657
|
+
const only = missing.join(",");
|
|
59658
|
+
let after = null;
|
|
59659
|
+
let applied = 0;
|
|
59660
|
+
for (let page = 0; page < MAX_DRAIN_PAGES; page++) {
|
|
59661
|
+
const q2 = `${OPTIONAL_QUERY}&only=${only}${after ? `&after=${encodeURIComponent(after)}` : ""}`;
|
|
59662
|
+
const res = await transport("GET", `/api/sync/bootstrap?${q2}`);
|
|
59663
|
+
if (res.status !== 200 || !Array.isArray(res.body?.records)) return applied;
|
|
59664
|
+
for (const raw of res.body.records) {
|
|
59665
|
+
const rec = validateIncomingRecord(raw);
|
|
59666
|
+
if (rec.op) {
|
|
59667
|
+
await applyRemote(rec.entity, rec.recordId, rec.version, rec.data, rec.op);
|
|
59668
|
+
applied++;
|
|
59669
|
+
}
|
|
59670
|
+
}
|
|
59671
|
+
const next = res.body.next ? String(res.body.next) : null;
|
|
59672
|
+
if (!res.body.hasMore || !next) break;
|
|
59673
|
+
after = next;
|
|
59674
|
+
}
|
|
59675
|
+
await setOptionalMarker(missing);
|
|
59689
59676
|
return applied;
|
|
59690
59677
|
}
|
|
59691
59678
|
async function syncOnce(transport) {
|
|
@@ -59704,9 +59691,18 @@ async function syncOnce(transport) {
|
|
|
59704
59691
|
let deferred = [];
|
|
59705
59692
|
for (let page = 0; page < MAX_DRAIN_PAGES; page++) {
|
|
59706
59693
|
const cursor = await getCursor();
|
|
59707
|
-
const pullRes = await transport("GET", `/api/sync/pull?since=${cursor}`);
|
|
59694
|
+
const pullRes = await transport("GET", `/api/sync/pull?since=${cursor}&${OPTIONAL_QUERY}`);
|
|
59708
59695
|
const rawRecords = Array.isArray(pullRes.body?.records) ? pullRes.body.records : [];
|
|
59709
|
-
|
|
59696
|
+
hubOptional = Array.isArray(pullRes.body?.entities) ? new Set(pullRes.body.entities) : null;
|
|
59697
|
+
const records = [];
|
|
59698
|
+
for (const raw of rawRecords) {
|
|
59699
|
+
try {
|
|
59700
|
+
records.push(validateIncomingRecord(raw));
|
|
59701
|
+
} catch (e) {
|
|
59702
|
+
if (!(e instanceof UnknownEntityError)) throw e;
|
|
59703
|
+
stats.dropped++;
|
|
59704
|
+
}
|
|
59705
|
+
}
|
|
59710
59706
|
for (const rec of records) {
|
|
59711
59707
|
if (!isEntity(rec.entity)) continue;
|
|
59712
59708
|
if (!rec.op) {
|
|
@@ -59745,6 +59741,7 @@ async function syncOnce(transport) {
|
|
|
59745
59741
|
console.warn(`sync: record ${rec.entity}:${rec.recordId} tak bisa diterapkan \u2014 dilewati`);
|
|
59746
59742
|
stats.dropped++;
|
|
59747
59743
|
}
|
|
59744
|
+
await catchUpOptional(transport).catch((e) => console.warn("sync: catch-up entitas opsional gagal \u2014", e.message));
|
|
59748
59745
|
for (const item of outbox) {
|
|
59749
59746
|
if (item.entity === "projectRename") {
|
|
59750
59747
|
const [oldId, newId] = item.recordId.split(RENAME_SEP);
|
|
@@ -59769,6 +59766,7 @@ async function syncOnce(transport) {
|
|
|
59769
59766
|
await clearOutbox(item.entity, item.recordId);
|
|
59770
59767
|
continue;
|
|
59771
59768
|
}
|
|
59769
|
+
if (isOptionalEntity(item.entity) && !hubOptional?.has(item.entity)) continue;
|
|
59772
59770
|
const snap = await snapshot(item.entity, item.recordId);
|
|
59773
59771
|
if (!snap) {
|
|
59774
59772
|
const tomb = await findTombstone(item.entity, item.recordId);
|
|
@@ -59838,6 +59836,11 @@ async function syncOnce(transport) {
|
|
|
59838
59836
|
} catch (e) {
|
|
59839
59837
|
console.warn("sync: unggah byte lampiran QA gagal \u2014", e.message);
|
|
59840
59838
|
}
|
|
59839
|
+
try {
|
|
59840
|
+
await downloadPendingQaBytes();
|
|
59841
|
+
} catch (e) {
|
|
59842
|
+
console.warn("sync: unduh byte lampiran QA gagal \u2014", e.message);
|
|
59843
|
+
}
|
|
59841
59844
|
return stats;
|
|
59842
59845
|
}
|
|
59843
59846
|
function fetchTransport(base3, token) {
|
|
@@ -59916,7 +59919,7 @@ async function startSyncClient(base3, token, tickMs2) {
|
|
|
59916
59919
|
const tick6 = () => syncTick(transport);
|
|
59917
59920
|
const connectWs = async () => {
|
|
59918
59921
|
const { WebSocket: WebSocket2 } = await Promise.resolve().then(() => (init_wrapper(), wrapper_exports));
|
|
59919
|
-
const wsUrl = base3.replace(/^http/, "ws").replace(/\/$/, "") + "/api/sync/ws";
|
|
59922
|
+
const wsUrl = base3.replace(/^http/, "ws").replace(/\/$/, "") + "/api/sync/ws?" + OPTIONAL_QUERY;
|
|
59920
59923
|
const sock2 = new WebSocket2(wsUrl, { headers: { authorization: `Bearer ${token}` } });
|
|
59921
59924
|
ws = sock2;
|
|
59922
59925
|
sock2.on("open", () => {
|
|
@@ -59984,7 +59987,7 @@ async function applySyncConfig() {
|
|
|
59984
59987
|
const token = effectiveStr("SYNC_DEVICE_TOKEN");
|
|
59985
59988
|
if (base3 && token) await startSyncClient(base3, token, effectiveInt("SYNC_TICK_MS"));
|
|
59986
59989
|
}
|
|
59987
|
-
var MAX_SYNC_RECORD_BYTES, feedHole, MAX_DRAIN_PAGES, timer5, ws, started, reconnectTimer, reconnectDelay, presence, pullSehat;
|
|
59990
|
+
var MAX_SYNC_RECORD_BYTES, OPTIONAL_QUERY, hubOptional, UnknownEntityError, feedHole, MAX_DRAIN_PAGES, timer5, ws, started, reconnectTimer, reconnectDelay, presence, pullSehat;
|
|
59988
59991
|
var init_sync_client = __esm({
|
|
59989
59992
|
"src/services/sync-client.ts"() {
|
|
59990
59993
|
"use strict";
|
|
@@ -60003,6 +60006,13 @@ var init_sync_client = __esm({
|
|
|
60003
60006
|
init_client2();
|
|
60004
60007
|
init_shipper();
|
|
60005
60008
|
MAX_SYNC_RECORD_BYTES = 1024 * 1024;
|
|
60009
|
+
OPTIONAL_QUERY = `entities=${OPTIONAL_ENTITIES.join(",")}`;
|
|
60010
|
+
hubOptional = null;
|
|
60011
|
+
UnknownEntityError = class extends Error {
|
|
60012
|
+
constructor(entity) {
|
|
60013
|
+
super(`sync entity tak dikenal: ${String(entity)}`);
|
|
60014
|
+
}
|
|
60015
|
+
};
|
|
60006
60016
|
feedHole = false;
|
|
60007
60017
|
MAX_DRAIN_PAGES = 500;
|
|
60008
60018
|
started = false;
|
|
@@ -61980,7 +61990,7 @@ var require_Decoder = __commonJS({
|
|
|
61980
61990
|
"../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/Decoder.js"(exports, module) {
|
|
61981
61991
|
"use strict";
|
|
61982
61992
|
var RE_PLUS = /\+/g;
|
|
61983
|
-
var
|
|
61993
|
+
var HEX = [
|
|
61984
61994
|
0,
|
|
61985
61995
|
0,
|
|
61986
61996
|
0,
|
|
@@ -62121,7 +62131,7 @@ var require_Decoder = __commonJS({
|
|
|
62121
62131
|
const len = str5.length;
|
|
62122
62132
|
for (; i < len; ++i) {
|
|
62123
62133
|
if (this.buffer !== void 0) {
|
|
62124
|
-
if (!
|
|
62134
|
+
if (!HEX[str5.charCodeAt(i)]) {
|
|
62125
62135
|
res += "%" + this.buffer;
|
|
62126
62136
|
this.buffer = void 0;
|
|
62127
62137
|
--i;
|
|
@@ -62542,7 +62552,7 @@ var require_fastify_plugin = __commonJS({
|
|
|
62542
62552
|
var require_generateId = __commonJS({
|
|
62543
62553
|
"../node_modules/.pnpm/@fastify+multipart@10.1.0/node_modules/@fastify/multipart/lib/generateId.js"(exports, module) {
|
|
62544
62554
|
"use strict";
|
|
62545
|
-
var
|
|
62555
|
+
var HEX = [
|
|
62546
62556
|
"00",
|
|
62547
62557
|
"01",
|
|
62548
62558
|
"02",
|
|
@@ -62802,13 +62812,13 @@ var require_generateId = __commonJS({
|
|
|
62802
62812
|
];
|
|
62803
62813
|
var random = Math.random;
|
|
62804
62814
|
function seed() {
|
|
62805
|
-
return
|
|
62815
|
+
return HEX[255 * random() | 0] + HEX[255 * random() | 0] + HEX[255 * random() | 0] + HEX[255 * random() | 0] + HEX[255 * random() | 0] + HEX[255 * random() | 0] + HEX[255 * random() | 0];
|
|
62806
62816
|
}
|
|
62807
62817
|
module.exports.generateId = function generateIdFn() {
|
|
62808
62818
|
let num = 0;
|
|
62809
62819
|
let str5 = seed();
|
|
62810
62820
|
return function generateId() {
|
|
62811
|
-
return num === 255 ? (str5 = seed()) +
|
|
62821
|
+
return num === 255 ? (str5 = seed()) + HEX[num = 0] : str5 + HEX[++num];
|
|
62812
62822
|
};
|
|
62813
62823
|
}();
|
|
62814
62824
|
}
|
|
@@ -63667,7 +63677,7 @@ import websocket from "@fastify/websocket";
|
|
|
63667
63677
|
import cookie from "@fastify/cookie";
|
|
63668
63678
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
63669
63679
|
import { dirname as dirname17 } from "node:path";
|
|
63670
|
-
import { existsSync as
|
|
63680
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
63671
63681
|
|
|
63672
63682
|
// src/web-dir.ts
|
|
63673
63683
|
import { resolve as resolve10 } from "node:path";
|
|
@@ -69235,11 +69245,11 @@ ${item.context}` : item.context;
|
|
|
69235
69245
|
if (live && parsed.data.confirm !== true)
|
|
69236
69246
|
return reply.code(409).send({ error: "confirm-required", session: { id: live.id, agent: live.agent } });
|
|
69237
69247
|
if (!live) {
|
|
69238
|
-
const
|
|
69248
|
+
const view12 = await presenceView();
|
|
69239
69249
|
const verdict = remoteSessionVerdict({
|
|
69240
69250
|
specId: id2,
|
|
69241
69251
|
now: Date.now(),
|
|
69242
|
-
devices:
|
|
69252
|
+
devices: view12.devices,
|
|
69243
69253
|
recentlyOffline: recentlyOffline(id2),
|
|
69244
69254
|
lastResultDeviceId: null
|
|
69245
69255
|
});
|
|
@@ -71083,7 +71093,7 @@ async function startSpecSession(spec, opts2) {
|
|
|
71083
71093
|
return withSessionAdmission({ id: id2, force: opts2.force }, async () => {
|
|
71084
71094
|
const pane = await getSessionAsync(id2);
|
|
71085
71095
|
if (!pane) {
|
|
71086
|
-
const
|
|
71096
|
+
const view12 = await presenceView();
|
|
71087
71097
|
const lastResult = await prisma.sessionResult.findFirst({
|
|
71088
71098
|
where: { specId: spec.id },
|
|
71089
71099
|
orderBy: { createdAt: "desc" },
|
|
@@ -71093,7 +71103,7 @@ async function startSpecSession(spec, opts2) {
|
|
|
71093
71103
|
const verdict = remoteSessionVerdict({
|
|
71094
71104
|
specId: spec.id,
|
|
71095
71105
|
now: Date.now(),
|
|
71096
|
-
devices:
|
|
71106
|
+
devices: view12.devices,
|
|
71097
71107
|
recentlyOffline: recentlyOffline(spec.id),
|
|
71098
71108
|
lastResultDeviceId
|
|
71099
71109
|
});
|
|
@@ -77016,6 +77026,7 @@ function detachSync(c) {
|
|
|
77016
77026
|
function broadcastSyncLog(row) {
|
|
77017
77027
|
const s2 = JSON.stringify({ t: "sync", ...row });
|
|
77018
77028
|
for (const c of clients2) {
|
|
77029
|
+
if (isOptionalEntity(row.entity) && !c.accept?.has(row.entity)) continue;
|
|
77019
77030
|
try {
|
|
77020
77031
|
c.send(s2);
|
|
77021
77032
|
} catch {
|
|
@@ -77156,12 +77167,16 @@ async function requireDeviceWs(req, reply) {
|
|
|
77156
77167
|
}
|
|
77157
77168
|
async function sync_default(app3) {
|
|
77158
77169
|
app3.get("/sync/pull", { preHandler: requireDeviceToken }, async (req, reply) => {
|
|
77159
|
-
const
|
|
77160
|
-
return maybeGzip(req, reply, await pull(since));
|
|
77170
|
+
const q2 = req.query;
|
|
77171
|
+
return maybeGzip(req, reply, await pull(q2.since ?? "0", void 0, void 0, { accept: acceptedOptional(q2.entities) }));
|
|
77161
77172
|
});
|
|
77162
77173
|
app3.get("/sync/bootstrap", { preHandler: requireDeviceToken }, async (req, reply) => {
|
|
77163
|
-
const
|
|
77164
|
-
return maybeGzip(req, reply, await bootstrapSnapshot(after
|
|
77174
|
+
const q2 = req.query;
|
|
77175
|
+
return maybeGzip(req, reply, await bootstrapSnapshot(q2.after ?? null, void 0, {
|
|
77176
|
+
accept: acceptedOptional(q2.entities),
|
|
77177
|
+
// ADR-0180 · catch-up: hanya entitas opsional yang diminta, tanpa memindahkan kursor client.
|
|
77178
|
+
...q2.only ? { only: acceptedOptional(q2.only) } : {}
|
|
77179
|
+
}));
|
|
77165
77180
|
});
|
|
77166
77181
|
app3.get("/sync/attachments/:storageKey", { preHandler: requireDeviceToken }, async (req, reply) => {
|
|
77167
77182
|
const { storageKey } = req.params;
|
|
@@ -77314,7 +77329,11 @@ async function sync_default(app3) {
|
|
|
77314
77329
|
socket2.close(1008, "connection limit");
|
|
77315
77330
|
return;
|
|
77316
77331
|
}
|
|
77317
|
-
const client = {
|
|
77332
|
+
const client = {
|
|
77333
|
+
send: (m) => socket2.send(m),
|
|
77334
|
+
close: () => socket2.close(),
|
|
77335
|
+
accept: acceptedOptional(req.query.entities)
|
|
77336
|
+
};
|
|
77318
77337
|
attachSync(client);
|
|
77319
77338
|
const unregisterSocket = registerDeviceSocket(principal.id, "sync", socket2);
|
|
77320
77339
|
const guard = new WsMessageGuard({ perWindow: PRESENCE_MAX_FRAMES_PER_MIN });
|
|
@@ -78414,338 +78433,6 @@ async function lead_default(app3) {
|
|
|
78414
78433
|
});
|
|
78415
78434
|
}
|
|
78416
78435
|
|
|
78417
|
-
// src/routes/changelog.ts
|
|
78418
|
-
init_src();
|
|
78419
|
-
init_db();
|
|
78420
|
-
init_local_binding();
|
|
78421
|
-
|
|
78422
|
-
// src/services/changelog/collect.ts
|
|
78423
|
-
init_db();
|
|
78424
|
-
import { execFile as execFile17 } from "node:child_process";
|
|
78425
|
-
import { promisify as promisify16 } from "node:util";
|
|
78426
|
-
import { existsSync as existsSync22 } from "node:fs";
|
|
78427
|
-
|
|
78428
|
-
// src/services/changelog/scrub.ts
|
|
78429
|
-
var MERGE = /^merge\s+(branch|pull request|remote-tracking branch|tag)\b/i;
|
|
78430
|
-
var CONVENTIONAL = /^(feat|fix|chore|docs|refactor|test|perf|build|ci|style|revert)(\([^)]*\))?!?:\s*/i;
|
|
78431
|
-
var PATH_LIKE = /(?:[\w.@-]+\/)+[\w.-]+\.\w{1,6}/g;
|
|
78432
|
-
var HEX = /\b(?=[0-9a-f]{7,40}\b)(?=[0-9a-f]*\d)(?=[0-9a-f]*[a-f])[0-9a-f]{7,40}\b/gi;
|
|
78433
|
-
var INTERNAL_REF = /\b(?:SPEC|ADR|PR|ISSUE)[-\s]?\d+\b/gi;
|
|
78434
|
-
var CAMEL = /\b[a-z]{2,}(?:[A-Z][a-z]{2,})+\b/g;
|
|
78435
|
-
var SNAKE = /\b[a-z][a-z0-9]*(?:_[a-z0-9]+)+\b/gi;
|
|
78436
|
-
var CALL = /\b[A-Za-z_][\w.]*\(\s*\)/g;
|
|
78437
|
-
var INLINE_CODE = /`[^`\n]*`/g;
|
|
78438
|
-
var FENCE = /^```[\s\S]*?^```$/gm;
|
|
78439
|
-
var TRAILER = /^[A-Za-z-]+:\s.*$/gm;
|
|
78440
|
-
var tidy = (s2) => s2.replace(/\(\s*\)/g, " ").replace(/[ \t]{2,}/g, " ").replace(/\s+([,.;:!?])/g, "$1").replace(/^[\s:;,·—–-]+/, "").replace(/[\s:;,·—–-]+$/, "").trim();
|
|
78441
|
-
function strip(s2) {
|
|
78442
|
-
return s2.replace(INLINE_CODE, " ").replace(PATH_LIKE, " ").replace(CALL, " ").replace(HEX, " ").replace(INTERNAL_REF, " ").replace(CAMEL, " ").replace(SNAKE, " ");
|
|
78443
|
-
}
|
|
78444
|
-
function scrubSubject(s2) {
|
|
78445
|
-
const one2 = (s2 ?? "").replace(/\s+/g, " ").trim();
|
|
78446
|
-
if (!one2 || MERGE.test(one2)) return "";
|
|
78447
|
-
return tidy(strip(one2.replace(CONVENTIONAL, "")));
|
|
78448
|
-
}
|
|
78449
|
-
function scrubBody(s2) {
|
|
78450
|
-
const first = (s2 ?? "").replace(TRAILER, "").split(/\n\s*\n/)[0] ?? "";
|
|
78451
|
-
return tidy(strip(first.replace(/\s+/g, " ").trim()));
|
|
78452
|
-
}
|
|
78453
|
-
function scrubOutput(md) {
|
|
78454
|
-
const noFence = (md ?? "").replace(FENCE, "");
|
|
78455
|
-
const lines2 = noFence.split("\n").map((line) => {
|
|
78456
|
-
const m = /^(\s*(?:[-*+]|\d+\.)\s+|\s*#{1,6}\s+|\s*>\s*)?(.*)$/.exec(line);
|
|
78457
|
-
const lead = m?.[1] ?? "";
|
|
78458
|
-
const rest = m?.[2] ?? line;
|
|
78459
|
-
const cleaned = tidy(strip(rest));
|
|
78460
|
-
return cleaned ? `${lead}${cleaned}` : "";
|
|
78461
|
-
});
|
|
78462
|
-
return lines2.join("\n").replace(/\n{3,}/g, "\n\n").trim() + "\n";
|
|
78463
|
-
}
|
|
78464
|
-
|
|
78465
|
-
// src/services/changelog/collect.ts
|
|
78466
|
-
var toItem = (label2, detail) => {
|
|
78467
|
-
const l = scrubSubject(label2);
|
|
78468
|
-
return l ? { label: l, detail: scrubBody(detail) } : null;
|
|
78469
|
-
};
|
|
78470
|
-
async function collectBacklog(projectId, from, to) {
|
|
78471
|
-
const f = dayStart(from), t = dayEnd(to);
|
|
78472
|
-
const rows = await prisma.spec.findMany({
|
|
78473
|
-
where: { projectId, stage: "done" },
|
|
78474
|
-
select: { title: true, objective: true, doneAt: true },
|
|
78475
|
-
orderBy: [{ doneAt: "asc" }, { id: "asc" }]
|
|
78476
|
-
});
|
|
78477
|
-
const stampless = rows.filter((r) => r.doneAt === null).length;
|
|
78478
|
-
const hit = rows.filter((r) => inDayRange(r.doneAt, f, t));
|
|
78479
|
-
const items = hit.map((r) => toItem(r.title, r.objective ?? "")).filter((x) => x !== null);
|
|
78480
|
-
if (items.length === 0)
|
|
78481
|
-
return { ok: false, reason: `tak ada backlog yang selesai antara ${from} dan ${to}` };
|
|
78482
|
-
const notes = [];
|
|
78483
|
-
if (stampless > 0)
|
|
78484
|
-
notes.push(`${stampless} item selesai tanpa stempel waktu (selesai sebelum stempel ini ada) dan tak ikut dihitung.`);
|
|
78485
|
-
return { ok: true, input: { mode: "backlog", title: `${from} \u2013 ${to}`, items, notes } };
|
|
78486
|
-
}
|
|
78487
|
-
var exec11 = promisify16(execFile17);
|
|
78488
|
-
var GIT8 = { maxBuffer: 1 << 24, encoding: "utf8" };
|
|
78489
|
-
var US2 = "";
|
|
78490
|
-
var RS = "";
|
|
78491
|
-
var NO_REPO = "project ini belum ditautkan ke repo di mesin ini";
|
|
78492
|
-
var NO_TAG = "repo project ini belum punya tag rilis";
|
|
78493
|
-
var usable2 = (repoDir) => !!repoDir && existsSync22(repoDir);
|
|
78494
|
-
async function listTags(repoDir) {
|
|
78495
|
-
if (!usable2(repoDir)) return { tags: [], head: null, reason: NO_REPO };
|
|
78496
|
-
try {
|
|
78497
|
-
const { stdout } = await exec11("git", ["tag", "--list", "--sort=-creatordate"], { cwd: repoDir, ...GIT8 });
|
|
78498
|
-
const tags = stdout.split("\n").map((s2) => s2.trim()).filter(Boolean);
|
|
78499
|
-
const head = await exec11("git", ["rev-parse", "--short", "HEAD"], { cwd: repoDir, ...GIT8 }).then((r) => r.stdout.trim()).catch(() => null);
|
|
78500
|
-
return { tags, head, reason: tags.length ? null : NO_TAG };
|
|
78501
|
-
} catch {
|
|
78502
|
-
return { tags: [], head: null, reason: NO_TAG };
|
|
78503
|
-
}
|
|
78504
|
-
}
|
|
78505
|
-
async function previousTag(repoDir, tag2) {
|
|
78506
|
-
return exec11("git", ["describe", "--tags", "--abbrev=0", "--end-of-options", `${tag2}^`], { cwd: repoDir, ...GIT8 }).then((r) => r.stdout.trim() || null).catch(() => null);
|
|
78507
|
-
}
|
|
78508
|
-
async function revExists(repoDir, rev) {
|
|
78509
|
-
return exec11("git", ["rev-parse", "--verify", "--quiet", "--end-of-options", `${rev}^{commit}`], { cwd: repoDir, ...GIT8 }).then(() => true).catch(() => false);
|
|
78510
|
-
}
|
|
78511
|
-
async function logRange(repoDir, range) {
|
|
78512
|
-
const { stdout } = await exec11(
|
|
78513
|
-
"git",
|
|
78514
|
-
["log", "--no-merges", `--pretty=format:%s${US2}%b${RS}`, ...range],
|
|
78515
|
-
{ cwd: repoDir, ...GIT8 }
|
|
78516
|
-
);
|
|
78517
|
-
return stdout.split(RS).map((rec) => rec.replace(/^\n+/, "")).filter((rec) => rec.trim()).map((rec) => {
|
|
78518
|
-
const [subject, body] = rec.split(US2);
|
|
78519
|
-
return toItem(subject ?? "", body ?? "");
|
|
78520
|
-
}).filter((x) => x !== null);
|
|
78521
|
-
}
|
|
78522
|
-
async function collectCommits(repoDir, fromSha, toSha) {
|
|
78523
|
-
if (!usable2(repoDir)) return { ok: false, reason: NO_REPO };
|
|
78524
|
-
for (const rev of [fromSha, toSha])
|
|
78525
|
-
if (!await revExists(repoDir, rev)) return { ok: false, reason: `revisi "${rev}" tak dikenal di repo project` };
|
|
78526
|
-
try {
|
|
78527
|
-
const items = await logRange(repoDir, ["--end-of-options", `${fromSha}..${toSha}`]);
|
|
78528
|
-
if (!items.length) return { ok: false, reason: `tak ada perubahan antara "${fromSha}" dan "${toSha}"` };
|
|
78529
|
-
const short = (s2) => s2.slice(0, 7);
|
|
78530
|
-
return { ok: true, input: { mode: "commit", title: `${short(fromSha)} \u2192 ${short(toSha)}`, items, notes: [] } };
|
|
78531
|
-
} catch (e) {
|
|
78532
|
-
return { ok: false, reason: `git menolak rentang itu: ${e.message.split("\n")[0]}` };
|
|
78533
|
-
}
|
|
78534
|
-
}
|
|
78535
|
-
async function collectVersions(repoDir, fromTag, toTag) {
|
|
78536
|
-
const { tags, reason } = await listTags(repoDir);
|
|
78537
|
-
if (reason) return { ok: false, reason };
|
|
78538
|
-
if (!usable2(repoDir)) return { ok: false, reason: NO_REPO };
|
|
78539
|
-
if (!tags.includes(toTag)) return { ok: false, reason: `tag "${toTag}" tak ada di repo project` };
|
|
78540
|
-
if (fromTag && !tags.includes(fromTag)) return { ok: false, reason: `tag "${fromTag}" tak ada di repo project` };
|
|
78541
|
-
const prev = fromTag ?? await previousTag(repoDir, toTag);
|
|
78542
|
-
try {
|
|
78543
|
-
const items = await logRange(repoDir, ["--end-of-options", prev ? `${prev}..${toTag}` : toTag]);
|
|
78544
|
-
if (!items.length) return { ok: false, reason: `tak ada perubahan yang masuk ke "${toTag}"` };
|
|
78545
|
-
const title = fromTag ? `${fromTag} \u2192 ${toTag}` : toTag;
|
|
78546
|
-
return { ok: true, input: { mode: "version", title, items, notes: [] } };
|
|
78547
|
-
} catch (e) {
|
|
78548
|
-
return { ok: false, reason: `git menolak rentang itu: ${e.message.split("\n")[0]}` };
|
|
78549
|
-
}
|
|
78550
|
-
}
|
|
78551
|
-
|
|
78552
|
-
// src/services/changelog/generate.ts
|
|
78553
|
-
init_src();
|
|
78554
|
-
init_db();
|
|
78555
|
-
init_local_binding();
|
|
78556
|
-
|
|
78557
|
-
// src/services/changelog/config.ts
|
|
78558
|
-
init_src();
|
|
78559
|
-
init_settings3();
|
|
78560
|
-
async function changelogAgentDefaults() {
|
|
78561
|
-
const e = (await getSetting()).changelog;
|
|
78562
|
-
if (!e.enabled) return sessionAgentDefaults();
|
|
78563
|
-
return e.agent === "codex" ? { agent: "codex", model: e.model, effort: coerceCodexEffort(e.model, e.effort) } : { agent: "claude", model: e.model, effort: e.effort };
|
|
78564
|
-
}
|
|
78565
|
-
|
|
78566
|
-
// src/services/changelog/render.ts
|
|
78567
|
-
init_src2();
|
|
78568
|
-
var MODE_LABEL = {
|
|
78569
|
-
backlog: "backlog yang selesai dalam rentang tanggal",
|
|
78570
|
-
commit: "riwayat perubahan repo dalam rentang yang dipilih",
|
|
78571
|
-
version: "perubahan yang masuk ke versi/rilis itu"
|
|
78572
|
-
};
|
|
78573
|
-
var DETAIL_MAX = 240;
|
|
78574
|
-
var clip3 = (s2) => s2.length > DETAIL_MAX ? `${s2.slice(0, DETAIL_MAX - 1).trimEnd()}\u2026` : s2;
|
|
78575
|
-
function fallbackMarkdown(input) {
|
|
78576
|
-
const lines2 = [`# Changelog \u2014 ${input.title}`, ""];
|
|
78577
|
-
if (input.items.length === 0) {
|
|
78578
|
-
lines2.push("_Tak ada perubahan yang bisa diringkas untuk rentang ini._", "");
|
|
78579
|
-
} else {
|
|
78580
|
-
lines2.push(`Ringkasan ${input.items.length} perubahan untuk pemakai.`, "");
|
|
78581
|
-
for (const it of input.items) {
|
|
78582
|
-
const d = clip3(it.detail.trim());
|
|
78583
|
-
lines2.push(d ? `- **${it.label}** \u2014 ${d}` : `- **${it.label}**`);
|
|
78584
|
-
}
|
|
78585
|
-
lines2.push("");
|
|
78586
|
-
}
|
|
78587
|
-
if (input.notes.length) {
|
|
78588
|
-
lines2.push("---", "");
|
|
78589
|
-
for (const n2 of input.notes) lines2.push(`> ${n2}`);
|
|
78590
|
-
lines2.push("");
|
|
78591
|
-
}
|
|
78592
|
-
return lines2.join("\n");
|
|
78593
|
-
}
|
|
78594
|
-
function changelogPrompt(input, budgetMs) {
|
|
78595
|
-
const budget = Math.max(1, Math.round(budgetMs / 1e3));
|
|
78596
|
-
const items = input.items.map((it, i) => `${i + 1}. ${it.label}${it.detail ? ` \u2014 ${it.detail}` : ""}`).join("\n");
|
|
78597
|
-
return [
|
|
78598
|
-
"Kamu menulis changelog untuk PEMAKAI sebuah produk, bukan untuk developer.",
|
|
78599
|
-
"",
|
|
78600
|
-
`Anggaran waktumu ${budget} detik. Jawab langsung \u2014 jangan membaca berkas, jangan memakai tool,`,
|
|
78601
|
-
"jangan menyelidiki apa pun. Seluruh bahan sudah ada di bawah.",
|
|
78602
|
-
"",
|
|
78603
|
-
`Judul rentang: ${input.title}`,
|
|
78604
|
-
`Bahan berasal dari ${MODE_LABEL[input.mode]}.`,
|
|
78605
|
-
"",
|
|
78606
|
-
"Bahan:",
|
|
78607
|
-
items || "(tidak ada)",
|
|
78608
|
-
"",
|
|
78609
|
-
"Aturan:",
|
|
78610
|
-
"- Bahasa Indonesia, gaya editorial: tenang, ringkas, kalimat penuh.",
|
|
78611
|
-
"- Tulis apa yang berubah BAGI PEMAKAI, bukan apa yang disentuh di dalam kode.",
|
|
78612
|
-
"- DILARANG menyebut nama berkas, nama fungsi, nama variabel, hash commit, nomor SPEC/ADR,",
|
|
78613
|
-
" nama branch, atau istilah internal apa pun.",
|
|
78614
|
-
"- Gabungkan bahan yang bicara hal yang sama jadi satu butir. 3\u201310 butir; kurangi bila memang sedikit.",
|
|
78615
|
-
"- Jangan mengarang perubahan yang tak ada di bahan.",
|
|
78616
|
-
"",
|
|
78617
|
-
// SPEC-543 · ADR-0108 · dipasang di SEMUA prompt agen, bukan hanya yang menulis kode; gerbang
|
|
78618
|
-
// di baris pertama klausa yang membuatnya diam untuk narator ini.
|
|
78619
|
-
CODE_STYLE_CLAUSE,
|
|
78620
|
-
"",
|
|
78621
|
-
"Bentuk keluaran \u2014 HANYA markdown ini, tanpa kalimat pembuka atau penutup di luarnya,",
|
|
78622
|
-
"tanpa blok kode:",
|
|
78623
|
-
"",
|
|
78624
|
-
`# Changelog \u2014 ${input.title}`,
|
|
78625
|
-
"",
|
|
78626
|
-
"<satu paragraf pembuka, maksimal dua kalimat>",
|
|
78627
|
-
"",
|
|
78628
|
-
"- **<judul singkat perubahan>** \u2014 <satu kalimat manfaatnya bagi pemakai>"
|
|
78629
|
-
].join("\n");
|
|
78630
|
-
}
|
|
78631
|
-
|
|
78632
|
-
// src/services/changelog/generate.ts
|
|
78633
|
-
var CHANGELOG_TIMEOUT_MS = 18e4;
|
|
78634
|
-
async function collect(projectId, req) {
|
|
78635
|
-
if (req.mode === "backlog") {
|
|
78636
|
-
const d = defaultRange(/* @__PURE__ */ new Date());
|
|
78637
|
-
return collectBacklog(projectId, req.from ?? d.from, req.to ?? d.to);
|
|
78638
|
-
}
|
|
78639
|
-
const repoDir = await resolveRepoDir(projectId);
|
|
78640
|
-
return req.mode === "commit" ? collectCommits(repoDir, req.fromSha, req.toSha) : collectVersions(repoDir, req.fromTag, req.toTag);
|
|
78641
|
-
}
|
|
78642
|
-
async function generateChangelog(projectId, req, deps = {}) {
|
|
78643
|
-
const got = await collect(projectId, req);
|
|
78644
|
-
if (!got.ok) return got;
|
|
78645
|
-
const input = got.input;
|
|
78646
|
-
const prompt = changelogPrompt(input, CHANGELOG_TIMEOUT_MS);
|
|
78647
|
-
const { agent, model, effort: effort2 } = await changelogAgentDefaults();
|
|
78648
|
-
const run5 = deps.think ?? think;
|
|
78649
|
-
let body = "";
|
|
78650
|
-
let generator = "agent";
|
|
78651
|
-
const warnings = [...input.notes];
|
|
78652
|
-
try {
|
|
78653
|
-
const raw = await run5(prompt, { agent, model, effort: effort2, timeoutMs: CHANGELOG_TIMEOUT_MS });
|
|
78654
|
-
body = scrubOutput(raw ?? "");
|
|
78655
|
-
if (!body.trim()) throw new Error("agen tak memulangkan teks apa pun");
|
|
78656
|
-
} catch (e) {
|
|
78657
|
-
generator = "fallback";
|
|
78658
|
-
body = fallbackMarkdown(input);
|
|
78659
|
-
warnings.push(`Narasi otomatis tak tersedia \u2014 ${e.message}. Yang tampil adalah draf ringkas.`);
|
|
78660
|
-
}
|
|
78661
|
-
const row = await prisma.changelog.create({
|
|
78662
|
-
data: {
|
|
78663
|
-
projectId,
|
|
78664
|
-
mode: input.mode,
|
|
78665
|
-
title: input.title,
|
|
78666
|
-
params: req,
|
|
78667
|
-
body,
|
|
78668
|
-
generator,
|
|
78669
|
-
itemCount: input.items.length,
|
|
78670
|
-
warning: warnings.length ? warnings.join(" ") : null
|
|
78671
|
-
}
|
|
78672
|
-
});
|
|
78673
|
-
return { ok: true, row };
|
|
78674
|
-
}
|
|
78675
|
-
|
|
78676
|
-
// src/routes/changelog.ts
|
|
78677
|
-
var view7 = (c) => ({
|
|
78678
|
-
id: c.id,
|
|
78679
|
-
projectId: c.projectId,
|
|
78680
|
-
mode: c.mode,
|
|
78681
|
-
title: c.title,
|
|
78682
|
-
params: c.params,
|
|
78683
|
-
body: c.body,
|
|
78684
|
-
generator: c.generator,
|
|
78685
|
-
warning: c.warning,
|
|
78686
|
-
itemCount: c.itemCount,
|
|
78687
|
-
createdAt: c.createdAt.toISOString()
|
|
78688
|
-
});
|
|
78689
|
-
async function changelog_default(app3) {
|
|
78690
|
-
app3.get("/projects/:id/changelog/sources", async (req, reply) => {
|
|
78691
|
-
const { id: id2 } = req.params;
|
|
78692
|
-
if (!await prisma.project.findUnique({ where: { id: id2 } })) return reply.code(404).send({ error: "not found" });
|
|
78693
|
-
const { tags, head, reason } = await listTags(await resolveRepoDir(id2));
|
|
78694
|
-
const done = await prisma.spec.findMany({
|
|
78695
|
-
where: { projectId: id2, stage: "done", doneAt: { not: null } },
|
|
78696
|
-
select: { doneAt: true },
|
|
78697
|
-
orderBy: { doneAt: "asc" }
|
|
78698
|
-
});
|
|
78699
|
-
return {
|
|
78700
|
-
hasRepo: reason === null || tags.length > 0,
|
|
78701
|
-
tags,
|
|
78702
|
-
head,
|
|
78703
|
-
reason,
|
|
78704
|
-
backlog: {
|
|
78705
|
-
doneCount: done.length,
|
|
78706
|
-
earliest: done[0]?.doneAt?.toISOString() ?? null,
|
|
78707
|
-
latest: done[done.length - 1]?.doneAt?.toISOString() ?? null
|
|
78708
|
-
},
|
|
78709
|
-
defaultRange: defaultRange(/* @__PURE__ */ new Date())
|
|
78710
|
-
};
|
|
78711
|
-
});
|
|
78712
|
-
app3.get("/projects/:id/changelog", async (req, reply) => {
|
|
78713
|
-
const { id: id2 } = req.params;
|
|
78714
|
-
if (!await prisma.project.findUnique({ where: { id: id2 } })) return reply.code(404).send({ error: "not found" });
|
|
78715
|
-
const { page, limit, q: q2 } = req.query;
|
|
78716
|
-
const rows = await prisma.changelog.findMany({ where: { projectId: id2 }, orderBy: { createdAt: "desc" } });
|
|
78717
|
-
return paginate(rows.filter((r) => changelogMatches(r, q2 ?? "")).map(view7), page, limit);
|
|
78718
|
-
});
|
|
78719
|
-
app3.get("/projects/:id/changelog/:cid", async (req, reply) => {
|
|
78720
|
-
const { id: id2, cid } = req.params;
|
|
78721
|
-
const row = await prisma.changelog.findFirst({ where: { id: cid, projectId: id2 } });
|
|
78722
|
-
if (!row) return reply.code(404).send({ error: "not found" });
|
|
78723
|
-
const fmt = downloadFormat(req.query);
|
|
78724
|
-
if (fmt) return sendDocDownload(reply, fmt, {
|
|
78725
|
-
content: row.body,
|
|
78726
|
-
name: `${row.title}.md`,
|
|
78727
|
-
prefix: `${id2}-changelog`,
|
|
78728
|
-
eyebrow: `hanoman \xB7 ${id2} \xB7 changelog`,
|
|
78729
|
-
path: row.title
|
|
78730
|
-
});
|
|
78731
|
-
return view7(row);
|
|
78732
|
-
});
|
|
78733
|
-
app3.post("/projects/:id/changelog", async (req, reply) => {
|
|
78734
|
-
const { id: id2 } = req.params;
|
|
78735
|
-
if (!await prisma.project.findUnique({ where: { id: id2 } })) return reply.code(404).send({ error: "not found" });
|
|
78736
|
-
const parsed = zChangelogRequest.safeParse(req.body);
|
|
78737
|
-
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
78738
|
-
const r = await generateChangelog(id2, parsed.data);
|
|
78739
|
-
if (!r.ok) return reply.code(422).send({ error: r.reason });
|
|
78740
|
-
return reply.code(201).send(view7(r.row));
|
|
78741
|
-
});
|
|
78742
|
-
app3.delete("/projects/:id/changelog/:cid", async (req, reply) => {
|
|
78743
|
-
const { id: id2, cid } = req.params;
|
|
78744
|
-
const { count } = await prisma.changelog.deleteMany({ where: { id: cid, projectId: id2 } });
|
|
78745
|
-
return count ? reply.code(204).send() : reply.code(404).send({ error: "not found" });
|
|
78746
|
-
});
|
|
78747
|
-
}
|
|
78748
|
-
|
|
78749
78436
|
// src/routes/custom-agents.ts
|
|
78750
78437
|
init_src2();
|
|
78751
78438
|
init_src();
|
|
@@ -78789,7 +78476,7 @@ var selectionReasonOf = (r, availability) => {
|
|
|
78789
78476
|
}
|
|
78790
78477
|
return "selalu tersedia sepanjang sesi";
|
|
78791
78478
|
};
|
|
78792
|
-
var
|
|
78479
|
+
var view7 = (r, projectId, stamps = {}, requestedRuntime) => {
|
|
78793
78480
|
const builtin = r.projectId === null && BUILTIN_AGENT_NAMES.includes(r.name);
|
|
78794
78481
|
const availability = availabilityOf(r, requestedRuntime);
|
|
78795
78482
|
return {
|
|
@@ -78861,7 +78548,7 @@ async function custom_agents_default(app3) {
|
|
|
78861
78548
|
for (const r of rows) if (r.projectId === null) byName.set(r.name, r);
|
|
78862
78549
|
for (const r of rows) if (r.projectId !== null) byName.set(r.name, r);
|
|
78863
78550
|
const stamps = await stampsOf();
|
|
78864
|
-
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name)).map((r) =>
|
|
78551
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name)).map((r) => view7(r, projectId, stamps, requestedRuntime));
|
|
78865
78552
|
});
|
|
78866
78553
|
app3.post("/custom-agents", async (req, reply) => {
|
|
78867
78554
|
const parsed = zCreateCustomAgent.safeParse(req.body);
|
|
@@ -78920,7 +78607,7 @@ async function custom_agents_default(app3) {
|
|
|
78920
78607
|
} });
|
|
78921
78608
|
await loadCustomAgents();
|
|
78922
78609
|
await notifySynced("customAgent", id2);
|
|
78923
|
-
return reply.code(201).send(
|
|
78610
|
+
return reply.code(201).send(view7(row, void 0, await stampsOf()));
|
|
78924
78611
|
});
|
|
78925
78612
|
app3.patch("/custom-agents/:id", async (req, reply) => {
|
|
78926
78613
|
const { id: id2 } = req.params;
|
|
@@ -78991,7 +78678,7 @@ async function custom_agents_default(app3) {
|
|
|
78991
78678
|
} });
|
|
78992
78679
|
await loadCustomAgents();
|
|
78993
78680
|
await notifySynced("customAgent", id2);
|
|
78994
|
-
return
|
|
78681
|
+
return view7(row, void 0, await stampsOf());
|
|
78995
78682
|
});
|
|
78996
78683
|
app3.delete("/custom-agents/:id", async (req, reply) => {
|
|
78997
78684
|
const { id: id2 } = req.params;
|
|
@@ -79147,13 +78834,13 @@ async function custom_agent_metrics_default(app3) {
|
|
|
79147
78834
|
const body = zPatch.safeParse(req.body);
|
|
79148
78835
|
if (!body.success) return reply.code(400).send({ error: body.error.issues[0]?.message });
|
|
79149
78836
|
const id2 = String(req.params.id ?? "");
|
|
79150
|
-
const
|
|
78837
|
+
const view12 = await updateAgentInvocationDisposition(
|
|
79151
78838
|
id2,
|
|
79152
78839
|
body.data.disposition,
|
|
79153
78840
|
body.data.note,
|
|
79154
78841
|
body.data.reworkRequired
|
|
79155
78842
|
);
|
|
79156
|
-
return
|
|
78843
|
+
return view12 ?? reply.code(404).send({ error: "invocation tidak ditemukan" });
|
|
79157
78844
|
});
|
|
79158
78845
|
}
|
|
79159
78846
|
|
|
@@ -79161,7 +78848,7 @@ async function custom_agent_metrics_default(app3) {
|
|
|
79161
78848
|
init_src();
|
|
79162
78849
|
init_db();
|
|
79163
78850
|
init_sync_notify();
|
|
79164
|
-
var
|
|
78851
|
+
var view8 = (m) => ({
|
|
79165
78852
|
id: m.id,
|
|
79166
78853
|
name: m.name,
|
|
79167
78854
|
email: m.email,
|
|
@@ -79179,7 +78866,7 @@ async function members_default(app3) {
|
|
|
79179
78866
|
// punya nama, dan menyembunyikannya membuat assignee-nya terbaca sebagai id mentah.
|
|
79180
78867
|
orderBy: [{ active: "desc" }, { name: "asc" }]
|
|
79181
78868
|
});
|
|
79182
|
-
return paginate(rows.map(
|
|
78869
|
+
return paginate(rows.map(view8), page, limit);
|
|
79183
78870
|
});
|
|
79184
78871
|
app3.post("/members", async (req, reply) => {
|
|
79185
78872
|
const parsed = zCreateMember.safeParse(req.body);
|
|
@@ -79192,7 +78879,7 @@ async function members_default(app3) {
|
|
|
79192
78879
|
data: { id: id2, name: p3.name, email: p3.email, role: p3.role ?? null }
|
|
79193
78880
|
});
|
|
79194
78881
|
await notifySynced("member", id2);
|
|
79195
|
-
return reply.code(201).send(
|
|
78882
|
+
return reply.code(201).send(view8(row));
|
|
79196
78883
|
});
|
|
79197
78884
|
app3.patch("/members/:id", async (req, reply) => {
|
|
79198
78885
|
const { id: id2 } = req.params;
|
|
@@ -79205,7 +78892,7 @@ async function members_default(app3) {
|
|
|
79205
78892
|
return reply.code(404).send({ error: "not found" });
|
|
79206
78893
|
const row = await prisma.member.update({ where: { id: id2 }, data: parsed.data });
|
|
79207
78894
|
await notifySynced("member", id2);
|
|
79208
|
-
return
|
|
78895
|
+
return view8(row);
|
|
79209
78896
|
});
|
|
79210
78897
|
app3.delete("/members/:id", async (req, reply) => {
|
|
79211
78898
|
const { id: id2 } = req.params;
|
|
@@ -79394,8 +79081,8 @@ async function tasks_default(app3) {
|
|
|
79394
79081
|
author: req.user?.email ?? (req.agent ? `agent:${req.agent.id}` : "system"),
|
|
79395
79082
|
launchApprovedBy: launchPrincipal(req)
|
|
79396
79083
|
});
|
|
79397
|
-
const
|
|
79398
|
-
return reply.code(r.created ? 201 : 200).send({ created: r.created, spec: r.spec, task:
|
|
79084
|
+
const view12 = taskView(r.task, { id: r.spec.id, stage: r.spec.stage, priority: r.spec.priority });
|
|
79085
|
+
return reply.code(r.created ? 201 : 200).send({ created: r.created, spec: r.spec, task: view12 });
|
|
79399
79086
|
});
|
|
79400
79087
|
app3.delete("/tasks/:id/escalate", async (req, reply) => {
|
|
79401
79088
|
const { id: id2 } = req.params;
|
|
@@ -80795,6 +80482,103 @@ async function qaTransfer(app3) {
|
|
|
80795
80482
|
// src/routes/qa-backlog.ts
|
|
80796
80483
|
init_src();
|
|
80797
80484
|
init_zod();
|
|
80485
|
+
|
|
80486
|
+
// src/services/qa-session.ts
|
|
80487
|
+
init_src2();
|
|
80488
|
+
init_local_binding();
|
|
80489
|
+
init_session_launch_gate();
|
|
80490
|
+
init_pty();
|
|
80491
|
+
init_settings3();
|
|
80492
|
+
init_codex_trust();
|
|
80493
|
+
init_qa_attachment_transfer();
|
|
80494
|
+
init_uploads();
|
|
80495
|
+
import { createHash as createHash18 } from "node:crypto";
|
|
80496
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
80497
|
+
import { join as join43 } from "node:path";
|
|
80498
|
+
var QaSessionError = class extends Error {
|
|
80499
|
+
constructor(code, message, needsBind = false) {
|
|
80500
|
+
super(message);
|
|
80501
|
+
this.code = code;
|
|
80502
|
+
this.needsBind = needsBind;
|
|
80503
|
+
}
|
|
80504
|
+
};
|
|
80505
|
+
var qaSessionId = (findingId) => `qa-${createHash18("sha256").update(findingId).digest("hex").slice(0, 24)}`;
|
|
80506
|
+
async function startQaFindingSession(d, fid) {
|
|
80507
|
+
const f = d.findings.find((x) => x.id === fid);
|
|
80508
|
+
if (!f) throw new QaSessionError(404, "temuan tidak ditemukan");
|
|
80509
|
+
if (f.status === "wontfix") throw new QaSessionError(409, "Temuan ditandai tidak akan diperbaiki. Buka kembali sebelum dikerjakan.");
|
|
80510
|
+
if (f.spec) throw new QaSessionError(409, "Temuan sudah masuk backlog. Mulai sesi dari backlog tersebut.");
|
|
80511
|
+
const repoDir = await resolveRepoDir(d.projectId);
|
|
80512
|
+
if (!repoDir) throw new QaSessionError(400, "Project belum terikat ke repo lokal", true);
|
|
80513
|
+
const id2 = qaSessionId(fid);
|
|
80514
|
+
return withSessionAdmission({ id: id2 }, async () => {
|
|
80515
|
+
const linkedCase = d.cases.find((c) => c.id === f.caseId);
|
|
80516
|
+
const attachments = d.attachments.filter((a) => a.ownerType === "report" || a.ownerType === "finding" && a.ownerId === fid || a.ownerType === "case" && a.ownerId === linkedCase?.id);
|
|
80517
|
+
const files = [];
|
|
80518
|
+
for (const a of attachments) {
|
|
80519
|
+
const bytes = await readQaAttachmentBytes(a.id);
|
|
80520
|
+
if (!bytes) throw new QaSessionError(409, `Lampiran belum tersedia: ${a.filename}. Sinkronkan QA lalu coba lagi.`);
|
|
80521
|
+
const saved = await saveSessionUpload(id2, bytes, a.mimeType);
|
|
80522
|
+
files.push(`${JSON.stringify(a.filename)} (${a.mimeType}): ${saved.path}`);
|
|
80523
|
+
}
|
|
80524
|
+
const wt = join43(repoDir, ".worktrees", id2);
|
|
80525
|
+
const branch = `qa/${id2.slice(3)}`;
|
|
80526
|
+
const reused = realGit.worktreeAlive(wt);
|
|
80527
|
+
try {
|
|
80528
|
+
if (!reused) {
|
|
80529
|
+
const base3 = realGit.revParse(repoDir, branch) ? branch : "HEAD";
|
|
80530
|
+
realGit.addWorktree(repoDir, wt, base3);
|
|
80531
|
+
execFileSync8("git", ["checkout", "-B", branch], { cwd: wt, encoding: "utf8" });
|
|
80532
|
+
}
|
|
80533
|
+
} catch (e) {
|
|
80534
|
+
throw new QaSessionError(422, `Gagal membuat worktree QA: ${e.message}`);
|
|
80535
|
+
}
|
|
80536
|
+
const verifyScope = (await getSetting()).verifyScope;
|
|
80537
|
+
const baseSha = realGit.headSha(wt);
|
|
80538
|
+
const { agent, model, effort: effort2 } = await sessionAgentDefaults();
|
|
80539
|
+
if (agent === "codex") ensureCodexTrust(repoDir);
|
|
80540
|
+
const prompt = [
|
|
80541
|
+
`hanoman \xB7 perbaiki temuan QA ${f.code}: ${f.title}`,
|
|
80542
|
+
`Project ${d.projectId}; laporan ${d.code}: ${d.title}; reportId=${d.id}; findingId=${fid}.`,
|
|
80543
|
+
"Baca instruksi project dan docs Source of Truth. Audit penyebab, pilih perbaikan yang sesuai, lakukan dan verifikasi. Untuk temuan kecil langsung execute; bila perlu desain/spec/plan tulis dokumennya di worktree ini. Jangan membuat backlog terlebih dahulu.",
|
|
80544
|
+
`Build: ${d.buildVersion}
|
|
80545
|
+
Lingkungan: ${JSON.stringify(d.environment)}
|
|
80546
|
+
Cakupan: ${d.scope}
|
|
80547
|
+
Ringkasan laporan: ${d.summary}`,
|
|
80548
|
+
`Dampak: ${f.severity}; prioritas: ${f.priority}; area: ${f.area}
|
|
80549
|
+
Repro:
|
|
80550
|
+
${f.steps.map((s3, i) => `${i + 1}. ${s3}`).join("\n")}
|
|
80551
|
+
Expected:
|
|
80552
|
+
${f.expected}
|
|
80553
|
+
Actual:
|
|
80554
|
+
${f.actual}`,
|
|
80555
|
+
linkedCase ? `Langkah uji terkait ${linkedCase.code}: ${linkedCase.title}
|
|
80556
|
+
${linkedCase.steps}
|
|
80557
|
+
Expected: ${linkedCase.expected}
|
|
80558
|
+
Actual: ${linkedCase.actual}
|
|
80559
|
+
Status: ${linkedCase.status}` : "",
|
|
80560
|
+
files.length ? `Lampiran konteks (baca berkas berikut):
|
|
80561
|
+
${files.join("\n")}` : "",
|
|
80562
|
+
`Kerjakan pada branch ${branch}. Laporkan perubahan, bukti verifikasi dan langkah retest; integrasi diputuskan operator melalui Terminal. Jangan menandai temuan selesai sebelum retest operator.`,
|
|
80563
|
+
verifyScopeClause(verifyScope),
|
|
80564
|
+
CODE_STYLE_CLAUSE,
|
|
80565
|
+
reused ? RESUMED_WORKTREE_NOTE : ""
|
|
80566
|
+
].filter(Boolean).join("\n\n");
|
|
80567
|
+
const s2 = createSession(d.projectId, wt, {
|
|
80568
|
+
id: id2,
|
|
80569
|
+
branch,
|
|
80570
|
+
agent,
|
|
80571
|
+
model,
|
|
80572
|
+
effort: effort2,
|
|
80573
|
+
prompt,
|
|
80574
|
+
env: { HANOMAN_BASE_SHA: baseSha, HANOMAN_VERIFY_SCOPE: verifyScope },
|
|
80575
|
+
attachmentsDir: files.length ? sessionUploadDir(id2) : void 0
|
|
80576
|
+
});
|
|
80577
|
+
return { id: s2.id, reused: false };
|
|
80578
|
+
}, (pane) => ({ id: pane.id, reused: true }));
|
|
80579
|
+
}
|
|
80580
|
+
|
|
80581
|
+
// src/routes/qa-backlog.ts
|
|
80798
80582
|
init_db();
|
|
80799
80583
|
|
|
80800
80584
|
// src/services/qa-backlog.ts
|
|
@@ -80923,6 +80707,21 @@ async function qaBacklog(app3) {
|
|
|
80923
80707
|
await prisma.qaReport.update({ where: { id: rid }, data: { updatedAt: /* @__PURE__ */ new Date() } });
|
|
80924
80708
|
return reply.code(r.created ? 201 : 200).send({ ...r, report: await reportDetail(pid, rid) });
|
|
80925
80709
|
});
|
|
80710
|
+
app3.post("/projects/:pid/qa/reports/:rid/findings/:fid/session", async (req, reply) => {
|
|
80711
|
+
if (!launchPrincipal(req)) return reply.code(403).send({ error: "Peluncuran sesi memerlukan sessions:write" });
|
|
80712
|
+
const { pid, rid, fid } = req.params;
|
|
80713
|
+
const parsed = external_exports.object({}).strict().safeParse(req.body ?? {});
|
|
80714
|
+
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
80715
|
+
const d = await reportDetail(pid, rid);
|
|
80716
|
+
if (!d || !d.findings.some((f) => f.id === fid)) return reply.code(404).send({ error: "not found" });
|
|
80717
|
+
try {
|
|
80718
|
+
const result = await startQaFindingSession(d, fid);
|
|
80719
|
+
return reply.code(result.reused ? 200 : 201).send(result);
|
|
80720
|
+
} catch (e) {
|
|
80721
|
+
if (e instanceof QaSessionError) return reply.code(e.code).send({ error: e.message, ...e.needsBind ? { needsBind: true } : {} });
|
|
80722
|
+
throw e;
|
|
80723
|
+
}
|
|
80724
|
+
});
|
|
80926
80725
|
app3.post("/projects/:pid/qa/reports/:rid/backlog", async (req, reply) => {
|
|
80927
80726
|
const { pid, rid } = req.params;
|
|
80928
80727
|
const parsed = zBody.safeParse(req.body ?? {});
|
|
@@ -80999,7 +80798,7 @@ async function resolveGithubRepo(projectId) {
|
|
|
80999
80798
|
|
|
81000
80799
|
// src/services/github-fetch.ts
|
|
81001
80800
|
init_config2();
|
|
81002
|
-
import { execFile as
|
|
80801
|
+
import { execFile as execFile17 } from "node:child_process";
|
|
81003
80802
|
var labelNames = (l) => (l ?? []).map((x) => typeof x === "string" ? x : x.name ?? "").filter(Boolean);
|
|
81004
80803
|
var norm4 = (s2) => s2.toLowerCase() === "closed" ? "closed" : "open";
|
|
81005
80804
|
function issueFromGh(raw) {
|
|
@@ -81040,7 +80839,7 @@ function issuesFromRest(raw) {
|
|
|
81040
80839
|
var GH_FIELDS = "number,title,body,author,labels,url,state,createdAt,updatedAt";
|
|
81041
80840
|
var API2 = "https://api.github.com";
|
|
81042
80841
|
var defaultRunGh = (args, env) => new Promise((resolve24, reject2) => {
|
|
81043
|
-
|
|
80842
|
+
execFile17(
|
|
81044
80843
|
args[0],
|
|
81045
80844
|
args.slice(1),
|
|
81046
80845
|
{ env, maxBuffer: 1 << 26, encoding: "utf8", timeout: 6e4 },
|
|
@@ -81272,7 +81071,7 @@ var STATUS = {
|
|
|
81272
81071
|
unauthorized: 401,
|
|
81273
81072
|
other: 502
|
|
81274
81073
|
};
|
|
81275
|
-
var
|
|
81074
|
+
var view9 = (i) => ({
|
|
81276
81075
|
id: i.id,
|
|
81277
81076
|
projectId: i.projectId,
|
|
81278
81077
|
repoSlug: i.repoSlug,
|
|
@@ -81307,7 +81106,7 @@ async function githubIssues(app3) {
|
|
|
81307
81106
|
where: { projectId: id2, ...status2 ? { status: status2 } : {} },
|
|
81308
81107
|
orderBy: [{ number: "desc" }]
|
|
81309
81108
|
});
|
|
81310
|
-
return reply.send(paginate(items.map(
|
|
81109
|
+
return reply.send(paginate(items.map(view9), page, limit));
|
|
81311
81110
|
});
|
|
81312
81111
|
app3.post("/github-issues/accept", async (req, reply) => {
|
|
81313
81112
|
const parsed = zAcceptMany.safeParse(req.body ?? {});
|
|
@@ -82076,7 +81875,7 @@ init_settings3();
|
|
|
82076
81875
|
// src/services/portal-chat/turn.ts
|
|
82077
81876
|
init_src();
|
|
82078
81877
|
init_db();
|
|
82079
|
-
import { execFile as
|
|
81878
|
+
import { execFile as execFile18 } from "node:child_process";
|
|
82080
81879
|
|
|
82081
81880
|
// src/services/portal-chat/guard-input.ts
|
|
82082
81881
|
import { randomBytes as randomBytes10 } from "node:crypto";
|
|
@@ -82190,7 +81989,7 @@ init_src();
|
|
|
82190
81989
|
init_db();
|
|
82191
81990
|
import { mkdirSync as mkdirSync16, mkdtempSync as mkdtempSync4, rmSync as rmSync11, writeFileSync as writeFileSync14 } from "node:fs";
|
|
82192
81991
|
import { tmpdir as tmpdir8 } from "node:os";
|
|
82193
|
-
import { join as
|
|
81992
|
+
import { join as join44 } from "node:path";
|
|
82194
81993
|
var STAGE_LABEL = {
|
|
82195
81994
|
brainstorming: "Dirumuskan",
|
|
82196
81995
|
objective: "Dirumuskan",
|
|
@@ -82230,22 +82029,10 @@ function renderTicketDoc(tickets2) {
|
|
|
82230
82029
|
`);
|
|
82231
82030
|
return `# Laporan yang pernah dikirim
|
|
82232
82031
|
|
|
82233
|
-
${baris.join("\n")}`;
|
|
82234
|
-
}
|
|
82235
|
-
function renderChangelogDoc(rows) {
|
|
82236
|
-
if (!rows.length) return "# Catatan rilis\n\nBelum ada catatan rilis.\n";
|
|
82237
|
-
const baris = rows.map((c) => `## ${c.title}
|
|
82238
|
-
|
|
82239
|
-
${tanggal(c.createdAt.toISOString())}
|
|
82240
|
-
|
|
82241
|
-
${c.body}
|
|
82242
|
-
`);
|
|
82243
|
-
return `# Catatan rilis
|
|
82244
|
-
|
|
82245
82032
|
${baris.join("\n")}`;
|
|
82246
82033
|
}
|
|
82247
82034
|
async function buildChatWorkspace(projectId) {
|
|
82248
|
-
const dir2 = mkdtempSync4(
|
|
82035
|
+
const dir2 = mkdtempSync4(join44(tmpdir8(), "hanoman-portal-chat-"));
|
|
82249
82036
|
try {
|
|
82250
82037
|
const project = await prisma.project.findUnique({
|
|
82251
82038
|
where: { id: projectId },
|
|
@@ -82258,28 +82045,21 @@ async function buildChatWorkspace(projectId) {
|
|
|
82258
82045
|
orderBy: { createdAt: "desc" },
|
|
82259
82046
|
take: 100
|
|
82260
82047
|
});
|
|
82261
|
-
const changelogs = await prisma.changelog.findMany({
|
|
82262
|
-
where: { projectId },
|
|
82263
|
-
orderBy: { createdAt: "desc" },
|
|
82264
|
-
take: 20,
|
|
82265
|
-
select: { title: true, body: true, createdAt: true }
|
|
82266
|
-
});
|
|
82267
82048
|
const files = [];
|
|
82268
82049
|
const tulis = (rel, isi) => {
|
|
82269
|
-
writeFileSync14(
|
|
82050
|
+
writeFileSync14(join44(dir2, rel), isi, { mode: 384 });
|
|
82270
82051
|
files.push(rel);
|
|
82271
82052
|
};
|
|
82272
82053
|
tulis("project.md", renderProjectDoc(project));
|
|
82273
82054
|
tulis("pekerjaan.md", renderBacklogDoc(specs));
|
|
82274
82055
|
tulis("laporan.md", renderTicketDoc(tickets2.map((t) => toPortalTicket(t, null))));
|
|
82275
|
-
tulis("catatan-rilis.md", renderChangelogDoc(changelogs));
|
|
82276
82056
|
const prds = await listPrds(projectId);
|
|
82277
82057
|
if (prds.length) {
|
|
82278
|
-
mkdirSync16(
|
|
82058
|
+
mkdirSync16(join44(dir2, "dokumen"), { mode: 448 });
|
|
82279
82059
|
for (const prd of prds) {
|
|
82280
82060
|
const isi = await readPrd(projectId, prd.path);
|
|
82281
82061
|
if (!isi) continue;
|
|
82282
|
-
tulis(
|
|
82062
|
+
tulis(join44("dokumen", `${prd.slug.replaceAll("/", "-")}.md`), isi);
|
|
82283
82063
|
}
|
|
82284
82064
|
}
|
|
82285
82065
|
return { dir: dir2, files, cleanup: () => rmSync11(dir2, { recursive: true, force: true }) };
|
|
@@ -82363,7 +82143,7 @@ function chatFailureReason(err, stdout, stderr, timeoutMs) {
|
|
|
82363
82143
|
}
|
|
82364
82144
|
function runProcess(p3, timeoutMs) {
|
|
82365
82145
|
return new Promise((resolve24, reject2) => {
|
|
82366
|
-
const child =
|
|
82146
|
+
const child = execFile18(p3.file, p3.args, {
|
|
82367
82147
|
cwd: p3.cwd,
|
|
82368
82148
|
timeout: timeoutMs,
|
|
82369
82149
|
maxBuffer: 16 * 1024 * 1024,
|
|
@@ -82701,7 +82481,7 @@ async function portal_chat_admin_default(app3) {
|
|
|
82701
82481
|
// src/routes/client-accounts.ts
|
|
82702
82482
|
init_src();
|
|
82703
82483
|
init_db();
|
|
82704
|
-
var
|
|
82484
|
+
var view10 = (u) => ({
|
|
82705
82485
|
id: u.id,
|
|
82706
82486
|
email: u.email,
|
|
82707
82487
|
disabled: u.disabled,
|
|
@@ -82732,7 +82512,7 @@ async function client_accounts_default(app3) {
|
|
|
82732
82512
|
orderBy: { createdAt: "asc" },
|
|
82733
82513
|
include: { projectAccess: { select: { projectId: true } } }
|
|
82734
82514
|
});
|
|
82735
|
-
return { items: rows.map(
|
|
82515
|
+
return { items: rows.map(view10) };
|
|
82736
82516
|
});
|
|
82737
82517
|
app3.post("/client-accounts", async (req, reply) => {
|
|
82738
82518
|
const p3 = zCreateClientAccount.safeParse(req.body);
|
|
@@ -82746,7 +82526,7 @@ async function client_accounts_default(app3) {
|
|
|
82746
82526
|
await prisma.user.delete({ where: { id: user.id } });
|
|
82747
82527
|
return reply.code(400).send({ error: "ada project yang tidak dikenal" });
|
|
82748
82528
|
}
|
|
82749
|
-
return reply.code(201).send(
|
|
82529
|
+
return reply.code(201).send(view10(await load(user.id)));
|
|
82750
82530
|
});
|
|
82751
82531
|
app3.patch("/client-accounts/:id", async (req, reply) => {
|
|
82752
82532
|
const { id: id2 } = req.params;
|
|
@@ -82760,7 +82540,7 @@ async function client_accounts_default(app3) {
|
|
|
82760
82540
|
if (p3.data.password)
|
|
82761
82541
|
await prisma.user.update({ where: { id: id2 }, data: { passwordHash: await hashPassword(p3.data.password) } });
|
|
82762
82542
|
if (p3.data.disabled || p3.data.password) await deleteUserSessions(id2);
|
|
82763
|
-
return
|
|
82543
|
+
return view10(await load(id2));
|
|
82764
82544
|
});
|
|
82765
82545
|
app3.delete("/client-accounts/:id", async (req, reply) => {
|
|
82766
82546
|
const { id: id2 } = req.params;
|
|
@@ -82792,15 +82572,15 @@ init_src();
|
|
|
82792
82572
|
init_db();
|
|
82793
82573
|
|
|
82794
82574
|
// src/services/bootstrap.ts
|
|
82795
|
-
import { createHash as
|
|
82575
|
+
import { createHash as createHash19, randomBytes as randomBytes11, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
|
|
82796
82576
|
import { chmod as chmod2, lstat as lstat3, mkdir as mkdir11, readFile as readFile9, unlink as unlink5, writeFile as writeFile8 } from "node:fs/promises";
|
|
82797
|
-
import { join as
|
|
82577
|
+
import { join as join45 } from "node:path";
|
|
82798
82578
|
var TTL_MS5 = 15 * 6e4;
|
|
82799
82579
|
var SETUP_TOKEN_FILE = "setup.token";
|
|
82800
82580
|
var BootstrapError = class extends Error {
|
|
82801
82581
|
code = "BOOTSTRAP_PROOF";
|
|
82802
82582
|
};
|
|
82803
|
-
var tokenPath = (home3) =>
|
|
82583
|
+
var tokenPath = (home3) => join45(home3, SETUP_TOKEN_FILE);
|
|
82804
82584
|
async function readStored(home3) {
|
|
82805
82585
|
try {
|
|
82806
82586
|
const info = await lstat3(tokenPath(home3));
|
|
@@ -82845,8 +82625,8 @@ ${new Date(expiresAt).toISOString()}
|
|
|
82845
82625
|
}
|
|
82846
82626
|
async function verifySetupToken(candidate, home3, now = Date.now()) {
|
|
82847
82627
|
const stored = await readStored(home3);
|
|
82848
|
-
const got =
|
|
82849
|
-
const want =
|
|
82628
|
+
const got = createHash19("sha256").update(candidate).digest();
|
|
82629
|
+
const want = createHash19("sha256").update(stored.token).digest();
|
|
82850
82630
|
if (stored.expiresAt <= now || !timingSafeEqual5(got, want)) throw new BootstrapError("invalid setup proof");
|
|
82851
82631
|
}
|
|
82852
82632
|
async function consumeSetupToken(home3) {
|
|
@@ -82857,7 +82637,7 @@ async function consumeSetupToken(home3) {
|
|
|
82857
82637
|
|
|
82858
82638
|
// src/routes/auth.ts
|
|
82859
82639
|
var BOOTSTRAP_USER_ID = "bootstrap-admin";
|
|
82860
|
-
var
|
|
82640
|
+
var view11 = (u) => ({ id: u.id, email: u.email, role: u.role, createdAt: u.createdAt.toISOString() });
|
|
82861
82641
|
async function issue(req, reply, userId) {
|
|
82862
82642
|
const token = await createSession2(userId);
|
|
82863
82643
|
reply.setCookie(COOKIE_NAME, token, cookieOpts(req));
|
|
@@ -82897,7 +82677,7 @@ async function auth_default(app3, opts2) {
|
|
|
82897
82677
|
if (opts2.bootstrapRequired) await consumeSetupToken(opts2.home);
|
|
82898
82678
|
setupAttempts.clear(req.ip);
|
|
82899
82679
|
await issue(req, reply, user.id);
|
|
82900
|
-
return { user:
|
|
82680
|
+
return { user: view11(user) };
|
|
82901
82681
|
});
|
|
82902
82682
|
app3.post("/auth/login", async (req, reply) => {
|
|
82903
82683
|
if (loginThrottle(req.ip).blocked) return reply.code(429).send({ error: "too many attempts" });
|
|
@@ -82910,7 +82690,7 @@ async function auth_default(app3, opts2) {
|
|
|
82910
82690
|
}
|
|
82911
82691
|
clearLoginFails(req.ip);
|
|
82912
82692
|
await issue(req, reply, user.id);
|
|
82913
|
-
return { user:
|
|
82693
|
+
return { user: view11(user) };
|
|
82914
82694
|
});
|
|
82915
82695
|
app3.post("/auth/logout", async (req, reply) => {
|
|
82916
82696
|
const token = req.cookies?.[COOKIE_NAME];
|
|
@@ -82918,7 +82698,7 @@ async function auth_default(app3, opts2) {
|
|
|
82918
82698
|
reply.clearCookie(COOKIE_NAME, { path: "/" });
|
|
82919
82699
|
return reply.code(204).send();
|
|
82920
82700
|
});
|
|
82921
|
-
app3.get("/auth/users", async () => (await prisma.user.findMany({ orderBy: { createdAt: "asc" } })).map(
|
|
82701
|
+
app3.get("/auth/users", async () => (await prisma.user.findMany({ orderBy: { createdAt: "asc" } })).map(view11));
|
|
82922
82702
|
app3.post("/auth/users", async (req, reply) => {
|
|
82923
82703
|
const p3 = zSignup.safeParse(req.body);
|
|
82924
82704
|
if (!p3.success) return reply.code(400).send({ error: p3.error.flatten() });
|
|
@@ -82929,7 +82709,7 @@ async function auth_default(app3, opts2) {
|
|
|
82929
82709
|
// (/api/client-accounts) supaya "undang rekan" dan "beri akses klien" tak pernah tertukar.
|
|
82930
82710
|
data: { email: p3.data.email, passwordHash: await hashPassword(p3.data.password), role: "admin" }
|
|
82931
82711
|
});
|
|
82932
|
-
return
|
|
82712
|
+
return view11(user);
|
|
82933
82713
|
});
|
|
82934
82714
|
app3.delete("/auth/users/:id", async (req, reply) => {
|
|
82935
82715
|
const target3 = await prisma.user.findUnique({ where: { id: req.params.id } });
|
|
@@ -82952,7 +82732,7 @@ async function auth_default(app3, opts2) {
|
|
|
82952
82732
|
});
|
|
82953
82733
|
await deleteUserSessions(user.id);
|
|
82954
82734
|
await issue(req, reply, user.id);
|
|
82955
|
-
return { user:
|
|
82735
|
+
return { user: view11(user) };
|
|
82956
82736
|
});
|
|
82957
82737
|
}
|
|
82958
82738
|
|
|
@@ -83041,15 +82821,15 @@ async function agent_tokens_default(app3) {
|
|
|
83041
82821
|
app3.post("/agent-tokens", async (req, reply) => {
|
|
83042
82822
|
const p3 = zAgentTokenCreate.safeParse(req.body);
|
|
83043
82823
|
if (!p3.success) return reply.code(400).send({ error: p3.error.flatten() });
|
|
83044
|
-
const { view:
|
|
83045
|
-
return reply.code(201).send({ ...
|
|
82824
|
+
const { view: view12, token } = await issueAgentToken({ ...p3.data, createdBy: req.user?.id });
|
|
82825
|
+
return reply.code(201).send({ ...view12, token });
|
|
83046
82826
|
});
|
|
83047
82827
|
app3.patch("/agent-tokens/:id", async (req, reply) => {
|
|
83048
82828
|
const p3 = zAgentTokenPatch.safeParse(req.body);
|
|
83049
82829
|
if (!p3.success) return reply.code(400).send({ error: p3.error.flatten() });
|
|
83050
82830
|
const { id: id2 } = req.params;
|
|
83051
|
-
const
|
|
83052
|
-
return
|
|
82831
|
+
const view12 = await patchAgentToken(id2, p3.data);
|
|
82832
|
+
return view12 ? reply.send(view12) : reply.code(404).send({ error: "not found" });
|
|
83053
82833
|
});
|
|
83054
82834
|
app3.delete("/agent-tokens/:id", async (req, reply) => {
|
|
83055
82835
|
const { id: id2 } = req.params;
|
|
@@ -83336,7 +83116,7 @@ init_event_log();
|
|
|
83336
83116
|
init_transcript_store();
|
|
83337
83117
|
init_src2();
|
|
83338
83118
|
import { readFile as readFile10 } from "node:fs/promises";
|
|
83339
|
-
import { join as
|
|
83119
|
+
import { join as join46 } from "node:path";
|
|
83340
83120
|
function decodeCursor(c) {
|
|
83341
83121
|
if (!c) return null;
|
|
83342
83122
|
try {
|
|
@@ -83374,7 +83154,7 @@ async function readRemoteTranscript(id2) {
|
|
|
83374
83154
|
const row = await prisma.logEntry.findUnique({ where: { id: id2 } });
|
|
83375
83155
|
if (!row || row.lane !== "transcript" || !row.transcriptKey) return null;
|
|
83376
83156
|
if (row.deviceId === "local") return readTranscript(row.transcriptKey);
|
|
83377
|
-
const path =
|
|
83157
|
+
const path = join46(resolveDataDirs().home, "remote-transcripts", row.transcriptKey);
|
|
83378
83158
|
try {
|
|
83379
83159
|
return await readFile10(path, "utf8");
|
|
83380
83160
|
} catch {
|
|
@@ -83566,7 +83346,7 @@ var SETUP_PUBLIC = /* @__PURE__ */ new Set([
|
|
|
83566
83346
|
"POST /api/setup"
|
|
83567
83347
|
]);
|
|
83568
83348
|
function buildApp({ requireAuth = true, agentDocFile, env = process.env } = {}) {
|
|
83569
|
-
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname17(fileURLToPath5(import.meta.url)), env,
|
|
83349
|
+
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname17(fileURLToPath5(import.meta.url)), env, existsSync22);
|
|
83570
83350
|
const ingress = loadIngressPolicy(env);
|
|
83571
83351
|
const app3 = Fastify({ logger: false, trustProxy: trustProxyFromEnv(env) });
|
|
83572
83352
|
app3.setErrorHandler((error, _req, reply) => {
|
|
@@ -83694,7 +83474,6 @@ function buildApp({ requireAuth = true, agentDocFile, env = process.env } = {})
|
|
|
83694
83474
|
await api.register(githubIssues);
|
|
83695
83475
|
await api.register(telegramRoutes);
|
|
83696
83476
|
await api.register(webhooks_default);
|
|
83697
|
-
await api.register(changelog_default);
|
|
83698
83477
|
await api.register(portal_default);
|
|
83699
83478
|
await api.register(portal_chat_default);
|
|
83700
83479
|
await api.register(portal_chat_admin_default);
|
|
@@ -83707,7 +83486,7 @@ function buildApp({ requireAuth = true, agentDocFile, env = process.env } = {})
|
|
|
83707
83486
|
await api.register(qaBacklog);
|
|
83708
83487
|
}, { prefix: "/api" });
|
|
83709
83488
|
if (shouldServeWeb(env)) {
|
|
83710
|
-
const dist = pickWebDir(dirname17(fileURLToPath5(import.meta.url)), env,
|
|
83489
|
+
const dist = pickWebDir(dirname17(fileURLToPath5(import.meta.url)), env, existsSync22);
|
|
83711
83490
|
if (dist) {
|
|
83712
83491
|
app3.register(fastifyStatic, { root: dist });
|
|
83713
83492
|
app3.setNotFoundHandler((req, reply) => req.url.startsWith("/api") ? reply.code(404).send({ error: "not found" }) : req.url.startsWith("/assets/") ? reply.code(404).type("text/plain").send("not found") : reply.sendFile("index.html"));
|
|
@@ -84411,18 +84190,18 @@ function startWebhookEngine() {
|
|
|
84411
84190
|
// src/services/auto-merge.ts
|
|
84412
84191
|
init_src();
|
|
84413
84192
|
init_db();
|
|
84414
|
-
import { execFile as
|
|
84415
|
-
import { promisify as
|
|
84193
|
+
import { execFile as execFile19 } from "node:child_process";
|
|
84194
|
+
import { promisify as promisify16 } from "node:util";
|
|
84416
84195
|
init_local_binding();
|
|
84417
84196
|
init_notifications2();
|
|
84418
84197
|
var AUTO_MERGE_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
84419
84198
|
var AUTO_MERGE_GRACE_MS = 15 * 60 * 1e3;
|
|
84420
84199
|
var TICK_MS4 = 6e4;
|
|
84421
|
-
var
|
|
84422
|
-
var
|
|
84200
|
+
var exec11 = promisify16(execFile19);
|
|
84201
|
+
var GIT8 = { timeout: 3e4, maxBuffer: 1 << 24, encoding: "utf8" };
|
|
84423
84202
|
var gitOut = async (cwd, args) => {
|
|
84424
84203
|
try {
|
|
84425
|
-
return (await
|
|
84204
|
+
return (await exec11("git", args, { cwd, ...GIT8 })).stdout.trim();
|
|
84426
84205
|
} catch {
|
|
84427
84206
|
return null;
|
|
84428
84207
|
}
|
|
@@ -84631,9 +84410,9 @@ async function pruneLogs(now, retention, opts2 = {}) {
|
|
|
84631
84410
|
}
|
|
84632
84411
|
async function reconcileRemoteTranscripts() {
|
|
84633
84412
|
const { readdir: readdir10, unlink: unlink6, stat: stat8 } = await import("node:fs/promises");
|
|
84634
|
-
const { join:
|
|
84413
|
+
const { join: join47 } = await import("node:path");
|
|
84635
84414
|
const { resolveDataDirs: resolveDataDirs2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
|
|
84636
|
-
const root =
|
|
84415
|
+
const root = join47(resolveDataDirs2().home, "remote-transcripts");
|
|
84637
84416
|
let deviceDirs;
|
|
84638
84417
|
try {
|
|
84639
84418
|
deviceDirs = await readdir10(root);
|
|
@@ -84647,16 +84426,16 @@ async function reconcileRemoteTranscripts() {
|
|
|
84647
84426
|
for (const dev of deviceDirs) {
|
|
84648
84427
|
let files;
|
|
84649
84428
|
try {
|
|
84650
|
-
files = await readdir10(
|
|
84429
|
+
files = await readdir10(join47(root, dev));
|
|
84651
84430
|
} catch {
|
|
84652
84431
|
continue;
|
|
84653
84432
|
}
|
|
84654
84433
|
for (const f of files) {
|
|
84655
84434
|
const key = `${dev}/${f}`;
|
|
84656
84435
|
if (referenced.has(key)) continue;
|
|
84657
|
-
const st = await stat8(
|
|
84436
|
+
const st = await stat8(join47(root, dev, f)).catch(() => null);
|
|
84658
84437
|
if (!st || Date.now() - st.mtimeMs < gracePeriodMs) continue;
|
|
84659
|
-
await unlink6(
|
|
84438
|
+
await unlink6(join47(root, dev, f)).catch(() => {
|
|
84660
84439
|
});
|
|
84661
84440
|
orphans++;
|
|
84662
84441
|
}
|