hanoman 0.9.36 → 0.9.38
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 +88 -11
- package/dist/server.js +712 -407
- package/docs/agent-integration.md +9 -2
- package/package.json +1 -1
- package/web/assets/{DalangHanomanScreen-NmpJIcg6.js → DalangHanomanScreen-wpAO0xvb.js} +1 -1
- package/web/assets/{DocPreviewModal-DK597e3s.js → DocPreviewModal-CbhkDUJ2.js} +1 -1
- package/web/assets/{DocsWorkspace-BQX-HlzW.js → DocsWorkspace-CcB0LGFV.js} +1 -1
- package/web/assets/{IdeScreen-LeUPaFsS.js → IdeScreen-DI4v3ZkU.js} +1 -1
- package/web/assets/{LeadScreen-BVCKv46y.js → LeadScreen-BdS_xX_f.js} +1 -1
- package/web/assets/{ReviewScreen-C6NcJChq.js → ReviewScreen-Ddw5U2sM.js} +1 -1
- package/web/assets/{SchedulerScreen-2AH0R3TJ.js → SchedulerScreen-D6LGVrhM.js} +1 -1
- package/web/assets/{SettingsScreen-B4SVzh9m.js → SettingsScreen-CFNcg1T8.js} +1 -1
- package/web/assets/{TeamScreen-6XEfuZUm.js → TeamScreen-BkxPDI5P.js} +1 -1
- package/web/assets/TerminalScreen-CEQFK9Rc.js +2 -0
- package/web/assets/{TriageScreen-BYJxyCar.js → TriageScreen-B-HdZsEt.js} +1 -1
- package/web/assets/{VpsScreen-Df37dgKx.js → VpsScreen-CWp29NL_.js} +2 -2
- package/web/assets/{fields-C94HI0V8.js → fields-BY7SYerd.js} +1 -1
- package/web/assets/{index-C3KwOJxI.css → index-B4b6i2tH.css} +1 -1
- package/web/assets/index-C4Py9MJV.js +1032 -0
- package/web/assets/{live-5ezoV9Fs.js → live-CBa_7-hn.js} +1 -1
- package/web/assets/{sched-lead-shared-BpZu6N2A.js → sched-lead-shared-CQIoYIzG.js} +1 -1
- package/web/assets/{settings-rebase-DaTGgcuO.js → settings-rebase-C3611gs8.js} +1 -1
- package/web/index.html +2 -2
- package/web/assets/TerminalScreen-DubD6vT-.js +0 -2
- package/web/assets/index-BeFSh2Ba.js +0 -1012
package/dist/server.js
CHANGED
|
@@ -4273,12 +4273,13 @@ 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, AGENT_SKILLS_CLAUSE, METHODS, METHOD_IDS, uniq, PLAN_DIRS, SPEC_DIRS;
|
|
4276
|
+
var DEFAULT_METHOD, VERIFICATION_GATE, MOCKUP_SKILL, 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
|
+
MOCKUP_SKILL = "codex-image";
|
|
4282
4283
|
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
4284
|
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.";
|
|
4284
4285
|
METHODS = {
|
|
@@ -4293,6 +4294,7 @@ var init_method_catalog = __esm({
|
|
|
4293
4294
|
phaseSkills: {
|
|
4294
4295
|
Brainstorm: ["superpowers:brainstorming"],
|
|
4295
4296
|
Audit: ["superpowers:systematic-debugging"],
|
|
4297
|
+
Mockup: [MOCKUP_SKILL],
|
|
4296
4298
|
Plan: ["superpowers:writing-plans"],
|
|
4297
4299
|
Execute: [
|
|
4298
4300
|
"superpowers:executing-plans",
|
|
@@ -4338,6 +4340,7 @@ var init_method_catalog = __esm({
|
|
|
4338
4340
|
phaseSkills: {
|
|
4339
4341
|
Brainstorm: ["mattpocock-skills:grilling"],
|
|
4340
4342
|
Audit: ["mattpocock-skills:diagnosing-bugs"],
|
|
4343
|
+
Mockup: [MOCKUP_SKILL],
|
|
4341
4344
|
Plan: ["mattpocock-skills:to-tickets"],
|
|
4342
4345
|
Execute: [
|
|
4343
4346
|
"mattpocock-skills:implement",
|
|
@@ -4376,6 +4379,7 @@ var init_method_catalog = __esm({
|
|
|
4376
4379
|
phaseSkills: {
|
|
4377
4380
|
Brainstorm: ["agent-skills:idea-refine"],
|
|
4378
4381
|
Spec: ["agent-skills:spec-driven-development"],
|
|
4382
|
+
Mockup: [MOCKUP_SKILL],
|
|
4379
4383
|
Audit: [
|
|
4380
4384
|
"agent-skills:debugging-and-error-recovery",
|
|
4381
4385
|
"agent-skills:code-review-and-quality"
|
|
@@ -4419,7 +4423,7 @@ var init_method_catalog = __esm({
|
|
|
4419
4423
|
|
|
4420
4424
|
// ../shared/src/method-status.ts
|
|
4421
4425
|
function methodSkills(m) {
|
|
4422
|
-
return [.../* @__PURE__ */ new Set([...Object.values(m.phaseSkills).flat(), ...m.exitSkills])];
|
|
4426
|
+
return [.../* @__PURE__ */ new Set([...Object.values(m.phaseSkills).flat(), ...m.exitSkills])].filter((id2) => !HANOMAN_PROVIDED_SKILLS.has(id2));
|
|
4423
4427
|
}
|
|
4424
4428
|
function methodStatus(m, agent, installed2) {
|
|
4425
4429
|
const haveSkills = new Set(installed2.skills);
|
|
@@ -4437,10 +4441,12 @@ function methodStatus(m, agent, installed2) {
|
|
|
4437
4441
|
install: [...m.install[agent]]
|
|
4438
4442
|
};
|
|
4439
4443
|
}
|
|
4440
|
-
var ANTIGRAVITY_METHOD_UNAVAILABLE_REASON;
|
|
4444
|
+
var HANOMAN_PROVIDED_SKILLS, ANTIGRAVITY_METHOD_UNAVAILABLE_REASON;
|
|
4441
4445
|
var init_method_status = __esm({
|
|
4442
4446
|
"../shared/src/method-status.ts"() {
|
|
4443
4447
|
"use strict";
|
|
4448
|
+
init_method_catalog();
|
|
4449
|
+
HANOMAN_PROVIDED_SKILLS = /* @__PURE__ */ new Set([MOCKUP_SKILL]);
|
|
4444
4450
|
ANTIGRAVITY_METHOD_UNAVAILABLE_REASON = "Installer metode Antigravity belum terverifikasi dari upstream";
|
|
4445
4451
|
}
|
|
4446
4452
|
});
|
|
@@ -5174,7 +5180,7 @@ var init_logs = __esm({
|
|
|
5174
5180
|
function phaseAgentName(phase) {
|
|
5175
5181
|
return PHASE_AGENT_PREFIX + phase.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
5176
5182
|
}
|
|
5177
|
-
var ORCHESTRATION_FLOWS, FLOW_PHASES, PHASE_AGENT_PREFIX, isPhaseAgentName, PHASE_EVIDENCE_GRACE_MS, EXECUTE_MODE_FLOWS, REVIEWER_CELL, REVIEWER_FLOWS, REVIEWER_DEFAULTS, UNSKIPPABLE_PHASES;
|
|
5183
|
+
var ORCHESTRATION_FLOWS, FLOW_PHASES, PHASE_AGENT_PREFIX, isPhaseAgentName, PHASE_EVIDENCE_GRACE_MS, EXECUTE_MODE_FLOWS, REVIEWER_CELL, REVIEWER_FLOWS, REVIEWER_DEFAULTS, UNSKIPPABLE_PHASES, MOCKUP_PHASE, OPTIONAL_PHASES;
|
|
5178
5184
|
var init_orchestration = __esm({
|
|
5179
5185
|
"../shared/src/orchestration.ts"() {
|
|
5180
5186
|
"use strict";
|
|
@@ -5191,8 +5197,10 @@ var init_orchestration = __esm({
|
|
|
5191
5197
|
];
|
|
5192
5198
|
FLOW_PHASES = {
|
|
5193
5199
|
// ADR-0230 · `Review` = fase resmi sesudah Execute (dulu langkah R di dalam Execute, ADR-0170 #4).
|
|
5194
|
-
|
|
5195
|
-
|
|
5200
|
+
// ADR-0233 · `Mockup` = fase opsional sesudah Spec: gambar UI disetujui sebelum Plan, dilewati agen
|
|
5201
|
+
// bila backlog tak menyentuh UI.
|
|
5202
|
+
feature: ["Brainstorm", "Objective", "Spec", "Mockup", "Plan", "Execute", "Review"],
|
|
5203
|
+
qa: ["Audit", "Spec", "Mockup", "Plan", "Execute", "Review"],
|
|
5196
5204
|
scaffold: ["Brainstorm", "Objective", "Doc index"],
|
|
5197
5205
|
reverse: ["Scan", "Docs teknis", "Wawancara", "Konvensi & index", "Serah terima"],
|
|
5198
5206
|
prd: ["Brainstorm", "PRD"],
|
|
@@ -5224,6 +5232,8 @@ var init_orchestration = __esm({
|
|
|
5224
5232
|
antigravity: { model: "inherit", effort: null }
|
|
5225
5233
|
};
|
|
5226
5234
|
UNSKIPPABLE_PHASES = /* @__PURE__ */ new Set([REVIEWER_CELL]);
|
|
5235
|
+
MOCKUP_PHASE = "Mockup";
|
|
5236
|
+
OPTIONAL_PHASES = /* @__PURE__ */ new Set([MOCKUP_PHASE]);
|
|
5227
5237
|
}
|
|
5228
5238
|
});
|
|
5229
5239
|
|
|
@@ -5234,7 +5244,7 @@ var init_runtime_defaults = __esm({
|
|
|
5234
5244
|
"use strict";
|
|
5235
5245
|
init_orchestration();
|
|
5236
5246
|
init_zod();
|
|
5237
|
-
RUNTIME_DEFAULTS_VERSION = "2026-10-
|
|
5247
|
+
RUNTIME_DEFAULTS_VERSION = "2026-10-31-v1";
|
|
5238
5248
|
zRuntimeDefaultState = external_exports.enum(["seeded", "user"]);
|
|
5239
5249
|
zBuiltinRuntimeDefaults = external_exports.object({
|
|
5240
5250
|
version: external_exports.string().default(RUNTIME_DEFAULTS_VERSION),
|
|
@@ -5290,6 +5300,7 @@ var init_runtime_defaults = __esm({
|
|
|
5290
5300
|
Brainstorm: cell("sonnet", "medium"),
|
|
5291
5301
|
Objective: cell("sonnet", "medium"),
|
|
5292
5302
|
Spec: cell("opus", "high"),
|
|
5303
|
+
Mockup: cell("sonnet", "medium"),
|
|
5293
5304
|
Plan: cell("opus", "high"),
|
|
5294
5305
|
Execute: cell("sonnet", "high"),
|
|
5295
5306
|
Review: cell("opus", "high")
|
|
@@ -5298,6 +5309,7 @@ var init_runtime_defaults = __esm({
|
|
|
5298
5309
|
Brainstorm: cell("gpt-5.6-terra", "medium"),
|
|
5299
5310
|
Objective: cell("gpt-5.6-terra", "medium"),
|
|
5300
5311
|
Spec: cell("gpt-5.6-sol", "high"),
|
|
5312
|
+
Mockup: cell("gpt-5.6-terra", "medium"),
|
|
5301
5313
|
Plan: cell("gpt-5.6-sol", "high"),
|
|
5302
5314
|
Execute: cell("gpt-5.6-terra", "high"),
|
|
5303
5315
|
Review: cell("gpt-5.6-sol", "high")
|
|
@@ -5307,6 +5319,7 @@ var init_runtime_defaults = __esm({
|
|
|
5307
5319
|
{
|
|
5308
5320
|
Audit: cell("opus", "high"),
|
|
5309
5321
|
Spec: cell("opus", "high"),
|
|
5322
|
+
Mockup: cell("sonnet", "medium"),
|
|
5310
5323
|
Plan: cell("sonnet", "high"),
|
|
5311
5324
|
Execute: cell("sonnet", "high"),
|
|
5312
5325
|
Review: cell("opus", "high")
|
|
@@ -5314,6 +5327,7 @@ var init_runtime_defaults = __esm({
|
|
|
5314
5327
|
{
|
|
5315
5328
|
Audit: cell("gpt-5.6-sol", "high"),
|
|
5316
5329
|
Spec: cell("gpt-5.6-sol", "high"),
|
|
5330
|
+
Mockup: cell("gpt-5.6-terra", "medium"),
|
|
5317
5331
|
Plan: cell("gpt-5.6-terra", "high"),
|
|
5318
5332
|
Execute: cell("gpt-5.6-terra", "high"),
|
|
5319
5333
|
Review: cell("gpt-5.6-sol", "high")
|
|
@@ -7699,7 +7713,7 @@ function subKey(topic, params) {
|
|
|
7699
7713
|
for (const k of Object.keys(params).sort()) if (params[k] !== void 0) canon[k] = params[k];
|
|
7700
7714
|
return `${topic}|${JSON.stringify(canon)}`;
|
|
7701
7715
|
}
|
|
7702
|
-
var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, zMarkSpecDone, zMarkSpecReviewed, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerQueueCounts, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, zFlow, isGoalShapedFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, USER_ROLES, zUserRole, zLogin, zSignup, zSetup, zSetupApply, zChangePassword, zAdminPasswordReset, zCreateClientAccount, zUpdateClientAccount, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, PROVISION_PROFILES, COMPONENT_IDS, zProvision, UPDATE_RESTART_EXIT, CONFIG_RESTART_EXIT, MANUAL_RESTART_EXIT, EXIT_INTENT_FILE, zUpdateApplyBody, MAX_SUBS, zSubPage, zSubLimit, zTopicParams, zEventsClientMsg, zTicketView, zTicketAttachmentView, zSpecAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus, zCronExpr, zCreateCron, zPatchCron, zSchedulerCron, zSchedulerCronRun;
|
|
7716
|
+
var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, zMarkSpecDone, zMarkSpecReviewed, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerQueueCounts, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, zFlow, isGoalShapedFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, USER_ROLES, zUserRole, zLogin, zSignup, zSetup, zSetupApply, zChangePassword, zAdminPasswordReset, zCreateClientAccount, zUpdateClientAccount, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, PROVISION_PROFILES, COMPONENT_IDS, zProvision, UPDATE_RESTART_EXIT, CONFIG_RESTART_EXIT, MANUAL_RESTART_EXIT, EXIT_INTENT_FILE, zUpdateApplyBody, MAX_SUBS, zSubPage, zSubLimit, zTopicParams, zEventsClientMsg, zTicketView, zTicketAttachmentView, zSpecAttachmentView, zMockupItem, zSpecMockups, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus, zCronExpr, zCreateCron, zPatchCron, zSchedulerCron, zSchedulerCronRun;
|
|
7703
7717
|
var init_dto = __esm({
|
|
7704
7718
|
"../shared/src/dto.ts"() {
|
|
7705
7719
|
"use strict";
|
|
@@ -8115,7 +8129,13 @@ var init_dto = __esm({
|
|
|
8115
8129
|
agent: zAgent.optional(),
|
|
8116
8130
|
model: external_exports.string().optional(),
|
|
8117
8131
|
effort: external_exports.string().optional(),
|
|
8118
|
-
force: external_exports.boolean().optional()
|
|
8132
|
+
force: external_exports.boolean().optional(),
|
|
8133
|
+
// ADR-0232 · opsional: `worktree:true` → sesi lahir di `.worktrees/<id>` pada branch baru
|
|
8134
|
+
// `term/<id>` dari `branchFrom` (kosong → HEAD repo), alih-alih langsung di repoDir. Tanpa
|
|
8135
|
+
// keduanya body-nya byte-identik dengan sebelumnya. `branchFrom` tanpa `worktree` ditolak di
|
|
8136
|
+
// route — ia tak berarti apa-apa di repoDir dan diam-diam mengabaikannya menyesatkan.
|
|
8137
|
+
worktree: external_exports.boolean().optional(),
|
|
8138
|
+
branchFrom: external_exports.string().min(1).max(255).optional()
|
|
8119
8139
|
}),
|
|
8120
8140
|
// SPEC-252 · ADR-0061 — model & effort per SESI: override opsional saat Start; kosong → global.
|
|
8121
8141
|
// SPEC-332 · ADR-0073 — mode goal per SESI: `goal` undefined → ikut Setting.goal.enabled,
|
|
@@ -8317,6 +8337,12 @@ var init_dto = __esm({
|
|
|
8317
8337
|
size: external_exports.number().int(),
|
|
8318
8338
|
createdAt: external_exports.string()
|
|
8319
8339
|
});
|
|
8340
|
+
zMockupItem = external_exports.object({ name: external_exports.string(), size: external_exports.number().int(), modifiedAt: external_exports.string() });
|
|
8341
|
+
zSpecMockups = external_exports.object({
|
|
8342
|
+
source: external_exports.enum(["worktree", "repo"]).nullable(),
|
|
8343
|
+
dir: external_exports.string().nullable(),
|
|
8344
|
+
items: external_exports.array(zMockupItem)
|
|
8345
|
+
});
|
|
8320
8346
|
zTicketDetail = zTicketView.extend({
|
|
8321
8347
|
detail: external_exports.string(),
|
|
8322
8348
|
attachments: external_exports.array(zTicketAttachmentView),
|
|
@@ -8467,6 +8493,9 @@ var init_api = __esm({
|
|
|
8467
8493
|
// SPEC-843 · ADR-0124 · lampiran backlog item (multi-berkas per request).
|
|
8468
8494
|
specAttachments: (id2) => `${API}/specs/${id2}/attachments`,
|
|
8469
8495
|
specAttachment: (id2, attId) => `${API}/specs/${id2}/attachments/${attId}`,
|
|
8496
|
+
// ADR-0233 · mockup UI backlog (docs/mockups/<spec-id>/).
|
|
8497
|
+
specMockups: (id2) => `${API}/specs/${id2}/mockups`,
|
|
8498
|
+
specMockup: (id2, name2) => `${API}/specs/${id2}/mockups/${encodeURIComponent(name2)}`,
|
|
8470
8499
|
specReview: (id2) => `${API}/specs/${id2}/review`,
|
|
8471
8500
|
specReviewFile: (id2, path) => `${API}/specs/${id2}/review/${path}`,
|
|
8472
8501
|
// ADR-0213 · hapus worktree satu backlog item (cabang & commit tetap).
|
|
@@ -11222,6 +11251,19 @@ var init_backlog = __esm({
|
|
|
11222
11251
|
build: (a) => ({ method: "GET", path: `/specs/${enc(String(a.spec))}/attachments` }),
|
|
11223
11252
|
shape: (raw) => raw
|
|
11224
11253
|
},
|
|
11254
|
+
// ADR-0233 · mockup UI hasil fase Mockup, dibaca dari worktree sesi atau checkout project.
|
|
11255
|
+
{
|
|
11256
|
+
name: "hanoman_backlog_mockups_list",
|
|
11257
|
+
title: "Daftar mockup backlog",
|
|
11258
|
+
description: "Daftar gambar mockup UI sebuah backlog item (hasil fase Mockup, `docs/mockups/<spec-id>/`): `source` (`worktree` sesi atau `repo` checkout project, null bila belum ada), `dir` (path absolut folder di MESIN SERVER hanoman \u2014 baca langsung bila kamu di mesin yang sama), dan `items` {name, size, modifiedAt}. Unduh satu gambar ke disk klien dengan hanoman_backlog_mockup_download.",
|
|
11259
|
+
inputSchema: obj({ properties: { spec: str(ID_HINT) }, required: ["spec"] }),
|
|
11260
|
+
mode: "read",
|
|
11261
|
+
capability: "backlog:read",
|
|
11262
|
+
samplePath: "/specs/SPEC-1/mockups",
|
|
11263
|
+
sampleMethod: "GET",
|
|
11264
|
+
build: (a) => ({ method: "GET", path: `/specs/${enc(String(a.spec))}/mockups` }),
|
|
11265
|
+
shape: (raw) => raw
|
|
11266
|
+
},
|
|
11225
11267
|
{
|
|
11226
11268
|
name: "hanoman_backlog_attachment_delete",
|
|
11227
11269
|
title: "Hapus lampiran backlog",
|
|
@@ -13238,6 +13280,8 @@ var init_sessions = __esm({
|
|
|
13238
13280
|
} })
|
|
13239
13281
|
},
|
|
13240
13282
|
prdPath: str("flow `breakdown`: jalur PRD yang dipecah. WAJIB untuk alur itu."),
|
|
13283
|
+
worktree: bool("Terminal agen biasa (hanya `project`, tanpa `flow`/`shell`): true = lahir di worktree terpisah pada branch baru `term/<id>`, bukan langsung di repo project. Kosong = di repo project."),
|
|
13284
|
+
branchFrom: str("Bersama `worktree: true`: branch/ref asal worktree (lokal atau origin). Kosong = HEAD repo."),
|
|
13241
13285
|
force: bool("Lewati gerbang dependency. Pakai HANYA sesudah melihat daftar pemblokirnya.")
|
|
13242
13286
|
},
|
|
13243
13287
|
allOf: [
|
|
@@ -13267,7 +13311,8 @@ var init_sessions = __esm({
|
|
|
13267
13311
|
if (flow2 === "breakdown") return { method: "POST", path: "/terminal/sessions", body: { project, flow: flow2, prdPath: String(a.prdPath) } };
|
|
13268
13312
|
if (flow2 === "reverse" || flow2 === "scaffold") return { method: "POST", path: "/terminal/sessions", body: { project, flow: flow2 } };
|
|
13269
13313
|
const body = { project };
|
|
13270
|
-
for (const k of ["agent", "model", "effort"]) if (s(a[k]) !== void 0) body[k] = s(a[k]);
|
|
13314
|
+
for (const k of ["agent", "model", "effort", "branchFrom"]) if (s(a[k]) !== void 0) body[k] = s(a[k]);
|
|
13315
|
+
if (typeof a.worktree === "boolean") body.worktree = a.worktree;
|
|
13271
13316
|
return { method: "POST", path: "/terminal/sessions", body };
|
|
13272
13317
|
},
|
|
13273
13318
|
shape: (raw) => raw
|
|
@@ -14837,6 +14882,26 @@ var init_transfer = __esm({
|
|
|
14837
14882
|
build: (a) => ({ method: "GET", path: `${specOne(a)}/${enc(String(a.attachment))}`, download: dlFile(a, "lampiran") }),
|
|
14838
14883
|
shape: (raw) => raw
|
|
14839
14884
|
},
|
|
14885
|
+
// ADR-0233 · mockup UI backlog (`docs/mockups/<spec-id>/`), biner → transfer tool (preseden ADR-0183).
|
|
14886
|
+
{
|
|
14887
|
+
name: "hanoman_backlog_mockup_download",
|
|
14888
|
+
title: "Unduh mockup backlog",
|
|
14889
|
+
description: `Mengunduh SATU gambar mockup UI backlog item ke disk lokal klien MCP. Nama berkas dari hanoman_backlog_mockups_list. ${DOWNLOAD_RESULT}`,
|
|
14890
|
+
inputSchema: obj({
|
|
14891
|
+
properties: { spec: SPEC, name: str("Nama berkas mockup, mis. `01-backlog-detail.png`."), savePath: SAVE_PATH, overwrite: OVERWRITE },
|
|
14892
|
+
required: ["spec", "name"]
|
|
14893
|
+
}),
|
|
14894
|
+
mode: "write",
|
|
14895
|
+
capability: "backlog:read",
|
|
14896
|
+
samplePath: "/specs/SPEC-1/mockups/01-x.png",
|
|
14897
|
+
sampleMethod: "GET",
|
|
14898
|
+
build: (a) => ({
|
|
14899
|
+
method: "GET",
|
|
14900
|
+
path: `/specs/${enc(String(a.spec))}/mockups/${enc(String(a.name))}`,
|
|
14901
|
+
download: dlFile(a, String(a.name ?? "mockup.png"))
|
|
14902
|
+
}),
|
|
14903
|
+
shape: (raw) => raw
|
|
14904
|
+
},
|
|
14840
14905
|
// ───────────────────────── lampiran tiket ─────────────────────────
|
|
14841
14906
|
{
|
|
14842
14907
|
name: "hanoman_ticket_attachment_download",
|
|
@@ -17386,6 +17451,7 @@ function orchestratorClause(plan, o = {}) {
|
|
|
17386
17451
|
const askEscalation = codex2 || antigravity2 ? "tanyakan di terminal ini lalu tunggu jawaban" : ASK_ESCALATION_TOOL;
|
|
17387
17452
|
const askDecisions = antigravity2 ? "di terminal ini sekaligus dalam satu pesan, tiap pertanyaan bernomor dan diakhiri `?` beserta opsi, rekomendasi dan alasannya, lalu tunggu jawaban" : codex2 ? "di terminal ini sekaligus dalam satu pesan, tiap pertanyaan bernomor dan diakhiri `?` beserta opsinya, lalu tunggu jawaban" : ASK_DECISIONS_TOOL;
|
|
17388
17453
|
const turnTaking = plan.flow === "reverse" || plan.flow === "scaffold" || plan.flow === "prd" ? " Fase yang bergiliran dengan manusia (Wawancara, Brainstorm prd/scaffold) melapor satu topik per putaran \u2014 relay tiap putaran dengan cara yang sama, bawa konteks & opsi dari laporannya, dan biarkan jawaban bebas." : "";
|
|
17454
|
+
const mockupStep = plan.phases.some((p3) => p3.phase === MOCKUP_PHASE) ? 'M. Fase Mockup: laporan `Status: selesai` dengan `Rekomendasi fase: lewati` \u2192 jalankan `echo "Mockup skipped" >> "$HANOMAN_PHASE_FILE"` (gerbang yang sama seperti langkah 2), BUKAN `Mockup done`. Permintaan persetujuan mockup datang sebagai `Keputusan terbuka:` \u2014 relay lewat langkah 4 apa adanya, termasuk path gambar dan baris `Lihat di hanoman:`; jangan menyetujuinya sendiri.' : "";
|
|
17389
17455
|
const handoff = plan.phases.some((p3) => p3.phase === "Serah terima") ? "Sesudah fase Serah terima tercatat, tampilkan bagian `Ringkasan serah terima:` dari laporannya APA ADANYA ke manusia di terminal ini (teks biasa \u2014 informasi, bukan pertanyaan) sebelum menutup pekerjaan." : "";
|
|
17390
17456
|
const runtimeLabel = (entry2) => antigravity2 ? `tier native ${entry2.model} \xB7 effort native unknown` : `${entry2.model} \xB7 ${entry2.effort}`;
|
|
17391
17457
|
const phaseList = plan.phases.map((p3, i) => `${i + 1}. ${p3.phase} \u2192 \`${p3.agentName}\` \xB7 ${runtimeLabel(p3)}`).join("\n");
|
|
@@ -17416,7 +17482,8 @@ ${artefactRule} ${describeRule}`,
|
|
|
17416
17482
|
'2. Baca laporannya. `Status: selesai` DENGAN bukti \u2192 SEBELUM hal lain (memanggil fase berikutnya, commit, atau push): jalankan persis `echo "<Nama Fase> done" >> "$HANOMAN_PHASE_FILE"`, lalu verifikasi dengan `tail -1 "$HANOMAN_PHASE_FILE"` bahwa barisnya benar tertulis. Kamu satu-satunya penulis berkas itu.' + (review ? " Khusus Review: marker hanya sesudah `Verdict: lulus` (langkah R)." : ""),
|
|
17417
17483
|
`3. \`Status: sebagian\`/\`terhalang\`, galat, atau laporan tanpa bukti \u2192 delegasikan ULANG SEKALI ke subagent fase yang sama${retry} dengan laporan gagalnya disertakan (\`Percobaan: 2/2\`). Gagal lagi \u2192 BERHENTI dan ${askEscalation} apa yang harus dilakukan. Aturan ini berlaku walau klausa otonomi di prompt ini menyuruhmu tak bertanya.`,
|
|
17418
17484
|
`4. \`Status: menunggu-keputusan\` atau \`Keputusan terbuka:\` yang bukan \`-\` \u2192 SEBELUM menulis marker fase atau memanggil fase berikutnya, ajukan SEMUA pertanyaannya ${askDecisions}, lalu LANJUTKAN subagent yang SAMA lewat ${resume} dengan seluruh jawabannya. Ulangi selama subagent masih melaporkan keputusan terbuka \u2014 tak ada batas jumlah pertanyaan maupun putaran, dan giliran relay ini bukan percobaan ulang. JANGAN pernah menjawab sendiri, termasuk di sesi scheduler: yang menjawab ${DECIDER} \u2014 hanoman yang merutekannya, kamu cukup bertanya.${turnTaking}`,
|
|
17419
|
-
o.fastPath ? '5. `Rekomendasi fase: jalur-cepat` sesudah Audit \u2192 SEBELUM lanjut ke Execute: jalankan `echo "Spec skipped" >> "$HANOMAN_PHASE_FILE"` lalu `echo "Plan skipped" >> "$HANOMAN_PHASE_FILE"` (gerbang yang sama seperti langkah 2), lalu lanjut ke Execute. `penuh` \u2192 Spec \u2192 Plan \u2192 Execute.' : "",
|
|
17485
|
+
o.fastPath ? '5. `Rekomendasi fase: jalur-cepat` sesudah Audit \u2192 SEBELUM lanjut ke Execute: jalankan `echo "Spec skipped" >> "$HANOMAN_PHASE_FILE"`, `echo "Mockup skipped" >> "$HANOMAN_PHASE_FILE"`, lalu `echo "Plan skipped" >> "$HANOMAN_PHASE_FILE"` (gerbang yang sama seperti langkah 2), lalu lanjut ke Execute. `penuh` \u2192 Spec \u2192 Mockup \u2192 Plan \u2192 Execute.' : "",
|
|
17486
|
+
mockupStep,
|
|
17420
17487
|
reviewStep,
|
|
17421
17488
|
handoff,
|
|
17422
17489
|
"DILARANG mengerjakan isi fase sendiri \u2014 termasuk saat subagent gagal. Menulis `skipped` untuk fase yang dilewati bukan mengerjakannya.",
|
|
@@ -17655,7 +17722,7 @@ Stack: ${project.stack || "\u2014"}`,
|
|
|
17655
17722
|
${cron.prompt}`
|
|
17656
17723
|
].join("\n\n");
|
|
17657
17724
|
}
|
|
17658
|
-
var PIPELINES, WORK_PHASES, DECIDER, ASK_ROUTE, ASK_ESCALATION_TOOL, ASK_DECISIONS_TOOL, AMBIGUOUS, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, PLAN_SHAPE, phaseInstruction, SELF_REVIEW_CLAUSE, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, auditContinuationForOrchestrator, writesCode, scopeClause, codeStyleClause, methodClause, attachmentClause, orchestratorAttachmentClause, PROJECT_METHOD, specContext, goalDetail, goalBlock, goalContext, projectContext, scaffoldContext, prdBriefBlock, prdAuditBlock, prdContext, breakdownContext, prdPhaseLines, breakdownPhaseLines, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
|
|
17725
|
+
var PIPELINES, WORK_PHASES, DECIDER, ASK_ROUTE, ASK_ESCALATION_TOOL, ASK_DECISIONS_TOOL, AMBIGUOUS, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, PLAN_SHAPE, phaseInstruction, SELF_REVIEW_CLAUSE, mockupGuide, MOCKUP_FOLLOW_UP, MOCKUP_SINGLE, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, auditContinuationForOrchestrator, writesCode, scopeClause, codeStyleClause, methodClause, attachmentClause, orchestratorAttachmentClause, PROJECT_METHOD, specContext, goalDetail, goalBlock, goalContext, projectContext, scaffoldContext, prdBriefBlock, prdAuditBlock, prdContext, breakdownContext, prdPhaseLines, breakdownPhaseLines, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
|
|
17659
17726
|
var init_prompt = __esm({
|
|
17660
17727
|
"../runner/src/prompt.ts"() {
|
|
17661
17728
|
"use strict";
|
|
@@ -17679,12 +17746,23 @@ var init_prompt = __esm({
|
|
|
17679
17746
|
const base3 = `Kerjakan fase berurutan: ${phases2.join(" \u2192 ")}.
|
|
17680
17747
|
Setiap kali sebuah fase selesai (atau kamu putuskan dilewati), append satu baris ke berkas di $HANOMAN_PHASE_FILE \u2014 persis: \`echo "<Nama Fase> done" >> "$HANOMAN_PHASE_FILE"\`, atau \`skipped\` sebagai ganti \`done\`. Nama fase ditulis apa adanya seperti di atas.`;
|
|
17681
17748
|
if (!phases2.includes("Plan") || !phases2.includes("Execute")) return base3;
|
|
17682
|
-
return base3 + `
|
|
17749
|
+
return base3 + (phases2.includes(MOCKUP_PHASE) ? `
|
|
17750
|
+
${MOCKUP_SINGLE}
|
|
17751
|
+
${MOCKUP_FOLLOW_UP.Plan} ${MOCKUP_FOLLOW_UP.Execute}` : "") + `
|
|
17683
17752
|
Execute BELUM selesai selama plan (\`${method.planDir}/**\`) masih punya task \`- [ ]\`: kerjakan SEMUA PR/task sampai tiap kotak jadi \`- [x]\` sebelum menulis \`Execute done\`. hanoman menahan backlog di \`executing\`, bukan \`done\`, selama masih ada \`- [ ]\`.
|
|
17684
17753
|
${PLAN_SHAPE}` + (phases2.includes(REVIEWER_CELL) ? `
|
|
17685
17754
|
${SELF_REVIEW_CLAUSE}` : "");
|
|
17686
17755
|
};
|
|
17687
|
-
SELF_REVIEW_CLAUSE = "Fase Review (sesudah `Execute done`): periksa ulang hasil Execute terhadap dokumen spec & plan backlog ini \u2014 baca seluruh perubahan sesi ini (diff dari commit awal worktree) dan jalankan test sesuai scope verifikasi. Tulis tabel `AC \u2192 putusan \u2192 bukti` (setiap acceptance criteria \u2192 perintah & hasil yang kamu baca, atau path:baris) lalu satu baris `Verdict: lulus` atau `Verdict: rework`. `rework` \u2192 perbaiki temuannya, lalu review ulang; maks 2 putaran rework, sisanya jadi keputusan terbuka. Tulis `Review done` HANYA sesudah `Verdict: lulus`; Review TIDAK PERNAH `skipped`.";
|
|
17756
|
+
SELF_REVIEW_CLAUSE = "Fase Review (sesudah `Execute done`): periksa ulang hasil Execute terhadap dokumen spec & plan backlog ini \u2014 baca seluruh perubahan sesi ini (diff dari commit awal worktree) dan jalankan test sesuai scope verifikasi. Tulis tabel `AC \u2192 putusan \u2192 bukti` (setiap acceptance criteria \u2192 perintah & hasil yang kamu baca, atau path:baris) lalu satu baris `Verdict: lulus` atau `Verdict: rework`. `rework` \u2192 perbaiki temuannya, lalu review ulang; maks 2 putaran rework, sisanya jadi keputusan terbuka. Tulis `Review done` HANYA sesudah `Verdict: lulus`; Review TIDAK PERNAH `skipped`. Bila `docs/mockups/<spec-id>/` ada, tabel itu juga memuat kesesuaian hasil dengan mockup yang disetujui.";
|
|
17757
|
+
mockupGuide = (ask, close) => `putuskan dulu dari dokumen spec (qa jalur cepat: dokumen audit) apakah backlog ini mengubah atau membuat UI \u2014 layar, komponen, atau state yang terlihat pengguna. Tidak ada (perbaikan backend, CLI, refactor, docs) \u2192 fase ini dilewati, sebutkan alasannya dalam satu baris; ragu \u2192 anggap ada UI. Ada \u2192 baca design system project bila ada (mis. \`internal/docs/design-system/**\`, \`DESIGN.md\`) dan kode layar yang akan berubah, muat skill \`codex-image\`, lalu hasilkan SATU gambar per layar/state yang berubah, maks 6: desktop \`1536x1024\`, tambah mobile \`1024x1536\` bila layout responsif tersentuh, ke \`docs/mockups/<spec-id>/<NN>-<slug>.png\` (spec-id huruf kecil, NN dua digit urut). Tambahkan bagian \`## Mockup\` di dokumen spec: tiap berkas, layar/state yang ditunjukkannya, dan apa yang berubah. Lalu minta persetujuan ${ask}: sebutkan path absolut setiap gambar dan baris \`Lihat di hanoman: Backlog <SPEC-ID> \u2192 Mockup\`, dengan opsi Setujui \xB7 Revisi (beserta catatan) \xB7 Lewati mockup. Revisi \u2192 buat ulang gambar yang diminta dengan spec gambar LENGKAP ditambah koreksinya (bukan koreksinya saja), timpa berkas yang sama, lalu minta persetujuan lagi. Lewati \u2192 hapus berkas mockup yang belum di-commit. Disetujui \u2192 commit \`docs/mockups/<spec-id>/\` beserta dokumen spec. Bila gambar tak bisa dihasilkan (Codex tak terpasang/belum login, image_gen gagal dua kali), JANGAN membuat pengganti (SVG/CSS/HTML yang dirasterisasi, emoji, atau placeholder bernama sama) \u2014 tanyakan dengan jalur yang sama: tunggu operator menjalankan \`codex login\` lalu lanjut, atau lewati mockup. ${close}`;
|
|
17758
|
+
MOCKUP_FOLLOW_UP = {
|
|
17759
|
+
Plan: "Bila `docs/mockups/<spec-id>/` ada, setiap task UI di plan merujuk berkas mockup yang diwujudkannya.",
|
|
17760
|
+
Execute: "Bila `docs/mockups/<spec-id>/` ada, bangun UI dengan kode sesuai mockup itu \u2014 PNG mockup bukan aset produk."
|
|
17761
|
+
};
|
|
17762
|
+
MOCKUP_SINGLE = "Fase Mockup: " + mockupGuide(
|
|
17763
|
+
"lewat jalur tanya di klausa otonomi prompt ini",
|
|
17764
|
+
"Tulis `Mockup done` sesudah disetujui & di-commit, atau `Mockup skipped` bila dilewati."
|
|
17765
|
+
);
|
|
17688
17766
|
skillInstruction = (flow2, phases2, method) => {
|
|
17689
17767
|
const lines2 = phases2.map((p3) => {
|
|
17690
17768
|
const skills2 = phaseSkillsFor(flow2, p3, method);
|
|
@@ -17693,7 +17771,7 @@ ${SELF_REVIEW_CLAUSE}` : "");
|
|
|
17693
17771
|
return lines2.length ? `Skills ${method.label} WAJIB: sebelum mengerjakan fase di bawah, muat & ikuti skill-nya dengan mekanisme yang tersedia di agenmu \u2014 bila skill relevan tersedia, pakai.
|
|
17694
17772
|
${lines2.join("\n")}` : "";
|
|
17695
17773
|
};
|
|
17696
|
-
auditDecisionInstruction = (flow2) => flow2 !== "qa" ? "" : 'Keputusan pasca-Audit (qa): bila temuan berconfidence tinggi dan perbaikannya bisa dikerjakan langsung (diff kecil, akar masalah jelas), LEWATI Spec dan Plan \u2014 tandai
|
|
17774
|
+
auditDecisionInstruction = (flow2) => flow2 !== "qa" ? "" : 'Keputusan pasca-Audit (qa): bila temuan berconfidence tinggi dan perbaikannya bisa dikerjakan langsung (diff kecil, akar masalah jelas), LEWATI Spec, Mockup, dan Plan \u2014 tandai ketiganya `skipped` (`echo "Spec skipped" >> "$HANOMAN_PHASE_FILE"`, `echo "Mockup skipped" >> "$HANOMAN_PHASE_FILE"`, lalu `echo "Plan skipped" >> "$HANOMAN_PHASE_FILE"`) dan langsung ke Execute; dokumen audit menjadi doc-of-record perbaikan itu. Bila temuan luas, berisiko, atau ambigu, jalankan Spec \u2192 Mockup \u2192 Plan \u2192 Execute penuh. Keputusan ini milikmu berdasarkan hasil Audit, bukan default \u2014 jangan bayar perencanaan yang tak perlu untuk perbaikan sepele.';
|
|
17697
17775
|
ESCALATION_CONTRACT = [
|
|
17698
17776
|
"REKOMENDASI ESKALASI (wajib, terbaca mesin). Di bagian akhir dokumen audit, tulis bagian",
|
|
17699
17777
|
"`## Rekomendasi eskalasi` berisi penjelasan singkat untuk manusia, LALU tepat SATU blok ```json",
|
|
@@ -17724,7 +17802,7 @@ ${lines2.join("\n")}` : "";
|
|
|
17724
17802
|
const doc = `internal/docs/research/audit-${fromAudit.toLowerCase()}-*.md`;
|
|
17725
17803
|
if (flow2 === "feature")
|
|
17726
17804
|
return `Backlog brief ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. BACA dokumen itu lebih dulu dan pakai sebagai bahan fase Brainstorm & Objective \u2014 temuannya sudah terbukti, jangan menginvestigasi ulang dari nol. Semua fase tetap dijalankan: dokumen audit memuat TEMUAN, bukan bentuk solusi, jadi perancangan fitur tetap pekerjaanmu.`;
|
|
17727
|
-
return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. JANGAN mengulang investigasi fase Audit dari nol \u2014 baca dokumen audit itu sebagai temuan, tandai fase Audit dilewati (\`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`), lalu ambil keputusan pasca-Audit: perbaikan jelas & kecil \u2192 langsung Execute (tandai \`Spec skipped\` dan \`Plan skipped\` bila sesuai); selain itu Spec \u2192 Plan \u2192 Execute penuh.`;
|
|
17805
|
+
return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. JANGAN mengulang investigasi fase Audit dari nol \u2014 baca dokumen audit itu sebagai temuan, tandai fase Audit dilewati (\`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`), lalu ambil keputusan pasca-Audit: perbaikan jelas & kecil \u2192 langsung Execute (tandai \`Spec skipped\`, \`Mockup skipped\`, dan \`Plan skipped\` bila sesuai); selain itu Spec \u2192 Mockup \u2192 Plan \u2192 Execute penuh.`;
|
|
17728
17806
|
};
|
|
17729
17807
|
auditContinuationForOrchestrator = (flow2, spec) => {
|
|
17730
17808
|
if (flow2 !== "qa" && flow2 !== "feature") return "";
|
|
@@ -17733,7 +17811,7 @@ ${lines2.join("\n")}` : "";
|
|
|
17733
17811
|
const doc = `internal/docs/research/audit-${fromAudit.toLowerCase()}-*.md`;
|
|
17734
17812
|
if (flow2 === "feature")
|
|
17735
17813
|
return `Backlog brief ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. Saat memanggil agen fase Brainstorm, cantumkan path itu di baris \`Artefak fase sebelumnya:\` blok serah-terima \u2014 JANGAN membacanya sendiri untuk merancang fitur ini. Saat memanggil agen fase Objective, baris itu biasanya sudah membawa path artefak Brainstorm (dokumen spec yang ditulis fase itu); tambahkan path dokumen audit di samping path itu, bukan menggantikannya. Temuannya sudah terbukti, tapi bentuk solusinya belum; itu tetap dikerjakan agen fasenya masing-masing, bukan olehmu.`;
|
|
17736
|
-
return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Temuannya sudah terbukti di ${doc} \u2014 JANGAN mendelegasikan ulang fase Audit. Sebelum fase lain: jalankan persis \`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`, verifikasi dengan \`tail -1 "$HANOMAN_PHASE_FILE"\` bahwa barisnya benar tertulis (gerbang yang sama seperti langkah 2), lalu cantumkan path dokumen audit itu di baris \`Artefak fase sebelumnya:\` saat memanggil agen fase Spec. Keputusan routing di sini menggantikan langkah 5 di atas \u2014 JANGAN menunggu frasa \`Rekomendasi fase: jalur-cepat\` dari agen Audit karena Audit tidak dijalankan pada kelanjutan ini. Kamu diizinkan mengambil SATU keputusan ROUTING dari isi dokumen audit itu SAJA, dibaca langsung dari kriterianya: temuan berconfidence tinggi dan perbaikan langsung berdiff kecil \u2192 tandai \`Spec skipped\` lalu \`Plan skipped\` (gerbang yang sama) lalu panggil agen fase Execute; selebihnya panggil Spec \u2192 Plan \u2192 Execute penuh lewat agen fasenya masing-masing. Kamu TIDAK menginvestigasi ulang maupun merancang perbaikannya sendiri \u2014 itu tetap pekerjaan agen fase.`;
|
|
17814
|
+
return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Temuannya sudah terbukti di ${doc} \u2014 JANGAN mendelegasikan ulang fase Audit. Sebelum fase lain: jalankan persis \`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`, verifikasi dengan \`tail -1 "$HANOMAN_PHASE_FILE"\` bahwa barisnya benar tertulis (gerbang yang sama seperti langkah 2), lalu cantumkan path dokumen audit itu di baris \`Artefak fase sebelumnya:\` saat memanggil agen fase Spec. Keputusan routing di sini menggantikan langkah 5 di atas \u2014 JANGAN menunggu frasa \`Rekomendasi fase: jalur-cepat\` dari agen Audit karena Audit tidak dijalankan pada kelanjutan ini. Kamu diizinkan mengambil SATU keputusan ROUTING dari isi dokumen audit itu SAJA, dibaca langsung dari kriterianya: temuan berconfidence tinggi dan perbaikan langsung berdiff kecil \u2192 tandai \`Spec skipped\`, \`Mockup skipped\`, lalu \`Plan skipped\` (gerbang yang sama) lalu panggil agen fase Execute; selebihnya panggil Spec \u2192 Mockup \u2192 Plan \u2192 Execute penuh lewat agen fasenya masing-masing. Kamu TIDAK menginvestigasi ulang maupun merancang perbaikannya sendiri \u2014 itu tetap pekerjaan agen fase.`;
|
|
17737
17815
|
};
|
|
17738
17816
|
writesCode = (flow2) => PIPELINES[flow2].some((p3) => WORK_PHASES.includes(p3));
|
|
17739
17817
|
scopeClause = (flow2, scope) => scope && writesCode(flow2) ? verifyScopeClause(scope) : "";
|
|
@@ -18053,7 +18131,7 @@ function defaultGoalCondition({ flow: flow2, specId, branchTo, spec }) {
|
|
|
18053
18131
|
const phases2 = PIPELINES[flow2];
|
|
18054
18132
|
const planGate = phases2.includes("Plan") && phases2.includes("Execute");
|
|
18055
18133
|
const clauses = [
|
|
18056
|
-
`1. output \`cat "$HANOMAN_PHASE_FILE"\` yang memuat satu baris untuk SETIAP fase ${phases2.join(" \u2192 ")}, masing-masing berakhiran \`done\` atau \`skipped\`` + phases2.filter((p3) => UNSKIPPABLE_PHASES.has(p3)).map((p3) => ` (\`${p3}\` wajib \`done\`)`).join("") + ";"
|
|
18134
|
+
`1. output \`cat "$HANOMAN_PHASE_FILE"\` yang memuat satu baris untuk SETIAP fase ${phases2.join(" \u2192 ")}, masing-masing berakhiran \`done\` atau \`skipped\`` + phases2.filter((p3) => UNSKIPPABLE_PHASES.has(p3)).map((p3) => ` (\`${p3}\` wajib \`done\`)`).join("") + phases2.filter((p3) => OPTIONAL_PHASES.has(p3)).map((p3) => ` (\`${p3}\` boleh tanpa baris bila fase sesudahnya sudah tercatat)`).join("") + ";"
|
|
18057
18135
|
];
|
|
18058
18136
|
if (planGate) {
|
|
18059
18137
|
const dirs = PLAN_DIRS.map((d) => `${d}/`).join(" ");
|
|
@@ -18124,12 +18202,13 @@ function codexGoalScript(o) {
|
|
|
18124
18202
|
`ST=${shq(o.stateFile)}`,
|
|
18125
18203
|
"missing=''"
|
|
18126
18204
|
];
|
|
18127
|
-
|
|
18205
|
+
phases2.forEach((p3, i) => {
|
|
18206
|
+
const name2 = OPTIONAL_PHASES.has(p3) ? `(${phases2.slice(i).join("|")})` : p3;
|
|
18128
18207
|
lines2.push(
|
|
18129
18208
|
// ADR-0230 · fase `UNSKIPPABLE_PHASES` (Review) hanya lolos dengan `done`.
|
|
18130
|
-
`grep -qE ${shq(`^${
|
|
18209
|
+
`grep -qE ${shq(`^${name2} ${UNSKIPPABLE_PHASES.has(p3) ? "done" : "(done|skipped)"}[[:space:]]*$`)} "$PF" 2>/dev/null || missing="$missing\\n- fase ${p3} belum tercatat di \\$HANOMAN_PHASE_FILE"`
|
|
18131
18210
|
);
|
|
18132
|
-
}
|
|
18211
|
+
});
|
|
18133
18212
|
if (planGate) {
|
|
18134
18213
|
for (const dir2 of PLAN_DIRS) {
|
|
18135
18214
|
lines2.push(
|
|
@@ -18882,9 +18961,16 @@ function backlogGuide(flow2, phase, ctx) {
|
|
|
18882
18961
|
// default skill) lolos hampa. Nama dikunci seperti Brainstorm, dan path persisnya diteruskan
|
|
18883
18962
|
// orchestrator ke Execute & reviewer — bukan pola glob yang bisa mencocoki plan backlog lain.
|
|
18884
18963
|
case "Plan":
|
|
18885
|
-
return `- Plan: tulis plan implementasi berkotak \`- [ ]\` untuk backlog ini dari dokumen spec-nya (path di serah-terima) ke SATU berkas \`${m.planDir}/<YYYY-MM-DD>-<spec-id>-<slug>.md\` (spec-id backlog ini huruf kecil, mis. \`2026-09-25-spec-123-ekspor-csv.md\` \u2014 nama tanpa spec-id membuat hanoman tak menemukan plan-nya). Laporkan path PERSIS berkas itu di \`Artefak:\`; orchestrator meneruskannya ke Execute. ${PLAN_SHAPE}`;
|
|
18964
|
+
return `- Plan: tulis plan implementasi berkotak \`- [ ]\` untuk backlog ini dari dokumen spec-nya (path di serah-terima) ke SATU berkas \`${m.planDir}/<YYYY-MM-DD>-<spec-id>-<slug>.md\` (spec-id backlog ini huruf kecil, mis. \`2026-09-25-spec-123-ekspor-csv.md\` \u2014 nama tanpa spec-id membuat hanoman tak menemukan plan-nya). Laporkan path PERSIS berkas itu di \`Artefak:\`; orchestrator meneruskannya ke Execute. ${PLAN_SHAPE} ${MOCKUP_FOLLOW_UP.Plan}`;
|
|
18886
18965
|
case "Execute":
|
|
18887
|
-
return `- Execute: kerjakan plan backlog ini \u2014 path persisnya di baris \`Artefak fase sebelumnya:\` serah-terima (bila tak ada: berkas di \`${m.planDir}/\` yang namanya memuat spec-id backlog ini). Execute BELUM selesai selama plan masih punya task \`- [ ]\`: kerjakan SEMUA task sampai tiap kotak jadi \`- [x]\` sebelum melapor \`Status: selesai\`. hanoman menahan backlog di \`executing\`, bukan \`done\`, selama masih ada \`- [ ]\`. Laporan \`Status: selesai\` wajib memuat tabel \`AC \u2192 bukti\`: setiap acceptance criteria dokumen spec (qa jalur-cepat: dokumen audit) \u2192 perintah yang kamu jalankan beserta hasil yang kamu baca, atau path:baris yang membuktikannya. AC tanpa bukti = belum selesai; laporanmu diperiksa ulang sebelum fase ditutup
|
|
18966
|
+
return `- Execute: kerjakan plan backlog ini \u2014 path persisnya di baris \`Artefak fase sebelumnya:\` serah-terima (bila tak ada: berkas di \`${m.planDir}/\` yang namanya memuat spec-id backlog ini). Execute BELUM selesai selama plan masih punya task \`- [ ]\`: kerjakan SEMUA task sampai tiap kotak jadi \`- [x]\` sebelum melapor \`Status: selesai\`. hanoman menahan backlog di \`executing\`, bukan \`done\`, selama masih ada \`- [ ]\`. Laporan \`Status: selesai\` wajib memuat tabel \`AC \u2192 bukti\`: setiap acceptance criteria dokumen spec (qa jalur-cepat: dokumen audit) \u2192 perintah yang kamu jalankan beserta hasil yang kamu baca, atau path:baris yang membuktikannya. AC tanpa bukti = belum selesai; laporanmu diperiksa ulang sebelum fase ditutup. ${MOCKUP_FOLLOW_UP.Execute}`;
|
|
18967
|
+
// ADR-0233 · panduan yang SAMA dengan sesi tunggal (`mockupGuide`); bedanya cuma jalur tanya & penutupan:
|
|
18968
|
+
// agen fase tak bisa bertanya langsung dan tak menulis marker sendiri.
|
|
18969
|
+
case "Mockup":
|
|
18970
|
+
return "- Mockup: " + mockupGuide(
|
|
18971
|
+
"sebagai butir `Keputusan terbuka:` laporanmu (`Status: menunggu-keputusan`) \u2014 orchestrator yang menanyakannya",
|
|
18972
|
+
"Disetujui & di-commit \u2192 `Status: selesai`. Dilewati (tanpa UI, atau dipilih Lewati) \u2192 `Status: selesai` dengan `Rekomendasi fase: lewati` beserta alasannya; orchestrator yang menulis marker-nya."
|
|
18973
|
+
);
|
|
18888
18974
|
case "Audit":
|
|
18889
18975
|
return flow2 === "audit" ? "- Audit: ini audit-only \u2014 investigasi SAJA, JANGAN menulis perbaikan kode apa pun. Telusuri akar masalah / log / jawaban dan nilai apakah issue terdefinisi dengan baik. Laporkan temuan beserta buktinya; dokumennya ditulis fase Laporan." : "- Audit: telusuri akar masalah dengan bukti (reproduksi, log, kode) dan tulis dokumen audit ke `internal/docs/research/audit-<spec-id>-<slug>.md`. Lalu putuskan jalurnya dari hasil Audit, bukan default: bila temuan berconfidence tinggi dan perbaikannya bisa dikerjakan langsung (diff kecil, akar masalah jelas), tulis `Rekomendasi fase: jalur-cepat` beserta alasannya \u2014 orchestrator akan melewati Spec & Plan dan dokumen audit menjadi doc-of-record perbaikan itu. Bila temuan luas, berisiko, atau ambigu, tulis `Rekomendasi fase: penuh`. " + FRONT_LOADED_DECISIONS("Spec, Plan, dan Execute") + " Catat jawabannya di dokumen audit.";
|
|
18890
18976
|
case "Laporan":
|
|
@@ -18962,7 +19048,7 @@ function reviewerInstructions(plan, ctx) {
|
|
|
18962
19048
|
"Prosedur:",
|
|
18963
19049
|
[
|
|
18964
19050
|
`1. Baca dokumen spec & plan dari baris \`Artefak:\` serah-terima (qa jalur-cepat: dokumen audit adalah doc-of-record; bila path tak ada, cari berkas ber-spec-id di \`${m.specDir}/\` & \`${m.planDir}/\`). Plan yang menyimpang dari spec adalah temuan.`,
|
|
18965
|
-
"2. Ubah acceptance criteria jadi daftar yang diperiksa SATU PER SATU. Kalimat tak terukur ditandai tak terukur, bukan ditafsirkan.",
|
|
19051
|
+
"2. Ubah acceptance criteria jadi daftar yang diperiksa SATU PER SATU. Kalimat tak terukur ditandai tak terukur, bukan ditafsirkan. Bila `docs/mockups/<spec-id>/` ada, kesesuaian UI hasil dengan mockup yang disetujui ikut jadi kriteria.",
|
|
18966
19052
|
'3. Baca `git diff "$HANOMAN_BASE_SHA"...HEAD` dan `git status --porcelain` (perubahan belum di-commit adalah temuan). Laporan Execute dan kotak `- [x]` hanyalah klaim.',
|
|
18967
19053
|
"4. Jalankan test yang relevan dengan berkas yang berubah, sesuai scope verifikasi di bawah, dan baca outputnya sendiri.",
|
|
18968
19054
|
`5. Per kriteria putuskan salah satu: ${SPEC_AUDIT_VERDICT_LIST}. Kriteria tanpa jangkar di diff bukan otomatis gagal \u2014 cari bukti keadaan akhir; tak bisa dibuktikan \u2192 belum terverifikasi.`
|
|
@@ -20962,6 +21048,7 @@ __export(src_exports, {
|
|
|
20962
21048
|
GOAL_MAX: () => GOAL_MAX,
|
|
20963
21049
|
GOAL_MAX_BLOCKS: () => GOAL_MAX_BLOCKS,
|
|
20964
21050
|
GOAL_TUI_PASTE_LIMIT: () => GOAL_TUI_PASTE_LIMIT,
|
|
21051
|
+
MOCKUP_FOLLOW_UP: () => MOCKUP_FOLLOW_UP,
|
|
20965
21052
|
PHASE_CONTEXT_HEADER: () => PHASE_CONTEXT_HEADER,
|
|
20966
21053
|
PIPELINES: () => PIPELINES,
|
|
20967
21054
|
PLAN_SHAPE: () => PLAN_SHAPE,
|
|
@@ -21028,6 +21115,7 @@ __export(src_exports, {
|
|
|
21028
21115
|
materializeAntigravityAgents: () => materializeAntigravityAgents,
|
|
21029
21116
|
materializeCodexAgents: () => materializeCodexAgents,
|
|
21030
21117
|
methodClause: () => methodClause,
|
|
21118
|
+
mockupGuide: () => mockupGuide,
|
|
21031
21119
|
orchestratorClause: () => orchestratorClause,
|
|
21032
21120
|
parseConfigEnv: () => parseConfigEnv,
|
|
21033
21121
|
phaseAgentInstructions: () => phaseAgentInstructions,
|
|
@@ -21500,10 +21588,13 @@ var init_session_phases = __esm({
|
|
|
21500
21588
|
decisionFilePath = (repoDir, sessionId2) => `${repoDir}/.worktrees/.decisions/${sessionId2}`;
|
|
21501
21589
|
phaseFileMemo = /* @__PURE__ */ new Map();
|
|
21502
21590
|
derivePhases = (seen2, flow2) => {
|
|
21591
|
+
const names = PIPELINES[flow2];
|
|
21503
21592
|
let activeTaken = false;
|
|
21504
|
-
return
|
|
21593
|
+
return names.map((name2, i) => {
|
|
21505
21594
|
const state = seen2.get(name2);
|
|
21506
21595
|
if (state) return { name: name2, state };
|
|
21596
|
+
if (OPTIONAL_PHASES.has(name2) && names.slice(i + 1).some((later) => seen2.has(later)))
|
|
21597
|
+
return { name: name2, state: "skipped" };
|
|
21507
21598
|
if (activeTaken) return { name: name2, state: "pending" };
|
|
21508
21599
|
activeTaken = true;
|
|
21509
21600
|
return { name: name2, state: "active" };
|
|
@@ -21784,18 +21875,146 @@ var init_antigravity_runtime = __esm({
|
|
|
21784
21875
|
}
|
|
21785
21876
|
});
|
|
21786
21877
|
|
|
21878
|
+
// src/runner/deps.ts
|
|
21879
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
21880
|
+
import { dirname as dirname10, join as join15 } from "node:path";
|
|
21881
|
+
function repoRootFrom(startDir) {
|
|
21882
|
+
let dir2 = startDir;
|
|
21883
|
+
for (let i = 0; i < 8; i++) {
|
|
21884
|
+
if (existsSync9(join15(dir2, "pnpm-workspace.yaml"))) return dir2;
|
|
21885
|
+
const parent = dirname10(dir2);
|
|
21886
|
+
if (parent === dir2) break;
|
|
21887
|
+
dir2 = parent;
|
|
21888
|
+
}
|
|
21889
|
+
return startDir;
|
|
21890
|
+
}
|
|
21891
|
+
var repoRoot;
|
|
21892
|
+
var init_deps = __esm({
|
|
21893
|
+
"src/runner/deps.ts"() {
|
|
21894
|
+
"use strict";
|
|
21895
|
+
repoRoot = (startDir = process.cwd()) => repoRootFrom(startDir);
|
|
21896
|
+
}
|
|
21897
|
+
});
|
|
21898
|
+
|
|
21899
|
+
// src/services/bundled-skills.ts
|
|
21900
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
21901
|
+
import { chmodSync as chmodSync7, cpSync as cpSync3, existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync11, readdirSync as readdirSync8, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync3, writeFileSync as writeFileSync11 } from "node:fs";
|
|
21902
|
+
import { join as join16, relative as relative5, sep as sep6 } from "node:path";
|
|
21903
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
21904
|
+
function bundledSkillsDir() {
|
|
21905
|
+
const packed = packagedDir();
|
|
21906
|
+
return existsSync10(packed) ? packed : join16(repoRoot(moduleDir()), "server", "skills");
|
|
21907
|
+
}
|
|
21908
|
+
function filesUnder(dir2) {
|
|
21909
|
+
const out4 = [];
|
|
21910
|
+
const walk = (d) => {
|
|
21911
|
+
for (const n2 of readdirSync8(d).sort()) {
|
|
21912
|
+
if (n2 === BUNDLED_MARKER || n2 === ".DS_Store") continue;
|
|
21913
|
+
const full = join16(d, n2);
|
|
21914
|
+
if (statSync3(full).isDirectory()) walk(full);
|
|
21915
|
+
else out4.push(full);
|
|
21916
|
+
}
|
|
21917
|
+
};
|
|
21918
|
+
walk(dir2);
|
|
21919
|
+
return out4;
|
|
21920
|
+
}
|
|
21921
|
+
function contentSha(dir2) {
|
|
21922
|
+
const h2 = createHash7("sha256");
|
|
21923
|
+
for (const f of filesUnder(dir2)) {
|
|
21924
|
+
h2.update(relative5(dir2, f).split(sep6).join("/"));
|
|
21925
|
+
h2.update("\0");
|
|
21926
|
+
h2.update(readFileSync11(f));
|
|
21927
|
+
h2.update("\0");
|
|
21928
|
+
}
|
|
21929
|
+
return h2.digest("hex");
|
|
21930
|
+
}
|
|
21931
|
+
function markerSha(dir2) {
|
|
21932
|
+
try {
|
|
21933
|
+
const parsed = JSON.parse(readFileSync11(join16(dir2, BUNDLED_MARKER), "utf8"));
|
|
21934
|
+
const sha2 = parsed?.sha;
|
|
21935
|
+
return typeof sha2 === "string" ? sha2 : null;
|
|
21936
|
+
} catch {
|
|
21937
|
+
return null;
|
|
21938
|
+
}
|
|
21939
|
+
}
|
|
21940
|
+
function install(src, dest, sha2) {
|
|
21941
|
+
const tmp = `${dest}.tmp-${process.pid}`;
|
|
21942
|
+
rmSync6(tmp, { recursive: true, force: true });
|
|
21943
|
+
cpSync3(src, tmp, { recursive: true });
|
|
21944
|
+
for (const f of filesUnder(tmp))
|
|
21945
|
+
if (relative5(tmp, f).split(sep6)[0] === "scripts") chmodSync7(f, statSync3(f).mode | 493);
|
|
21946
|
+
writeFileSync11(join16(tmp, BUNDLED_MARKER), `${JSON.stringify({ sha: sha2, seededAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
21947
|
+
`);
|
|
21948
|
+
rmSync6(dest, { recursive: true, force: true });
|
|
21949
|
+
renameSync4(tmp, dest);
|
|
21950
|
+
}
|
|
21951
|
+
function seedBundledSkills(hanomanHome, o = {}) {
|
|
21952
|
+
const out4 = { seeded: [], updated: [], kept: [] };
|
|
21953
|
+
const source = o.source ?? bundledSkillsDir();
|
|
21954
|
+
const log = o.log ?? ((m) => console.warn(m));
|
|
21955
|
+
const names = skillDirsIn(source);
|
|
21956
|
+
if (!names.length) return out4;
|
|
21957
|
+
const root = hanomanSkillsDir(hanomanHome);
|
|
21958
|
+
for (const name2 of names) {
|
|
21959
|
+
try {
|
|
21960
|
+
const src = join16(source, name2);
|
|
21961
|
+
const dest = join16(root, name2);
|
|
21962
|
+
const bundleSha = contentSha(src);
|
|
21963
|
+
if (!existsSync10(dest)) {
|
|
21964
|
+
mkdirSync9(root, { recursive: true });
|
|
21965
|
+
install(src, dest, bundleSha);
|
|
21966
|
+
out4.seeded.push(name2);
|
|
21967
|
+
continue;
|
|
21968
|
+
}
|
|
21969
|
+
const recorded2 = markerSha(dest);
|
|
21970
|
+
if (recorded2 === bundleSha) continue;
|
|
21971
|
+
if (recorded2 === null || contentSha(dest) !== recorded2) {
|
|
21972
|
+
out4.kept.push(name2);
|
|
21973
|
+
log(`skill bundel ${name2} tak diperbarui: salinan di ${dest} milik operator (tanpa marker atau sudah disunting)`);
|
|
21974
|
+
continue;
|
|
21975
|
+
}
|
|
21976
|
+
install(src, dest, bundleSha);
|
|
21977
|
+
out4.updated.push(name2);
|
|
21978
|
+
} catch (e) {
|
|
21979
|
+
log(`skill bundel ${name2} gagal di-seed: ${e.message}`);
|
|
21980
|
+
}
|
|
21981
|
+
}
|
|
21982
|
+
return out4;
|
|
21983
|
+
}
|
|
21984
|
+
var BUNDLED_MARKER, packagedDir, moduleDir, skillDirsIn, isBundledCopy;
|
|
21985
|
+
var init_bundled_skills = __esm({
|
|
21986
|
+
"src/services/bundled-skills.ts"() {
|
|
21987
|
+
"use strict";
|
|
21988
|
+
init_src2();
|
|
21989
|
+
init_deps();
|
|
21990
|
+
BUNDLED_MARKER = ".hanoman-bundled.json";
|
|
21991
|
+
packagedDir = () => fileURLToPath3(new URL("../skills", import.meta.url));
|
|
21992
|
+
moduleDir = () => fileURLToPath3(new URL(".", import.meta.url));
|
|
21993
|
+
skillDirsIn = (source) => {
|
|
21994
|
+
let names;
|
|
21995
|
+
try {
|
|
21996
|
+
names = readdirSync8(source);
|
|
21997
|
+
} catch {
|
|
21998
|
+
return [];
|
|
21999
|
+
}
|
|
22000
|
+
return names.filter((n2) => !n2.startsWith(".") && existsSync10(join16(source, n2, "SKILL.md"))).sort();
|
|
22001
|
+
};
|
|
22002
|
+
isBundledCopy = (dir2) => existsSync10(join16(dir2, BUNDLED_MARKER));
|
|
22003
|
+
}
|
|
22004
|
+
});
|
|
22005
|
+
|
|
21787
22006
|
// src/services/skill-inject.ts
|
|
21788
22007
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
21789
|
-
import { createHash as
|
|
21790
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
21791
|
-
import { isAbsolute as isAbsolute8, join as
|
|
22008
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
22009
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync11, lstatSync as lstatSync7, mkdirSync as mkdirSync10, readFileSync as readFileSync12, readlinkSync as readlinkSync2, symlinkSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync12 } from "node:fs";
|
|
22010
|
+
import { isAbsolute as isAbsolute8, join as join17 } from "node:path";
|
|
21792
22011
|
function excludeFile(repoDir) {
|
|
21793
22012
|
const common = execFileSync4("git", ["rev-parse", "--git-common-dir"], { cwd: repoDir, env: agentEnvironment(null, process.env), encoding: "utf8" }).trim();
|
|
21794
|
-
return
|
|
22013
|
+
return join17(isAbsolute8(common) ? common : join17(repoDir, common), "info", "exclude");
|
|
21795
22014
|
}
|
|
21796
22015
|
function ownedSkills(tempDir) {
|
|
21797
22016
|
try {
|
|
21798
|
-
const parsed = JSON.parse(
|
|
22017
|
+
const parsed = JSON.parse(readFileSync12(manifestPath(tempDir), "utf8"));
|
|
21799
22018
|
return Array.isArray(parsed) ? parsed.filter((row) => row && typeof row.link === "string" && typeof row.target === "string" && typeof row.exclude === "string" && typeof row.pair === "string" && typeof row.ino === "number" && typeof row.dev === "number") : [];
|
|
21800
22019
|
} catch {
|
|
21801
22020
|
return [];
|
|
@@ -21808,8 +22027,8 @@ function cleanupInjectedSkills(tempDir) {
|
|
|
21808
22027
|
if (!st2.isSymbolicLink() || st2.ino !== own.ino || st2.dev !== own.dev || readlinkSync2(own.link) !== own.target) continue;
|
|
21809
22028
|
unlinkSync2(own.link);
|
|
21810
22029
|
if (own.pair) {
|
|
21811
|
-
const bytes =
|
|
21812
|
-
if (bytes.includes(own.pair))
|
|
22030
|
+
const bytes = readFileSync12(own.exclude, "utf8");
|
|
22031
|
+
if (bytes.includes(own.pair)) writeFileSync12(own.exclude, bytes.replace(own.pair, ""));
|
|
21813
22032
|
}
|
|
21814
22033
|
} catch {
|
|
21815
22034
|
}
|
|
@@ -21817,18 +22036,20 @@ function cleanupInjectedSkills(tempDir) {
|
|
|
21817
22036
|
}
|
|
21818
22037
|
function injectGlobalSkills(o) {
|
|
21819
22038
|
const out4 = { injected: [], warnings: [] };
|
|
21820
|
-
const opts2 = { hanomanHome: o.hanomanHome, env: process.env };
|
|
21821
|
-
const
|
|
22039
|
+
const opts2 = { hanomanHome: o.hanomanHome, env: process.env, ...o.osHome ? { osHome: o.osHome } : {} };
|
|
22040
|
+
const scanned = scanGlobalSkills(opts2);
|
|
22041
|
+
const global2 = scanned.filter((s2) => s2.layer === "hanoman");
|
|
21822
22042
|
if (!global2.length) return out4;
|
|
21823
22043
|
const taken = new Set(scanProjectSkills("session", o.cwd, opts2).map((s2) => s2.name));
|
|
21824
|
-
const
|
|
22044
|
+
const userOwned = new Set(scanned.filter((s2) => s2.layer === "user" && s2.loadedBy.includes(o.agent)).map((s2) => s2.name));
|
|
22045
|
+
const todo = global2.filter((s2) => !taken.has(s2.name) && !(userOwned.has(s2.name) && isBundledCopy(s2.dir)));
|
|
21825
22046
|
if (!todo.length) return out4;
|
|
21826
22047
|
if (o.agent === "claude") {
|
|
21827
|
-
const root =
|
|
22048
|
+
const root = join17(o.tempDir, "skills-root");
|
|
21828
22049
|
try {
|
|
21829
|
-
|
|
22050
|
+
mkdirSync10(join17(root, ".claude", "skills"), { recursive: true });
|
|
21830
22051
|
for (const s2 of todo) {
|
|
21831
|
-
const link2 =
|
|
22052
|
+
const link2 = join17(root, ".claude", "skills", s2.name);
|
|
21832
22053
|
if (!lstatSync7(link2, { throwIfNoEntry: false })) symlinkSync(s2.dir, link2);
|
|
21833
22054
|
out4.injected.push(s2.name);
|
|
21834
22055
|
}
|
|
@@ -21849,25 +22070,25 @@ function injectGlobalSkills(o) {
|
|
|
21849
22070
|
let ownedLink;
|
|
21850
22071
|
let ownedPair = "";
|
|
21851
22072
|
try {
|
|
21852
|
-
const link2 =
|
|
22073
|
+
const link2 = join17(o.cwd, ".agents", "skills", s2.name);
|
|
21853
22074
|
if (o.agent === "antigravity") {
|
|
21854
|
-
for (const dir2 of [
|
|
22075
|
+
for (const dir2 of [join17(o.cwd, ".agents"), join17(o.cwd, ".agents", "skills")]) {
|
|
21855
22076
|
const st2 = lstatSync7(dir2, { throwIfNoEntry: false });
|
|
21856
22077
|
if (st2 && (!st2.isDirectory() || st2.isSymbolicLink())) throw new Error("akar skill bukan direktori aman");
|
|
21857
22078
|
}
|
|
21858
22079
|
}
|
|
21859
22080
|
if (lstatSync7(link2, { throwIfNoEntry: false })) continue;
|
|
21860
|
-
|
|
22081
|
+
mkdirSync10(join17(o.cwd, ".agents", "skills"), { recursive: true });
|
|
21861
22082
|
symlinkSync(s2.dir, link2);
|
|
21862
22083
|
ownedLink = link2;
|
|
21863
|
-
|
|
21864
|
-
const cur =
|
|
22084
|
+
mkdirSync10(join17(exclude, ".."), { recursive: true });
|
|
22085
|
+
const cur = existsSync11(exclude) ? readFileSync12(exclude, "utf8") : "";
|
|
21865
22086
|
const line = excludeLine(s2.name);
|
|
21866
22087
|
const lines2 = cur.split("\n");
|
|
21867
22088
|
const hasOperatorLine = lines2.some((entry2, index) => entry2 === line && !lines2[index - 1]?.startsWith(`${INJECT_MARKER}:antigravity:`));
|
|
21868
22089
|
if (o.agent === "antigravity" ? !hasOperatorLine : !lines2.includes(line)) {
|
|
21869
22090
|
if (o.agent === "antigravity") {
|
|
21870
|
-
const hash4 =
|
|
22091
|
+
const hash4 = createHash8("sha256").update(`${link2}\0${s2.dir}`).digest("hex");
|
|
21871
22092
|
ownedPair = `${INJECT_MARKER}:antigravity:${hash4}
|
|
21872
22093
|
${line}
|
|
21873
22094
|
`;
|
|
@@ -21880,9 +22101,9 @@ ${line}
|
|
|
21880
22101
|
}
|
|
21881
22102
|
}
|
|
21882
22103
|
if (o.agent === "antigravity") {
|
|
21883
|
-
|
|
22104
|
+
mkdirSync10(o.tempDir, { recursive: true, mode: 448 });
|
|
21884
22105
|
const st2 = lstatSync7(link2);
|
|
21885
|
-
|
|
22106
|
+
writeFileSync12(manifestPath(o.tempDir), JSON.stringify([...ownedSkills(o.tempDir), { link: link2, target: s2.dir, exclude, pair: ownedPair, ino: st2.ino, dev: st2.dev }]), { mode: 384 });
|
|
21886
22107
|
}
|
|
21887
22108
|
out4.injected.push(s2.name);
|
|
21888
22109
|
} catch (e) {
|
|
@@ -21892,7 +22113,7 @@ ${line}
|
|
|
21892
22113
|
} catch {
|
|
21893
22114
|
}
|
|
21894
22115
|
try {
|
|
21895
|
-
if (ownedPair)
|
|
22116
|
+
if (ownedPair) writeFileSync12(exclude, readFileSync12(exclude, "utf8").replace(ownedPair, ""));
|
|
21896
22117
|
} catch {
|
|
21897
22118
|
}
|
|
21898
22119
|
}
|
|
@@ -21904,9 +22125,9 @@ ${line}
|
|
|
21904
22125
|
function unexcludeInjected(repoDir, name2) {
|
|
21905
22126
|
try {
|
|
21906
22127
|
const f = excludeFile(repoDir);
|
|
21907
|
-
if (!
|
|
22128
|
+
if (!existsSync11(f)) return;
|
|
21908
22129
|
const line = excludeLine(name2);
|
|
21909
|
-
|
|
22130
|
+
writeFileSync12(f, readFileSync12(f, "utf8").split("\n").filter((l) => l !== line).join("\n"));
|
|
21910
22131
|
} catch {
|
|
21911
22132
|
}
|
|
21912
22133
|
}
|
|
@@ -21915,17 +22136,18 @@ var init_skill_inject = __esm({
|
|
|
21915
22136
|
"src/services/skill-inject.ts"() {
|
|
21916
22137
|
"use strict";
|
|
21917
22138
|
init_src2();
|
|
22139
|
+
init_bundled_skills();
|
|
21918
22140
|
INJECT_MARKER = "# hanoman:skill-inject";
|
|
21919
22141
|
excludeLine = (name2) => `/.agents/skills/${name2}`;
|
|
21920
|
-
manifestPath = (tempDir) =>
|
|
22142
|
+
manifestPath = (tempDir) => join17(tempDir, "antigravity-skills.json");
|
|
21921
22143
|
}
|
|
21922
22144
|
});
|
|
21923
22145
|
|
|
21924
22146
|
// src/services/antigravity-trust.ts
|
|
21925
22147
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
21926
|
-
import { closeSync as closeSync2, lstatSync as lstatSync8, mkdirSync as
|
|
22148
|
+
import { closeSync as closeSync2, lstatSync as lstatSync8, mkdirSync as mkdirSync11, openSync as openSync2, readFileSync as readFileSync13, realpathSync as realpathSync6, renameSync as renameSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync13 } from "node:fs";
|
|
21927
22149
|
import { homedir as homedir4 } from "node:os";
|
|
21928
|
-
import { join as
|
|
22150
|
+
import { join as join18 } from "node:path";
|
|
21929
22151
|
function safeOwned(path, directory) {
|
|
21930
22152
|
const st2 = lstatSync8(path);
|
|
21931
22153
|
if (st2.isSymbolicLink() || (directory ? !st2.isDirectory() : !st2.isFile()) || process.getuid && st2.uid !== process.getuid()) throw new Error("unsafe path");
|
|
@@ -21935,18 +22157,18 @@ function ensureAntigravityTrust(cwd, home3 = homedir4()) {
|
|
|
21935
22157
|
let fd;
|
|
21936
22158
|
try {
|
|
21937
22159
|
const canonical = realpathSync6(cwd);
|
|
21938
|
-
|
|
22160
|
+
mkdirSync11(home3, { recursive: true });
|
|
21939
22161
|
const root = realpathSync6(home3);
|
|
21940
|
-
for (const dir2 of [
|
|
21941
|
-
if (!lstatSync8(dir2, { throwIfNoEntry: false }))
|
|
22162
|
+
for (const dir2 of [join18(root, ".gemini"), join18(root, ".gemini", "antigravity-cli")]) {
|
|
22163
|
+
if (!lstatSync8(dir2, { throwIfNoEntry: false })) mkdirSync11(dir2, { mode: 448 });
|
|
21942
22164
|
safeOwned(dir2, true);
|
|
21943
22165
|
}
|
|
21944
|
-
const file =
|
|
22166
|
+
const file = join18(root, ".gemini", "antigravity-cli", "settings.json");
|
|
21945
22167
|
const original = lstatSync8(file, { throwIfNoEntry: false });
|
|
21946
22168
|
let settings = {};
|
|
21947
22169
|
if (original) {
|
|
21948
22170
|
safeOwned(file, false);
|
|
21949
|
-
const parsed = JSON.parse(
|
|
22171
|
+
const parsed = JSON.parse(readFileSync13(file, "utf8"));
|
|
21950
22172
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("invalid settings");
|
|
21951
22173
|
settings = parsed;
|
|
21952
22174
|
}
|
|
@@ -21958,14 +22180,14 @@ function ensureAntigravityTrust(cwd, home3 = homedir4()) {
|
|
|
21958
22180
|
const candidate = `${file}.${randomUUID2()}.tmp`;
|
|
21959
22181
|
fd = openSync2(candidate, "wx", 384);
|
|
21960
22182
|
temp = candidate;
|
|
21961
|
-
|
|
22183
|
+
writeFileSync13(fd, `${JSON.stringify({ ...settings, trustedWorkspaces: [...workspaces, canonical] }, null, 2)}
|
|
21962
22184
|
`);
|
|
21963
22185
|
closeSync2(fd);
|
|
21964
22186
|
fd = void 0;
|
|
21965
22187
|
const latest = lstatSync8(file, { throwIfNoEntry: false });
|
|
21966
22188
|
if (original ? !latest || latest.ino !== original.ino || latest.mtimeMs !== original.mtimeMs : latest)
|
|
21967
22189
|
throw new Error("settings changed during preparation");
|
|
21968
|
-
|
|
22190
|
+
renameSync5(temp, file);
|
|
21969
22191
|
temp = void 0;
|
|
21970
22192
|
} catch {
|
|
21971
22193
|
throw new Error("antigravity-trust-preparation-failed");
|
|
@@ -22015,14 +22237,14 @@ var init_local_binding = __esm({
|
|
|
22015
22237
|
});
|
|
22016
22238
|
|
|
22017
22239
|
// src/services/skill-library.ts
|
|
22018
|
-
import { join as
|
|
22019
|
-
import { existsSync as
|
|
22240
|
+
import { join as join19 } from "node:path";
|
|
22241
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
22020
22242
|
async function libraryAll() {
|
|
22021
22243
|
const global2 = scanGlobalSkills(opts());
|
|
22022
22244
|
const projects = await prisma.project.findMany({ orderBy: { name: "asc" }, select: { id: true, name: true } });
|
|
22023
22245
|
const groups = await Promise.all(projects.map(async (p3) => {
|
|
22024
22246
|
const repo = await resolveRepoDir(p3.id);
|
|
22025
|
-
if (!repo || !
|
|
22247
|
+
if (!repo || !existsSync12(repo)) return { projectId: p3.id, name: p3.name, skills: [], error: "repo tidak ditemukan" };
|
|
22026
22248
|
return { projectId: p3.id, name: p3.name, skills: scanProjectSkills(p3.id, repo, opts()) };
|
|
22027
22249
|
}));
|
|
22028
22250
|
return { global: global2, projects: groups };
|
|
@@ -22045,17 +22267,17 @@ async function targetParent(layer, source, projectId) {
|
|
|
22045
22267
|
if (layer === "user") {
|
|
22046
22268
|
const s2 = source ?? ".claude";
|
|
22047
22269
|
if (!USER_SOURCES.includes(s2)) throw new SkillError(400, "source user harus .claude/.codex/.agents/.gemini/antigravity-cli");
|
|
22048
|
-
if (s2 === ".agents") return
|
|
22049
|
-
if (s2 === ".gemini/antigravity-cli") return
|
|
22050
|
-
return
|
|
22270
|
+
if (s2 === ".agents") return join19(agentsSkillHome(process.env), "skills");
|
|
22271
|
+
if (s2 === ".gemini/antigravity-cli") return join19(agentSkillHome("antigravity", process.env), "skills");
|
|
22272
|
+
return join19(agentSkillHome(s2 === ".codex" ? "codex" : "claude", process.env), "skills");
|
|
22051
22273
|
}
|
|
22052
22274
|
if (layer === "project") {
|
|
22053
22275
|
if (!projectId) throw new SkillError(400, "projectId wajib untuk lapis project");
|
|
22054
22276
|
const s2 = source ?? ".claude";
|
|
22055
22277
|
if (!PROJECT_SOURCES.includes(s2)) throw new SkillError(400, "source project harus .claude/.agents/.codex");
|
|
22056
22278
|
const repo = await resolveRepoDir(projectId);
|
|
22057
|
-
if (!repo || !
|
|
22058
|
-
return
|
|
22279
|
+
if (!repo || !existsSync12(repo)) throw new SkillError(404, "repo project tidak ditemukan");
|
|
22280
|
+
return join19(repo, s2, "skills");
|
|
22059
22281
|
}
|
|
22060
22282
|
throw new SkillError(400, "layer harus hanoman, user, atau project");
|
|
22061
22283
|
}
|
|
@@ -35858,7 +36080,7 @@ var require_snapshot_utils = __commonJS({
|
|
|
35858
36080
|
var require_snapshot_recorder = __commonJS({
|
|
35859
36081
|
"../node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
|
|
35860
36082
|
"use strict";
|
|
35861
|
-
var { writeFile: writeFile9, readFile:
|
|
36083
|
+
var { writeFile: writeFile9, readFile: readFile12, mkdir: mkdir13 } = __require("node:fs/promises");
|
|
35862
36084
|
var { dirname: dirname20, resolve: resolve25 } = __require("node:path");
|
|
35863
36085
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers");
|
|
35864
36086
|
var { InvalidArgumentError, UndiciError } = require_errors();
|
|
@@ -36060,7 +36282,7 @@ var require_snapshot_recorder = __commonJS({
|
|
|
36060
36282
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
36061
36283
|
}
|
|
36062
36284
|
try {
|
|
36063
|
-
const data = await
|
|
36285
|
+
const data = await readFile12(resolve25(path), "utf8");
|
|
36064
36286
|
const parsed = JSON.parse(data);
|
|
36065
36287
|
if (Array.isArray(parsed)) {
|
|
36066
36288
|
this.#snapshots.clear();
|
|
@@ -47911,7 +48133,7 @@ ${captureLines}` : capture.stack;
|
|
|
47911
48133
|
module.exports.mockErrors = mockErrors;
|
|
47912
48134
|
var { EventSource } = require_eventsource();
|
|
47913
48135
|
module.exports.EventSource = EventSource;
|
|
47914
|
-
function
|
|
48136
|
+
function install2() {
|
|
47915
48137
|
globalThis.fetch = module.exports.fetch;
|
|
47916
48138
|
globalThis.Headers = module.exports.Headers;
|
|
47917
48139
|
globalThis.Response = module.exports.Response;
|
|
@@ -47923,7 +48145,7 @@ ${captureLines}` : capture.stack;
|
|
|
47923
48145
|
globalThis.MessageEvent = module.exports.MessageEvent;
|
|
47924
48146
|
globalThis.EventSource = module.exports.EventSource;
|
|
47925
48147
|
}
|
|
47926
|
-
module.exports.install =
|
|
48148
|
+
module.exports.install = install2;
|
|
47927
48149
|
}
|
|
47928
48150
|
});
|
|
47929
48151
|
|
|
@@ -48169,7 +48391,7 @@ var init_safe_outbound_request = __esm({
|
|
|
48169
48391
|
// src/services/uploads.ts
|
|
48170
48392
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
48171
48393
|
import { mkdir, writeFile, readFile as readFile2, unlink, rm } from "node:fs/promises";
|
|
48172
|
-
import { join as
|
|
48394
|
+
import { join as join20, resolve as resolve9 } from "node:path";
|
|
48173
48395
|
function extFor(mimeType) {
|
|
48174
48396
|
return EXT[mimeType] ?? ".bin";
|
|
48175
48397
|
}
|
|
@@ -48180,17 +48402,17 @@ async function saveUpload(buf, mimeType) {
|
|
|
48180
48402
|
const dir2 = uploadDir();
|
|
48181
48403
|
await mkdir(dir2, { recursive: true, mode: 448 });
|
|
48182
48404
|
const storageKey = `${randomUUID3()}${extFor(mimeType)}`;
|
|
48183
|
-
await writeFile(
|
|
48405
|
+
await writeFile(join20(dir2, storageKey), buf, { mode: 384 });
|
|
48184
48406
|
return { storageKey, size: buf.length };
|
|
48185
48407
|
}
|
|
48186
48408
|
function sessionUploadDir(sessionId2) {
|
|
48187
48409
|
if (!SESSION_ID.test(sessionId2)) throw new Error(`sessionId tak sah: ${sessionId2}`);
|
|
48188
|
-
return
|
|
48410
|
+
return join20(uploadDir(), "terminal", sessionId2);
|
|
48189
48411
|
}
|
|
48190
48412
|
async function saveSessionUpload(sessionId2, buf, mimeType) {
|
|
48191
48413
|
const dir2 = sessionUploadDir(sessionId2);
|
|
48192
48414
|
await mkdir(dir2, { recursive: true, mode: 448 });
|
|
48193
|
-
const path =
|
|
48415
|
+
const path = join20(dir2, `${randomUUID3()}${extFor(mimeType)}`);
|
|
48194
48416
|
await writeFile(path, buf, { mode: 384 });
|
|
48195
48417
|
return { path, size: buf.length };
|
|
48196
48418
|
}
|
|
@@ -48206,11 +48428,11 @@ async function dropSessionUploads(sessionId2) {
|
|
|
48206
48428
|
}
|
|
48207
48429
|
async function readUpload(storageKey) {
|
|
48208
48430
|
const safe3 = storageKey.replace(/[/\\]/g, "");
|
|
48209
|
-
return readFile2(
|
|
48431
|
+
return readFile2(join20(uploadDir(), safe3));
|
|
48210
48432
|
}
|
|
48211
48433
|
async function readUploadOrFetch(storageKey) {
|
|
48212
48434
|
const safe3 = storageKey.replace(/[/\\]/g, "");
|
|
48213
|
-
const target3 =
|
|
48435
|
+
const target3 = join20(uploadDir(), safe3);
|
|
48214
48436
|
try {
|
|
48215
48437
|
return await readFile2(target3);
|
|
48216
48438
|
} catch {
|
|
@@ -48237,7 +48459,7 @@ async function readUploadOrFetch(storageKey) {
|
|
|
48237
48459
|
}
|
|
48238
48460
|
async function deleteUpload(storageKey) {
|
|
48239
48461
|
const safe3 = storageKey.replace(/[/\\]/g, "");
|
|
48240
|
-
await unlink(
|
|
48462
|
+
await unlink(join20(uploadDir(), safe3)).catch(() => {
|
|
48241
48463
|
});
|
|
48242
48464
|
}
|
|
48243
48465
|
var EXT, SESSION_ID;
|
|
@@ -48680,14 +48902,14 @@ var init_session_sandbox = __esm({
|
|
|
48680
48902
|
});
|
|
48681
48903
|
|
|
48682
48904
|
// src/services/session-event-spool.ts
|
|
48683
|
-
import { join as
|
|
48905
|
+
import { join as join21 } from "node:path";
|
|
48684
48906
|
var sessionEventSpoolRoot, sessionEventDir;
|
|
48685
48907
|
var init_session_event_spool = __esm({
|
|
48686
48908
|
"src/services/session-event-spool.ts"() {
|
|
48687
48909
|
"use strict";
|
|
48688
48910
|
init_src2();
|
|
48689
|
-
sessionEventSpoolRoot = (env = process.env) =>
|
|
48690
|
-
sessionEventDir = (sessionId2, env = process.env) =>
|
|
48911
|
+
sessionEventSpoolRoot = (env = process.env) => join21(resolveHome(env), "session-events");
|
|
48912
|
+
sessionEventDir = (sessionId2, env = process.env) => join21(sessionEventSpoolRoot(env), sessionId2);
|
|
48691
48913
|
}
|
|
48692
48914
|
});
|
|
48693
48915
|
|
|
@@ -48711,9 +48933,9 @@ import { spawn } from "node-pty";
|
|
|
48711
48933
|
import { execFile as execFile2, execFileSync as execFileSync5 } from "node:child_process";
|
|
48712
48934
|
import { createRequire } from "node:module";
|
|
48713
48935
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
48714
|
-
import { chmodSync as
|
|
48936
|
+
import { chmodSync as chmodSync8, mkdirSync as mkdirSync12, readFileSync as readFileSync14, rmSync as rmSync7, statSync as statSync4, writeFileSync as writeFileSync14 } from "node:fs";
|
|
48715
48937
|
import { stat as stat2 } from "node:fs/promises";
|
|
48716
|
-
import { dirname as
|
|
48938
|
+
import { dirname as dirname11, resolve as resolvePath } from "node:path";
|
|
48717
48939
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
48718
48940
|
function doneSeenOf(id2) {
|
|
48719
48941
|
const known2 = phaseDoneSeen.get(id2);
|
|
@@ -48774,8 +48996,8 @@ function fitCustomAgentsToArgBudget(customDefs, render, limit = AGENTS_ARG_SAFE_
|
|
|
48774
48996
|
}
|
|
48775
48997
|
function noTtyPromptEnv() {
|
|
48776
48998
|
const path = askpassDenyPath();
|
|
48777
|
-
|
|
48778
|
-
|
|
48999
|
+
mkdirSync12(dirname11(path), { recursive: true, mode: 448 });
|
|
49000
|
+
writeFileSync14(path, ASKPASS_DENY, { mode: 448 });
|
|
48779
49001
|
return { SSH_ASKPASS: path, SSH_ASKPASS_REQUIRE: "force", GIT_TERMINAL_PROMPT: "0" };
|
|
48780
49002
|
}
|
|
48781
49003
|
function hanomanDbInheritUnsets(env = process.env) {
|
|
@@ -49028,8 +49250,8 @@ function createSession(projectId, cwd, opts2 = {}) {
|
|
|
49028
49250
|
phaseDoneSeen.delete(id2);
|
|
49029
49251
|
const eventDir = opts2.command ? void 0 : sessionEventDir(id2);
|
|
49030
49252
|
if (eventDir) {
|
|
49031
|
-
|
|
49032
|
-
|
|
49253
|
+
mkdirSync12(eventDir, { recursive: true, mode: 448 });
|
|
49254
|
+
chmodSync8(eventDir, 448);
|
|
49033
49255
|
}
|
|
49034
49256
|
const agentForDefs = opts2.agent ?? "claude";
|
|
49035
49257
|
const sessionEffort = agentForDefs === "codex" && opts2.model && opts2.effort ? coerceCodexEffort(opts2.model, opts2.effort) : opts2.effort;
|
|
@@ -49073,7 +49295,7 @@ function createSession(projectId, cwd, opts2 = {}) {
|
|
|
49073
49295
|
if (customDefs.length > 0 || requestedPhaseDefs.length > 0) {
|
|
49074
49296
|
const tempDir = agentTempDir(id2);
|
|
49075
49297
|
agentConfigDir = tempDir;
|
|
49076
|
-
|
|
49298
|
+
mkdirSync12(tempDir, { recursive: true, mode: 448 });
|
|
49077
49299
|
let hook;
|
|
49078
49300
|
const readOnlyHookFor = () => customDefs.some((def2) => def2.workspacePolicy === "read-only") ? hook ??= writeReadOnlyHook(tempDir) : void 0;
|
|
49079
49301
|
const attempt = (requested) => {
|
|
@@ -49088,16 +49310,16 @@ function createSession(projectId, cwd, opts2 = {}) {
|
|
|
49088
49310
|
let phaseContextFile;
|
|
49089
49311
|
if (shared !== void 0) {
|
|
49090
49312
|
const path = phaseContextFilePath(id2);
|
|
49091
|
-
|
|
49092
|
-
|
|
49313
|
+
writeFileSync14(path, shared, { mode: 384 });
|
|
49314
|
+
chmodSync8(path, 384);
|
|
49093
49315
|
phaseContextFile = { context: shared, path };
|
|
49094
49316
|
}
|
|
49095
49317
|
const agentContractFiles = [];
|
|
49096
49318
|
for (const policy of new Set(customDefs.map((d) => d.workspacePolicy ?? "inherit"))) {
|
|
49097
49319
|
const path = agentContractFilePath(id2, policy);
|
|
49098
49320
|
const content = agentContractOf(policy);
|
|
49099
|
-
|
|
49100
|
-
|
|
49321
|
+
writeFileSync14(path, content, { mode: 384 });
|
|
49322
|
+
chmodSync8(path, 384);
|
|
49101
49323
|
agentContractFiles.push({ policy, content, path });
|
|
49102
49324
|
}
|
|
49103
49325
|
const fit = fitCustomAgentsToArgBudget(customDefs, (kept) => renderAgentsJson(
|
|
@@ -49119,7 +49341,7 @@ function createSession(projectId, cwd, opts2 = {}) {
|
|
|
49119
49341
|
customDefs = fit.kept;
|
|
49120
49342
|
defs = [...withPhaseDelegation(requested, customDefs, agentForDefs), ...customDefs];
|
|
49121
49343
|
if (defs.length === 0) return [];
|
|
49122
|
-
|
|
49344
|
+
writeFileSync14(file, fit.json, { mode: 384 });
|
|
49123
49345
|
} catch (error) {
|
|
49124
49346
|
if (phaseDefs.length === 0) throw error;
|
|
49125
49347
|
return [error instanceof Error ? error.message : String(error)];
|
|
@@ -49199,8 +49421,8 @@ function createSession(projectId, cwd, opts2 = {}) {
|
|
|
49199
49421
|
const sessionPrompt = bornPrompt(orchestrated);
|
|
49200
49422
|
if (!opts2.command && sessionPrompt) {
|
|
49201
49423
|
promptFile = promptFilePath(id2);
|
|
49202
|
-
|
|
49203
|
-
|
|
49424
|
+
mkdirSync12(dirname11(promptFile), { recursive: true, mode: 448 });
|
|
49425
|
+
writeFileSync14(promptFile, sessionPrompt + rosterBlock, { mode: 384 });
|
|
49204
49426
|
promptArg = `"$(cat ${sq(promptFile)})"`;
|
|
49205
49427
|
}
|
|
49206
49428
|
const agent = opts2.agent ?? "claude";
|
|
@@ -49211,8 +49433,8 @@ function createSession(projectId, cwd, opts2 = {}) {
|
|
|
49211
49433
|
let goalGate;
|
|
49212
49434
|
if (agent === "codex" && opts2.goal && opts2.flow && opts2.specId) {
|
|
49213
49435
|
goalGate = goalGatePath(id2);
|
|
49214
|
-
|
|
49215
|
-
|
|
49436
|
+
mkdirSync12(dirname11(goalGate), { recursive: true, mode: 448 });
|
|
49437
|
+
writeFileSync14(goalGate, codexGoalScript({
|
|
49216
49438
|
flow: opts2.flow,
|
|
49217
49439
|
specId: opts2.specId,
|
|
49218
49440
|
condition: opts2.goal,
|
|
@@ -49259,7 +49481,7 @@ function createSession(projectId, cwd, opts2 = {}) {
|
|
|
49259
49481
|
for (const [k, v] of Object.entries(sessionEventEnv(id2))) envPairs.push(`${k}=${sq(v)}`);
|
|
49260
49482
|
}
|
|
49261
49483
|
if (opts2.phaseFile) {
|
|
49262
|
-
|
|
49484
|
+
mkdirSync12(dirname11(opts2.phaseFile), { recursive: true });
|
|
49263
49485
|
envPairs.push(`HANOMAN_PHASE_FILE=${sq(opts2.phaseFile)}`);
|
|
49264
49486
|
}
|
|
49265
49487
|
if (!opts2.command) {
|
|
@@ -49288,7 +49510,7 @@ function createSession(projectId, cwd, opts2 = {}) {
|
|
|
49288
49510
|
});
|
|
49289
49511
|
cmd = wrapped;
|
|
49290
49512
|
}
|
|
49291
|
-
if (opts2.decisionFile)
|
|
49513
|
+
if (opts2.decisionFile) mkdirSync12(dirname11(opts2.decisionFile), { recursive: true });
|
|
49292
49514
|
const bootstrap = runtime === "antigravity" ? createAntigravityEnvBootstrap(cmd, agentEnvironment(runtime, { ...process.env, ...opts2.env })) : void 0;
|
|
49293
49515
|
if (bootstrap) cmd = bootstrap.command;
|
|
49294
49516
|
try {
|
|
@@ -49695,11 +49917,11 @@ function killSession(id2) {
|
|
|
49695
49917
|
cleanupAntigravityAgents({ cwd: p3.cwd, sessionId: id2 });
|
|
49696
49918
|
cleanupInjectedSkills(agentTempDir(id2));
|
|
49697
49919
|
try {
|
|
49698
|
-
|
|
49920
|
+
rmSync7(agentTempDir(id2), { recursive: true, force: true });
|
|
49699
49921
|
} catch {
|
|
49700
49922
|
}
|
|
49701
49923
|
try {
|
|
49702
|
-
|
|
49924
|
+
rmSync7(sessionEventDir(id2), { recursive: true, force: true });
|
|
49703
49925
|
} catch {
|
|
49704
49926
|
}
|
|
49705
49927
|
emitDeath({ sessionId: id2, exitCode: p3.exited ? p3.code : null, transcript });
|
|
@@ -49758,7 +49980,7 @@ var init_pty = __esm({
|
|
|
49758
49980
|
POLL_MS = 500;
|
|
49759
49981
|
markerFilled = (f) => {
|
|
49760
49982
|
try {
|
|
49761
|
-
return
|
|
49983
|
+
return statSync4(f).size > 0;
|
|
49762
49984
|
} catch {
|
|
49763
49985
|
return false;
|
|
49764
49986
|
}
|
|
@@ -49772,7 +49994,7 @@ var init_pty = __esm({
|
|
|
49772
49994
|
};
|
|
49773
49995
|
clearMarker = (f) => {
|
|
49774
49996
|
try {
|
|
49775
|
-
|
|
49997
|
+
writeFileSync14(f, "");
|
|
49776
49998
|
} catch {
|
|
49777
49999
|
}
|
|
49778
50000
|
};
|
|
@@ -49782,7 +50004,7 @@ var init_pty = __esm({
|
|
|
49782
50004
|
markerOnset = (f) => {
|
|
49783
50005
|
let raw;
|
|
49784
50006
|
try {
|
|
49785
|
-
raw =
|
|
50007
|
+
raw = readFileSync14(f, "utf8");
|
|
49786
50008
|
} catch {
|
|
49787
50009
|
return 0;
|
|
49788
50010
|
}
|
|
@@ -50036,7 +50258,7 @@ var init_redact_known = __esm({
|
|
|
50036
50258
|
// src/services/transcript-store.ts
|
|
50037
50259
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
50038
50260
|
import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile3, unlink as unlink2, readdir as readdir2, stat as stat3 } from "node:fs/promises";
|
|
50039
|
-
import { join as
|
|
50261
|
+
import { join as join22, resolve as resolve10, basename as basename6 } from "node:path";
|
|
50040
50262
|
function transcriptDir() {
|
|
50041
50263
|
return resolve10(effectiveStr("HANOMAN_TRANSCRIPT_DIR")?.trim() || resolveDataDirs().transcripts);
|
|
50042
50264
|
}
|
|
@@ -50056,13 +50278,13 @@ async function saveTranscript(text2) {
|
|
|
50056
50278
|
const dir2 = transcriptDir();
|
|
50057
50279
|
await mkdir2(dir2, { recursive: true, mode: 448 });
|
|
50058
50280
|
const key = `${randomUUID5()}.log`;
|
|
50059
|
-
await writeFile2(
|
|
50281
|
+
await writeFile2(join22(dir2, key), body, { encoding: "utf8", mode: 384 });
|
|
50060
50282
|
return { key, bytes: Buffer.byteLength(body, "utf8"), truncated };
|
|
50061
50283
|
}
|
|
50062
50284
|
async function readTranscript(key) {
|
|
50063
50285
|
if (!key) return null;
|
|
50064
50286
|
try {
|
|
50065
|
-
return await readFile3(
|
|
50287
|
+
return await readFile3(join22(transcriptDir(), basename6(key)), "utf8");
|
|
50066
50288
|
} catch {
|
|
50067
50289
|
return null;
|
|
50068
50290
|
}
|
|
@@ -50070,7 +50292,7 @@ async function readTranscript(key) {
|
|
|
50070
50292
|
async function deleteTranscript(key) {
|
|
50071
50293
|
if (!key) return;
|
|
50072
50294
|
try {
|
|
50073
|
-
await unlink2(
|
|
50295
|
+
await unlink2(join22(transcriptDir(), basename6(key)));
|
|
50074
50296
|
} catch (e) {
|
|
50075
50297
|
if (e.code !== "ENOENT") throw e;
|
|
50076
50298
|
}
|
|
@@ -50088,7 +50310,7 @@ async function listTranscripts() {
|
|
|
50088
50310
|
for (const name2 of names) {
|
|
50089
50311
|
if (!name2.endsWith(".log")) continue;
|
|
50090
50312
|
try {
|
|
50091
|
-
rows.push({ key: name2, mtimeMs: (await stat3(
|
|
50313
|
+
rows.push({ key: name2, mtimeMs: (await stat3(join22(dir2, name2))).mtimeMs });
|
|
50092
50314
|
} catch {
|
|
50093
50315
|
}
|
|
50094
50316
|
}
|
|
@@ -55381,13 +55603,13 @@ var init_tombstone = __esm({
|
|
|
55381
55603
|
});
|
|
55382
55604
|
|
|
55383
55605
|
// src/services/qa-attachment-sync.ts
|
|
55384
|
-
import { createHash as
|
|
55606
|
+
import { createHash as createHash9, randomUUID as randomUUID7 } from "node:crypto";
|
|
55385
55607
|
import { chmod, mkdir as mkdir4, rename as rename2, stat as stat4, writeFile as writeFile4 } from "node:fs/promises";
|
|
55386
|
-
import { join as
|
|
55608
|
+
import { join as join24 } from "node:path";
|
|
55387
55609
|
async function qaBytesPresent(storageKey) {
|
|
55388
55610
|
if (!QA_STORAGE_KEY.test(storageKey)) return false;
|
|
55389
55611
|
try {
|
|
55390
|
-
return (await stat4(
|
|
55612
|
+
return (await stat4(join24(uploadDir(), storageKey))).isFile();
|
|
55391
55613
|
} catch {
|
|
55392
55614
|
return false;
|
|
55393
55615
|
}
|
|
@@ -55402,7 +55624,7 @@ async function settleNewQaAttachment(id2) {
|
|
|
55402
55624
|
}
|
|
55403
55625
|
async function verifyQaBytes(meta, buf) {
|
|
55404
55626
|
if (buf.length !== meta.size || buf.length === 0 || buf.length > QA_SYNC_MAX_BYTES) return { ok: false, status: 400, error: "size" };
|
|
55405
|
-
if (
|
|
55627
|
+
if (createHash9("sha256").update(buf).digest("hex") !== meta.sha256) return { ok: false, status: 400, error: "sha256" };
|
|
55406
55628
|
if (SNIFFABLE2.has(meta.mimeType)) {
|
|
55407
55629
|
if ((await fileTypeFromBuffer(buf))?.mime !== meta.mimeType) return { ok: false, status: 415, error: "type" };
|
|
55408
55630
|
} else if (!isUtf8Text2(buf)) return { ok: false, status: 415, error: "type" };
|
|
@@ -55412,10 +55634,10 @@ async function storeQaBytes(storageKey, buf) {
|
|
|
55412
55634
|
if (!QA_STORAGE_KEY.test(storageKey)) throw new Error("storageKey tak sah");
|
|
55413
55635
|
const dir2 = uploadDir();
|
|
55414
55636
|
await mkdir4(dir2, { recursive: true, mode: 448 });
|
|
55415
|
-
const tmp =
|
|
55637
|
+
const tmp = join24(dir2, `${storageKey}.tmp-${randomUUID7()}`);
|
|
55416
55638
|
await writeFile4(tmp, buf, { mode: 384 });
|
|
55417
|
-
await rename2(tmp,
|
|
55418
|
-
await chmod(
|
|
55639
|
+
await rename2(tmp, join24(dir2, storageKey));
|
|
55640
|
+
await chmod(join24(dir2, storageKey), 384).catch(() => {
|
|
55419
55641
|
});
|
|
55420
55642
|
}
|
|
55421
55643
|
var QA_STORAGE_KEY, QA_SYNC_MAX_BYTES, SNIFFABLE2, isUtf8Text2;
|
|
@@ -55659,9 +55881,9 @@ async function pull(sinceCursor, limit = 500, maxBytes = PULL_MAX_BYTES, opts2 =
|
|
|
55659
55881
|
async function bootstrapSnapshot(after, maxBytes = PULL_MAX_BYTES, opts2 = {}) {
|
|
55660
55882
|
const tip = await prisma.syncLog.findFirst({ orderBy: { seq: "desc" }, select: { seq: true } });
|
|
55661
55883
|
const cursor = tip ? String(tip.seq) : "0";
|
|
55662
|
-
const
|
|
55663
|
-
const afterEntity =
|
|
55664
|
-
const afterId =
|
|
55884
|
+
const sep11 = after ? after.indexOf(":") : -1;
|
|
55885
|
+
const afterEntity = sep11 > 0 ? after.slice(0, sep11) : null;
|
|
55886
|
+
const afterId = sep11 > 0 ? after.slice(sep11 + 1) : null;
|
|
55665
55887
|
const startAt = afterEntity ? BOOTSTRAP_ORDER.indexOf(afterEntity) : 0;
|
|
55666
55888
|
if (startAt < 0) throw new Error("bootstrap cursor tak dikenal");
|
|
55667
55889
|
const records = [];
|
|
@@ -56066,9 +56288,9 @@ var init_sync_notify = __esm({
|
|
|
56066
56288
|
// src/services/safe-repo-path.ts
|
|
56067
56289
|
import { constants as constants3 } from "node:fs";
|
|
56068
56290
|
import { lstat, mkdir as mkdir5, open as open2, realpath, rename as rename3, unlink as unlink4 } from "node:fs/promises";
|
|
56069
|
-
import { isAbsolute as isAbsolute10, join as
|
|
56291
|
+
import { isAbsolute as isAbsolute10, join as join25, relative as relative6, resolve as resolve13, sep as sep7 } from "node:path";
|
|
56070
56292
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
56071
|
-
import { closeSync as closeSync3, fstatSync as fstatSync2, lstatSync as lstatSync9, mkdirSync as
|
|
56293
|
+
import { closeSync as closeSync3, fstatSync as fstatSync2, lstatSync as lstatSync9, mkdirSync as mkdirSync13, openSync as openSync3, readFileSync as readFileSync15, realpathSync as realpathSync7, renameSync as renameSync6, unlinkSync as unlinkSync4, writeFileSync as writeFileSync15 } from "node:fs";
|
|
56072
56294
|
function components(rel) {
|
|
56073
56295
|
if (!rel || isAbsolute10(rel) || rel.includes("\0")) denied("path invalid");
|
|
56074
56296
|
const parts = rel.replace(/\\/g, "/").split("/");
|
|
@@ -56082,15 +56304,15 @@ async function canonicalRoot(root) {
|
|
|
56082
56304
|
return out4;
|
|
56083
56305
|
}
|
|
56084
56306
|
function beneath(root, candidate) {
|
|
56085
|
-
const rel =
|
|
56086
|
-
return rel === "" || !rel.startsWith(`..${
|
|
56307
|
+
const rel = relative6(root, candidate);
|
|
56308
|
+
return rel === "" || !rel.startsWith(`..${sep7}`) && rel !== ".." && !isAbsolute10(rel);
|
|
56087
56309
|
}
|
|
56088
56310
|
async function ensureRepoParents(root, rel) {
|
|
56089
56311
|
const base3 = await canonicalRoot(root);
|
|
56090
56312
|
const parts = components(rel).slice(0, -1);
|
|
56091
56313
|
let current = base3;
|
|
56092
56314
|
for (const part of parts) {
|
|
56093
|
-
current =
|
|
56315
|
+
current = join25(current, part);
|
|
56094
56316
|
try {
|
|
56095
56317
|
await mkdir5(current, { mode: 448 });
|
|
56096
56318
|
} catch (error) {
|
|
@@ -56107,9 +56329,9 @@ function ensureRepoParentsSync(root, rel) {
|
|
|
56107
56329
|
const parts = components(rel).slice(0, -1);
|
|
56108
56330
|
let current = base3;
|
|
56109
56331
|
for (const part of parts) {
|
|
56110
|
-
current =
|
|
56332
|
+
current = join25(current, part);
|
|
56111
56333
|
try {
|
|
56112
|
-
|
|
56334
|
+
mkdirSync13(current, { mode: 448 });
|
|
56113
56335
|
} catch (error) {
|
|
56114
56336
|
if (error.code !== "EEXIST") throw error;
|
|
56115
56337
|
}
|
|
@@ -56123,7 +56345,7 @@ async function resolveRepoEntry(root, rel, opts2 = {}) {
|
|
|
56123
56345
|
const parts = components(rel);
|
|
56124
56346
|
let current = base3;
|
|
56125
56347
|
for (let i = 0; i < parts.length; i++) {
|
|
56126
|
-
current =
|
|
56348
|
+
current = join25(current, parts[i]);
|
|
56127
56349
|
const stat8 = await lstat(current).catch((error) => {
|
|
56128
56350
|
if (error.code === "ENOENT" && opts2.allowMissingTail) return null;
|
|
56129
56351
|
if (error.code === "ENOENT" && opts2.allowMissingFinal && i === parts.length - 1) return null;
|
|
@@ -56144,7 +56366,7 @@ function assertSafeRepoPathSync(root, rel, allowMissingFinal = false, allowMissi
|
|
|
56144
56366
|
const parts = components(rel);
|
|
56145
56367
|
let current = base3;
|
|
56146
56368
|
for (let i = 0; i < parts.length; i++) {
|
|
56147
|
-
current =
|
|
56369
|
+
current = join25(current, parts[i]);
|
|
56148
56370
|
try {
|
|
56149
56371
|
const stat8 = lstatSync9(current);
|
|
56150
56372
|
if (stat8.isSymbolicLink()) denied("symlink");
|
|
@@ -56163,7 +56385,7 @@ function readRepoFileSync(root, rel) {
|
|
|
56163
56385
|
const fd = openSync3(path, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
|
|
56164
56386
|
try {
|
|
56165
56387
|
if (!fstatSync2(fd).isFile()) denied("bukan file regular");
|
|
56166
|
-
return
|
|
56388
|
+
return readFileSync15(fd);
|
|
56167
56389
|
} finally {
|
|
56168
56390
|
closeSync3(fd);
|
|
56169
56391
|
}
|
|
@@ -56172,12 +56394,12 @@ function writeRepoFileAtomicSync(root, rel, data) {
|
|
|
56172
56394
|
ensureRepoParentsSync(root, rel);
|
|
56173
56395
|
const path = assertSafeRepoPathSync(root, rel, true);
|
|
56174
56396
|
const parent = resolve13(path, "..");
|
|
56175
|
-
const parentRel =
|
|
56397
|
+
const parentRel = relative6(realpathSync7(root), parent);
|
|
56176
56398
|
if (parentRel) assertSafeRepoPathSync(root, parentRel);
|
|
56177
|
-
const temp =
|
|
56399
|
+
const temp = join25(parent, `.hanoman-${randomUUID8()}.tmp`);
|
|
56178
56400
|
const fd = openSync3(temp, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
|
|
56179
56401
|
try {
|
|
56180
|
-
|
|
56402
|
+
writeFileSync15(fd, data);
|
|
56181
56403
|
} finally {
|
|
56182
56404
|
closeSync3(fd);
|
|
56183
56405
|
}
|
|
@@ -56190,7 +56412,7 @@ function writeRepoFileAtomicSync(root, rel, data) {
|
|
|
56190
56412
|
}
|
|
56191
56413
|
})();
|
|
56192
56414
|
if (final?.isSymbolicLink() || final && !final.isFile()) denied("target berubah");
|
|
56193
|
-
|
|
56415
|
+
renameSync6(temp, path);
|
|
56194
56416
|
} catch (error) {
|
|
56195
56417
|
try {
|
|
56196
56418
|
unlinkSync4(temp);
|
|
@@ -56221,7 +56443,7 @@ async function writeRepoFileAtomic(root, rel, data) {
|
|
|
56221
56443
|
await mkdir5(entry2.parent, { recursive: false, mode: 448 }).catch((error) => {
|
|
56222
56444
|
if (error.code !== "EEXIST") throw error;
|
|
56223
56445
|
});
|
|
56224
|
-
const temp =
|
|
56446
|
+
const temp = join25(entry2.parent, `.hanoman-${randomUUID8()}.tmp`);
|
|
56225
56447
|
const handle = await open2(temp, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
|
|
56226
56448
|
try {
|
|
56227
56449
|
await handle.writeFile(data);
|
|
@@ -56257,8 +56479,8 @@ var init_safe_repo_path = __esm({
|
|
|
56257
56479
|
// src/services/integrate.ts
|
|
56258
56480
|
import { execFile as execFile5 } from "node:child_process";
|
|
56259
56481
|
import { promisify as promisify4 } from "node:util";
|
|
56260
|
-
import { rmSync as
|
|
56261
|
-
import { join as
|
|
56482
|
+
import { rmSync as rmSync9 } from "node:fs";
|
|
56483
|
+
import { join as join26 } from "node:path";
|
|
56262
56484
|
async function worktreeForBranch(repoDir, branch) {
|
|
56263
56485
|
let path = null;
|
|
56264
56486
|
for (const line of (await out(repoDir, ["worktree", "list", "--porcelain"])).split("\n")) {
|
|
@@ -56283,7 +56505,7 @@ async function resolveTarget(repoDir, target3) {
|
|
|
56283
56505
|
async function reclaim(repoDir, wt) {
|
|
56284
56506
|
await sh(repoDir, ["worktree", "remove", "--force", wt]);
|
|
56285
56507
|
await sh(repoDir, ["worktree", "prune"]);
|
|
56286
|
-
|
|
56508
|
+
rmSync9(wt, { recursive: true, force: true });
|
|
56287
56509
|
}
|
|
56288
56510
|
async function integrateBranch(repoDir, src, op, target3) {
|
|
56289
56511
|
const source = await resolveSource(repoDir, src.branch);
|
|
@@ -56291,7 +56513,7 @@ async function integrateBranch(repoDir, src, op, target3) {
|
|
|
56291
56513
|
const tgt = await resolveTarget(repoDir, target3);
|
|
56292
56514
|
if (!tgt) return { status: "error", code: 400, error: `target "${target3}" tidak dikenal` };
|
|
56293
56515
|
await sh(repoDir, ["fetch", "origin"]);
|
|
56294
|
-
const wt =
|
|
56516
|
+
const wt = join26(repoDir, ".worktrees", `merge-${sanitize(src.mergeId)}`);
|
|
56295
56517
|
await reclaim(repoDir, wt);
|
|
56296
56518
|
const baseRef = op === "merge" ? tgt.ref : source;
|
|
56297
56519
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `${baseRef}^{commit}`]);
|
|
@@ -56352,7 +56574,7 @@ async function mergeIntoCurrent(repoDir, source, opts2 = {}) {
|
|
|
56352
56574
|
const src = await resolveGraphSource(repoDir, source);
|
|
56353
56575
|
if (!src) return { status: "error", code: 400, error: `source "${source}" tak dikenal` };
|
|
56354
56576
|
await sh(repoDir, ["fetch", "origin"]);
|
|
56355
|
-
const wt =
|
|
56577
|
+
const wt = join26(repoDir, ".worktrees", `merge-${sanitize(current)}`);
|
|
56356
56578
|
await reclaim(repoDir, wt);
|
|
56357
56579
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
|
|
56358
56580
|
if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
|
|
@@ -56390,7 +56612,7 @@ async function replayCurrent(repoDir, source, cmd) {
|
|
|
56390
56612
|
const current = await out(repoDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
56391
56613
|
if (!current || current === "HEAD")
|
|
56392
56614
|
return { status: "error", code: 409, error: "HEAD detached \u2014 checkout sebuah branch dulu" };
|
|
56393
|
-
const wt =
|
|
56615
|
+
const wt = join26(repoDir, ".worktrees", `merge-${sanitize(current)}`);
|
|
56394
56616
|
await reclaim(repoDir, wt);
|
|
56395
56617
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
|
|
56396
56618
|
if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
|
|
@@ -56450,11 +56672,11 @@ import { execFile as execFile6 } from "node:child_process";
|
|
|
56450
56672
|
import { promisify as promisify5 } from "node:util";
|
|
56451
56673
|
import { mkdtemp, copyFile, rm as rm2 } from "node:fs/promises";
|
|
56452
56674
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
56453
|
-
import { join as
|
|
56675
|
+
import { join as join27, resolve as resolve15 } from "node:path";
|
|
56454
56676
|
async function withTempIndex(wt, fn) {
|
|
56455
56677
|
const idx = (await exec4("git", ["rev-parse", "--git-path", "index"], { cwd: wt, ...GIT3 })).stdout.trim();
|
|
56456
|
-
const dir2 = await mkdtemp(
|
|
56457
|
-
const tmp =
|
|
56678
|
+
const dir2 = await mkdtemp(join27(tmpdir5(), "hanoman-idx-"));
|
|
56679
|
+
const tmp = join27(dir2, "index");
|
|
56458
56680
|
await copyFile(resolve15(wt, idx), tmp);
|
|
56459
56681
|
const env = { ...process.env, GIT_INDEX_FILE: tmp };
|
|
56460
56682
|
try {
|
|
@@ -56600,7 +56822,7 @@ var init_spec_review2 = __esm({
|
|
|
56600
56822
|
GIT3 = { maxBuffer: 1 << 24 };
|
|
56601
56823
|
MAX = 256 * 1024;
|
|
56602
56824
|
emptySpecReview = (reason) => ({ base: "", files: [], changed: [], empty: true, reason });
|
|
56603
|
-
worktreeDir = (repoDir, specId) =>
|
|
56825
|
+
worktreeDir = (repoDir, specId) => join27(repoDir, ".worktrees", specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
|
|
56604
56826
|
splitZ = (s2) => s2.split("\0").filter(Boolean);
|
|
56605
56827
|
EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
56606
56828
|
}
|
|
@@ -56908,7 +57130,7 @@ var init_client = __esm({
|
|
|
56908
57130
|
});
|
|
56909
57131
|
|
|
56910
57132
|
// src/services/telegram/protocol.ts
|
|
56911
|
-
import { createHash as
|
|
57133
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
56912
57134
|
function normalizeTelegramCommand(text2) {
|
|
56913
57135
|
const clean3 = text2.trim();
|
|
56914
57136
|
const match = clean3.match(/^\/([a-z0-9_]+)(?:@[a-z0-9_]+)?(?:\s+([\s\S]*))?$/i);
|
|
@@ -56997,7 +57219,7 @@ function parseTelegramUpdate(update2, allowlist, maxText = 12e3) {
|
|
|
56997
57219
|
};
|
|
56998
57220
|
}
|
|
56999
57221
|
function inboundDigest(input) {
|
|
57000
|
-
return
|
|
57222
|
+
return createHash11("sha256").update(`${input.updateId}\0${input.chatId}\0${input.userId}\0${input.kind}\0${input.text}`, "utf8").digest("hex");
|
|
57001
57223
|
}
|
|
57002
57224
|
function sanitizeTelegramOutput(text2, exactSecrets = []) {
|
|
57003
57225
|
let clean3 = text2.replace(ANSI, "").replace(CONTROLS, "");
|
|
@@ -57300,7 +57522,7 @@ var init_notifications2 = __esm({
|
|
|
57300
57522
|
});
|
|
57301
57523
|
|
|
57302
57524
|
// src/services/codex-trust.ts
|
|
57303
|
-
import { appendFileSync as appendFileSync3, mkdirSync as
|
|
57525
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync14, readFileSync as readFileSync16, realpathSync as realpathSync9 } from "node:fs";
|
|
57304
57526
|
import { homedir as homedir5 } from "node:os";
|
|
57305
57527
|
function ensureCodexTrust(repoDir, home3) {
|
|
57306
57528
|
const path = codexConfigPath(home3);
|
|
@@ -57313,11 +57535,11 @@ function ensureCodexTrust(repoDir, home3) {
|
|
|
57313
57535
|
try {
|
|
57314
57536
|
let existing = "";
|
|
57315
57537
|
try {
|
|
57316
|
-
existing =
|
|
57538
|
+
existing = readFileSync16(path, "utf8");
|
|
57317
57539
|
} catch {
|
|
57318
57540
|
}
|
|
57319
57541
|
if (existing.includes(header2)) return;
|
|
57320
|
-
|
|
57542
|
+
mkdirSync14(codexHome(home3), { recursive: true });
|
|
57321
57543
|
const lead = existing === "" || existing.endsWith("\n") ? "" : "\n";
|
|
57322
57544
|
appendFileSync3(path, `${lead}
|
|
57323
57545
|
${header2}
|
|
@@ -57457,8 +57679,8 @@ var init_device_sockets = __esm({
|
|
|
57457
57679
|
|
|
57458
57680
|
// src/services/exit-guard.ts
|
|
57459
57681
|
import { spawn as spawn3 } from "node:child_process";
|
|
57460
|
-
import { writeFileSync as
|
|
57461
|
-
import { join as
|
|
57682
|
+
import { writeFileSync as writeFileSync16 } from "node:fs";
|
|
57683
|
+
import { join as join31 } from "node:path";
|
|
57462
57684
|
function watchdogScript(pid, graceMs) {
|
|
57463
57685
|
const secs = Math.max(1, Math.ceil(graceMs / 1e3));
|
|
57464
57686
|
return `s=$(ps -o lstart= -p ${pid}); sleep ${secs}; [ -n "$s" ] && [ "$(ps -o lstart= -p ${pid})" = "$s" ] && kill -9 ${pid}`;
|
|
@@ -57474,8 +57696,8 @@ function launchWatchdog(script2, env = process.env) {
|
|
|
57474
57696
|
function exitGuarded(code, d = {}) {
|
|
57475
57697
|
const pid = d.pid ?? process.pid;
|
|
57476
57698
|
try {
|
|
57477
|
-
const write2 = d.writeIntent ?? ((p3, t) =>
|
|
57478
|
-
write2(
|
|
57699
|
+
const write2 = d.writeIntent ?? ((p3, t) => writeFileSync16(p3, t, { mode: 384 }));
|
|
57700
|
+
write2(join31(d.home ?? resolveHome(), EXIT_INTENT_FILE), formatExitIntent(pid, code));
|
|
57479
57701
|
} catch {
|
|
57480
57702
|
}
|
|
57481
57703
|
try {
|
|
@@ -57495,9 +57717,9 @@ var init_exit_guard = __esm({
|
|
|
57495
57717
|
});
|
|
57496
57718
|
|
|
57497
57719
|
// src/services/update.ts
|
|
57498
|
-
import { readFileSync as
|
|
57499
|
-
import { resolve as resolve20, dirname as
|
|
57500
|
-
import { fileURLToPath as
|
|
57720
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
57721
|
+
import { resolve as resolve20, dirname as dirname14 } from "node:path";
|
|
57722
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
57501
57723
|
function composeUpdate(x) {
|
|
57502
57724
|
const available = x.registryStatus === "ok" && x.latestVersion != null && compareSemver(x.latestVersion, x.currentVersion) > 0;
|
|
57503
57725
|
return {
|
|
@@ -57513,10 +57735,10 @@ function composeUpdate(x) {
|
|
|
57513
57735
|
};
|
|
57514
57736
|
}
|
|
57515
57737
|
function runningVersion() {
|
|
57516
|
-
const here =
|
|
57738
|
+
const here = dirname14(fileURLToPath4(import.meta.url));
|
|
57517
57739
|
for (const p3 of [resolve20(here, "build-info.json"), resolve20(here, "../package.json")]) {
|
|
57518
57740
|
try {
|
|
57519
|
-
const v = JSON.parse(
|
|
57741
|
+
const v = JSON.parse(readFileSync17(p3, "utf8")).version;
|
|
57520
57742
|
if (typeof v === "string" && v) return v;
|
|
57521
57743
|
} catch {
|
|
57522
57744
|
}
|
|
@@ -57952,7 +58174,7 @@ var init_remote_session = __esm({
|
|
|
57952
58174
|
|
|
57953
58175
|
// src/services/spec-attachment-dir.ts
|
|
57954
58176
|
import { mkdir as mkdir6, readdir as readdir5, readFile as readFile6, rm as rm4, writeFile as writeFile5 } from "node:fs/promises";
|
|
57955
|
-
import { join as
|
|
58177
|
+
import { join as join32 } from "node:path";
|
|
57956
58178
|
function uniqueName(taken, filename) {
|
|
57957
58179
|
if (!taken.has(filename)) {
|
|
57958
58180
|
taken.add(filename);
|
|
@@ -58000,19 +58222,19 @@ async function syncSpecAttachmentsDir(specId, projectId) {
|
|
|
58000
58222
|
for (const a of rows) {
|
|
58001
58223
|
let bytes;
|
|
58002
58224
|
try {
|
|
58003
|
-
bytes = await readFile6(
|
|
58225
|
+
bytes = await readFile6(join32(uploadDir(), a.storageKey));
|
|
58004
58226
|
} catch {
|
|
58005
58227
|
continue;
|
|
58006
58228
|
}
|
|
58007
58229
|
const filename = uniqueName(taken, a.filename);
|
|
58008
|
-
const path =
|
|
58230
|
+
const path = join32(dir2, filename);
|
|
58009
58231
|
await writeFile5(path, bytes, { mode: 384 });
|
|
58010
58232
|
items.push({ filename, mimeType: a.mimeType, size: a.size, path });
|
|
58011
58233
|
}
|
|
58012
|
-
await writeFile5(
|
|
58234
|
+
await writeFile5(join32(dir2, INDEX), renderIndex(specId, items), { mode: 384 });
|
|
58013
58235
|
const keep = /* @__PURE__ */ new Set([INDEX, ...items.map((a) => a.filename)]);
|
|
58014
58236
|
for (const name2 of await readdir5(dir2)) {
|
|
58015
|
-
if (!keep.has(name2)) await rm4(
|
|
58237
|
+
if (!keep.has(name2)) await rm4(join32(dir2, name2), { recursive: true, force: true }).catch(() => {
|
|
58016
58238
|
});
|
|
58017
58239
|
}
|
|
58018
58240
|
return items;
|
|
@@ -58031,7 +58253,7 @@ var init_spec_attachment_dir = __esm({
|
|
|
58031
58253
|
init_local_binding();
|
|
58032
58254
|
init_session_id();
|
|
58033
58255
|
init_uploads();
|
|
58034
|
-
specAttachmentsDir = (repoDir, sessionId2) =>
|
|
58256
|
+
specAttachmentsDir = (repoDir, sessionId2) => join32(repoDir, ".worktrees", ".attachments", sessionId2);
|
|
58035
58257
|
INDEX = "INDEX.md";
|
|
58036
58258
|
humanSize = (n2) => n2 >= 1024 * 1024 ? `${(n2 / 1024 / 1024).toFixed(1)} MB` : n2 >= 1024 ? `${Math.round(n2 / 1024)} KB` : `${n2} B`;
|
|
58037
58259
|
}
|
|
@@ -58426,7 +58648,7 @@ var init_runtime_defaults2 = __esm({
|
|
|
58426
58648
|
});
|
|
58427
58649
|
|
|
58428
58650
|
// src/services/agent-token.ts
|
|
58429
|
-
import { randomBytes as randomBytes2, createHash as
|
|
58651
|
+
import { randomBytes as randomBytes2, createHash as createHash13, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
58430
58652
|
function toAgentTokenView(t) {
|
|
58431
58653
|
return {
|
|
58432
58654
|
id: t.id,
|
|
@@ -58513,7 +58735,7 @@ var init_agent_token = __esm({
|
|
|
58513
58735
|
"src/services/agent-token.ts"() {
|
|
58514
58736
|
"use strict";
|
|
58515
58737
|
init_db();
|
|
58516
|
-
hash2 = (token) =>
|
|
58738
|
+
hash2 = (token) => createHash13("sha256").update(token).digest("hex");
|
|
58517
58739
|
AGENT_CREATOR_PREFIX = "agent:";
|
|
58518
58740
|
}
|
|
58519
58741
|
});
|
|
@@ -58842,11 +59064,11 @@ var init_project_config = __esm({
|
|
|
58842
59064
|
|
|
58843
59065
|
// src/services/limits.ts
|
|
58844
59066
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
58845
|
-
import { readFileSync as
|
|
59067
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
58846
59068
|
import { homedir as homedir6, userInfo } from "node:os";
|
|
58847
|
-
import { join as
|
|
59069
|
+
import { join as join34 } from "node:path";
|
|
58848
59070
|
function credsFile() {
|
|
58849
|
-
return
|
|
59071
|
+
return join34(effectiveStr("CLAUDE_CONFIG_DIR") ?? join34(homedir6(), ".claude"), ".credentials.json");
|
|
58850
59072
|
}
|
|
58851
59073
|
function keychainAccessToken(run6 = securityRun, account = osAccount()) {
|
|
58852
59074
|
const base3 = ["find-generic-password", "-s", "Claude Code-credentials"];
|
|
@@ -58865,7 +59087,7 @@ function readAccessToken() {
|
|
|
58865
59087
|
if (tok) return tok;
|
|
58866
59088
|
}
|
|
58867
59089
|
try {
|
|
58868
|
-
return JSON.parse(
|
|
59090
|
+
return JSON.parse(readFileSync18(credsFile(), "utf8"))?.claudeAiOauth?.accessToken ?? null;
|
|
58869
59091
|
} catch {
|
|
58870
59092
|
return null;
|
|
58871
59093
|
}
|
|
@@ -58949,7 +59171,7 @@ var init_limits = __esm({
|
|
|
58949
59171
|
// src/services/codex-limits.ts
|
|
58950
59172
|
import { open as open3, readdir as readdir6, stat as stat6 } from "node:fs/promises";
|
|
58951
59173
|
import { homedir as homedir7 } from "node:os";
|
|
58952
|
-
import { join as
|
|
59174
|
+
import { join as join35 } from "node:path";
|
|
58953
59175
|
function windowLabel(minutes) {
|
|
58954
59176
|
if (minutes === 300) return "Sesi 5 jam";
|
|
58955
59177
|
if (minutes === 10080) return "Mingguan";
|
|
@@ -58980,7 +59202,7 @@ async function recentRollouts(dir2) {
|
|
|
58980
59202
|
return [];
|
|
58981
59203
|
}
|
|
58982
59204
|
const stamped = await Promise.all(entries4.map(async (rel) => {
|
|
58983
|
-
const full =
|
|
59205
|
+
const full = join35(dir2, rel);
|
|
58984
59206
|
try {
|
|
58985
59207
|
return { full, mtime: (await stat6(full)).mtimeMs };
|
|
58986
59208
|
} catch {
|
|
@@ -59060,7 +59282,7 @@ var init_codex_limits = __esm({
|
|
|
59060
59282
|
MAX_FILES = 8;
|
|
59061
59283
|
cache2 = null;
|
|
59062
59284
|
freshUntil2 = 0;
|
|
59063
|
-
codexSessionsDir = () =>
|
|
59285
|
+
codexSessionsDir = () => join35(process.env.CODEX_HOME ?? join35(homedir7(), ".codex"), "sessions");
|
|
59064
59286
|
UNAVAILABLE = { status: "unavailable", windows: [], fetchedAt: null, plan: null };
|
|
59065
59287
|
severityOf = (pct3) => pct3 >= 90 ? "critical" : pct3 >= 70 ? "warning" : "normal";
|
|
59066
59288
|
}
|
|
@@ -59068,9 +59290,9 @@ var init_codex_limits = __esm({
|
|
|
59068
59290
|
|
|
59069
59291
|
// src/services/antigravity-limits.ts
|
|
59070
59292
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
59071
|
-
import { readFileSync as
|
|
59293
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
59072
59294
|
import { homedir as homedir8 } from "node:os";
|
|
59073
|
-
import { join as
|
|
59295
|
+
import { join as join36 } from "node:path";
|
|
59074
59296
|
function readAgyToken(run6 = securityRun2) {
|
|
59075
59297
|
try {
|
|
59076
59298
|
let raw = run6(["find-generic-password", "-s", "gemini", "-a", "antigravity", "-w"]).trim();
|
|
@@ -59086,7 +59308,7 @@ function readAgyToken(run6 = securityRun2) {
|
|
|
59086
59308
|
}
|
|
59087
59309
|
function defaultProject() {
|
|
59088
59310
|
try {
|
|
59089
|
-
return
|
|
59311
|
+
return readFileSync19(join36(agyHome(), "cache", "default_project_id.txt"), "utf8").trim() || null;
|
|
59090
59312
|
} catch {
|
|
59091
59313
|
return null;
|
|
59092
59314
|
}
|
|
@@ -59177,7 +59399,7 @@ var init_antigravity_limits = __esm({
|
|
|
59177
59399
|
freshUntil3 = 0;
|
|
59178
59400
|
UNAVAILABLE2 = { status: "unavailable", windows: [], fetchedAt: null };
|
|
59179
59401
|
securityRun2 = (args) => execFileSync7("security", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
59180
|
-
agyHome = () =>
|
|
59402
|
+
agyHome = () => join36(homedir8(), ".gemini", "antigravity-cli");
|
|
59181
59403
|
severityOf2 = (pct3) => pct3 >= 90 ? "critical" : pct3 >= 70 ? "warning" : "normal";
|
|
59182
59404
|
prodDeps = {
|
|
59183
59405
|
platform: process.platform,
|
|
@@ -59276,7 +59498,7 @@ var init_budget = __esm({
|
|
|
59276
59498
|
});
|
|
59277
59499
|
|
|
59278
59500
|
// src/services/session-dialog.ts
|
|
59279
|
-
import { createHash as
|
|
59501
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
59280
59502
|
function readSessionDialog(io) {
|
|
59281
59503
|
const text2 = io.capture();
|
|
59282
59504
|
const s2 = answerable(text2);
|
|
@@ -59332,7 +59554,7 @@ var init_session_dialog2 = __esm({
|
|
|
59332
59554
|
init_tui_dialog();
|
|
59333
59555
|
init_pty();
|
|
59334
59556
|
DIALOG_CHUNK_MS = 50;
|
|
59335
|
-
screenHashOf = (paneText2) =>
|
|
59557
|
+
screenHashOf = (paneText2) => createHash14("sha256").update(dialogKey(paneText2)).digest("hex").slice(0, 16);
|
|
59336
59558
|
answerable = (paneText2) => {
|
|
59337
59559
|
const s2 = readDialogScreen(paneText2);
|
|
59338
59560
|
if (s2?.kind !== "question") return null;
|
|
@@ -59635,8 +59857,8 @@ var init_prompt2 = __esm({
|
|
|
59635
59857
|
});
|
|
59636
59858
|
|
|
59637
59859
|
// src/services/lead/verdict.ts
|
|
59638
|
-
import { existsSync as
|
|
59639
|
-
import { isAbsolute as isAbsolute11, resolve as resolvePath2, relative as
|
|
59860
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
59861
|
+
import { isAbsolute as isAbsolute11, resolve as resolvePath2, relative as relative7 } from "node:path";
|
|
59640
59862
|
function extractJsonBlock(raw) {
|
|
59641
59863
|
const re = /```json\s*\n([\s\S]*?)```/g;
|
|
59642
59864
|
let last2 = null;
|
|
@@ -59669,9 +59891,9 @@ function keepExistingRefs(refs2, repoDir) {
|
|
|
59669
59891
|
if (!repoDir) continue;
|
|
59670
59892
|
if (isAbsolute11(ref)) continue;
|
|
59671
59893
|
const full = resolvePath2(repoDir, ref);
|
|
59672
|
-
const rel =
|
|
59894
|
+
const rel = relative7(repoDir, full);
|
|
59673
59895
|
if (rel.startsWith("..")) continue;
|
|
59674
|
-
if (
|
|
59896
|
+
if (existsSync18(full)) out4.push(ref);
|
|
59675
59897
|
}
|
|
59676
59898
|
return [...new Set(out4)];
|
|
59677
59899
|
}
|
|
@@ -60861,9 +61083,9 @@ var init_queue = __esm({
|
|
|
60861
61083
|
});
|
|
60862
61084
|
|
|
60863
61085
|
// src/services/git-auth.ts
|
|
60864
|
-
import { chmodSync as
|
|
61086
|
+
import { chmodSync as chmodSync9, mkdirSync as mkdirSync15, writeFileSync as writeFileSync17 } from "node:fs";
|
|
60865
61087
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
60866
|
-
import { dirname as
|
|
61088
|
+
import { dirname as dirname15 } from "node:path";
|
|
60867
61089
|
function httpsRemote(remote) {
|
|
60868
61090
|
const scp = /^[\w.-]+@github\.com:(.+)$/.exec(remote);
|
|
60869
61091
|
if (scp) return `https://github.com/${scp[1]}`;
|
|
@@ -60873,9 +61095,9 @@ function httpsRemote(remote) {
|
|
|
60873
61095
|
}
|
|
60874
61096
|
function gitAuthEnv(token) {
|
|
60875
61097
|
const path = gitAskpassPath();
|
|
60876
|
-
|
|
60877
|
-
|
|
60878
|
-
|
|
61098
|
+
mkdirSync15(dirname15(path), { recursive: true, mode: 448 });
|
|
61099
|
+
writeFileSync17(path, ASKPASS_TOKEN, { mode: 448 });
|
|
61100
|
+
chmodSync9(path, 448);
|
|
60879
61101
|
return { GIT_ASKPASS: path, GIT_TERMINAL_PROMPT: "0", HANOMAN_GIT_TOKEN: token };
|
|
60880
61102
|
}
|
|
60881
61103
|
var gitAskpassPath, ASKPASS_TOKEN, isGithubHttps;
|
|
@@ -63875,7 +64097,7 @@ var require_websocket2 = __commonJS({
|
|
|
63875
64097
|
var http = __require("http");
|
|
63876
64098
|
var net = __require("net");
|
|
63877
64099
|
var tls = __require("tls");
|
|
63878
|
-
var { randomBytes: randomBytes12, createHash:
|
|
64100
|
+
var { randomBytes: randomBytes12, createHash: createHash24 } = __require("crypto");
|
|
63879
64101
|
var { Duplex, Readable } = __require("stream");
|
|
63880
64102
|
var { URL: URL2 } = __require("url");
|
|
63881
64103
|
var PerMessageDeflate2 = require_permessage_deflate2();
|
|
@@ -64543,7 +64765,7 @@ var require_websocket2 = __commonJS({
|
|
|
64543
64765
|
abortHandshake(websocket2, socket2, "Invalid Upgrade header");
|
|
64544
64766
|
return;
|
|
64545
64767
|
}
|
|
64546
|
-
const digest3 =
|
|
64768
|
+
const digest3 = createHash24("sha1").update(key + GUID).digest("base64");
|
|
64547
64769
|
if (res.headers["sec-websocket-accept"] !== digest3) {
|
|
64548
64770
|
abortHandshake(websocket2, socket2, "Invalid Sec-WebSocket-Accept header");
|
|
64549
64771
|
return;
|
|
@@ -64912,7 +65134,7 @@ var require_websocket_server = __commonJS({
|
|
|
64912
65134
|
var EventEmitter = __require("events");
|
|
64913
65135
|
var http = __require("http");
|
|
64914
65136
|
var { Duplex } = __require("stream");
|
|
64915
|
-
var { createHash:
|
|
65137
|
+
var { createHash: createHash24 } = __require("crypto");
|
|
64916
65138
|
var extension2 = require_extension();
|
|
64917
65139
|
var PerMessageDeflate2 = require_permessage_deflate2();
|
|
64918
65140
|
var subprotocol2 = require_subprotocol();
|
|
@@ -65219,7 +65441,7 @@ var require_websocket_server = __commonJS({
|
|
|
65219
65441
|
);
|
|
65220
65442
|
}
|
|
65221
65443
|
if (this._state > RUNNING) return abortHandshake(socket2, 503);
|
|
65222
|
-
const digest3 =
|
|
65444
|
+
const digest3 = createHash24("sha1").update(key + GUID).digest("base64");
|
|
65223
65445
|
const headers = [
|
|
65224
65446
|
"HTTP/1.1 101 Switching Protocols",
|
|
65225
65447
|
"Upgrade: websocket",
|
|
@@ -65515,16 +65737,16 @@ var init_client2 = __esm({
|
|
|
65515
65737
|
});
|
|
65516
65738
|
|
|
65517
65739
|
// src/services/logs/spool.ts
|
|
65518
|
-
import { appendFile, mkdir as mkdir7, readFile as
|
|
65519
|
-
import { join as
|
|
65740
|
+
import { appendFile, mkdir as mkdir7, readFile as readFile8, readdir as readdir7, rm as rm5, stat as stat7 } from "node:fs/promises";
|
|
65741
|
+
import { join as join37 } from "node:path";
|
|
65520
65742
|
function spoolDir(lane) {
|
|
65521
|
-
return
|
|
65743
|
+
return join37(resolveDataDirs().home, "log-spool", lane);
|
|
65522
65744
|
}
|
|
65523
65745
|
async function currentSegment(lane) {
|
|
65524
65746
|
const dir2 = spoolDir(lane);
|
|
65525
65747
|
await mkdir7(dir2, { recursive: true, mode: 448 });
|
|
65526
65748
|
if (currentFile && currentBytes < LOG_SPOOL_SEGMENT_BYTES) return currentFile;
|
|
65527
|
-
currentFile =
|
|
65749
|
+
currentFile = join37(dir2, `${Date.now()}-${++segCounter}.ndjson`);
|
|
65528
65750
|
currentBytes = 0;
|
|
65529
65751
|
return currentFile;
|
|
65530
65752
|
}
|
|
@@ -65546,8 +65768,8 @@ async function readSpoolSegments(lane) {
|
|
|
65546
65768
|
const files2 = names.filter((n2) => n2.endsWith(".ndjson")).sort();
|
|
65547
65769
|
const out4 = [];
|
|
65548
65770
|
for (const name2 of files2) {
|
|
65549
|
-
const file =
|
|
65550
|
-
const raw = await
|
|
65771
|
+
const file = join37(dir2, name2);
|
|
65772
|
+
const raw = await readFile8(file, "utf8").catch(() => "");
|
|
65551
65773
|
const entries4 = raw.split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
65552
65774
|
out4.push({ file, entries: entries4 });
|
|
65553
65775
|
}
|
|
@@ -66211,8 +66433,8 @@ var init_git_token = __esm({
|
|
|
66211
66433
|
|
|
66212
66434
|
// src/services/project-checkout.ts
|
|
66213
66435
|
import { execFile as execFile14, spawnSync as spawnSync4 } from "node:child_process";
|
|
66214
|
-
import { existsSync as
|
|
66215
|
-
import { join as
|
|
66436
|
+
import { existsSync as existsSync19, readdirSync as readdirSync10, rmSync as rmSync10 } from "node:fs";
|
|
66437
|
+
import { join as join38 } from "node:path";
|
|
66216
66438
|
import { promisify as promisify13 } from "node:util";
|
|
66217
66439
|
async function runGit(args, o) {
|
|
66218
66440
|
const full = o.token ? ["-c", "credential.helper=", ...args] : args;
|
|
@@ -66234,8 +66456,8 @@ async function doClone(projectId, opts2) {
|
|
|
66234
66456
|
if (!project) throw new CheckoutError(`project "${projectId}" tidak ada`, "not-found");
|
|
66235
66457
|
if (!project.gitRemote) throw new CheckoutError(`project "${projectId}" tidak punya gitRemote untuk di-clone`, "no-remote");
|
|
66236
66458
|
const remote = project.gitRemote;
|
|
66237
|
-
const dest = opts2.dir ??
|
|
66238
|
-
if (
|
|
66459
|
+
const dest = opts2.dir ?? join38(checkoutRoot(), slug(project.id));
|
|
66460
|
+
if (existsSync19(dest) && !isEmptyDir(dest)) {
|
|
66239
66461
|
const origin = originOf2(dest);
|
|
66240
66462
|
if (origin && sameRemote(origin, remote)) {
|
|
66241
66463
|
await setBinding(projectId, dest);
|
|
@@ -66251,7 +66473,7 @@ async function doClone(projectId, opts2) {
|
|
|
66251
66473
|
timeout: CLONE_TIMEOUT_MS
|
|
66252
66474
|
});
|
|
66253
66475
|
if (!res.ok) {
|
|
66254
|
-
|
|
66476
|
+
rmSync10(dest, { recursive: true, force: true });
|
|
66255
66477
|
const auth = AUTH_RE.test(res.stderr);
|
|
66256
66478
|
throw new CheckoutError(
|
|
66257
66479
|
auth ? useAuth ? `token GitHub ditolak atau tanpa akses ke ${remote}` : `akses ke ${remote} ditolak: isi GITHUB_TOKEN di Settings atau hubungkan instance ini ke hub` : `git clone ${remote} gagal`,
|
|
@@ -66298,7 +66520,7 @@ var init_project_checkout = __esm({
|
|
|
66298
66520
|
CLONE_TIMEOUT_MS = 10 * 6e4;
|
|
66299
66521
|
FETCH_TIMEOUT_MS = 2 * 6e4;
|
|
66300
66522
|
AUTH_RE = /Authentication failed|could not read (Username|Password)|Invalid username or password|HTTP 40[13]|returned error: 40[13]|Repository not found|Permission denied \(publickey\)/i;
|
|
66301
|
-
checkoutRoot = () =>
|
|
66523
|
+
checkoutRoot = () => join38(resolveHome(), "projects");
|
|
66302
66524
|
slug = (id2) => id2.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "project";
|
|
66303
66525
|
sameRemote = (a, b) => {
|
|
66304
66526
|
const norm5 = (u) => httpsRemote(u).replace(/\.git$/, "").replace(/\/+$/, "").toLowerCase();
|
|
@@ -66307,7 +66529,7 @@ var init_project_checkout = __esm({
|
|
|
66307
66529
|
redact = (text2, token) => token ? text2.split(token).join("***") : text2;
|
|
66308
66530
|
isEmptyDir = (dir2) => {
|
|
66309
66531
|
try {
|
|
66310
|
-
return
|
|
66532
|
+
return readdirSync10(dir2).length === 0;
|
|
66311
66533
|
} catch {
|
|
66312
66534
|
return false;
|
|
66313
66535
|
}
|
|
@@ -67076,7 +67298,7 @@ var init_approvals = __esm({
|
|
|
67076
67298
|
});
|
|
67077
67299
|
|
|
67078
67300
|
// src/services/telegram/gateway.ts
|
|
67079
|
-
import { createHash as
|
|
67301
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
67080
67302
|
var digestRaw, TelegramGateway;
|
|
67081
67303
|
var init_gateway = __esm({
|
|
67082
67304
|
"src/services/telegram/gateway.ts"() {
|
|
@@ -67085,7 +67307,7 @@ var init_gateway = __esm({
|
|
|
67085
67307
|
init_protocol();
|
|
67086
67308
|
init_runtime();
|
|
67087
67309
|
init_typing();
|
|
67088
|
-
digestRaw = (update2) =>
|
|
67310
|
+
digestRaw = (update2) => createHash15("sha256").update(JSON.stringify(update2)).digest("hex");
|
|
67089
67311
|
TelegramGateway = class {
|
|
67090
67312
|
constructor(deps) {
|
|
67091
67313
|
this.deps = deps;
|
|
@@ -67409,14 +67631,14 @@ var init_engine_command = __esm({
|
|
|
67409
67631
|
});
|
|
67410
67632
|
|
|
67411
67633
|
// src/services/telegram/session.ts
|
|
67412
|
-
import { createHash as
|
|
67634
|
+
import { createHash as createHash16 } from "node:crypto";
|
|
67413
67635
|
var chatHash, telegramOperatorSessionId, formatTelegramTurn, TelegramSessionCoordinator;
|
|
67414
67636
|
var init_session = __esm({
|
|
67415
67637
|
"src/services/telegram/session.ts"() {
|
|
67416
67638
|
"use strict";
|
|
67417
67639
|
init_src2();
|
|
67418
67640
|
init_engine_command();
|
|
67419
|
-
chatHash = (chatId) =>
|
|
67641
|
+
chatHash = (chatId) => createHash16("sha256").update(chatId).digest("hex").slice(0, 16);
|
|
67420
67642
|
telegramOperatorSessionId = (chatId) => `telegram-${chatHash(chatId)}`;
|
|
67421
67643
|
formatTelegramTurn = (input) => `[Telegram update ${input.updateId} \xB7 chat ${input.chatId} \xB7 kind ${input.kind}]
|
|
67422
67644
|
${input.text}`;
|
|
@@ -67813,7 +68035,7 @@ __export(bootstrap_exports, {
|
|
|
67813
68035
|
reloadTelegramGateway: () => reloadTelegramGateway,
|
|
67814
68036
|
telegramSessionDeps: () => telegramSessionDeps
|
|
67815
68037
|
});
|
|
67816
|
-
import { mkdirSync as
|
|
68038
|
+
import { mkdirSync as mkdirSync16 } from "node:fs";
|
|
67817
68039
|
function parseTelegramAllowedUserIds(raw) {
|
|
67818
68040
|
const ids = raw.split(/[\s,]+/).filter(Boolean);
|
|
67819
68041
|
if (!ids.length) throw new Error("Telegram allowlist wajib berisi numeric user id");
|
|
@@ -67838,7 +68060,7 @@ function telegramSessionDeps(input) {
|
|
|
67838
68060
|
home: resolveHome(),
|
|
67839
68061
|
apiBase: input.apiBase,
|
|
67840
68062
|
agentToken: input.agentToken,
|
|
67841
|
-
ensureDir: (path) =>
|
|
68063
|
+
ensureDir: (path) => mkdirSync16(path, { recursive: true })
|
|
67842
68064
|
};
|
|
67843
68065
|
}
|
|
67844
68066
|
async function productionFactory(input) {
|
|
@@ -68060,17 +68282,17 @@ var init_codex_version = __esm({
|
|
|
68060
68282
|
});
|
|
68061
68283
|
|
|
68062
68284
|
// src/services/agent-tool-catalog.ts
|
|
68063
|
-
import { readFileSync as
|
|
68285
|
+
import { readFileSync as readFileSync27 } from "node:fs";
|
|
68064
68286
|
import { homedir as homedir13 } from "node:os";
|
|
68065
|
-
import { join as
|
|
68287
|
+
import { join as join51 } from "node:path";
|
|
68066
68288
|
function mcpServerNames(repoDir) {
|
|
68067
68289
|
const names = [];
|
|
68068
|
-
const claudeJson = readJson2(
|
|
68290
|
+
const claudeJson = readJson2(join51(home(), ".claude.json"));
|
|
68069
68291
|
names.push(...serversOf(claudeJson));
|
|
68070
68292
|
if (repoDir) {
|
|
68071
68293
|
const projects = claudeJson?.projects;
|
|
68072
68294
|
if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
|
|
68073
|
-
names.push(...serversOf(readJson2(
|
|
68295
|
+
names.push(...serversOf(readJson2(join51(repoDir, ".mcp.json"))));
|
|
68074
68296
|
}
|
|
68075
68297
|
names.push(...codexServers());
|
|
68076
68298
|
return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
@@ -68086,7 +68308,7 @@ var init_agent_tool_catalog = __esm({
|
|
|
68086
68308
|
home = () => process.env.HOME || homedir13();
|
|
68087
68309
|
readJson2 = (path) => {
|
|
68088
68310
|
try {
|
|
68089
|
-
return JSON.parse(
|
|
68311
|
+
return JSON.parse(readFileSync27(path, "utf8"));
|
|
68090
68312
|
} catch {
|
|
68091
68313
|
return null;
|
|
68092
68314
|
}
|
|
@@ -68099,7 +68321,7 @@ var init_agent_tool_catalog = __esm({
|
|
|
68099
68321
|
codexServers = () => {
|
|
68100
68322
|
let text2;
|
|
68101
68323
|
try {
|
|
68102
|
-
text2 =
|
|
68324
|
+
text2 = readFileSync27(join51(home(), ".codex", "config.toml"), "utf8");
|
|
68103
68325
|
} catch {
|
|
68104
68326
|
return [];
|
|
68105
68327
|
}
|
|
@@ -68115,7 +68337,7 @@ var init_agent_tool_catalog = __esm({
|
|
|
68115
68337
|
});
|
|
68116
68338
|
|
|
68117
68339
|
// src/services/builtin-agents.ts
|
|
68118
|
-
import { createHash as
|
|
68340
|
+
import { createHash as createHash20 } from "node:crypto";
|
|
68119
68341
|
async function seedBuiltinAgents() {
|
|
68120
68342
|
try {
|
|
68121
68343
|
const setting = await getSetting();
|
|
@@ -68217,7 +68439,7 @@ var init_builtin_agents2 = __esm({
|
|
|
68217
68439
|
init_settings3();
|
|
68218
68440
|
init_tombstone();
|
|
68219
68441
|
init_sync_notify();
|
|
68220
|
-
digest2 = (parts) =>
|
|
68442
|
+
digest2 = (parts) => createHash20("sha256").update(parts.join(" ")).digest("hex").slice(0, 16);
|
|
68221
68443
|
legacyFingerprint = (name2, description, instructions, tools) => digest2([name2, description, instructions, [...tools].join(",")]);
|
|
68222
68444
|
mentionPart = (v) => {
|
|
68223
68445
|
const m = mentionsOf(v);
|
|
@@ -71717,9 +71939,9 @@ import Fastify from "fastify";
|
|
|
71717
71939
|
import fastifyStatic from "@fastify/static";
|
|
71718
71940
|
import websocket from "@fastify/websocket";
|
|
71719
71941
|
import cookie from "@fastify/cookie";
|
|
71720
|
-
import { fileURLToPath as
|
|
71942
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
71721
71943
|
import { dirname as dirname19 } from "node:path";
|
|
71722
|
-
import { existsSync as
|
|
71944
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
71723
71945
|
|
|
71724
71946
|
// src/web-dir.ts
|
|
71725
71947
|
import { resolve as resolve11 } from "node:path";
|
|
@@ -71773,7 +71995,7 @@ async function agent_doc_default(app3, opts2) {
|
|
|
71773
71995
|
init_config2();
|
|
71774
71996
|
init_db();
|
|
71775
71997
|
init_uploads();
|
|
71776
|
-
import { createHash as
|
|
71998
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
71777
71999
|
|
|
71778
72000
|
// src/services/upload-pipeline.ts
|
|
71779
72001
|
init_file_type();
|
|
@@ -71783,7 +72005,7 @@ init_uploads();
|
|
|
71783
72005
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
71784
72006
|
import { spawn as spawn2 } from "node:child_process";
|
|
71785
72007
|
import { mkdir as mkdir3, rename, unlink as unlink3, writeFile as writeFile3 } from "node:fs/promises";
|
|
71786
|
-
import { basename as basename7, isAbsolute as isAbsolute9, join as
|
|
72008
|
+
import { basename as basename7, isAbsolute as isAbsolute9, join as join23 } from "node:path";
|
|
71787
72009
|
import sharp from "sharp";
|
|
71788
72010
|
var UPLOAD_LIMITS = {
|
|
71789
72011
|
fileBytes: 5 * 1024 * 1024,
|
|
@@ -71857,16 +72079,16 @@ function safeFilename2(input, extension2) {
|
|
|
71857
72079
|
}
|
|
71858
72080
|
async function commitToStorage(buffer, extension2, deps, beforePromote) {
|
|
71859
72081
|
const storageDir = deps.storageDir ?? uploadDir();
|
|
71860
|
-
const quarantineDir =
|
|
72082
|
+
const quarantineDir = join23(storageDir, ".quarantine");
|
|
71861
72083
|
await mkdir3(quarantineDir, { recursive: true, mode: 448 });
|
|
71862
72084
|
await mkdir3(storageDir, { recursive: true, mode: 448 });
|
|
71863
|
-
const quarantine =
|
|
72085
|
+
const quarantine = join23(quarantineDir, `${randomUUID6()}.upload`);
|
|
71864
72086
|
const storageKey = `${randomUUID6()}${extension2}`;
|
|
71865
72087
|
await writeFile3(quarantine, buffer, { mode: 384, flag: "wx" });
|
|
71866
72088
|
try {
|
|
71867
72089
|
await timeout((deps.scanner ?? scannerFromEnv)(quarantine), UPLOAD_LIMITS.scanMs, "UPLOAD_SCAN");
|
|
71868
72090
|
await beforePromote?.();
|
|
71869
|
-
await rename(quarantine,
|
|
72091
|
+
await rename(quarantine, join23(storageDir, storageKey));
|
|
71870
72092
|
} catch (error) {
|
|
71871
72093
|
await unlink3(quarantine).catch(() => {
|
|
71872
72094
|
});
|
|
@@ -72268,7 +72490,7 @@ async function addQaAttachments(report, owner, files2) {
|
|
|
72268
72490
|
continue;
|
|
72269
72491
|
}
|
|
72270
72492
|
try {
|
|
72271
|
-
const sha256 =
|
|
72493
|
+
const sha256 = createHash10("sha256").update(await readUpload(safe3.storageKey)).digest("hex");
|
|
72272
72494
|
const row = await prisma.qaAttachment.create({ data: {
|
|
72273
72495
|
reportId: report.id,
|
|
72274
72496
|
projectId: report.projectId,
|
|
@@ -72322,7 +72544,7 @@ init_src();
|
|
|
72322
72544
|
init_safe_repo_path();
|
|
72323
72545
|
import { execFile as execFile3 } from "node:child_process";
|
|
72324
72546
|
import { promisify as promisify2 } from "node:util";
|
|
72325
|
-
import { existsSync as
|
|
72547
|
+
import { existsSync as existsSync13, rmSync as rmSync8 } from "node:fs";
|
|
72326
72548
|
import { resolve as resolve14 } from "node:path";
|
|
72327
72549
|
var exec = promisify2(execFile3);
|
|
72328
72550
|
async function listRepoDocs(repoDir) {
|
|
@@ -72347,12 +72569,12 @@ function docsDirOf(repoDir) {
|
|
|
72347
72569
|
}
|
|
72348
72570
|
function resolveIndex(repoDir, docsDir) {
|
|
72349
72571
|
const rel = `${docsDir}/README.md`;
|
|
72350
|
-
return
|
|
72572
|
+
return existsSync13(resolve14(repoDir, rel)) ? rel : "";
|
|
72351
72573
|
}
|
|
72352
72574
|
var catOf = (rel) => rel.includes("/") ? rel.slice(0, rel.lastIndexOf("/")) : ".";
|
|
72353
72575
|
var nameOf = (rel) => rel.includes("/") ? rel.slice(rel.lastIndexOf("/") + 1) : rel;
|
|
72354
72576
|
async function scanRepoDocs(repoDir) {
|
|
72355
|
-
if (!repoDir || !
|
|
72577
|
+
if (!repoDir || !existsSync13(repoDir)) return { coverage: 0, tree: [] };
|
|
72356
72578
|
const files2 = await listRepoDocs(repoDir);
|
|
72357
72579
|
const docsDir = docsDirOf(repoDir);
|
|
72358
72580
|
const index = resolveIndex(repoDir, docsDir);
|
|
@@ -72400,8 +72622,8 @@ function writeDocFile(repoDir, rel, content) {
|
|
|
72400
72622
|
}
|
|
72401
72623
|
function deleteDocFile(repoDir, rel) {
|
|
72402
72624
|
const abs = docAbsPath(repoDir, rel);
|
|
72403
|
-
if (!
|
|
72404
|
-
|
|
72625
|
+
if (!existsSync13(abs)) return false;
|
|
72626
|
+
rmSync8(abs);
|
|
72405
72627
|
return true;
|
|
72406
72628
|
}
|
|
72407
72629
|
|
|
@@ -72719,7 +72941,7 @@ async function projects_default(app3) {
|
|
|
72719
72941
|
init_src();
|
|
72720
72942
|
init_src2();
|
|
72721
72943
|
init_integrate();
|
|
72722
|
-
import { existsSync as
|
|
72944
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
72723
72945
|
import { basename as basename9 } from "node:path";
|
|
72724
72946
|
|
|
72725
72947
|
// src/services/worktree-delete.ts
|
|
@@ -72738,7 +72960,7 @@ init_spec_review2();
|
|
|
72738
72960
|
init_safe_repo_path();
|
|
72739
72961
|
import { execFile as execFile7 } from "node:child_process";
|
|
72740
72962
|
import { promisify as promisify6 } from "node:util";
|
|
72741
|
-
import { existsSync as
|
|
72963
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
72742
72964
|
import { readdir as readdir3 } from "node:fs/promises";
|
|
72743
72965
|
import { resolve as resolve16 } from "node:path";
|
|
72744
72966
|
var exec5 = promisify6(execFile7);
|
|
@@ -72751,7 +72973,7 @@ function repoAbsPath(repoDir, rel, allowMissingFinal = false) {
|
|
|
72751
72973
|
return resolve16(repoDir, rel);
|
|
72752
72974
|
}
|
|
72753
72975
|
async function listRepoTree(repoDir, ref = "") {
|
|
72754
|
-
if (!repoDir || !
|
|
72976
|
+
if (!repoDir || !existsSync14(repoDir)) return [];
|
|
72755
72977
|
try {
|
|
72756
72978
|
const { stdout } = ref ? await exec5("git", ["ls-tree", "-r", "--name-only", "-z", ref], { cwd: repoDir, ...GIT4 }) : await exec5("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], { cwd: repoDir, ...GIT4 });
|
|
72757
72979
|
return [...new Set(splitZ2(stdout))].sort();
|
|
@@ -72761,7 +72983,7 @@ async function listRepoTree(repoDir, ref = "") {
|
|
|
72761
72983
|
}
|
|
72762
72984
|
async function listIgnoredEntries(repoDir) {
|
|
72763
72985
|
const empty = { files: [], dirs: [] };
|
|
72764
|
-
if (!repoDir || !
|
|
72986
|
+
if (!repoDir || !existsSync14(repoDir)) return empty;
|
|
72765
72987
|
try {
|
|
72766
72988
|
const { stdout } = await exec5(
|
|
72767
72989
|
"git",
|
|
@@ -72814,7 +73036,7 @@ async function currentBranch(repoDir) {
|
|
|
72814
73036
|
}
|
|
72815
73037
|
async function repoStatus(repoDir) {
|
|
72816
73038
|
const empty = { branch: "", ahead: 0, behind: 0, staged: [], unstaged: [], untracked: [], clean: true };
|
|
72817
|
-
if (!repoDir || !
|
|
73039
|
+
if (!repoDir || !existsSync14(repoDir)) return empty;
|
|
72818
73040
|
try {
|
|
72819
73041
|
const { stdout } = await exec5("git", ["status", "--porcelain=v1", "-z", "-b"], { cwd: repoDir, ...GIT4 });
|
|
72820
73042
|
const parts = stdout.split("\0");
|
|
@@ -72847,7 +73069,7 @@ async function repoStatus(repoDir) {
|
|
|
72847
73069
|
}
|
|
72848
73070
|
}
|
|
72849
73071
|
async function listStashes(repoDir) {
|
|
72850
|
-
if (!repoDir || !
|
|
73072
|
+
if (!repoDir || !existsSync14(repoDir)) return [];
|
|
72851
73073
|
try {
|
|
72852
73074
|
const { stdout } = await exec5("git", ["stash", "list", `--format=%gd${US}%s${US}%aI`], { cwd: repoDir, ...GIT4 });
|
|
72853
73075
|
return stdout.split("\n").filter(Boolean).map((line) => {
|
|
@@ -72859,7 +73081,7 @@ async function listStashes(repoDir) {
|
|
|
72859
73081
|
}
|
|
72860
73082
|
}
|
|
72861
73083
|
async function listGraph(repoDir, limit = 200, opts2 = {}) {
|
|
72862
|
-
if (!repoDir || !
|
|
73084
|
+
if (!repoDir || !existsSync14(repoDir)) return { commits: [], current: "", total: 0 };
|
|
72863
73085
|
try {
|
|
72864
73086
|
const fmt = ["%H", "%P", "%an", "%aI", "%s", "%D"].join(US);
|
|
72865
73087
|
const refArgs = opts2.branches?.length ? ["--end-of-options", ...opts2.branches] : [
|
|
@@ -72929,7 +73151,7 @@ async function changedOf(repoDir, sha2) {
|
|
|
72929
73151
|
}
|
|
72930
73152
|
async function compareCommits(repoDir, from, to) {
|
|
72931
73153
|
const empty = { from, to, changed: [] };
|
|
72932
|
-
if (!repoDir || !
|
|
73154
|
+
if (!repoDir || !existsSync14(repoDir)) return empty;
|
|
72933
73155
|
try {
|
|
72934
73156
|
const [num, name2] = await Promise.all([
|
|
72935
73157
|
exec5("git", ["diff", "--numstat", "-z", "--no-renames", "--end-of-options", from, to], { cwd: repoDir, ...GIT4 }),
|
|
@@ -72941,7 +73163,7 @@ async function compareCommits(repoDir, from, to) {
|
|
|
72941
73163
|
}
|
|
72942
73164
|
}
|
|
72943
73165
|
async function searchCommits(repoDir, q2, by = "all") {
|
|
72944
|
-
if (!repoDir || !
|
|
73166
|
+
if (!repoDir || !existsSync14(repoDir) || !q2) return [];
|
|
72945
73167
|
const run6 = async (extra) => {
|
|
72946
73168
|
try {
|
|
72947
73169
|
return (await exec5("git", ["log", "--all", "--format=%H", ...extra], { cwd: repoDir, ...GIT4 })).stdout.split("\n").filter(Boolean);
|
|
@@ -73188,7 +73410,7 @@ async function runGitOp(repoDir, op) {
|
|
|
73188
73410
|
}
|
|
73189
73411
|
}
|
|
73190
73412
|
async function workingStatus(repoDir) {
|
|
73191
|
-
if (!repoDir || !
|
|
73413
|
+
if (!repoDir || !existsSync14(repoDir)) return { branch: "", staged: [], unstaged: [] };
|
|
73192
73414
|
const [branch, staged, unstaged] = await Promise.all([
|
|
73193
73415
|
currentBranch(repoDir),
|
|
73194
73416
|
changedFiles(repoDir, ["--cached"]),
|
|
@@ -73197,7 +73419,7 @@ async function workingStatus(repoDir) {
|
|
|
73197
73419
|
return { branch, staged, unstaged };
|
|
73198
73420
|
}
|
|
73199
73421
|
async function workingFileDiff(repoDir, path, staged) {
|
|
73200
|
-
if (!repoDir || !
|
|
73422
|
+
if (!repoDir || !existsSync14(repoDir)) return null;
|
|
73201
73423
|
repoAbsPath(repoDir, path, true);
|
|
73202
73424
|
const changed = staged ? await changedFiles(repoDir, ["--cached"]) : await withTempIndex(repoDir, (env) => changedFiles(repoDir, [], env));
|
|
73203
73425
|
const cf = changed.find((c) => c.path === path);
|
|
@@ -73406,7 +73628,7 @@ async function deleteBranches(repoDir, names, opts2) {
|
|
|
73406
73628
|
import { execFile as execFile9 } from "node:child_process";
|
|
73407
73629
|
import { realpathSync as realpathSync8 } from "node:fs";
|
|
73408
73630
|
import { stat as stat5 } from "node:fs/promises";
|
|
73409
|
-
import { basename as basename8, dirname as
|
|
73631
|
+
import { basename as basename8, dirname as dirname13, join as join28, resolve as resolve17, sep as sep8 } from "node:path";
|
|
73410
73632
|
import { promisify as promisify8 } from "node:util";
|
|
73411
73633
|
var exec7 = promisify8(execFile9);
|
|
73412
73634
|
var GIT6 = { timeout: 6e4, maxBuffer: 1 << 24, encoding: "utf8" };
|
|
@@ -73447,20 +73669,20 @@ var real = (p3) => {
|
|
|
73447
73669
|
return realpathSync8(p3);
|
|
73448
73670
|
} catch {
|
|
73449
73671
|
const path = resolve17(p3);
|
|
73450
|
-
const parent =
|
|
73451
|
-
return parent === path ? path :
|
|
73672
|
+
const parent = dirname13(path);
|
|
73673
|
+
return parent === path ? path : join28(real(parent), basename8(path));
|
|
73452
73674
|
}
|
|
73453
73675
|
};
|
|
73454
73676
|
function hasWorktreeSession(path, sessionId2, sessions) {
|
|
73455
73677
|
const target3 = real(path);
|
|
73456
73678
|
return sessions.some((s2) => {
|
|
73457
73679
|
const current = real(s2.cwd);
|
|
73458
|
-
return s2.id === sessionId2 || current === target3 || current.startsWith(target3 +
|
|
73680
|
+
return s2.id === sessionId2 || current === target3 || current.startsWith(target3 + sep8);
|
|
73459
73681
|
});
|
|
73460
73682
|
}
|
|
73461
73683
|
async function bornAt(path) {
|
|
73462
73684
|
try {
|
|
73463
|
-
const st2 = await stat5(
|
|
73685
|
+
const st2 = await stat5(join28(path, ".git"));
|
|
73464
73686
|
const ms = st2.birthtimeMs > 0 ? st2.birthtimeMs : st2.mtimeMs;
|
|
73465
73687
|
return new Date(ms).toISOString();
|
|
73466
73688
|
} catch {
|
|
@@ -73485,7 +73707,7 @@ async function listWorktrees(repoDir, inputs) {
|
|
|
73485
73707
|
const rows = [];
|
|
73486
73708
|
for (const w of raw) {
|
|
73487
73709
|
const path = resolve17(w.path);
|
|
73488
|
-
if (path === trash || path.startsWith(trash +
|
|
73710
|
+
if (path === trash || path.startsWith(trash + sep8)) continue;
|
|
73489
73711
|
const name2 = basename8(path);
|
|
73490
73712
|
const deletable = path !== baseReal && path !== mainPath;
|
|
73491
73713
|
const spec = inputs.specs.get(name2);
|
|
@@ -73672,12 +73894,12 @@ async function recordHeadSha(specId, worktree, read2 = readHead) {
|
|
|
73672
73894
|
init_local_binding();
|
|
73673
73895
|
|
|
73674
73896
|
// src/services/session-worktree.ts
|
|
73675
|
-
import { resolve as resolve18, sep as
|
|
73897
|
+
import { resolve as resolve18, sep as sep9 } from "node:path";
|
|
73676
73898
|
function ownsWorktree(repoDir, cwd) {
|
|
73677
73899
|
const base3 = resolve18(repoDir);
|
|
73678
73900
|
const dir2 = resolve18(cwd);
|
|
73679
73901
|
const container = resolve18(base3, ".worktrees");
|
|
73680
|
-
return dir2 !== base3 && dir2 !== container && dir2.startsWith(container +
|
|
73902
|
+
return dir2 !== base3 && dir2 !== container && dir2.startsWith(container + sep9);
|
|
73681
73903
|
}
|
|
73682
73904
|
|
|
73683
73905
|
// src/services/worktree-reaper.ts
|
|
@@ -73688,7 +73910,7 @@ init_notifications2();
|
|
|
73688
73910
|
import { execFile as execFile11 } from "node:child_process";
|
|
73689
73911
|
import { readdir as readdir4, rm as rm3 } from "node:fs/promises";
|
|
73690
73912
|
import { promisify as promisify10 } from "node:util";
|
|
73691
|
-
import { join as
|
|
73913
|
+
import { join as join29, resolve as resolve19 } from "node:path";
|
|
73692
73914
|
var TICK_MS = 6e4;
|
|
73693
73915
|
var trashDirOf = (repoDir) => resolve19(repoDir, ".worktrees", ".trash");
|
|
73694
73916
|
var exec8 = promisify10(execFile11);
|
|
@@ -73749,7 +73971,7 @@ async function sweepRepo(repoDir, projectId, deps = prodReaperDeps) {
|
|
|
73749
73971
|
}
|
|
73750
73972
|
let removed = 0;
|
|
73751
73973
|
for (const entry2 of entries4) {
|
|
73752
|
-
const path =
|
|
73974
|
+
const path = join29(dir2, entry2);
|
|
73753
73975
|
const known2 = pending.get(path);
|
|
73754
73976
|
const row = known2 ?? { path, repoDir, projectId, entry: entry2, sessionId: sessionIdOf(entry2), since: Date.now() };
|
|
73755
73977
|
pending.set(path, row);
|
|
@@ -74213,8 +74435,8 @@ function appendSourceHistory(current, entry2) {
|
|
|
74213
74435
|
|
|
74214
74436
|
// src/services/spec-reset.ts
|
|
74215
74437
|
import { execFile as execFile13 } from "node:child_process";
|
|
74216
|
-
import { existsSync as
|
|
74217
|
-
import { join as
|
|
74438
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
74439
|
+
import { join as join30 } from "node:path";
|
|
74218
74440
|
import { promisify as promisify12 } from "node:util";
|
|
74219
74441
|
|
|
74220
74442
|
// src/services/stage-artifacts.ts
|
|
@@ -74269,14 +74491,14 @@ async function planSpecReset(spec) {
|
|
|
74269
74491
|
const repoDir = await resolveRepoDir(spec.projectId);
|
|
74270
74492
|
if (!repoDir) return EMPTY3;
|
|
74271
74493
|
const sid = sessionIdForSpec(spec.id);
|
|
74272
|
-
const wt =
|
|
74494
|
+
const wt = join30(repoDir, ".worktrees", sid);
|
|
74273
74495
|
const branch = `hanoman/${sid}`;
|
|
74274
74496
|
const hasBranch = await shaResolvable(repoDir, `refs/heads/${branch}`);
|
|
74275
74497
|
return {
|
|
74276
74498
|
// Berkas fase seluruh stage yang ditinggalkan — rentang yang sama persis dengan revert stage
|
|
74277
74499
|
// SPEC-167, karena tujuan akhirnya memang sama: item berdiri lagi di `brainstorming`.
|
|
74278
74500
|
wouldDelete: await artifactsToRemove(spec.projectId, spec.id, "brainstorming", spec.stage),
|
|
74279
|
-
worktree:
|
|
74501
|
+
worktree: existsSync15(wt) ? wt : null,
|
|
74280
74502
|
branch: hasBranch ? branch : null
|
|
74281
74503
|
};
|
|
74282
74504
|
}
|
|
@@ -74353,6 +74575,61 @@ init_remote_session();
|
|
|
74353
74575
|
init_registry();
|
|
74354
74576
|
init_spec_attachment_dir();
|
|
74355
74577
|
init_uploads();
|
|
74578
|
+
|
|
74579
|
+
// src/services/spec-mockups.ts
|
|
74580
|
+
init_local_binding();
|
|
74581
|
+
init_spec_review2();
|
|
74582
|
+
import { existsSync as existsSync16, readdirSync as readdirSync9, realpathSync as realpathSync10, statSync as statSync5 } from "node:fs";
|
|
74583
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
74584
|
+
import { extname as extname2, join as join33, sep as sep10 } from "node:path";
|
|
74585
|
+
var MOCKUP_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*\.(png|jpe?g|webp)$/i;
|
|
74586
|
+
var MIME = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp" };
|
|
74587
|
+
var isDir = (p3) => {
|
|
74588
|
+
try {
|
|
74589
|
+
return statSync5(p3).isDirectory();
|
|
74590
|
+
} catch {
|
|
74591
|
+
return false;
|
|
74592
|
+
}
|
|
74593
|
+
};
|
|
74594
|
+
async function resolveMockupDir(spec) {
|
|
74595
|
+
const repoDir = await resolveRepoDir(spec.projectId);
|
|
74596
|
+
if (!repoDir) return null;
|
|
74597
|
+
const rel = join33("docs", "mockups", spec.id.toLowerCase());
|
|
74598
|
+
const worktree = join33(worktreeDir(repoDir, spec.id), rel);
|
|
74599
|
+
if (isDir(worktree)) return { source: "worktree", dir: worktree };
|
|
74600
|
+
const repo = join33(repoDir, rel);
|
|
74601
|
+
return isDir(repo) ? { source: "repo", dir: repo } : null;
|
|
74602
|
+
}
|
|
74603
|
+
async function listMockups(spec) {
|
|
74604
|
+
const found = await resolveMockupDir(spec);
|
|
74605
|
+
if (!found) return { source: null, dir: null, items: [] };
|
|
74606
|
+
const items = readdirSync9(found.dir).filter((n2) => MOCKUP_NAME.test(n2)).sort().flatMap((name2) => {
|
|
74607
|
+
try {
|
|
74608
|
+
const st2 = statSync5(join33(found.dir, name2));
|
|
74609
|
+
return st2.isFile() ? [{ name: name2, size: st2.size, modifiedAt: st2.mtime.toISOString() }] : [];
|
|
74610
|
+
} catch {
|
|
74611
|
+
return [];
|
|
74612
|
+
}
|
|
74613
|
+
});
|
|
74614
|
+
return { source: found.source, dir: found.dir, items };
|
|
74615
|
+
}
|
|
74616
|
+
async function readMockup(spec, name2) {
|
|
74617
|
+
if (!MOCKUP_NAME.test(name2)) return null;
|
|
74618
|
+
const found = await resolveMockupDir(spec);
|
|
74619
|
+
if (!found) return null;
|
|
74620
|
+
const file = join33(found.dir, name2);
|
|
74621
|
+
if (!existsSync16(file)) return null;
|
|
74622
|
+
try {
|
|
74623
|
+
const root = realpathSync10(found.dir);
|
|
74624
|
+
const real2 = realpathSync10(file);
|
|
74625
|
+
if (!real2.startsWith(root + sep10) || !statSync5(real2).isFile()) return null;
|
|
74626
|
+
return { buf: await readFile7(real2), mime: MIME[extname2(real2).toLowerCase()] ?? "application/octet-stream" };
|
|
74627
|
+
} catch {
|
|
74628
|
+
return null;
|
|
74629
|
+
}
|
|
74630
|
+
}
|
|
74631
|
+
|
|
74632
|
+
// src/routes/specs.ts
|
|
74356
74633
|
init_stage_machine();
|
|
74357
74634
|
|
|
74358
74635
|
// src/services/spec-docs.ts
|
|
@@ -76834,7 +77111,7 @@ init_stage_machine();
|
|
|
76834
77111
|
init_notifications2();
|
|
76835
77112
|
init_sync_notify();
|
|
76836
77113
|
init_spec_deps();
|
|
76837
|
-
import { createHash as
|
|
77114
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
76838
77115
|
var specNum = (id2) => Number.parseInt(id2.match(/\d+/)?.[0] ?? "0", 10);
|
|
76839
77116
|
var selectOf = (shape) => Object.fromEntries(
|
|
76840
77117
|
Object.keys(shape).filter((k) => k !== "blockedBy").map((k) => [k, true])
|
|
@@ -76926,7 +77203,7 @@ async function liveSignature() {
|
|
|
76926
77203
|
async function specsDigest() {
|
|
76927
77204
|
const agg = await prisma.spec.aggregate({ _count: { _all: true }, _max: { updatedAt: true }, _sum: { version: true } });
|
|
76928
77205
|
const db = `${agg._count._all}:${agg._max.updatedAt?.getTime() ?? 0}:${agg._sum.version ?? 0}`;
|
|
76929
|
-
return
|
|
77206
|
+
return createHash12("sha1").update(`${db}|${await liveSignature()}`).digest("hex");
|
|
76930
77207
|
}
|
|
76931
77208
|
async function liveSpecs(filter = {}) {
|
|
76932
77209
|
await liveOverlayTick();
|
|
@@ -77254,6 +77531,24 @@ ${item.context}` : item.context;
|
|
|
77254
77531
|
await syncSpecAttachmentsDir(spec.id, spec.projectId);
|
|
77255
77532
|
return reply.code(201).send(result);
|
|
77256
77533
|
});
|
|
77534
|
+
app3.get("/specs/:id/mockups", async (req, reply) => {
|
|
77535
|
+
const { id: id2 } = req.params;
|
|
77536
|
+
const spec = await prisma.spec.findUnique({ where: { id: id2 }, select: { id: true, projectId: true } });
|
|
77537
|
+
if (!spec) return reply.code(404).send({ error: "not found" });
|
|
77538
|
+
return listMockups(spec);
|
|
77539
|
+
});
|
|
77540
|
+
app3.get("/specs/:id/mockups/:name", async (req, reply) => {
|
|
77541
|
+
const { id: id2, name: name2 } = req.params;
|
|
77542
|
+
const spec = await prisma.spec.findUnique({ where: { id: id2 }, select: { id: true, projectId: true } });
|
|
77543
|
+
const file = spec ? await readMockup(spec, name2) : null;
|
|
77544
|
+
if (!file) return reply.code(404).send({ error: "not found" });
|
|
77545
|
+
reply.header("content-type", file.mime);
|
|
77546
|
+
reply.header("content-disposition", `inline; filename="${name2}"`);
|
|
77547
|
+
reply.header("x-content-type-options", "nosniff");
|
|
77548
|
+
reply.header("content-security-policy", "sandbox; default-src 'none'");
|
|
77549
|
+
reply.header("cache-control", "no-cache");
|
|
77550
|
+
return reply.send(file.buf);
|
|
77551
|
+
});
|
|
77257
77552
|
app3.get("/specs/:id/attachments/:attId", async (req, reply) => {
|
|
77258
77553
|
const { id: id2, attId } = req.params;
|
|
77259
77554
|
const a = await prisma.specAttachment.findUnique({ where: { id: attId } });
|
|
@@ -77349,7 +77644,7 @@ ${item.context}` : item.context;
|
|
|
77349
77644
|
});
|
|
77350
77645
|
const specWithProject = (id2) => prisma.spec.findUnique({ where: { id: id2 }, include: { project: true } });
|
|
77351
77646
|
const resolveReview = async (repoDir, spec) => {
|
|
77352
|
-
if (
|
|
77647
|
+
if (existsSync17(worktreeDir(repoDir, spec.id))) return { wt: true };
|
|
77353
77648
|
if (spec.baseSha && spec.headSha && await shaResolvable(repoDir, spec.baseSha) && await shaResolvable(repoDir, spec.headSha))
|
|
77354
77649
|
return { wt: false, base: spec.baseSha, head: spec.headSha };
|
|
77355
77650
|
const r = await specCommitRange(repoDir, spec.id);
|
|
@@ -77645,7 +77940,7 @@ import { spawn as spawn5 } from "node:child_process";
|
|
|
77645
77940
|
// src/services/git-remotes.ts
|
|
77646
77941
|
import { execFile as execFile16 } from "node:child_process";
|
|
77647
77942
|
import { promisify as promisify15 } from "node:util";
|
|
77648
|
-
import { existsSync as
|
|
77943
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
77649
77944
|
var exec10 = promisify15(execFile16);
|
|
77650
77945
|
var GIT7 = { maxBuffer: 1 << 24, encoding: "utf8" };
|
|
77651
77946
|
function parseRemote(url2) {
|
|
@@ -77664,7 +77959,7 @@ function prUrl(remoteUrl, branch, base3 = "main") {
|
|
|
77664
77959
|
return null;
|
|
77665
77960
|
}
|
|
77666
77961
|
async function listRemotes(repoDir) {
|
|
77667
|
-
if (!repoDir || !
|
|
77962
|
+
if (!repoDir || !existsSync20(repoDir)) return [];
|
|
77668
77963
|
try {
|
|
77669
77964
|
const { stdout } = await exec10("git", ["remote", "-v"], { cwd: repoDir, ...GIT7 });
|
|
77670
77965
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -77712,7 +78007,7 @@ init_integrate();
|
|
|
77712
78007
|
init_safe_repo_path();
|
|
77713
78008
|
import { lstat as lstat2, rename as rename4, rm as rm6 } from "node:fs/promises";
|
|
77714
78009
|
import { createWriteStream } from "node:fs";
|
|
77715
|
-
import { join as
|
|
78010
|
+
import { join as join39 } from "node:path";
|
|
77716
78011
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
77717
78012
|
import { pipeline } from "node:stream/promises";
|
|
77718
78013
|
var EntryExistsError = class extends Error {
|
|
@@ -77773,7 +78068,7 @@ async function saveUpload2(repoDir, rel, source, opts2 = {}) {
|
|
|
77773
78068
|
if (current && !opts2.overwrite) return { status: "exists" };
|
|
77774
78069
|
if (current && !current.isFile())
|
|
77775
78070
|
throw new PathContainmentError("repository path ditolak: target bukan file regular");
|
|
77776
|
-
const temp =
|
|
78071
|
+
const temp = join39(entry2.parent, `.hanoman-${randomUUID11()}.tmp`);
|
|
77777
78072
|
try {
|
|
77778
78073
|
await pipeline(source, createWriteStream(temp, { flags: "wx", mode: 384 }));
|
|
77779
78074
|
if (opts2.isTruncated?.()) {
|
|
@@ -78264,15 +78559,15 @@ async function finishGraphOp(reply, id2, repoDir, r, verb) {
|
|
|
78264
78559
|
// src/routes/fs.ts
|
|
78265
78560
|
import { readdir as readdir8 } from "node:fs/promises";
|
|
78266
78561
|
import { homedir as homedir9 } from "node:os";
|
|
78267
|
-
import { resolve as resolve22, dirname as
|
|
78562
|
+
import { resolve as resolve22, dirname as dirname16, join as join40 } from "node:path";
|
|
78268
78563
|
async function fs_default(app3) {
|
|
78269
78564
|
app3.get("/fs/browse", async (req, reply) => {
|
|
78270
78565
|
const q2 = req.query.path;
|
|
78271
78566
|
const dir2 = q2 && q2.trim() ? resolve22(q2.trim()) : homedir9();
|
|
78272
78567
|
try {
|
|
78273
78568
|
const ents = await readdir8(dir2, { withFileTypes: true });
|
|
78274
|
-
const entries4 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path:
|
|
78275
|
-
const parent =
|
|
78569
|
+
const entries4 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join40(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
|
|
78570
|
+
const parent = dirname16(dir2);
|
|
78276
78571
|
return { path: dir2, parent: parent === dir2 ? null : parent, entries: entries4 };
|
|
78277
78572
|
} catch {
|
|
78278
78573
|
return reply.code(400).send({ error: `tak bisa membaca folder "${dir2}"` });
|
|
@@ -78286,7 +78581,9 @@ init_src();
|
|
|
78286
78581
|
init_src2();
|
|
78287
78582
|
init_session_phases();
|
|
78288
78583
|
init_spec_review2();
|
|
78289
|
-
import { existsSync as
|
|
78584
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
78585
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
78586
|
+
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
78290
78587
|
init_integrate();
|
|
78291
78588
|
init_settings3();
|
|
78292
78589
|
init_codex_trust();
|
|
@@ -78349,7 +78646,7 @@ function setBounded(map, key, value, maxKeys) {
|
|
|
78349
78646
|
// src/services/auth.ts
|
|
78350
78647
|
init_src2();
|
|
78351
78648
|
init_db();
|
|
78352
|
-
import { randomBytes as randomBytes4, scrypt as scryptCb, timingSafeEqual as timingSafeEqual4, createHash as
|
|
78649
|
+
import { randomBytes as randomBytes4, scrypt as scryptCb, timingSafeEqual as timingSafeEqual4, createHash as createHash17 } from "node:crypto";
|
|
78353
78650
|
import { promisify as promisify16 } from "node:util";
|
|
78354
78651
|
var scrypt = promisify16(scryptCb);
|
|
78355
78652
|
var COOKIE_NAME = "hn_session";
|
|
@@ -78367,7 +78664,7 @@ async function verifyPassword(pw, stored) {
|
|
|
78367
78664
|
return key.length === want.length && timingSafeEqual4(key, want);
|
|
78368
78665
|
}
|
|
78369
78666
|
var newSessionToken = () => randomBytes4(32).toString("base64url");
|
|
78370
|
-
var sessionId = (token) =>
|
|
78667
|
+
var sessionId = (token) => createHash17("sha256").update(token).digest("hex");
|
|
78371
78668
|
async function createSession2(userId) {
|
|
78372
78669
|
const token = newSessionToken();
|
|
78373
78670
|
await prisma.session.create({
|
|
@@ -78428,9 +78725,9 @@ function cookieOpts(req) {
|
|
|
78428
78725
|
|
|
78429
78726
|
// src/services/device-token.ts
|
|
78430
78727
|
init_db();
|
|
78431
|
-
import { randomBytes as randomBytes5, createHash as
|
|
78728
|
+
import { randomBytes as randomBytes5, createHash as createHash18 } from "node:crypto";
|
|
78432
78729
|
var newDeviceToken = () => randomBytes5(32).toString("base64url");
|
|
78433
|
-
var tokenHash = (token) =>
|
|
78730
|
+
var tokenHash = (token) => createHash18("sha256").update(token).digest("hex");
|
|
78434
78731
|
async function issueDeviceToken(userId, name2) {
|
|
78435
78732
|
const token = newDeviceToken();
|
|
78436
78733
|
const row = await prisma.deviceToken.create({ data: { userId, name: name2, tokenHash: tokenHash(token) } });
|
|
@@ -78643,14 +78940,14 @@ function installCommand(m, agent, shell = shellBin()) {
|
|
|
78643
78940
|
init_local_binding();
|
|
78644
78941
|
|
|
78645
78942
|
// src/services/terminal-diag.ts
|
|
78646
|
-
import { appendFileSync as appendFileSync4, mkdirSync as
|
|
78647
|
-
import { join as
|
|
78943
|
+
import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync17, statSync as statSync6, rmSync as rmSync11 } from "node:fs";
|
|
78944
|
+
import { join as join41 } from "node:path";
|
|
78648
78945
|
var DIAG_MAX_BYTES = 2 * 1024 * 1024;
|
|
78649
78946
|
var KINDS = /* @__PURE__ */ new Set(["key", "comp", "data", "ack", "pred"]);
|
|
78650
78947
|
var ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
78651
78948
|
function diagFile(home3, sessionId2) {
|
|
78652
78949
|
if (!ID.test(sessionId2)) throw new Error(`id sesi tak sah untuk diag: ${sessionId2}`);
|
|
78653
|
-
return
|
|
78950
|
+
return join41(home3, "diag", `${sessionId2}.jsonl`);
|
|
78654
78951
|
}
|
|
78655
78952
|
function usable(ev) {
|
|
78656
78953
|
if (!ev || typeof ev !== "object") return false;
|
|
@@ -78661,9 +78958,9 @@ function appendDiag(home3, sessionId2, events) {
|
|
|
78661
78958
|
const file = diagFile(home3, sessionId2);
|
|
78662
78959
|
const rows = events.filter(usable);
|
|
78663
78960
|
if (!rows.length) return;
|
|
78664
|
-
|
|
78961
|
+
mkdirSync17(join41(home3, "diag"), { recursive: true });
|
|
78665
78962
|
try {
|
|
78666
|
-
if (
|
|
78963
|
+
if (statSync6(file).size > DIAG_MAX_BYTES) rmSync11(file);
|
|
78667
78964
|
} catch {
|
|
78668
78965
|
}
|
|
78669
78966
|
appendFileSync4(file, rows.map((r) => JSON.stringify(r)).join("\n") + "\n");
|
|
@@ -78743,11 +79040,11 @@ init_uploads();
|
|
|
78743
79040
|
// src/services/agent-invocations.ts
|
|
78744
79041
|
init_src();
|
|
78745
79042
|
init_db();
|
|
78746
|
-
import { createHash as
|
|
79043
|
+
import { createHash as createHash19, randomUUID as randomUUID12 } from "node:crypto";
|
|
78747
79044
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
78748
79045
|
import { homedir as homedir10 } from "node:os";
|
|
78749
|
-
import { readFileSync as
|
|
78750
|
-
import { isAbsolute as isAbsolute12, relative as
|
|
79046
|
+
import { readFileSync as readFileSync20, realpathSync as realpathSync11, statSync as statSync7 } from "node:fs";
|
|
79047
|
+
import { isAbsolute as isAbsolute12, relative as relative8, resolve as resolve23 } from "node:path";
|
|
78751
79048
|
|
|
78752
79049
|
// src/services/session-event-relay.ts
|
|
78753
79050
|
init_session_event_token();
|
|
@@ -78755,7 +79052,7 @@ init_session_event_spool();
|
|
|
78755
79052
|
init_ingress_policy();
|
|
78756
79053
|
import { constants as constants4 } from "node:fs";
|
|
78757
79054
|
import { mkdir as mkdir8, open as open4, readdir as readdir9, rm as rm7 } from "node:fs/promises";
|
|
78758
|
-
import { join as
|
|
79055
|
+
import { join as join42 } from "node:path";
|
|
78759
79056
|
var MAX_EVENT_BYTES = 1e6;
|
|
78760
79057
|
var MAX_FILES_PER_DRAIN = 1e3;
|
|
78761
79058
|
var SESSION_ID_RE = /^[a-z0-9_-]+$/;
|
|
@@ -78806,7 +79103,7 @@ async function drainSpool(app3, root, observation, host2) {
|
|
|
78806
79103
|
let readBuffer;
|
|
78807
79104
|
for (const session of await readdir9(root, { withFileTypes: true })) {
|
|
78808
79105
|
if (!session.isDirectory() || !SESSION_ID_RE.test(session.name)) continue;
|
|
78809
|
-
const dir2 =
|
|
79106
|
+
const dir2 = join42(root, session.name);
|
|
78810
79107
|
let entries4;
|
|
78811
79108
|
try {
|
|
78812
79109
|
entries4 = await readdir9(dir2, { withFileTypes: true });
|
|
@@ -78818,7 +79115,7 @@ async function drainSpool(app3, root, observation, host2) {
|
|
|
78818
79115
|
if (!entry2.isFile() || !entry2.name.endsWith(".json")) continue;
|
|
78819
79116
|
const eventAt = /^(\d{1,15})-/.exec(entry2.name)?.[1];
|
|
78820
79117
|
if (++examined > MAX_FILES_PER_DRAIN) return delivered;
|
|
78821
|
-
const path =
|
|
79118
|
+
const path = join42(dir2, entry2.name);
|
|
78822
79119
|
let payload;
|
|
78823
79120
|
let handle;
|
|
78824
79121
|
try {
|
|
@@ -78905,7 +79202,7 @@ var MAX_TRANSCRIPT_BYTES2 = 10 * 1024 * 1024;
|
|
|
78905
79202
|
var ANSI2 = /[\u001b\u009b](?:\][^\u0007]*(?:\u0007|\u001b\\)|\[[0-?]*[ -/]*[@-~]|[@-_])/g;
|
|
78906
79203
|
var snapshotHashes = /* @__PURE__ */ new Map();
|
|
78907
79204
|
var keyOf = (x) => `${x.sessionId}\0${x.runtimeInvocationId}`;
|
|
78908
|
-
var hash3 = (value) =>
|
|
79205
|
+
var hash3 = (value) => createHash19("sha256").update(value).digest("hex");
|
|
78909
79206
|
var defaultGitStatus = (cwd) => {
|
|
78910
79207
|
try {
|
|
78911
79208
|
return execFileSync8("git", ["-C", cwd, "status", "--porcelain=v1", "-z"], {
|
|
@@ -78940,7 +79237,7 @@ var transcriptRoots = () => [
|
|
|
78940
79237
|
resolve23(process.env.CODEX_HOME ?? `${homedir10()}/.codex`)
|
|
78941
79238
|
];
|
|
78942
79239
|
var inside = (path, root) => {
|
|
78943
|
-
const rel =
|
|
79240
|
+
const rel = relative8(root, path);
|
|
78944
79241
|
return rel === "" || !rel.startsWith("..") && !isAbsolute12(rel);
|
|
78945
79242
|
};
|
|
78946
79243
|
var EMPTY_USAGE = { inputTokens: null, outputTokens: null, cachedTokens: null };
|
|
@@ -79013,12 +79310,12 @@ function usageFromText(text2) {
|
|
|
79013
79310
|
function transcriptUsage(path, roots = transcriptRoots()) {
|
|
79014
79311
|
if (!path) return EMPTY_USAGE;
|
|
79015
79312
|
try {
|
|
79016
|
-
const real2 =
|
|
79017
|
-
const safeRoots = roots.map((root) =>
|
|
79313
|
+
const real2 = realpathSync11(path);
|
|
79314
|
+
const safeRoots = roots.map((root) => realpathSync11(root));
|
|
79018
79315
|
if (!safeRoots.some((root) => inside(real2, root))) return EMPTY_USAGE;
|
|
79019
|
-
const info =
|
|
79316
|
+
const info = statSync7(real2);
|
|
79020
79317
|
if (!info.isFile() || info.size > MAX_TRANSCRIPT_BYTES2) return EMPTY_USAGE;
|
|
79021
|
-
return usageFromText(
|
|
79318
|
+
return usageFromText(readFileSync20(real2, "utf8"));
|
|
79022
79319
|
} catch {
|
|
79023
79320
|
return EMPTY_USAGE;
|
|
79024
79321
|
}
|
|
@@ -79303,25 +79600,25 @@ init_ask();
|
|
|
79303
79600
|
// src/services/decision-guard.ts
|
|
79304
79601
|
init_src2();
|
|
79305
79602
|
init_db();
|
|
79306
|
-
import { mkdirSync as
|
|
79307
|
-
import { join as
|
|
79603
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync21, readdirSync as readdirSync11, rmSync as rmSync12, statSync as statSync8, writeFileSync as writeFileSync18 } from "node:fs";
|
|
79604
|
+
import { join as join43 } from "node:path";
|
|
79308
79605
|
var SESSION_HEADER = "x-hanoman-session";
|
|
79309
79606
|
var SAFE_ID = /^[A-Za-z0-9._-]{1,200}$/;
|
|
79310
79607
|
var launchers = /* @__PURE__ */ new Map();
|
|
79311
79608
|
var STALE_MS = 30 * 24 * 60 * 6e4;
|
|
79312
|
-
var launcherDir = (env = process.env) =>
|
|
79609
|
+
var launcherDir = (env = process.env) => join43(resolveHome(env), "session-launchers");
|
|
79313
79610
|
function recordLauncher(sessionId2, agentId, env = process.env) {
|
|
79314
79611
|
if (!SAFE_ID.test(sessionId2)) return;
|
|
79315
79612
|
const known2 = launchersFor(sessionId2, env);
|
|
79316
79613
|
known2.add(agentId);
|
|
79317
79614
|
try {
|
|
79318
79615
|
const dir2 = launcherDir(env);
|
|
79319
|
-
|
|
79320
|
-
|
|
79616
|
+
mkdirSync18(dir2, { recursive: true, mode: 448 });
|
|
79617
|
+
writeFileSync18(join43(dir2, sessionId2), [...known2].join("\n"), { mode: 384 });
|
|
79321
79618
|
const now = Date.now();
|
|
79322
|
-
for (const f of
|
|
79619
|
+
for (const f of readdirSync11(dir2)) {
|
|
79323
79620
|
try {
|
|
79324
|
-
if (now -
|
|
79621
|
+
if (now - statSync8(join43(dir2, f)).mtimeMs > STALE_MS) rmSync12(join43(dir2, f), { force: true });
|
|
79325
79622
|
} catch {
|
|
79326
79623
|
}
|
|
79327
79624
|
}
|
|
@@ -79333,7 +79630,7 @@ function launchersFor(sessionId2, env) {
|
|
|
79333
79630
|
if (hot) return hot;
|
|
79334
79631
|
const ids = /* @__PURE__ */ new Set();
|
|
79335
79632
|
try {
|
|
79336
|
-
for (const l of
|
|
79633
|
+
for (const l of readFileSync21(join43(launcherDir(env), sessionId2), "utf8").split("\n")) if (l.trim()) ids.add(l.trim());
|
|
79337
79634
|
} catch {
|
|
79338
79635
|
}
|
|
79339
79636
|
launchers.set(sessionId2, ids);
|
|
@@ -79647,6 +79944,26 @@ async function terminal_default(app3, opts2) {
|
|
|
79647
79944
|
}
|
|
79648
79945
|
const { agent, model, effort: effort2 } = await terminalAgentDefaults(parsed.data);
|
|
79649
79946
|
if (agent === "codex") ensureCodexTrust(repoDir);
|
|
79947
|
+
if (parsed.data.worktree) {
|
|
79948
|
+
const id2 = `term-${randomUUID13().slice(0, 8)}`;
|
|
79949
|
+
const branch = `term/${id2.slice("term-".length)}`;
|
|
79950
|
+
const from = parsed.data.branchFrom ?? "HEAD";
|
|
79951
|
+
const result = await withSessionAdmission({ id: id2, exempt: false, force: parsed.data.force }, async () => {
|
|
79952
|
+
const wt = `${repoDir}/.worktrees/${id2}`;
|
|
79953
|
+
try {
|
|
79954
|
+
realGit.addWorktree(repoDir, wt, from);
|
|
79955
|
+
execFileSync9("git", ["switch", "-q", "-c", branch], { cwd: wt, stdio: "pipe" });
|
|
79956
|
+
} catch (e) {
|
|
79957
|
+
if (ownsWorktree(repoDir, wt)) realGit.removeWorktree(repoDir, wt);
|
|
79958
|
+
return { code: 422, body: { error: `gagal membuat worktree dari "${from}": ${e.message}` } };
|
|
79959
|
+
}
|
|
79960
|
+
const s3 = createSession(project.id, wt, { id: id2, agent, model, effort: effort2, branch });
|
|
79961
|
+
return { code: 201, body: { id: s3.id } };
|
|
79962
|
+
}, (pane) => ({ code: 201, body: { id: pane.id } }));
|
|
79963
|
+
return reply.code(result.code).send(result.body);
|
|
79964
|
+
}
|
|
79965
|
+
if (parsed.data.branchFrom)
|
|
79966
|
+
return reply.code(400).send({ error: "branchFrom hanya berlaku bersama worktree:true" });
|
|
79650
79967
|
const s2 = await createOperatorSession(
|
|
79651
79968
|
project.id,
|
|
79652
79969
|
repoDir,
|
|
@@ -79765,7 +80082,7 @@ async function terminal_default(app3, opts2) {
|
|
|
79765
80082
|
if (!repoDir) return { ok: false, code: 409, msg: "project belum punya repoDir" };
|
|
79766
80083
|
if (!s2.cwd.includes("/.worktrees/"))
|
|
79767
80084
|
return { ok: false, code: 409, msg: "belum ada worktree untuk di-review \u2014 jalankan/lanjutkan sesi dulu", empty: "no-worktree" };
|
|
79768
|
-
if (!
|
|
80085
|
+
if (!existsSync21(s2.cwd))
|
|
79769
80086
|
return { ok: false, code: 409, msg: "belum ada worktree untuk di-review \u2014 jalankan/lanjutkan sesi dulu", empty: "no-commit" };
|
|
79770
80087
|
return { ok: true, id: s2.id, repoDir };
|
|
79771
80088
|
};
|
|
@@ -80028,7 +80345,7 @@ async function terminalWorkspaceRoutes(app3) {
|
|
|
80028
80345
|
// src/routes/vps.ts
|
|
80029
80346
|
init_db();
|
|
80030
80347
|
init_src();
|
|
80031
|
-
import { readFileSync as
|
|
80348
|
+
import { readFileSync as readFileSync26, existsSync as existsSync24 } from "node:fs";
|
|
80032
80349
|
import { homedir as homedir12 } from "node:os";
|
|
80033
80350
|
|
|
80034
80351
|
// src/vps/catalog/catalog.data.ts
|
|
@@ -80236,14 +80553,14 @@ function resolveComponents(ids, profile) {
|
|
|
80236
80553
|
}
|
|
80237
80554
|
|
|
80238
80555
|
// src/services/vps-provision.ts
|
|
80239
|
-
import { readFileSync as
|
|
80556
|
+
import { readFileSync as readFileSync23 } from "node:fs";
|
|
80240
80557
|
|
|
80241
80558
|
// src/services/vps-ssh.ts
|
|
80242
80559
|
init_config2();
|
|
80243
80560
|
import { spawn as spawn6 } from "node:child_process";
|
|
80244
|
-
import { mkdtempSync as mkdtempSync4, rmSync as
|
|
80561
|
+
import { mkdtempSync as mkdtempSync4, rmSync as rmSync13, writeFileSync as writeFileSync19 } from "node:fs";
|
|
80245
80562
|
import { tmpdir as tmpdir7 } from "node:os";
|
|
80246
|
-
import { dirname as
|
|
80563
|
+
import { dirname as dirname17, join as join44 } from "node:path";
|
|
80247
80564
|
var sshBin = () => effectiveStr("HANOMAN_SSH_BIN") ?? "ssh";
|
|
80248
80565
|
function consoleArgv(t) {
|
|
80249
80566
|
return [
|
|
@@ -80258,9 +80575,9 @@ function consoleArgv(t) {
|
|
|
80258
80575
|
];
|
|
80259
80576
|
}
|
|
80260
80577
|
function askpassScript() {
|
|
80261
|
-
const dir2 = mkdtempSync4(
|
|
80262
|
-
const path =
|
|
80263
|
-
|
|
80578
|
+
const dir2 = mkdtempSync4(join44(tmpdir7(), "hanoman-askpass-"));
|
|
80579
|
+
const path = join44(dir2, "askpass.sh");
|
|
80580
|
+
writeFileSync19(path, `#!/bin/sh
|
|
80264
80581
|
printf '%s' "$HANOMAN_SSH_PASSWORD"
|
|
80265
80582
|
`, { mode: 448 });
|
|
80266
80583
|
return path;
|
|
@@ -80299,7 +80616,7 @@ function sshExec(t, remoteCmd, opts2 = {}) {
|
|
|
80299
80616
|
const timer11 = setTimeout(() => p3.kill("SIGKILL"), opts2.timeoutMs ?? 6e4);
|
|
80300
80617
|
const done = (r) => {
|
|
80301
80618
|
clearTimeout(timer11);
|
|
80302
|
-
if (askpass)
|
|
80619
|
+
if (askpass) rmSync13(dirname17(askpass), { recursive: true, force: true });
|
|
80303
80620
|
resolve25(r);
|
|
80304
80621
|
};
|
|
80305
80622
|
p3.stdout.on("data", (d) => {
|
|
@@ -80319,26 +80636,10 @@ function sshExec(t, remoteCmd, opts2 = {}) {
|
|
|
80319
80636
|
|
|
80320
80637
|
// src/services/vps-audit.ts
|
|
80321
80638
|
init_db();
|
|
80322
|
-
|
|
80323
|
-
import {
|
|
80324
|
-
import {
|
|
80325
|
-
|
|
80326
|
-
// src/runner/deps.ts
|
|
80327
|
-
import { existsSync as existsSync19 } from "node:fs";
|
|
80328
|
-
import { dirname as dirname17, join as join42 } from "node:path";
|
|
80329
|
-
function repoRootFrom(startDir) {
|
|
80330
|
-
let dir2 = startDir;
|
|
80331
|
-
for (let i = 0; i < 8; i++) {
|
|
80332
|
-
if (existsSync19(join42(dir2, "pnpm-workspace.yaml"))) return dir2;
|
|
80333
|
-
const parent = dirname17(dir2);
|
|
80334
|
-
if (parent === dir2) break;
|
|
80335
|
-
dir2 = parent;
|
|
80336
|
-
}
|
|
80337
|
-
return startDir;
|
|
80338
|
-
}
|
|
80339
|
-
var repoRoot = (startDir = process.cwd()) => repoRootFrom(startDir);
|
|
80340
|
-
|
|
80341
|
-
// src/services/vps-audit.ts
|
|
80639
|
+
init_deps();
|
|
80640
|
+
import { existsSync as existsSync22, readFileSync as readFileSync22 } from "node:fs";
|
|
80641
|
+
import { join as join45 } from "node:path";
|
|
80642
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
80342
80643
|
init_sync_notify();
|
|
80343
80644
|
|
|
80344
80645
|
// src/vps/scoring.ts
|
|
@@ -80438,11 +80739,11 @@ function parseHealth(out4) {
|
|
|
80438
80739
|
return { uptime: h2.uptime ?? "", disk: h2.disk ?? "", mem: h2.mem ?? "", load: h2.load ?? "" };
|
|
80439
80740
|
}
|
|
80440
80741
|
var target2 = (v) => ({ host: v.host, port: v.port, user: v.user, keyPath: v.keyPath });
|
|
80441
|
-
var packagedScript = (f) =>
|
|
80442
|
-
var
|
|
80742
|
+
var packagedScript = (f) => fileURLToPath5(new URL(`../scripts/vps/${f}`, import.meta.url));
|
|
80743
|
+
var moduleDir2 = () => fileURLToPath5(new URL(".", import.meta.url));
|
|
80443
80744
|
var scriptPath = (f) => {
|
|
80444
80745
|
const packed = packagedScript(f);
|
|
80445
|
-
return
|
|
80746
|
+
return existsSync22(packed) ? packed : join45(repoRoot(moduleDir2()), "server", "scripts", "vps", f);
|
|
80446
80747
|
};
|
|
80447
80748
|
async function itemStatesOf(vpsId) {
|
|
80448
80749
|
const rows = await prisma.vpsItemState.findMany({ where: { vpsId } });
|
|
@@ -80452,7 +80753,7 @@ async function runAudit(v) {
|
|
|
80452
80753
|
const r = await sshExec(
|
|
80453
80754
|
target2(v),
|
|
80454
80755
|
"sudo -n bash -s",
|
|
80455
|
-
{ stdin:
|
|
80756
|
+
{ stdin: readFileSync22(scriptPath("audit.sh"), "utf8"), timeoutMs: 6e4 }
|
|
80456
80757
|
);
|
|
80457
80758
|
const audit = parseAudit(r.out);
|
|
80458
80759
|
if (r.code !== 0 || audit.length === 0) return { ok: false, out: r.out };
|
|
@@ -80532,7 +80833,7 @@ function parseProvisionSteps(out4) {
|
|
|
80532
80833
|
return m ? [{ item: m[1], status: m[2], detail: (m[3] ?? "").trim() }] : [];
|
|
80533
80834
|
});
|
|
80534
80835
|
}
|
|
80535
|
-
var script = () =>
|
|
80836
|
+
var script = () => readFileSync23(scriptPath("provision.sh"), "utf8");
|
|
80536
80837
|
async function probeComponents(v) {
|
|
80537
80838
|
const r = await sshExec(v, "env MODE=probe bash -s", { stdin: script(), timeoutMs: PROBE_TIMEOUT_MS });
|
|
80538
80839
|
const components2 = parseComponents(r.out);
|
|
@@ -80559,7 +80860,7 @@ async function readSetupToken(v, home3 = "/var/lib/hanoman") {
|
|
|
80559
80860
|
}
|
|
80560
80861
|
|
|
80561
80862
|
// src/services/vps-remediate.ts
|
|
80562
|
-
import { readFileSync as
|
|
80863
|
+
import { readFileSync as readFileSync24 } from "node:fs";
|
|
80563
80864
|
function parseSteps2(out4) {
|
|
80564
80865
|
return out4.split("\n").flatMap((line) => {
|
|
80565
80866
|
const m = line.match(/^STEP (\S+) (would|ok|fail)(?: (.*))?$/);
|
|
@@ -80570,7 +80871,7 @@ async function remediate(v, items, dryRun) {
|
|
|
80570
80871
|
const r = await sshExec(
|
|
80571
80872
|
v,
|
|
80572
80873
|
`sudo -n env ITEMS=${items.join(",")} SSH_PORT=${v.port} DRY_RUN=${dryRun ? "1" : ""} bash -s`,
|
|
80573
|
-
{ stdin:
|
|
80874
|
+
{ stdin: readFileSync24(scriptPath("remediate.sh"), "utf8"), timeoutMs: 3e5 }
|
|
80574
80875
|
);
|
|
80575
80876
|
return { ok: r.code === 0, steps: parseSteps2(r.out), out: r.out };
|
|
80576
80877
|
}
|
|
@@ -80686,48 +80987,48 @@ async function listVpsWithScore() {
|
|
|
80686
80987
|
// src/services/vps-key.ts
|
|
80687
80988
|
init_src2();
|
|
80688
80989
|
init_config2();
|
|
80689
|
-
import { execFileSync as
|
|
80690
|
-
import { chmodSync as
|
|
80990
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
80991
|
+
import { chmodSync as chmodSync10, copyFileSync, existsSync as existsSync23, mkdirSync as mkdirSync19, readFileSync as readFileSync25, unlinkSync as unlinkSync5 } from "node:fs";
|
|
80691
80992
|
import { homedir as homedir11 } from "node:os";
|
|
80692
|
-
import { join as
|
|
80993
|
+
import { join as join46 } from "node:path";
|
|
80693
80994
|
var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ?? resolveDataDirs().sshKeys;
|
|
80694
80995
|
var KEY_FILES = ["id_ed25519", "id_ed25519.pub"];
|
|
80695
80996
|
function adoptLegacyKey(dir2) {
|
|
80696
80997
|
if (effectiveStr("HANOMAN_SSH_KEY_DIR")) return;
|
|
80697
|
-
const legacy =
|
|
80998
|
+
const legacy = join46(homedir11(), ".hanoman");
|
|
80698
80999
|
if (legacy === dir2) return;
|
|
80699
|
-
if (!KEY_FILES.every((f) =>
|
|
80700
|
-
|
|
81000
|
+
if (!KEY_FILES.every((f) => existsSync23(join46(legacy, f)))) return;
|
|
81001
|
+
mkdirSync19(dir2, { recursive: true, mode: 448 });
|
|
80701
81002
|
for (const f of KEY_FILES) {
|
|
80702
|
-
copyFileSync(
|
|
80703
|
-
|
|
81003
|
+
copyFileSync(join46(legacy, f), join46(dir2, f));
|
|
81004
|
+
chmodSync10(join46(dir2, f), 384);
|
|
80704
81005
|
}
|
|
80705
|
-
for (const f of KEY_FILES) unlinkSync5(
|
|
81006
|
+
for (const f of KEY_FILES) unlinkSync5(join46(legacy, f));
|
|
80706
81007
|
console.log(`vps: key SSH dipindah dari ${legacy} ke ${dir2} (SPEC-846 \u2014 satu batas backup)`);
|
|
80707
81008
|
}
|
|
80708
81009
|
function ensureHanomanKey() {
|
|
80709
81010
|
const dir2 = keyDir();
|
|
80710
|
-
const privPath =
|
|
81011
|
+
const privPath = join46(dir2, KEY_FILES[0]);
|
|
80711
81012
|
const pubPath = `${privPath}.pub`;
|
|
80712
|
-
if (!
|
|
80713
|
-
if (!
|
|
80714
|
-
|
|
80715
|
-
|
|
81013
|
+
if (!existsSync23(privPath)) adoptLegacyKey(dir2);
|
|
81014
|
+
if (!existsSync23(privPath)) {
|
|
81015
|
+
mkdirSync19(dir2, { recursive: true, mode: 448 });
|
|
81016
|
+
execFileSync10(
|
|
80716
81017
|
"ssh-keygen",
|
|
80717
81018
|
["-t", "ed25519", "-N", "", "-C", "hanoman", "-f", privPath],
|
|
80718
81019
|
{ stdio: ["ignore", "ignore", "pipe"] }
|
|
80719
81020
|
);
|
|
80720
81021
|
}
|
|
80721
|
-
return { privPath, pubPath, pub:
|
|
81022
|
+
return { privPath, pubPath, pub: readFileSync25(pubPath, "utf8").trim() };
|
|
80722
81023
|
}
|
|
80723
81024
|
|
|
80724
81025
|
// src/services/vps-bootstrap.ts
|
|
80725
81026
|
var INSTALL_CMD = `mkdir -p ~/.ssh && chmod 700 ~/.ssh && touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && k=$(cat) && { grep -qxF "$k" ~/.ssh/authorized_keys || printf '%s\\n' "$k" >> ~/.ssh/authorized_keys; }`;
|
|
80726
81027
|
async function bootstrapKey(t, password) {
|
|
80727
81028
|
const { privPath, pub } = ensureHanomanKey();
|
|
80728
|
-
const
|
|
81029
|
+
const install2 = await sshExec(t, INSTALL_CMD, { stdin: `${pub}
|
|
80729
81030
|
`, password, timeoutMs: 6e4 });
|
|
80730
|
-
if (
|
|
81031
|
+
if (install2.code !== 0) return { ok: false, out: install2.out };
|
|
80731
81032
|
const verify = await sshExec({ ...t, keyPath: privPath }, "true", { timeoutMs: 3e4 });
|
|
80732
81033
|
if (verify.code !== 0) return { ok: false, out: verify.out };
|
|
80733
81034
|
return { ok: true, keyPath: privPath };
|
|
@@ -80738,7 +81039,7 @@ init_session_launch_gate();
|
|
|
80738
81039
|
init_settings3();
|
|
80739
81040
|
init_sync_notify();
|
|
80740
81041
|
function keyMissing(v) {
|
|
80741
|
-
return !!v.keyPath && !
|
|
81042
|
+
return !!v.keyPath && !existsSync24(v.keyPath);
|
|
80742
81043
|
}
|
|
80743
81044
|
async function vps_default(app3) {
|
|
80744
81045
|
app3.get("/vps", async () => listVpsWithScore());
|
|
@@ -80966,7 +81267,7 @@ async function vps_default(app3) {
|
|
|
80966
81267
|
const r = await sshExec(
|
|
80967
81268
|
v,
|
|
80968
81269
|
`sudo -n env SSH_PORT=${v.port} SSH_USER=${v.user} bash -s`,
|
|
80969
|
-
{ stdin:
|
|
81270
|
+
{ stdin: readFileSync26(scriptPath("harden.sh"), "utf8"), timeoutMs: 3e5 }
|
|
80970
81271
|
);
|
|
80971
81272
|
if (r.code !== 0) return reply.code(502).send({ error: "harden gagal lewat ssh", transcript: r.out });
|
|
80972
81273
|
const verify = await sshExec(v, "true", { timeoutMs: 3e4 });
|
|
@@ -81114,9 +81415,9 @@ async function antigravity(app3) {
|
|
|
81114
81415
|
// src/services/model-catalog.ts
|
|
81115
81416
|
init_src2();
|
|
81116
81417
|
init_src();
|
|
81117
|
-
import { randomUUID as
|
|
81118
|
-
import { readFile as
|
|
81119
|
-
import { dirname as dirname18, join as
|
|
81418
|
+
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
81419
|
+
import { readFile as readFile9, writeFile as writeFile6, rename as rename5, mkdir as mkdir9, rm as rm9 } from "node:fs/promises";
|
|
81420
|
+
import { dirname as dirname18, join as join48 } from "node:path";
|
|
81120
81421
|
|
|
81121
81422
|
// src/services/model-catalog-parser.ts
|
|
81122
81423
|
init_zod();
|
|
@@ -81223,7 +81524,7 @@ init_session_sandbox();
|
|
|
81223
81524
|
import { spawn as spawn7 } from "node:child_process";
|
|
81224
81525
|
import { mkdtemp as mkdtemp2, rm as rm8 } from "node:fs/promises";
|
|
81225
81526
|
import { tmpdir as tmpdir8 } from "node:os";
|
|
81226
|
-
import { join as
|
|
81527
|
+
import { join as join47 } from "node:path";
|
|
81227
81528
|
var MAX_BYTES = 4 * 1024 * 1024;
|
|
81228
81529
|
var TIMEOUT_MS = 2e4;
|
|
81229
81530
|
var quote3 = (s2) => "'" + s2.replaceAll("'", "'\\''") + "'";
|
|
@@ -81322,7 +81623,7 @@ function runCatalogCommand(command, agent, cwd, env, timeoutMs = TIMEOUT_MS) {
|
|
|
81322
81623
|
}
|
|
81323
81624
|
async function probeModelCatalog(agent) {
|
|
81324
81625
|
assertAntigravityLaunch(agent);
|
|
81325
|
-
const cwd = await mkdtemp2(
|
|
81626
|
+
const cwd = await mkdtemp2(join47(tmpdir8(), "hanoman-models-"));
|
|
81326
81627
|
const env = agentEnvironment(agent, process.env);
|
|
81327
81628
|
for (const key of ["HANOMAN_CLAUDE_BIN", "HANOMAN_CODEX_BIN", "HANOMAN_PODMAN_BIN", "HANOMAN_ANTIGRAVITY_BIN"]) {
|
|
81328
81629
|
const value = effectiveStr(key);
|
|
@@ -81425,7 +81726,7 @@ function createModelCatalogService(deps) {
|
|
|
81425
81726
|
async function writeModelCatalogCache(file, catalog, current = () => true) {
|
|
81426
81727
|
const sanitized = parseCachedCatalog(catalog);
|
|
81427
81728
|
await mkdir9(dirname18(file), { recursive: true });
|
|
81428
|
-
const temp = `${file}.${process.pid}.${
|
|
81729
|
+
const temp = `${file}.${process.pid}.${randomUUID14()}.tmp`;
|
|
81429
81730
|
try {
|
|
81430
81731
|
await writeFile6(temp, JSON.stringify(sanitized), { mode: 384, flag: "wx" });
|
|
81431
81732
|
if (current()) await rename5(temp, file);
|
|
@@ -81433,12 +81734,12 @@ async function writeModelCatalogCache(file, catalog, current = () => true) {
|
|
|
81433
81734
|
await rm9(temp, { force: true });
|
|
81434
81735
|
}
|
|
81435
81736
|
}
|
|
81436
|
-
var cacheFile = () =>
|
|
81737
|
+
var cacheFile = () => join48(resolveHome(), "model-catalog.json");
|
|
81437
81738
|
var modelCatalogService = createModelCatalogService({
|
|
81438
81739
|
probe: probeModelCatalog,
|
|
81439
81740
|
key: catalogProbeKey,
|
|
81440
81741
|
read: async () => {
|
|
81441
|
-
const content = await
|
|
81742
|
+
const content = await readFile9(cacheFile(), "utf8");
|
|
81442
81743
|
if (Buffer.byteLength(content) > 4 * 1024 * 1024) throw new Error("oversized cache");
|
|
81443
81744
|
return JSON.parse(content);
|
|
81444
81745
|
},
|
|
@@ -82622,8 +82923,8 @@ init_db();
|
|
|
82622
82923
|
init_pty();
|
|
82623
82924
|
init_config5();
|
|
82624
82925
|
init_inbox();
|
|
82625
|
-
import { mkdirSync as
|
|
82626
|
-
import { join as
|
|
82926
|
+
import { mkdirSync as mkdirSync20, writeFileSync as writeFileSync20 } from "node:fs";
|
|
82927
|
+
import { join as join49 } from "node:path";
|
|
82627
82928
|
init_human_input();
|
|
82628
82929
|
var idle = /* @__PURE__ */ new Set();
|
|
82629
82930
|
var sending = /* @__PURE__ */ new Set();
|
|
@@ -82634,7 +82935,7 @@ function isLeadIdle(id2) {
|
|
|
82634
82935
|
return idle.has(id2);
|
|
82635
82936
|
}
|
|
82636
82937
|
var HUMAN_QUIET_MS = 12e4;
|
|
82637
|
-
var batchDir = (projectId) =>
|
|
82938
|
+
var batchDir = (projectId) => join49(resolveHome(), "lead-batches", projectId);
|
|
82638
82939
|
var briefFn = async () => "(situasi tak tersedia \u2014 panggil /brief)";
|
|
82639
82940
|
function setBriefBuilder(fn) {
|
|
82640
82941
|
briefFn = fn;
|
|
@@ -82654,9 +82955,9 @@ var prodCourierDeps = {
|
|
|
82654
82955
|
brief: (p3) => briefFn(p3).catch(() => "(situasi tak tersedia \u2014 panggil /brief)"),
|
|
82655
82956
|
write: (projectId, text2) => {
|
|
82656
82957
|
const dir2 = batchDir(projectId);
|
|
82657
|
-
|
|
82658
|
-
const file =
|
|
82659
|
-
|
|
82958
|
+
mkdirSync20(dir2, { recursive: true });
|
|
82959
|
+
const file = join49(dir2, `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.md`);
|
|
82960
|
+
writeFileSync20(file, text2);
|
|
82660
82961
|
return file;
|
|
82661
82962
|
},
|
|
82662
82963
|
send: (id2, line) => sendToPane(id2, line),
|
|
@@ -83454,7 +83755,7 @@ init_src();
|
|
|
83454
83755
|
init_db();
|
|
83455
83756
|
import { gunzipSync, gzipSync } from "node:zlib";
|
|
83456
83757
|
import { mkdir as mkdir10, rename as rename6, writeFile as writeFile7 } from "node:fs/promises";
|
|
83457
|
-
import { join as
|
|
83758
|
+
import { join as join50 } from "node:path";
|
|
83458
83759
|
|
|
83459
83760
|
// src/services/device-auth.ts
|
|
83460
83761
|
function bearerToken2(req) {
|
|
@@ -83712,9 +84013,9 @@ async function sync_default(app3) {
|
|
|
83712
84013
|
const deviceId = req.device.id;
|
|
83713
84014
|
const withTranscriptKey = await Promise.all(parsed.data.entries.map(async (e) => {
|
|
83714
84015
|
if (!e.transcript) return e;
|
|
83715
|
-
const dir2 =
|
|
84016
|
+
const dir2 = join50(resolveDataDirs().home, "remote-transcripts", deviceId);
|
|
83716
84017
|
await mkdir10(dir2, { recursive: true, mode: 448 });
|
|
83717
|
-
const final =
|
|
84018
|
+
const final = join50(dir2, `${e.seq}.txt`);
|
|
83718
84019
|
const tmp = `${final}.tmp`;
|
|
83719
84020
|
await writeFile7(tmp, e.transcript, { encoding: "utf8", mode: 384 });
|
|
83720
84021
|
await rename6(tmp, final);
|
|
@@ -84269,9 +84570,9 @@ init_db();
|
|
|
84269
84570
|
init_db();
|
|
84270
84571
|
init_uploads();
|
|
84271
84572
|
init_src();
|
|
84272
|
-
import { createHash as
|
|
84573
|
+
import { createHash as createHash21, randomBytes as randomBytes7 } from "node:crypto";
|
|
84273
84574
|
function hashAccessKey(key) {
|
|
84274
|
-
return
|
|
84575
|
+
return createHash21("sha256").update(key).digest("hex");
|
|
84275
84576
|
}
|
|
84276
84577
|
function generateAccessKey() {
|
|
84277
84578
|
const key = "hnm_tkt_" + randomBytes7(24).toString("hex");
|
|
@@ -87117,9 +87418,9 @@ init_codex_trust();
|
|
|
87117
87418
|
init_qa_attachment_transfer();
|
|
87118
87419
|
init_uploads();
|
|
87119
87420
|
init_ask_via2();
|
|
87120
|
-
import { createHash as
|
|
87121
|
-
import { execFileSync as
|
|
87122
|
-
import { join as
|
|
87421
|
+
import { createHash as createHash22 } from "node:crypto";
|
|
87422
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
87423
|
+
import { join as join52 } from "node:path";
|
|
87123
87424
|
var QaSessionError = class extends Error {
|
|
87124
87425
|
constructor(code, message, needsBind = false) {
|
|
87125
87426
|
super(message);
|
|
@@ -87127,7 +87428,7 @@ var QaSessionError = class extends Error {
|
|
|
87127
87428
|
this.needsBind = needsBind;
|
|
87128
87429
|
}
|
|
87129
87430
|
};
|
|
87130
|
-
var qaSessionId = (findingId) => `qa-${
|
|
87431
|
+
var qaSessionId = (findingId) => `qa-${createHash22("sha256").update(findingId).digest("hex").slice(0, 24)}`;
|
|
87131
87432
|
async function startQaFindingSession(d, fid) {
|
|
87132
87433
|
const f = d.findings.find((x) => x.id === fid);
|
|
87133
87434
|
if (!f) throw new QaSessionError(404, "temuan tidak ditemukan");
|
|
@@ -87146,14 +87447,14 @@ async function startQaFindingSession(d, fid) {
|
|
|
87146
87447
|
const saved = await saveSessionUpload(id2, bytes, a.mimeType);
|
|
87147
87448
|
files2.push(`${JSON.stringify(a.filename)} (${a.mimeType}): ${saved.path}`);
|
|
87148
87449
|
}
|
|
87149
|
-
const wt =
|
|
87450
|
+
const wt = join52(repoDir, ".worktrees", id2);
|
|
87150
87451
|
const branch = `qa/${id2.slice(3)}`;
|
|
87151
87452
|
const reused = realGit.worktreeAlive(wt);
|
|
87152
87453
|
try {
|
|
87153
87454
|
if (!reused) {
|
|
87154
87455
|
const base3 = realGit.revParse(repoDir, branch) ? branch : "HEAD";
|
|
87155
87456
|
realGit.addWorktree(repoDir, wt, base3);
|
|
87156
|
-
|
|
87457
|
+
execFileSync11("git", ["checkout", "-B", branch], { cwd: wt, encoding: "utf8" });
|
|
87157
87458
|
}
|
|
87158
87459
|
} catch (e) {
|
|
87159
87460
|
throw new QaSessionError(422, `Gagal membuat worktree QA: ${e.message}`);
|
|
@@ -88689,9 +88990,9 @@ ${baru}`;
|
|
|
88689
88990
|
// src/services/portal-chat/workspace.ts
|
|
88690
88991
|
init_src();
|
|
88691
88992
|
init_db();
|
|
88692
|
-
import { mkdirSync as
|
|
88993
|
+
import { mkdirSync as mkdirSync21, mkdtempSync as mkdtempSync5, rmSync as rmSync14, writeFileSync as writeFileSync21 } from "node:fs";
|
|
88693
88994
|
import { tmpdir as tmpdir9 } from "node:os";
|
|
88694
|
-
import { join as
|
|
88995
|
+
import { join as join53 } from "node:path";
|
|
88695
88996
|
var STAGE_LABEL = {
|
|
88696
88997
|
brainstorming: "Dirumuskan",
|
|
88697
88998
|
objective: "Dirumuskan",
|
|
@@ -88734,7 +89035,7 @@ function renderTicketDoc(tickets2) {
|
|
|
88734
89035
|
${baris.join("\n")}`;
|
|
88735
89036
|
}
|
|
88736
89037
|
async function buildChatWorkspace(projectId) {
|
|
88737
|
-
const dir2 = mkdtempSync5(
|
|
89038
|
+
const dir2 = mkdtempSync5(join53(tmpdir9(), "hanoman-portal-chat-"));
|
|
88738
89039
|
try {
|
|
88739
89040
|
const project = await prisma.project.findUnique({
|
|
88740
89041
|
where: { id: projectId },
|
|
@@ -88749,7 +89050,7 @@ async function buildChatWorkspace(projectId) {
|
|
|
88749
89050
|
});
|
|
88750
89051
|
const files2 = [];
|
|
88751
89052
|
const tulis = (rel, isi) => {
|
|
88752
|
-
|
|
89053
|
+
writeFileSync21(join53(dir2, rel), isi, { mode: 384 });
|
|
88753
89054
|
files2.push(rel);
|
|
88754
89055
|
};
|
|
88755
89056
|
tulis("project.md", renderProjectDoc(project));
|
|
@@ -88757,16 +89058,16 @@ async function buildChatWorkspace(projectId) {
|
|
|
88757
89058
|
tulis("laporan.md", renderTicketDoc(tickets2.map((t) => toPortalTicket(t, null))));
|
|
88758
89059
|
const prds = await listPrds(projectId);
|
|
88759
89060
|
if (prds.length) {
|
|
88760
|
-
|
|
89061
|
+
mkdirSync21(join53(dir2, "dokumen"), { mode: 448 });
|
|
88761
89062
|
for (const prd of prds) {
|
|
88762
89063
|
const isi = await readPrd(projectId, prd.path);
|
|
88763
89064
|
if (!isi) continue;
|
|
88764
|
-
tulis(
|
|
89065
|
+
tulis(join53("dokumen", `${prd.slug.replaceAll("/", "-")}.md`), isi);
|
|
88765
89066
|
}
|
|
88766
89067
|
}
|
|
88767
|
-
return { dir: dir2, files: files2, cleanup: () =>
|
|
89068
|
+
return { dir: dir2, files: files2, cleanup: () => rmSync14(dir2, { recursive: true, force: true }) };
|
|
88768
89069
|
} catch (error) {
|
|
88769
|
-
|
|
89070
|
+
rmSync14(dir2, { recursive: true, force: true });
|
|
88770
89071
|
throw error;
|
|
88771
89072
|
}
|
|
88772
89073
|
}
|
|
@@ -89286,20 +89587,20 @@ init_src();
|
|
|
89286
89587
|
init_db();
|
|
89287
89588
|
|
|
89288
89589
|
// src/services/bootstrap.ts
|
|
89289
|
-
import { createHash as
|
|
89290
|
-
import { chmod as chmod2, lstat as lstat3, mkdir as mkdir11, readFile as
|
|
89291
|
-
import { join as
|
|
89590
|
+
import { createHash as createHash23, randomBytes as randomBytes11, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
|
|
89591
|
+
import { chmod as chmod2, lstat as lstat3, mkdir as mkdir11, readFile as readFile10, unlink as unlink5, writeFile as writeFile8 } from "node:fs/promises";
|
|
89592
|
+
import { join as join54 } from "node:path";
|
|
89292
89593
|
var TTL_MS7 = 15 * 6e4;
|
|
89293
89594
|
var SETUP_TOKEN_FILE = "setup.token";
|
|
89294
89595
|
var BootstrapError = class extends Error {
|
|
89295
89596
|
code = "BOOTSTRAP_PROOF";
|
|
89296
89597
|
};
|
|
89297
|
-
var tokenPath = (home3) =>
|
|
89598
|
+
var tokenPath = (home3) => join54(home3, SETUP_TOKEN_FILE);
|
|
89298
89599
|
async function readStored(home3) {
|
|
89299
89600
|
try {
|
|
89300
89601
|
const info = await lstat3(tokenPath(home3));
|
|
89301
89602
|
if (info.isSymbolicLink() || !info.isFile()) throw new BootstrapError("invalid setup proof");
|
|
89302
|
-
const [token, expires] = (await
|
|
89603
|
+
const [token, expires] = (await readFile10(tokenPath(home3), "utf8")).trim().split("\n");
|
|
89303
89604
|
const expiresAt = Date.parse(expires ?? "");
|
|
89304
89605
|
if (!token || !Number.isFinite(expiresAt)) throw new BootstrapError("invalid setup proof");
|
|
89305
89606
|
return { token, expiresAt };
|
|
@@ -89339,8 +89640,8 @@ ${new Date(expiresAt).toISOString()}
|
|
|
89339
89640
|
}
|
|
89340
89641
|
async function verifySetupToken(candidate, home3, now = Date.now()) {
|
|
89341
89642
|
const stored = await readStored(home3);
|
|
89342
|
-
const got =
|
|
89343
|
-
const want =
|
|
89643
|
+
const got = createHash23("sha256").update(candidate).digest();
|
|
89644
|
+
const want = createHash23("sha256").update(stored.token).digest();
|
|
89344
89645
|
if (stored.expiresAt <= now || !timingSafeEqual5(got, want)) throw new BootstrapError("invalid setup proof");
|
|
89345
89646
|
}
|
|
89346
89647
|
async function consumeSetupToken(home3) {
|
|
@@ -89759,7 +90060,7 @@ init_event_log();
|
|
|
89759
90060
|
|
|
89760
90061
|
// src/services/remote-access-request.ts
|
|
89761
90062
|
init_src();
|
|
89762
|
-
import { randomUUID as
|
|
90063
|
+
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
89763
90064
|
var REQUEST_TIER_LABEL = { lihat: "Lihat", kerja: "Kerja", tulis: "Tulis" };
|
|
89764
90065
|
var entries3 = [];
|
|
89765
90066
|
var accepted = [];
|
|
@@ -89783,7 +90084,7 @@ function createAccessRequest(input, now = Date.now()) {
|
|
|
89783
90084
|
return { ok: false, reason: "rate-limited", retryAfterMs: accepted[0] + ACCESS_REQUEST_WINDOW_MS - now };
|
|
89784
90085
|
accepted.push(now);
|
|
89785
90086
|
const entry2 = {
|
|
89786
|
-
id:
|
|
90087
|
+
id: randomUUID15(),
|
|
89787
90088
|
tier: input.tier,
|
|
89788
90089
|
state: "pending",
|
|
89789
90090
|
requestedAt: now,
|
|
@@ -90131,8 +90432,8 @@ init_db();
|
|
|
90131
90432
|
init_event_log();
|
|
90132
90433
|
init_transcript_store();
|
|
90133
90434
|
init_src2();
|
|
90134
|
-
import { readFile as
|
|
90135
|
-
import { join as
|
|
90435
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
90436
|
+
import { join as join55 } from "node:path";
|
|
90136
90437
|
function decodeCursor(c) {
|
|
90137
90438
|
if (!c) return null;
|
|
90138
90439
|
try {
|
|
@@ -90170,9 +90471,9 @@ async function readRemoteTranscript(id2) {
|
|
|
90170
90471
|
const row = await prisma.logEntry.findUnique({ where: { id: id2 } });
|
|
90171
90472
|
if (!row || row.lane !== "transcript" || !row.transcriptKey) return null;
|
|
90172
90473
|
if (row.deviceId === "local") return readTranscript(row.transcriptKey);
|
|
90173
|
-
const path =
|
|
90474
|
+
const path = join55(resolveDataDirs().home, "remote-transcripts", row.transcriptKey);
|
|
90174
90475
|
try {
|
|
90175
|
-
return await
|
|
90476
|
+
return await readFile11(path, "utf8");
|
|
90176
90477
|
} catch {
|
|
90177
90478
|
return null;
|
|
90178
90479
|
}
|
|
@@ -90367,7 +90668,7 @@ var SETUP_PUBLIC = /* @__PURE__ */ new Set([
|
|
|
90367
90668
|
"POST /api/setup"
|
|
90368
90669
|
]);
|
|
90369
90670
|
function buildApp({ requireAuth = true, agentDocFile, env = process.env } = {}) {
|
|
90370
|
-
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname19(
|
|
90671
|
+
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname19(fileURLToPath6(import.meta.url)), env, existsSync25);
|
|
90371
90672
|
const ingress = loadIngressPolicy(env);
|
|
90372
90673
|
const app3 = Fastify({ logger: false, trustProxy: trustProxyFromEnv(env) });
|
|
90373
90674
|
app3.setErrorHandler((error, _req, reply) => {
|
|
@@ -90516,7 +90817,7 @@ function buildApp({ requireAuth = true, agentDocFile, env = process.env } = {})
|
|
|
90516
90817
|
await api.register(qaBacklog);
|
|
90517
90818
|
}, { prefix: "/api" });
|
|
90518
90819
|
if (shouldServeWeb(env)) {
|
|
90519
|
-
const dist = pickWebDir(dirname19(
|
|
90820
|
+
const dist = pickWebDir(dirname19(fileURLToPath6(import.meta.url)), env, existsSync25);
|
|
90520
90821
|
if (dist) {
|
|
90521
90822
|
app3.register(fastifyStatic, { root: dist });
|
|
90522
90823
|
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"));
|
|
@@ -90961,6 +91262,7 @@ async function resumeReconciledSessions(cutoff, deps = prodDeps4) {
|
|
|
90961
91262
|
init_event_log();
|
|
90962
91263
|
init_settings3();
|
|
90963
91264
|
init_custom_agents2();
|
|
91265
|
+
init_bundled_skills();
|
|
90964
91266
|
init_pty();
|
|
90965
91267
|
init_bootstrap();
|
|
90966
91268
|
|
|
@@ -90971,7 +91273,7 @@ init_tap();
|
|
|
90971
91273
|
init_src();
|
|
90972
91274
|
init_db();
|
|
90973
91275
|
init_actor();
|
|
90974
|
-
import { randomUUID as
|
|
91276
|
+
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
90975
91277
|
function skipped(def2, row) {
|
|
90976
91278
|
if (!def2.skipWhen || !row) return false;
|
|
90977
91279
|
return row[def2.skipWhen.field] === def2.skipWhen.equals;
|
|
@@ -91037,7 +91339,7 @@ async function emitWebhook(i) {
|
|
|
91037
91339
|
const targets = matchingEndpoints(type, projectId);
|
|
91038
91340
|
if (!targets.length) return;
|
|
91039
91341
|
const name2 = projectId ? (await prisma.project.findUnique({ where: { id: projectId }, select: { name: true } }))?.name ?? null : null;
|
|
91040
|
-
const env = buildEnvelope(i, name2, (/* @__PURE__ */ new Date()).toISOString(), `evt_${
|
|
91342
|
+
const env = buildEnvelope(i, name2, (/* @__PURE__ */ new Date()).toISOString(), `evt_${randomUUID16().replace(/-/g, "")}`);
|
|
91041
91343
|
if (!env) return;
|
|
91042
91344
|
await enqueueEnvelope(env, targets);
|
|
91043
91345
|
} catch (e) {
|
|
@@ -91452,9 +91754,9 @@ async function pruneLogs(now, retention, opts2 = {}) {
|
|
|
91452
91754
|
}
|
|
91453
91755
|
async function reconcileRemoteTranscripts() {
|
|
91454
91756
|
const { readdir: readdir10, unlink: unlink6, stat: stat8 } = await import("node:fs/promises");
|
|
91455
|
-
const { join:
|
|
91757
|
+
const { join: join56 } = await import("node:path");
|
|
91456
91758
|
const { resolveDataDirs: resolveDataDirs2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
|
|
91457
|
-
const root =
|
|
91759
|
+
const root = join56(resolveDataDirs2().home, "remote-transcripts");
|
|
91458
91760
|
let deviceDirs;
|
|
91459
91761
|
try {
|
|
91460
91762
|
deviceDirs = await readdir10(root);
|
|
@@ -91468,16 +91770,16 @@ async function reconcileRemoteTranscripts() {
|
|
|
91468
91770
|
for (const dev of deviceDirs) {
|
|
91469
91771
|
let files2;
|
|
91470
91772
|
try {
|
|
91471
|
-
files2 = await readdir10(
|
|
91773
|
+
files2 = await readdir10(join56(root, dev));
|
|
91472
91774
|
} catch {
|
|
91473
91775
|
continue;
|
|
91474
91776
|
}
|
|
91475
91777
|
for (const f of files2) {
|
|
91476
91778
|
const key = `${dev}/${f}`;
|
|
91477
91779
|
if (referenced.has(key)) continue;
|
|
91478
|
-
const st2 = await stat8(
|
|
91780
|
+
const st2 = await stat8(join56(root, dev, f)).catch(() => null);
|
|
91479
91781
|
if (!st2 || Date.now() - st2.mtimeMs < gracePeriodMs) continue;
|
|
91480
|
-
await unlink6(
|
|
91782
|
+
await unlink6(join56(root, dev, f)).catch(() => {
|
|
91481
91783
|
});
|
|
91482
91784
|
orphans++;
|
|
91483
91785
|
}
|
|
@@ -91712,6 +92014,9 @@ bootstrapReady.then(async () => {
|
|
|
91712
92014
|
console.error("config runtime gagal dimuat \u2014 memakai env/default:", e);
|
|
91713
92015
|
}
|
|
91714
92016
|
await installCustomAgents();
|
|
92017
|
+
const skills2 = seedBundledSkills(home2);
|
|
92018
|
+
if (skills2.seeded.length || skills2.updated.length)
|
|
92019
|
+
console.log(`skill bundel: di-seed ${skills2.seeded.join(", ") || "-"} \xB7 diperbarui ${skills2.updated.join(", ") || "-"}`);
|
|
91715
92020
|
return app2.listen({ port, host });
|
|
91716
92021
|
}).then(async () => {
|
|
91717
92022
|
console.log(`hanoman api ${host}:${port}`);
|