hanoman 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +3 -3
- package/dist/cli.js +80 -8
- package/dist/server.js +366 -66
- package/docs/agent-integration.md +5 -2
- package/package.json +1 -1
- package/prisma/schema.prisma +1 -1
- package/web/assets/{index-BrlNu5Qe.js → index-BJTj--gG.js} +1546 -1546
- package/web/index.html +1 -1
package/dist/server.js
CHANGED
|
@@ -4341,7 +4341,7 @@ function cmpVersion(a, b) {
|
|
|
4341
4341
|
}
|
|
4342
4342
|
return 0;
|
|
4343
4343
|
}
|
|
4344
|
-
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSpec, NOTIFY_SOUNDS, MODELS, EFFORTS, E_5_6, E_LUNA, E_BASE, CODEX_MODELS, RETIRED_CODEX_MODELS, zCodex, CODEX_DEFAULTS, zSourceCommon, zScheduler, SCHEDULER_DEFAULTS, zGoal, GOAL_DEFAULTS, zConflict, CONFLICT_DEFAULTS, zLeadEngine, zLead, LEAD_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
|
|
4344
|
+
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSpec, NOTIFY_SOUNDS, MODELS, EFFORTS, E_5_6, E_LUNA, E_BASE, CODEX_MODELS, RETIRED_CODEX_MODELS, zCodex, CODEX_DEFAULTS, zSourceCommon, zScheduler, SCHEDULER_DEFAULTS, zGoal, GOAL_DEFAULTS, zConflict, CONFLICT_DEFAULTS, CHANGELOG_ENGINE_DEFAULTS, zLeadEngine, zLead, LEAD_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
|
|
4345
4345
|
var init_entities = __esm({
|
|
4346
4346
|
"../shared/src/entities.ts"() {
|
|
4347
4347
|
"use strict";
|
|
@@ -4491,6 +4491,7 @@ var init_entities = __esm({
|
|
|
4491
4491
|
effort: external_exports.string().default("xhigh")
|
|
4492
4492
|
});
|
|
4493
4493
|
CONFLICT_DEFAULTS = zConflict.parse({});
|
|
4494
|
+
CHANGELOG_ENGINE_DEFAULTS = zAgentEngine.parse({});
|
|
4494
4495
|
zLeadEngine = zAgentEngine;
|
|
4495
4496
|
zLead = external_exports.object({
|
|
4496
4497
|
enabled: external_exports.boolean().default(false),
|
|
@@ -4562,8 +4563,10 @@ var init_entities = __esm({
|
|
|
4562
4563
|
// SPEC-383 · ADR-0081 · default sesi konflik rebase/merge
|
|
4563
4564
|
lead: zLead.default(LEAD_DEFAULTS),
|
|
4564
4565
|
// SPEC-409 · ADR-0091 · hanoman-lead (default mati)
|
|
4565
|
-
telegram: zTelegramSettings.default(TELEGRAM_DEFAULTS)
|
|
4566
|
+
telegram: zTelegramSettings.default(TELEGRAM_DEFAULTS),
|
|
4566
4567
|
// SPEC-476 · ADR-0096 · gateway Telegram (default mati)
|
|
4568
|
+
changelog: zAgentEngine.default(CHANGELOG_ENGINE_DEFAULTS)
|
|
4569
|
+
// SPEC-518 · agen pembuat changelog (opt-in, mati)
|
|
4567
4570
|
});
|
|
4568
4571
|
zNotification = external_exports.object({
|
|
4569
4572
|
id: external_exports.string(),
|
|
@@ -5092,11 +5095,46 @@ var init_lead = __esm({
|
|
|
5092
5095
|
}
|
|
5093
5096
|
});
|
|
5094
5097
|
|
|
5098
|
+
// ../shared/src/prd-status.ts
|
|
5099
|
+
function prdBranchFor(prdPath) {
|
|
5100
|
+
if (!prdPath.startsWith(PRD_DIR) || !prdPath.endsWith(".md")) return null;
|
|
5101
|
+
const slug = prdPath.slice(PRD_DIR.length, -3);
|
|
5102
|
+
return slug ? `prd/${slug}` : null;
|
|
5103
|
+
}
|
|
5104
|
+
function payloadText(payload) {
|
|
5105
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return "";
|
|
5106
|
+
const p = payload;
|
|
5107
|
+
return [p.context, p.goal].filter((v) => typeof v === "string").join("\n");
|
|
5108
|
+
}
|
|
5109
|
+
function specDerivesFromPrd(spec, prdPath) {
|
|
5110
|
+
const branch = prdBranchFor(prdPath);
|
|
5111
|
+
if (!branch) return false;
|
|
5112
|
+
if (spec.branchFrom === branch) return true;
|
|
5113
|
+
return payloadText(spec.payload).includes(prdPath);
|
|
5114
|
+
}
|
|
5115
|
+
function prdStatusOf(prdPath, specs) {
|
|
5116
|
+
const derived = specs.filter((s2) => specDerivesFromPrd(s2, prdPath));
|
|
5117
|
+
const specCount = derived.length;
|
|
5118
|
+
const doneCount = derived.filter((s2) => s2.stage === "done").length;
|
|
5119
|
+
const status = specCount === 0 ? "draft" : doneCount === specCount ? "terwujud" : "dieskalasi";
|
|
5120
|
+
return { status, specCount, doneCount };
|
|
5121
|
+
}
|
|
5122
|
+
var PRD_STATUSES, zPrdStatus, PRD_DIR;
|
|
5123
|
+
var init_prd_status = __esm({
|
|
5124
|
+
"../shared/src/prd-status.ts"() {
|
|
5125
|
+
"use strict";
|
|
5126
|
+
init_zod();
|
|
5127
|
+
PRD_STATUSES = ["draft", "dieskalasi", "terwujud"];
|
|
5128
|
+
zPrdStatus = external_exports.enum(PRD_STATUSES);
|
|
5129
|
+
PRD_DIR = "docs/prd/";
|
|
5130
|
+
}
|
|
5131
|
+
});
|
|
5132
|
+
|
|
5095
5133
|
// ../shared/src/dto.ts
|
|
5096
5134
|
function flowForSource(source) {
|
|
5097
5135
|
return source === "qa" ? "qa" : source === "audit" ? "audit" : source === "goal" ? "goal" : "feature";
|
|
5098
5136
|
}
|
|
5099
|
-
var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, zFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, zLogin, zSignup, zChangePassword, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, UPDATE_RESTART_EXIT, zUpdateApplyBody, zTicketView, zTicketAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus;
|
|
5137
|
+
var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerQueueCounts, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, zFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, zLogin, zSignup, zChangePassword, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, UPDATE_RESTART_EXIT, zUpdateApplyBody, zTicketView, zTicketAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus;
|
|
5100
5138
|
var init_dto = __esm({
|
|
5101
5139
|
"../shared/src/dto.ts"() {
|
|
5102
5140
|
"use strict";
|
|
@@ -5104,6 +5142,7 @@ var init_dto = __esm({
|
|
|
5104
5142
|
init_entities();
|
|
5105
5143
|
init_lead();
|
|
5106
5144
|
init_auto_merge();
|
|
5145
|
+
init_prd_status();
|
|
5107
5146
|
init_enums();
|
|
5108
5147
|
zIssueDeviceToken = external_exports.object({ name: external_exports.string().min(1) });
|
|
5109
5148
|
zSessionResult = external_exports.object({
|
|
@@ -5248,12 +5287,21 @@ var init_dto = __esm({
|
|
|
5248
5287
|
decision: external_exports.boolean(),
|
|
5249
5288
|
exited: external_exports.boolean()
|
|
5250
5289
|
});
|
|
5290
|
+
zSchedulerQueueCounts = external_exports.object({
|
|
5291
|
+
queued: external_exports.number().int(),
|
|
5292
|
+
launched: external_exports.number().int(),
|
|
5293
|
+
done: external_exports.number().int(),
|
|
5294
|
+
failed: external_exports.number().int(),
|
|
5295
|
+
// SPEC-522 · `canceled` adalah nilai kelima `status`; hitungannya ikut supaya panel bisa
|
|
5296
|
+
// menampilkan seksi "Dibatalkan" tanpa memuat daftarnya.
|
|
5297
|
+
canceled: external_exports.number().int()
|
|
5298
|
+
});
|
|
5251
5299
|
zSchedulerState = external_exports.object({
|
|
5252
5300
|
config: zScheduler,
|
|
5253
5301
|
cap: external_exports.number(),
|
|
5254
5302
|
liveCount: external_exports.number(),
|
|
5255
5303
|
sources: external_exports.array(zSchedulerSourceView),
|
|
5256
|
-
|
|
5304
|
+
queueCounts: zSchedulerQueueCounts,
|
|
5257
5305
|
sessions: external_exports.array(zSchedulerSessionView)
|
|
5258
5306
|
});
|
|
5259
5307
|
zLeadDecisionView = external_exports.object({
|
|
@@ -5361,7 +5409,13 @@ var init_dto = __esm({
|
|
|
5361
5409
|
title: external_exports.string(),
|
|
5362
5410
|
live: external_exports.boolean(),
|
|
5363
5411
|
projectId: external_exports.string(),
|
|
5364
|
-
projectName: external_exports.string()
|
|
5412
|
+
projectName: external_exports.string(),
|
|
5413
|
+
// SPEC-520 · status TURUNAN dari backlog yang lahir dari PRD ini (ADR-0018/0019) — bukan
|
|
5414
|
+
// kolom, bukan prosa di dalam dokumennya. `live` di atas menjawab pertanyaan LAIN
|
|
5415
|
+
// (freshest-wins dari worktree sesi prd hidup) dan sengaja tetap ortogonal.
|
|
5416
|
+
status: zPrdStatus,
|
|
5417
|
+
specCount: external_exports.number().int().nonnegative(),
|
|
5418
|
+
doneCount: external_exports.number().int().nonnegative()
|
|
5365
5419
|
});
|
|
5366
5420
|
zBreakdownItem = external_exports.object({
|
|
5367
5421
|
title: external_exports.string().min(1),
|
|
@@ -5406,9 +5460,10 @@ var init_dto = __esm({
|
|
|
5406
5460
|
// pipeline claude). DIDAHULUKAN: z.object non-strict membuang key asing, jadi bila varian
|
|
5407
5461
|
// longgar {project,flow?} lebih dulu, {project,shell:true} akan lolos sbg plain (shell dibuang).
|
|
5408
5462
|
external_exports.object({ project: external_exports.string(), shell: external_exports.literal(true) }),
|
|
5409
|
-
//
|
|
5410
|
-
//
|
|
5411
|
-
|
|
5463
|
+
// SPEC-166 · "reverse" = sesi project-level di worktree-nya sendiri, menyusun Source of Truth
|
|
5464
|
+
// dari kode. TANPA override runtime: sesi project-level mengikuti Setting.agent (ADR-0074).
|
|
5465
|
+
// Terminal biasa (tanpa flow) kini punya variannya SENDIRI di bawah — lihat SPEC-517.
|
|
5466
|
+
external_exports.object({ project: external_exports.string(), flow: external_exports.literal("reverse") }),
|
|
5412
5467
|
// SPEC-210 · sesi prd project-level di worktree sendiri; menghasilkan dokumen PRD dari brief.
|
|
5413
5468
|
// SPEC-340 · ADR-0076 · eskalasi audit → PRD: branchFrom = branch audit (worktree lahir dari sana,
|
|
5414
5469
|
// resolveCommit + fallback origin/<rev>), fromAudit = id spec audit (isi dokumennya disematkan ke
|
|
@@ -5425,6 +5480,18 @@ var init_dto = __esm({
|
|
|
5425
5480
|
// SPEC-222 · scaffold: sesi project-level from-scratch, menyusun SoT dari ide. Tanpa brief
|
|
5426
5481
|
// (diseed dari Project.desc), tanpa Spec — cermin reverse.
|
|
5427
5482
|
external_exports.object({ project: external_exports.string(), flow: external_exports.literal("scaffold") }),
|
|
5483
|
+
// SPEC-517 · terminal agen biasa: agen (claude|codex) + model + effort boleh dipilih PER SESI,
|
|
5484
|
+
// seperti picker Start backlog (ADR-0061/0074). Kosong → default global (Setting).
|
|
5485
|
+
// `flow: z.undefined()` BUKAN hiasan: varian ini permisif dan diletakkan SESUDAH semua varian
|
|
5486
|
+
// ber-flow, jadi tanpa gerbang itu body flow yang CACAT ({project, flow:"prd"} tanpa brief)
|
|
5487
|
+
// akan lolos ke sini dan melahirkan terminal biasa secara senyap alih-alih dijawab 400.
|
|
5488
|
+
external_exports.object({
|
|
5489
|
+
project: external_exports.string(),
|
|
5490
|
+
flow: external_exports.undefined(),
|
|
5491
|
+
agent: zAgent.optional(),
|
|
5492
|
+
model: external_exports.string().optional(),
|
|
5493
|
+
effort: external_exports.string().optional()
|
|
5494
|
+
}),
|
|
5428
5495
|
// SPEC-252 · ADR-0061 — model & effort per SESI: override opsional saat Start; kosong → global.
|
|
5429
5496
|
// SPEC-332 · ADR-0073 — mode goal per SESI: `goal` undefined → ikut Setting.goal.enabled,
|
|
5430
5497
|
// false → mati walau global nyala; `goalCondition` kosong → template global → default bawaan.
|
|
@@ -5674,6 +5741,12 @@ var init_api = __esm({
|
|
|
5674
5741
|
// SPEC-299 · panel scheduler (daun #6) — konsumen read-only fondasi SPEC-294/ADR-0072.
|
|
5675
5742
|
schedulerConfig: `${API}/scheduler/config`,
|
|
5676
5743
|
schedulerState: `${API}/scheduler/state`,
|
|
5744
|
+
// SPEC-522 · ADR-0106 · batalkan / antre lagi SATU baris antrean. Di bawah prefix `scheduler`
|
|
5745
|
+
// supaya capability-nya turunan peta yang sudah ada (settings, MENURUT METHOD) — tanpa baris peta baru.
|
|
5746
|
+
schedulerQueueCancel: (id) => `${API}/scheduler/queue/${encodeURIComponent(id)}/cancel`,
|
|
5747
|
+
schedulerQueueRequeue: (id) => `${API}/scheduler/queue/${encodeURIComponent(id)}/requeue`,
|
|
5748
|
+
// SPEC-523 · antrean sebagai daftar berhalaman (page/limit + status), lepas dari `state`.
|
|
5749
|
+
schedulerQueue: `${API}/scheduler/queue`,
|
|
5677
5750
|
// SPEC-409 · ADR-0091 · hanoman-lead. Semua HTTP (polling) — tak ada kanal WS baru (AC-26).
|
|
5678
5751
|
leadConfig: `${API}/lead/config`,
|
|
5679
5752
|
leadStatus: `${API}/lead/status`,
|
|
@@ -7131,6 +7204,11 @@ function defaultRange(today) {
|
|
|
7131
7204
|
const from = new Date(today.getFullYear(), today.getMonth(), today.getDate() - (DEFAULT_RANGE_DAYS - 1));
|
|
7132
7205
|
return { from: dayString(from), to: dayString(today) };
|
|
7133
7206
|
}
|
|
7207
|
+
function changelogMatches(row, q) {
|
|
7208
|
+
const needle = (q ?? "").trim().toLowerCase();
|
|
7209
|
+
if (!needle) return true;
|
|
7210
|
+
return row.title.toLowerCase().includes(needle) || row.body.toLowerCase().includes(needle) || row.mode.toLowerCase().includes(needle);
|
|
7211
|
+
}
|
|
7134
7212
|
var CHANGELOG_MODES, zChangelogMode, zDay, zChangelogRequest, zChangelogView, DEFAULT_RANGE_DAYS;
|
|
7135
7213
|
var init_changelog = __esm({
|
|
7136
7214
|
"../shared/src/changelog.ts"() {
|
|
@@ -7183,6 +7261,7 @@ var init_src = __esm({
|
|
|
7183
7261
|
init_api();
|
|
7184
7262
|
init_coverage();
|
|
7185
7263
|
init_ticket_status();
|
|
7264
|
+
init_prd_status();
|
|
7186
7265
|
init_session_kind();
|
|
7187
7266
|
init_config();
|
|
7188
7267
|
init_config_registry();
|
|
@@ -7387,6 +7466,25 @@ var init_verify_scope = __esm({
|
|
|
7387
7466
|
}
|
|
7388
7467
|
});
|
|
7389
7468
|
|
|
7469
|
+
// ../runner/src/code-style.ts
|
|
7470
|
+
var CODE_STYLE_CLAUSE;
|
|
7471
|
+
var init_code_style = __esm({
|
|
7472
|
+
"../runner/src/code-style.ts"() {
|
|
7473
|
+
"use strict";
|
|
7474
|
+
CODE_STYLE_CLAUSE = [
|
|
7475
|
+
"Gaya kode \u2014 berlaku setiap kali kamu menulis atau mengubah kode:",
|
|
7476
|
+
"- Tulis kode yang rapi dan mengikuti idiom, penamaan, serta struktur kode di sekitarnya.",
|
|
7477
|
+
" Kodemu harus terbaca seperti kode yang sudah ada di berkas itu, bukan seperti tempelan.",
|
|
7478
|
+
"- Jangan menulis komentar yang cuma mengulang apa yang sudah dinyatakan kode.",
|
|
7479
|
+
"- Komentar hanya untuk hal yang TIDAK terbaca dari kode: alasan/why sebuah keputusan,",
|
|
7480
|
+
" trade-off yang diambil, workaround beserta rujukan SPEC/ADR-nya, atau invariant yang tak",
|
|
7481
|
+
" kelihatan. Komentar semacam itu justru berharga \u2014 jangan ikut dibuang.",
|
|
7482
|
+
"- Jangan menambahkan komentar pembatas seksi, header berhiasan, atau narasi langkah demi langkah.",
|
|
7483
|
+
"- Jangan meninggalkan kode mati atau kode yang dikomentari. Hapus saja; riwayat git yang menyimpannya."
|
|
7484
|
+
].join("\n");
|
|
7485
|
+
}
|
|
7486
|
+
});
|
|
7487
|
+
|
|
7390
7488
|
// ../runner/src/goal-spec.ts
|
|
7391
7489
|
function readGoalPayload(payload) {
|
|
7392
7490
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
|
|
@@ -7415,6 +7513,7 @@ Detail: ${JSON.stringify(spec.payload)}` : "";
|
|
|
7415
7513
|
auditOnlyInstruction(flow),
|
|
7416
7514
|
autonomyClause(autonomy),
|
|
7417
7515
|
scopeClause(flow, verifyScope),
|
|
7516
|
+
codeStyleClause(flow),
|
|
7418
7517
|
skillInstruction(PIPELINES[flow]),
|
|
7419
7518
|
`Setelah fase terakhir: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Worktree ini detached HEAD \u2014 itu memang disengaja.`,
|
|
7420
7519
|
`Backlog item ${spec.id} \xB7 sumber ${spec.source} \xB7 prioritas ${spec.priority}
|
|
@@ -7430,6 +7529,7 @@ Detail: ${JSON.stringify(spec.payload)}` : "";
|
|
|
7430
7529
|
`JANGAN mengulang fase awal \u2014 spec & plan sudah ada. Lanjut di fase Execute: baca plan di docs/superpowers/plans/** untuk backlog item ini, periksa task yang sudah \`[x]\` dan selesaikan yang masih \`[ ]\`. Verifikasi nyata sebelum klaim selesai.`,
|
|
7431
7530
|
autonomyClause(autonomy),
|
|
7432
7531
|
scopeClause(flow, verifyScope),
|
|
7532
|
+
codeStyleClause(flow),
|
|
7433
7533
|
skillInstruction(["Execute"]),
|
|
7434
7534
|
`Setelah selesai: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Worktree ini detached HEAD \u2014 itu memang disengaja.`,
|
|
7435
7535
|
`Backlog item ${spec.id} \xB7 sumber ${spec.source} \xB7 prioritas ${spec.priority}
|
|
@@ -7448,6 +7548,7 @@ Detail: ${JSON.stringify(spec.payload)}` : "";
|
|
|
7448
7548
|
auditDecided ? "" : auditDecisionInstruction(flow),
|
|
7449
7549
|
autonomyClause(autonomy),
|
|
7450
7550
|
scopeClause(flow, verifyScope),
|
|
7551
|
+
codeStyleClause(flow),
|
|
7451
7552
|
skillInstruction(PIPELINES[flow]),
|
|
7452
7553
|
`Setelah fase terakhir: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Worktree ini detached HEAD \u2014 itu memang disengaja.`,
|
|
7453
7554
|
`Backlog item ${spec.id} \xB7 sumber ${spec.source} \xB7 prioritas ${spec.priority}
|
|
@@ -7470,6 +7571,7 @@ function startGoalPrompt(spec, branchTo, opts = {}) {
|
|
|
7470
7571
|
"Fase Verifikasi bukan formalitas: jalankan perintah yang membuktikan goal-nya tercapai (test/typecheck/benchmark/perintah yang relevan) dan baca outputnya. Klaim tanpa output bukan bukti.",
|
|
7471
7572
|
autonomyClause(opts.autonomy),
|
|
7472
7573
|
scopeClause("goal", opts.verifyScope),
|
|
7574
|
+
codeStyleClause("goal"),
|
|
7473
7575
|
skillInstruction(PIPELINES.goal),
|
|
7474
7576
|
`Setelah fase terakhir: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Worktree ini detached HEAD \u2014 itu memang disengaja.`,
|
|
7475
7577
|
`Backlog item ${spec.id} \xB7 sumber ${spec.source} \xB7 prioritas ${spec.priority}
|
|
@@ -7542,12 +7644,13 @@ Stack: ${project.stack || "\u2014"}`,
|
|
|
7542
7644
|
${REVERSE_STANDARD}`
|
|
7543
7645
|
].filter(Boolean).join("\n\n");
|
|
7544
7646
|
}
|
|
7545
|
-
var PIPELINES, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, phaseInstruction, PHASE_SKILLS, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, writesCode, scopeClause, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
|
|
7647
|
+
var PIPELINES, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, phaseInstruction, PHASE_SKILLS, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, writesCode, scopeClause, codeStyleClause, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
|
|
7546
7648
|
var init_prompt = __esm({
|
|
7547
7649
|
"../runner/src/prompt.ts"() {
|
|
7548
7650
|
"use strict";
|
|
7549
7651
|
init_reverse_standard();
|
|
7550
7652
|
init_verify_scope();
|
|
7653
|
+
init_code_style();
|
|
7551
7654
|
init_goal_spec();
|
|
7552
7655
|
PIPELINES = {
|
|
7553
7656
|
feature: ["Brainstorm", "Objective", "Spec", "Plan", "Execute"],
|
|
@@ -7627,6 +7730,7 @@ ${lines2.join("\n")}` : "";
|
|
|
7627
7730
|
};
|
|
7628
7731
|
writesCode = (flow) => PIPELINES[flow].includes("Execute") || PIPELINES[flow].includes("Goal");
|
|
7629
7732
|
scopeClause = (flow, scope) => scope && writesCode(flow) ? verifyScopeClause(scope) : "";
|
|
7733
|
+
codeStyleClause = (flow) => writesCode(flow) ? CODE_STYLE_CLAUSE : "";
|
|
7630
7734
|
resumeClause = (r, branchTo, hasPlan = true) => {
|
|
7631
7735
|
const fase = r.recorded.length ? `Fase yang SUDAH tercatat di $HANOMAN_PHASE_FILE: ${r.recorded.join(" \xB7 ")}. JANGAN mengulang fase itu dan JANGAN menulis ulang barisnya.` : "Belum ada fase yang tercatat di $HANOMAN_PHASE_FILE \u2014 worktree ini sendiri yang jadi alasan melanjutkan.";
|
|
7632
7736
|
const lanjut = r.next ? `Lanjutkan dari fase: ${r.next}.` : hasPlan ? "Semua fase sudah tercatat. Periksa apakah plan di `docs/superpowers/plans/**` masih menyisakan task `- [ ]` dan selesaikan sisanya; bila sudah bersih, tinggal commit & push." : "Semua fase sudah tercatat. Buktikan sekali lagi goal-nya benar-benar tercapai, lalu commit & push.";
|
|
@@ -7955,10 +8059,14 @@ var init_agent_cli = __esm({
|
|
|
7955
8059
|
function agentPromptOf(def2, roster) {
|
|
7956
8060
|
const can = liveMentions(def2, roster);
|
|
7957
8061
|
if (can.length === 0) {
|
|
7958
|
-
return
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
8062
|
+
return [
|
|
8063
|
+
def2.instructions,
|
|
8064
|
+
"",
|
|
8065
|
+
"---",
|
|
8066
|
+
"Kamu TIDAK boleh mendelegasikan ke agen lain. Selesaikan sendiri lalu laporkan hasilnya.",
|
|
8067
|
+
"",
|
|
8068
|
+
CODE_STYLE_CLAUSE
|
|
8069
|
+
].join("\n");
|
|
7962
8070
|
}
|
|
7963
8071
|
const list2 = can.map((m) => `@${m}`).join(", ");
|
|
7964
8072
|
return [
|
|
@@ -7967,7 +8075,9 @@ Kamu TIDAK boleh mendelegasikan ke agen lain. Selesaikan sendiri lalu laporkan h
|
|
|
7967
8075
|
"---",
|
|
7968
8076
|
`Kamu boleh mendelegasikan HANYA ke: ${list2}. Panggil lewat ${MENTION_TOOL} dengan nama agennya.`,
|
|
7969
8077
|
`Anggaran rantai delegasi seluruh sesi ini ${MENTION_MAX_HOPS} hop. Bila kamu sudah berada di hop ke-${MENTION_MAX_HOPS}, JANGAN mendelegasikan lagi \u2014 selesaikan sendiri lalu laporkan.`,
|
|
7970
|
-
"Sebutkan hop keberapa kamu berada saat mendelegasikan, dan jangan pernah memanggil agen yang sudah ada di rantai yang membawamu ke sini."
|
|
8078
|
+
"Sebutkan hop keberapa kamu berada saat mendelegasikan, dan jangan pernah memanggil agen yang sudah ada di rantai yang membawamu ke sini.",
|
|
8079
|
+
"",
|
|
8080
|
+
CODE_STYLE_CLAUSE
|
|
7971
8081
|
].join("\n");
|
|
7972
8082
|
}
|
|
7973
8083
|
function renderAgentsJson(defs) {
|
|
@@ -8012,6 +8122,7 @@ var init_custom_agents = __esm({
|
|
|
8012
8122
|
"../runner/src/custom-agents.ts"() {
|
|
8013
8123
|
"use strict";
|
|
8014
8124
|
init_src();
|
|
8125
|
+
init_code_style();
|
|
8015
8126
|
liveMentions = (def2, roster) => {
|
|
8016
8127
|
const names = new Set(roster.map((r) => r.name));
|
|
8017
8128
|
return def2.mentions.filter((m) => names.has(m) && m !== def2.name);
|
|
@@ -8152,6 +8263,7 @@ var init_src2 = __esm({
|
|
|
8152
8263
|
init_agent_cli();
|
|
8153
8264
|
init_custom_agents();
|
|
8154
8265
|
init_verify_scope();
|
|
8266
|
+
init_code_style();
|
|
8155
8267
|
init_paths();
|
|
8156
8268
|
init_telegram_operator();
|
|
8157
8269
|
}
|
|
@@ -9724,6 +9836,13 @@ async function conflictSessionDefaults() {
|
|
|
9724
9836
|
if (!c.enabled) return agentDefaultsOf(s2);
|
|
9725
9837
|
return c.agent === "codex" ? { agent: "codex", model: c.model, effort: coerceCodexEffort(c.model, c.effort) } : { agent: "claude", model: c.model, effort: c.effort };
|
|
9726
9838
|
}
|
|
9839
|
+
async function terminalAgentDefaults(o) {
|
|
9840
|
+
const s2 = await getSetting();
|
|
9841
|
+
const base2 = o.agent ? o.agent === "codex" ? { agent: "codex", model: s2.codex.model, effort: s2.codex.effort } : { agent: "claude", model: s2.model, effort: s2.effort } : agentDefaultsOf(s2);
|
|
9842
|
+
const model = o.model ?? base2.model;
|
|
9843
|
+
const effort = o.effort ?? base2.effort;
|
|
9844
|
+
return base2.agent === "codex" ? { agent: "codex", model, effort: coerceCodexEffort(model, effort) } : { agent: "claude", model, effort };
|
|
9845
|
+
}
|
|
9727
9846
|
var STEP, DEFAULT_SETTING, RETIRED_MODELS;
|
|
9728
9847
|
var init_settings2 = __esm({
|
|
9729
9848
|
"src/services/settings.ts"() {
|
|
@@ -9756,8 +9875,10 @@ var init_settings2 = __esm({
|
|
|
9756
9875
|
// SPEC-383 · ADR-0081 · default sesi konflik (opt-in, mati)
|
|
9757
9876
|
lead: LEAD_DEFAULTS,
|
|
9758
9877
|
// SPEC-409 · ADR-0091 · hanoman-lead (master switch mati)
|
|
9759
|
-
telegram: TELEGRAM_DEFAULTS
|
|
9878
|
+
telegram: TELEGRAM_DEFAULTS,
|
|
9760
9879
|
// SPEC-476 · ADR-0096 · gateway Telegram opt-in
|
|
9880
|
+
changelog: CHANGELOG_ENGINE_DEFAULTS
|
|
9881
|
+
// SPEC-518 · agen pembuat changelog (opt-in, mati)
|
|
9761
9882
|
};
|
|
9762
9883
|
RETIRED_MODELS = { "claude-opus-4-8": "claude-opus-5" };
|
|
9763
9884
|
}
|
|
@@ -18837,6 +18958,7 @@ async function projects_default(app2) {
|
|
|
18837
18958
|
|
|
18838
18959
|
// src/routes/specs.ts
|
|
18839
18960
|
init_src();
|
|
18961
|
+
init_src2();
|
|
18840
18962
|
import { existsSync as existsSync4 } from "node:fs";
|
|
18841
18963
|
|
|
18842
18964
|
// src/services/integrate.ts
|
|
@@ -21923,11 +22045,19 @@ async function scanDecisions(read = liveDecisions) {
|
|
|
21923
22045
|
});
|
|
21924
22046
|
}
|
|
21925
22047
|
}
|
|
21926
|
-
|
|
22048
|
+
var DEFAULT_FEED_TAKE = 50;
|
|
22049
|
+
async function notificationsFeed(p = {}) {
|
|
21927
22050
|
await scanDecisions();
|
|
21928
|
-
const
|
|
22051
|
+
const pageSize = p.limit ? Math.max(1, Math.floor(+p.limit) || 1) : DEFAULT_FEED_TAKE;
|
|
22052
|
+
const page = p.page ? Math.max(1, Math.floor(+p.page) || 1) : 1;
|
|
22053
|
+
const total = await prisma.notification.count();
|
|
22054
|
+
const items = await prisma.notification.findMany({
|
|
22055
|
+
orderBy: { createdAt: "desc" },
|
|
22056
|
+
skip: (page - 1) * pageSize,
|
|
22057
|
+
take: pageSize
|
|
22058
|
+
});
|
|
21929
22059
|
const unread = await prisma.notification.count({ where: { readAt: null } });
|
|
21930
|
-
return { items, unread };
|
|
22060
|
+
return { items, unread, total, page, pageSize };
|
|
21931
22061
|
}
|
|
21932
22062
|
|
|
21933
22063
|
// src/services/spec-head.ts
|
|
@@ -22199,6 +22329,9 @@ ${item.context}` : item.context;
|
|
|
22199
22329
|
`hanoman \xB7 selesaikan konflik ${r.op} branch \`${sourceBranch(spec.id)}\` ${r.op === "merge" ? "ke" : "di atas"} \`${r.target}\`.`,
|
|
22200
22330
|
`Kamu berada di worktree yang tertinggal di tengah operasi ${r.op} dengan konflik. Resolve konflik pada file bertanda, jaga kedua sisi perubahan sesuai maksudnya.`,
|
|
22201
22331
|
r.finalize,
|
|
22332
|
+
// SPEC-543 · ADR-0108 · menyelesaikan konflik selalu berarti menyunting kode, dan prompt ini
|
|
22333
|
+
// dirakit inline di route — gerbang `writesCode` di runner/src/prompt.ts tak menjangkaunya.
|
|
22334
|
+
CODE_STYLE_CLAUSE,
|
|
22202
22335
|
`Backlog item ${spec.id} \u2014 ${spec.title}.`
|
|
22203
22336
|
].join("\n\n");
|
|
22204
22337
|
const s2 = createSession(spec.projectId, r.worktree, {
|
|
@@ -22249,7 +22382,7 @@ ${item.context}` : item.context;
|
|
|
22249
22382
|
// src/routes/notifications.ts
|
|
22250
22383
|
init_db();
|
|
22251
22384
|
async function notifications_default(app2) {
|
|
22252
|
-
app2.get("/notifications", async () => notificationsFeed());
|
|
22385
|
+
app2.get("/notifications", async (req) => notificationsFeed(req.query));
|
|
22253
22386
|
app2.post("/notifications/read", async (_req, reply) => {
|
|
22254
22387
|
await prisma.notification.updateMany({ where: { readAt: null }, data: { readAt: /* @__PURE__ */ new Date() } });
|
|
22255
22388
|
return reply.code(204).send();
|
|
@@ -22288,11 +22421,12 @@ async function settings_default(app2) {
|
|
|
22288
22421
|
init_src();
|
|
22289
22422
|
|
|
22290
22423
|
// src/services/project-prds.ts
|
|
22424
|
+
init_src();
|
|
22291
22425
|
init_db();
|
|
22292
22426
|
init_pty();
|
|
22293
|
-
var
|
|
22294
|
-
var isPrd = (rel) => rel.startsWith(
|
|
22295
|
-
var slugOf = (rel) => rel.slice(
|
|
22427
|
+
var PRD_DIR2 = "docs/prd/";
|
|
22428
|
+
var isPrd = (rel) => rel.startsWith(PRD_DIR2) && rel.endsWith(".md") && !rel.endsWith(".breakdown.md");
|
|
22429
|
+
var slugOf = (rel) => rel.slice(PRD_DIR2.length, -3);
|
|
22296
22430
|
var titleOf = (content, slug) => {
|
|
22297
22431
|
const m = content?.match(/^#\s+(.+)$/m);
|
|
22298
22432
|
return m ? m[1].trim() : slug;
|
|
@@ -22302,13 +22436,26 @@ async function resolveDir2(projectId, sessions = listSessions()) {
|
|
|
22302
22436
|
if (live) return { dir: live.cwd, live: true };
|
|
22303
22437
|
return { dir: await resolveRepoDir(projectId), live: false };
|
|
22304
22438
|
}
|
|
22305
|
-
async function
|
|
22439
|
+
async function loadTraces(projectIds) {
|
|
22440
|
+
const map = new Map(projectIds.map((id) => [id, []]));
|
|
22441
|
+
if (!projectIds.length) return map;
|
|
22442
|
+
const rows = await prisma.spec.findMany({
|
|
22443
|
+
where: { projectId: { in: projectIds } },
|
|
22444
|
+
select: { projectId: true, stage: true, payload: true, branchFrom: true }
|
|
22445
|
+
});
|
|
22446
|
+
for (const r of rows)
|
|
22447
|
+
map.get(r.projectId)?.push({ stage: r.stage, payload: r.payload, branchFrom: r.branchFrom });
|
|
22448
|
+
return map;
|
|
22449
|
+
}
|
|
22450
|
+
async function listPrds(projectId, sessions = listSessions(), traces) {
|
|
22306
22451
|
const { dir: dir2, live } = await resolveDir2(projectId, sessions);
|
|
22307
22452
|
if (!dir2) return [];
|
|
22308
22453
|
const project = await prisma.project.findUnique({ where: { id: projectId }, select: { name: true } });
|
|
22309
22454
|
const projectName = project?.name ?? projectId;
|
|
22455
|
+
const specs = traces ?? (await loadTraces([projectId])).get(projectId) ?? [];
|
|
22310
22456
|
return (await listRepoDocs(dir2)).filter(isPrd).map((rel) => {
|
|
22311
22457
|
const slug = slugOf(rel);
|
|
22458
|
+
const { status, specCount, doneCount } = prdStatusOf(rel, specs);
|
|
22312
22459
|
return {
|
|
22313
22460
|
slug,
|
|
22314
22461
|
name: rel.slice(rel.lastIndexOf("/") + 1),
|
|
@@ -22316,7 +22463,10 @@ async function listPrds(projectId, sessions = listSessions()) {
|
|
|
22316
22463
|
title: titleOf(readDocFile(dir2, rel), slug),
|
|
22317
22464
|
live,
|
|
22318
22465
|
projectId,
|
|
22319
|
-
projectName
|
|
22466
|
+
projectName,
|
|
22467
|
+
status,
|
|
22468
|
+
specCount,
|
|
22469
|
+
doneCount
|
|
22320
22470
|
};
|
|
22321
22471
|
}).sort((a, b) => a.slug.localeCompare(b.slug));
|
|
22322
22472
|
}
|
|
@@ -22325,7 +22475,10 @@ async function listAllPrds(sessions = listSessions()) {
|
|
|
22325
22475
|
orderBy: { createdAt: "desc" },
|
|
22326
22476
|
select: { id: true }
|
|
22327
22477
|
});
|
|
22328
|
-
const
|
|
22478
|
+
const traces = await loadTraces(projects.map((p) => p.id));
|
|
22479
|
+
const nested = await Promise.all(
|
|
22480
|
+
projects.map((p) => listPrds(p.id, sessions, traces.get(p.id) ?? []))
|
|
22481
|
+
);
|
|
22329
22482
|
return nested.flat();
|
|
22330
22483
|
}
|
|
22331
22484
|
async function readPrd(projectId, path, sessions = listSessions()) {
|
|
@@ -22337,8 +22490,8 @@ async function readPrd(projectId, path, sessions = listSessions()) {
|
|
|
22337
22490
|
// src/services/project-breakdowns.ts
|
|
22338
22491
|
init_src();
|
|
22339
22492
|
init_pty();
|
|
22340
|
-
var
|
|
22341
|
-
var isPrd2 = (rel) => rel.startsWith(
|
|
22493
|
+
var PRD_DIR3 = "docs/prd/";
|
|
22494
|
+
var isPrd2 = (rel) => rel.startsWith(PRD_DIR3) && rel.endsWith(".md");
|
|
22342
22495
|
function breakdownPathFor(prdPath) {
|
|
22343
22496
|
if (!isPrd2(prdPath) || prdPath.endsWith(".breakdown.md")) return null;
|
|
22344
22497
|
return prdPath.slice(0, -3) + ".breakdown.md";
|
|
@@ -22427,6 +22580,7 @@ async function docs_default(app2) {
|
|
|
22427
22580
|
}
|
|
22428
22581
|
|
|
22429
22582
|
// src/routes/ide.ts
|
|
22583
|
+
init_src2();
|
|
22430
22584
|
import { basename } from "node:path";
|
|
22431
22585
|
import { spawn as spawn2 } from "node:child_process";
|
|
22432
22586
|
|
|
@@ -22578,7 +22732,7 @@ async function listStashes(repoDir) {
|
|
|
22578
22732
|
}
|
|
22579
22733
|
}
|
|
22580
22734
|
async function listGraph(repoDir, limit = 200, opts = {}) {
|
|
22581
|
-
if (!repoDir || !existsSync6(repoDir)) return { commits: [], current: "" };
|
|
22735
|
+
if (!repoDir || !existsSync6(repoDir)) return { commits: [], current: "", total: 0 };
|
|
22582
22736
|
try {
|
|
22583
22737
|
const fmt = ["%H", "%P", "%an", "%aI", "%s", "%D"].join(US);
|
|
22584
22738
|
const refArgs = opts.branches?.length ? ["--end-of-options", ...opts.branches] : [
|
|
@@ -22610,9 +22764,16 @@ async function listGraph(repoDir, limit = 200, opts = {}) {
|
|
|
22610
22764
|
tags
|
|
22611
22765
|
};
|
|
22612
22766
|
});
|
|
22613
|
-
|
|
22767
|
+
let total = commits.length;
|
|
22768
|
+
try {
|
|
22769
|
+
const c = await exec6("git", ["rev-list", "--count", ...refArgs], { cwd: repoDir, ...GIT5 });
|
|
22770
|
+
const n2 = Number(c.stdout.trim());
|
|
22771
|
+
if (Number.isFinite(n2)) total = n2;
|
|
22772
|
+
} catch {
|
|
22773
|
+
}
|
|
22774
|
+
return { commits, current: await currentBranch(repoDir), total };
|
|
22614
22775
|
} catch {
|
|
22615
|
-
return { commits: [], current: "" };
|
|
22776
|
+
return { commits: [], current: "", total: 0 };
|
|
22616
22777
|
}
|
|
22617
22778
|
}
|
|
22618
22779
|
function mergeNumName(numOut, nameOut) {
|
|
@@ -23378,6 +23539,8 @@ async function finishGraphOp(reply, id, repoDir, r, verb) {
|
|
|
23378
23539
|
`hanoman \xB7 selesaikan konflik ${verb} \`${r.source}\` \u2192 \`${r.target}\`.`,
|
|
23379
23540
|
`Kamu berada di worktree yang tertinggal di tengah ${verb} dengan konflik. Resolve konflik pada file bertanda, jaga kedua sisi perubahan sesuai maksudnya.`,
|
|
23380
23541
|
r.finalize,
|
|
23542
|
+
// SPEC-543 · ADR-0108 · cermin pintu konflik backlog di routes/specs.ts.
|
|
23543
|
+
CODE_STYLE_CLAUSE,
|
|
23381
23544
|
`${verb} via git graph project ${id}.`
|
|
23382
23545
|
].join("\n\n");
|
|
23383
23546
|
const s2 = createSession(id, r.worktree, { id: basename(r.worktree), model, effort, agent, prompt });
|
|
@@ -23773,7 +23936,7 @@ async function terminal_default(app2) {
|
|
|
23773
23936
|
});
|
|
23774
23937
|
return reply.code(201).send({ id: s3.id });
|
|
23775
23938
|
}
|
|
23776
|
-
const { agent, model, effort } = await
|
|
23939
|
+
const { agent, model, effort } = await terminalAgentDefaults(parsed.data);
|
|
23777
23940
|
if (agent === "codex") ensureCodexTrust(repoDir);
|
|
23778
23941
|
const s2 = createSession(project.id, repoDir, { agent, model, effort });
|
|
23779
23942
|
return reply.code(201).send({ id: s2.id });
|
|
@@ -23840,6 +24003,8 @@ async function terminal_default(app2) {
|
|
|
23840
24003
|
`hanoman \xB7 selesaikan konflik ${r.op} branch \`${s2.branch}\` ${r.op === "merge" ? "ke" : "di atas"} \`${r.target}\`.`,
|
|
23841
24004
|
`Kamu berada di worktree yang tertinggal di tengah operasi ${r.op} dengan konflik. Resolve konflik pada file bertanda, jaga kedua sisi perubahan sesuai maksudnya.`,
|
|
23842
24005
|
r.finalize,
|
|
24006
|
+
// SPEC-543 · ADR-0108 · cermin pintu konflik backlog di routes/specs.ts.
|
|
24007
|
+
CODE_STYLE_CLAUSE,
|
|
23843
24008
|
`Sesi PRD ${s2.id}.`
|
|
23844
24009
|
].join("\n\n");
|
|
23845
24010
|
const cs = createSession(s2.projectId, r.worktree, {
|
|
@@ -25984,7 +26149,9 @@ async function tickets_default(app2) {
|
|
|
25984
26149
|
}
|
|
25985
26150
|
|
|
25986
26151
|
// src/routes/scheduler.ts
|
|
26152
|
+
init_zod();
|
|
25987
26153
|
init_src();
|
|
26154
|
+
init_db();
|
|
25988
26155
|
|
|
25989
26156
|
// src/services/scheduler/config.ts
|
|
25990
26157
|
init_db();
|
|
@@ -26013,12 +26180,39 @@ async function enqueue(i) {
|
|
|
26013
26180
|
function listQueue(status) {
|
|
26014
26181
|
return prisma.schedulerQueueItem.findMany({ where: status ? { status } : void 0 });
|
|
26015
26182
|
}
|
|
26183
|
+
async function listQueuePage(f = {}) {
|
|
26184
|
+
const where = f.status ? { status: f.status } : void 0;
|
|
26185
|
+
const total = await prisma.schedulerQueueItem.count({ where });
|
|
26186
|
+
const pageSize = f.limit ? Math.max(1, Math.floor(+f.limit) || 1) : total || 1;
|
|
26187
|
+
const page = f.page ? Math.max(1, Math.floor(+f.page) || 1) : 1;
|
|
26188
|
+
const items = await prisma.schedulerQueueItem.findMany({
|
|
26189
|
+
where,
|
|
26190
|
+
orderBy: { enqueuedAt: "desc" },
|
|
26191
|
+
skip: (page - 1) * pageSize,
|
|
26192
|
+
take: pageSize
|
|
26193
|
+
});
|
|
26194
|
+
return { items, total, page, pageSize };
|
|
26195
|
+
}
|
|
26196
|
+
async function queueCounts() {
|
|
26197
|
+
const [queued3, launched, done, failed, canceled] = await Promise.all([
|
|
26198
|
+
prisma.schedulerQueueItem.count({ where: { status: "queued" } }),
|
|
26199
|
+
prisma.schedulerQueueItem.count({ where: { status: "launched" } }),
|
|
26200
|
+
prisma.schedulerQueueItem.count({ where: { status: "done" } }),
|
|
26201
|
+
prisma.schedulerQueueItem.count({ where: { status: "failed" } }),
|
|
26202
|
+
prisma.schedulerQueueItem.count({ where: { status: "canceled" } })
|
|
26203
|
+
]);
|
|
26204
|
+
return { queued: queued3, launched, done, failed, canceled };
|
|
26205
|
+
}
|
|
26016
26206
|
async function queued2() {
|
|
26017
26207
|
const items = await prisma.schedulerQueueItem.findMany({ where: { status: "queued" } });
|
|
26018
26208
|
return items.sort((a, b) => (RANK[a.priority] ?? 1) - (RANK[b.priority] ?? 1) || a.enqueuedAt.getTime() - b.enqueuedAt.getTime());
|
|
26019
26209
|
}
|
|
26020
26210
|
async function markLaunched(id, sessionId2) {
|
|
26021
|
-
|
|
26211
|
+
const { count } = await prisma.schedulerQueueItem.updateMany({
|
|
26212
|
+
where: { id, status: "queued" },
|
|
26213
|
+
data: { status: "launched", sessionId: sessionId2, launchedAt: /* @__PURE__ */ new Date() }
|
|
26214
|
+
});
|
|
26215
|
+
return count > 0;
|
|
26022
26216
|
}
|
|
26023
26217
|
async function markFailed(id, note) {
|
|
26024
26218
|
await prisma.schedulerQueueItem.update({ where: { id }, data: { status: "failed", note: note ?? null } });
|
|
@@ -26029,11 +26223,29 @@ async function markDone(id, note) {
|
|
|
26029
26223
|
data: { status: "done", ...note ? { note } : {} }
|
|
26030
26224
|
});
|
|
26031
26225
|
}
|
|
26032
|
-
async function
|
|
26226
|
+
async function noteRow(id, note) {
|
|
26033
26227
|
const row = await prisma.schedulerQueueItem.findUnique({ where: { id }, select: { note: true } });
|
|
26034
26228
|
if (row?.note === note) return;
|
|
26035
26229
|
await prisma.schedulerQueueItem.update({ where: { id }, data: { note } });
|
|
26036
26230
|
}
|
|
26231
|
+
async function markCanceled(id, note) {
|
|
26232
|
+
const { count } = await prisma.schedulerQueueItem.updateMany({
|
|
26233
|
+
where: { id, status: "queued" },
|
|
26234
|
+
data: { status: "canceled", note }
|
|
26235
|
+
});
|
|
26236
|
+
return count > 0;
|
|
26237
|
+
}
|
|
26238
|
+
async function markRequeued(id) {
|
|
26239
|
+
const { count } = await prisma.schedulerQueueItem.updateMany({
|
|
26240
|
+
where: { id, status: "canceled" },
|
|
26241
|
+
data: { status: "queued", note: null }
|
|
26242
|
+
});
|
|
26243
|
+
return count > 0;
|
|
26244
|
+
}
|
|
26245
|
+
async function isQueued(id) {
|
|
26246
|
+
const row = await prisma.schedulerQueueItem.findUnique({ where: { id }, select: { status: true } });
|
|
26247
|
+
return row?.status === "queued";
|
|
26248
|
+
}
|
|
26037
26249
|
|
|
26038
26250
|
// src/services/scheduler/registry.ts
|
|
26039
26251
|
var sources = /* @__PURE__ */ new Map();
|
|
@@ -26057,6 +26269,14 @@ function isDue(id, everyMin, now) {
|
|
|
26057
26269
|
|
|
26058
26270
|
// src/routes/scheduler.ts
|
|
26059
26271
|
init_pty();
|
|
26272
|
+
var CANCEL_NOTE = "dibatalkan operator";
|
|
26273
|
+
var zCancelBody = external_exports.object({ reason: external_exports.string().trim().max(200).optional() });
|
|
26274
|
+
async function refuse(reply, id, verb) {
|
|
26275
|
+
const row = await prisma.schedulerQueueItem.findUnique({ where: { id }, select: { status: true } });
|
|
26276
|
+
if (!row) return reply.code(404).send({ error: "item antrean tak ada" });
|
|
26277
|
+
const why = row.status === "launched" ? "sesinya sudah berjalan \u2014 tutup dari Terminal bila memang tak diperlukan" : `statusnya sudah ${row.status}`;
|
|
26278
|
+
return reply.code(409).send({ error: `tak bisa ${verb}: ${why}`, status: row.status });
|
|
26279
|
+
}
|
|
26060
26280
|
async function scheduler_default(app2) {
|
|
26061
26281
|
app2.get("/scheduler/config", async () => getScheduler());
|
|
26062
26282
|
app2.put("/scheduler/config", async (req, reply) => {
|
|
@@ -26064,10 +26284,22 @@ async function scheduler_default(app2) {
|
|
|
26064
26284
|
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
26065
26285
|
return setScheduler(parsed.data);
|
|
26066
26286
|
});
|
|
26287
|
+
app2.post("/scheduler/queue/:id/cancel", async (req, reply) => {
|
|
26288
|
+
const { id } = req.params;
|
|
26289
|
+
const parsed = zCancelBody.safeParse(req.body ?? {});
|
|
26290
|
+
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
26291
|
+
const note = parsed.data.reason ? `${CANCEL_NOTE}: ${parsed.data.reason}` : CANCEL_NOTE;
|
|
26292
|
+
if (!await markCanceled(id, note)) return refuse(reply, id, "membatalkan");
|
|
26293
|
+
return prisma.schedulerQueueItem.findUnique({ where: { id } });
|
|
26294
|
+
});
|
|
26295
|
+
app2.post("/scheduler/queue/:id/requeue", async (req, reply) => {
|
|
26296
|
+
const { id } = req.params;
|
|
26297
|
+
if (!await markRequeued(id)) return refuse(reply, id, "mengembalikan ke antrean");
|
|
26298
|
+
return prisma.schedulerQueueItem.findUnique({ where: { id } });
|
|
26299
|
+
});
|
|
26067
26300
|
app2.get("/scheduler/state", async () => {
|
|
26068
26301
|
const cfg = await getScheduler();
|
|
26069
26302
|
const live = listSessions().filter((s2) => !s2.exited);
|
|
26070
|
-
const queue2 = await listQueue();
|
|
26071
26303
|
const srcView = (id, sc, minCount) => {
|
|
26072
26304
|
const last = getLastRun(id);
|
|
26073
26305
|
return {
|
|
@@ -26083,9 +26315,31 @@ async function scheduler_default(app2) {
|
|
|
26083
26315
|
srcView("backlog", cfg.sources.backlog),
|
|
26084
26316
|
srcView("triase", cfg.sources.triase)
|
|
26085
26317
|
];
|
|
26086
|
-
const
|
|
26318
|
+
const counts = await queueCounts();
|
|
26319
|
+
const launchedSpecs = new Set((await listQueue("launched")).map((q) => q.specId));
|
|
26087
26320
|
const sessions = live.filter((s2) => s2.specId && launchedSpecs.has(s2.specId));
|
|
26088
|
-
return { config: cfg, cap: cfg.maxConcurrent, liveCount: live.length, sources: sources2,
|
|
26321
|
+
return { config: cfg, cap: cfg.maxConcurrent, liveCount: live.length, sources: sources2, queueCounts: counts, sessions };
|
|
26322
|
+
});
|
|
26323
|
+
app2.get("/scheduler/queue", async (req) => {
|
|
26324
|
+
const { status, page, limit } = req.query;
|
|
26325
|
+
const r = await listQueuePage({ status, page, limit });
|
|
26326
|
+
return {
|
|
26327
|
+
items: r.items.map((q) => ({
|
|
26328
|
+
id: q.id,
|
|
26329
|
+
specId: q.specId,
|
|
26330
|
+
projectId: q.projectId,
|
|
26331
|
+
source: q.source,
|
|
26332
|
+
priority: q.priority,
|
|
26333
|
+
status: q.status,
|
|
26334
|
+
sessionId: q.sessionId,
|
|
26335
|
+
note: q.note,
|
|
26336
|
+
enqueuedAt: q.enqueuedAt.toISOString(),
|
|
26337
|
+
launchedAt: q.launchedAt ? q.launchedAt.toISOString() : null
|
|
26338
|
+
})),
|
|
26339
|
+
total: r.total,
|
|
26340
|
+
page: r.page,
|
|
26341
|
+
pageSize: r.pageSize
|
|
26342
|
+
};
|
|
26089
26343
|
});
|
|
26090
26344
|
}
|
|
26091
26345
|
|
|
@@ -26130,6 +26384,7 @@ init_pty();
|
|
|
26130
26384
|
|
|
26131
26385
|
// src/services/lead/prompt.ts
|
|
26132
26386
|
init_src();
|
|
26387
|
+
init_src2();
|
|
26133
26388
|
var bullet2 = (s2) => `- ${s2}`;
|
|
26134
26389
|
function leadPrompt(q, c) {
|
|
26135
26390
|
const lines2 = [];
|
|
@@ -26193,6 +26448,8 @@ function leadPrompt(q, c) {
|
|
|
26193
26448
|
lines2.push(` ${LEAD_ACTIONS.join(" \xB7 ")}`);
|
|
26194
26449
|
lines2.push(" Deploy, perintah/konsol VPS, data produksi, dan penghapusan apa pun (project, backlog, branch, worktree, notifikasi, jejak) TERKUNCI dan tidak akan pernah dijalankan.");
|
|
26195
26450
|
lines2.push("");
|
|
26451
|
+
lines2.push(CODE_STYLE_CLAUSE);
|
|
26452
|
+
lines2.push("");
|
|
26196
26453
|
lines2.push("## Sepanjang apa (WAJIB)");
|
|
26197
26454
|
lines2.push(`Peminta jawabanmu adalah MESIN yang sedang menunggu, bukan pembaca laporan. \`decision\` paling banyak ${LEAD_DECISION_MAX} karakter (satu kalimat) dan \`reason\` paling banyak ${LEAD_REASON_MAX} karakter (dua-tiga kalimat). Yang lebih panjang dipangkas server sebelum sampai ke peminta.`);
|
|
26198
26455
|
lines2.push("JANGAN menuliskan: ringkasan ulang konteks yang sudah kamu terima di atas, latar belakang atau sejarah masalahnya, daftar alternatif yang tak diminta, maupun rencana kerja bertahap. Langsung putusannya dan alasannya.");
|
|
@@ -26264,6 +26521,22 @@ function keepExistingRefs(refs2, repoDir) {
|
|
|
26264
26521
|
// src/services/lead/trail.ts
|
|
26265
26522
|
init_db();
|
|
26266
26523
|
init_src();
|
|
26524
|
+
|
|
26525
|
+
// src/services/lead/page.ts
|
|
26526
|
+
var LEAD_MAX_TAKE = 200;
|
|
26527
|
+
var LEAD_DEFAULT_TAKE = 50;
|
|
26528
|
+
function leadWindow(f) {
|
|
26529
|
+
if (f.limit !== void 0 || f.page !== void 0) {
|
|
26530
|
+
const pageSize = Math.min(Math.max(1, Math.floor(f.limit ?? LEAD_DEFAULT_TAKE) || 1), LEAD_MAX_TAKE);
|
|
26531
|
+
const page = Math.max(1, Math.floor(f.page ?? 1) || 1);
|
|
26532
|
+
return { skip: (page - 1) * pageSize, take: pageSize, page, pageSize };
|
|
26533
|
+
}
|
|
26534
|
+
const take2 = Math.min(f.take ?? LEAD_DEFAULT_TAKE, LEAD_MAX_TAKE);
|
|
26535
|
+
const skip = f.skip ?? 0;
|
|
26536
|
+
return { skip, take: take2, page: Math.floor(skip / Math.max(1, take2)) + 1, pageSize: take2 };
|
|
26537
|
+
}
|
|
26538
|
+
|
|
26539
|
+
// src/services/lead/trail.ts
|
|
26267
26540
|
async function recordDecision(i) {
|
|
26268
26541
|
return prisma.leadDecision.create({
|
|
26269
26542
|
data: {
|
|
@@ -26298,20 +26571,24 @@ async function recordDecision(i) {
|
|
|
26298
26571
|
});
|
|
26299
26572
|
}
|
|
26300
26573
|
async function listDecisions(f = {}) {
|
|
26301
|
-
|
|
26302
|
-
|
|
26303
|
-
|
|
26304
|
-
|
|
26305
|
-
|
|
26306
|
-
|
|
26307
|
-
|
|
26308
|
-
|
|
26574
|
+
const where = {
|
|
26575
|
+
...f.projectId ? { projectId: f.projectId } : {},
|
|
26576
|
+
...f.specId ? { specId: f.specId } : {},
|
|
26577
|
+
...f.sessionId ? { sessionId: f.sessionId } : {},
|
|
26578
|
+
...f.flowId ? { flowId: f.flowId } : {},
|
|
26579
|
+
...f.status ? { status: f.status } : {}
|
|
26580
|
+
};
|
|
26581
|
+
const w = leadWindow(f);
|
|
26582
|
+
const total = await prisma.leadDecision.count({ where });
|
|
26583
|
+
const rows = await prisma.leadDecision.findMany({
|
|
26584
|
+
where,
|
|
26309
26585
|
// SPEC-485 · satu RANTAI dibaca dari awal: urutan pertanyaannya adalah isi jejaknya. Daftar
|
|
26310
26586
|
// umum tetap terbaru-dulu (AC-24) — dua pertanyaan yang berbeda, dua urutan yang berbeda.
|
|
26311
26587
|
orderBy: { createdAt: f.flowId ? "asc" : "desc" },
|
|
26312
|
-
take:
|
|
26313
|
-
skip:
|
|
26588
|
+
take: w.take,
|
|
26589
|
+
skip: w.skip
|
|
26314
26590
|
});
|
|
26591
|
+
return { rows, total, page: w.page, pageSize: w.pageSize };
|
|
26315
26592
|
}
|
|
26316
26593
|
async function overrideDecision(id, answer, reason, rawChoices = []) {
|
|
26317
26594
|
const old = await prisma.leadDecision.findUnique({ where: { id } });
|
|
@@ -26555,15 +26832,19 @@ async function closeFlow(id, reason) {
|
|
|
26555
26832
|
});
|
|
26556
26833
|
}
|
|
26557
26834
|
async function listFlows(f = {}) {
|
|
26558
|
-
|
|
26559
|
-
|
|
26560
|
-
|
|
26561
|
-
|
|
26562
|
-
|
|
26835
|
+
const where = {
|
|
26836
|
+
...f.projectId ? { projectId: f.projectId } : {},
|
|
26837
|
+
...f.status ? { status: f.status } : {}
|
|
26838
|
+
};
|
|
26839
|
+
const w = leadWindow(f);
|
|
26840
|
+
const total = await prisma.leadFlow.count({ where });
|
|
26841
|
+
const rows = await prisma.leadFlow.findMany({
|
|
26842
|
+
where,
|
|
26563
26843
|
orderBy: { createdAt: "desc" },
|
|
26564
|
-
take:
|
|
26565
|
-
skip:
|
|
26844
|
+
take: w.take,
|
|
26845
|
+
skip: w.skip
|
|
26566
26846
|
});
|
|
26847
|
+
return { rows, total, page: w.page, pageSize: w.pageSize };
|
|
26567
26848
|
}
|
|
26568
26849
|
async function expireFlows(now) {
|
|
26569
26850
|
const due = await prisma.leadFlow.findMany({
|
|
@@ -27691,7 +27972,7 @@ async function lead_default(app2) {
|
|
|
27691
27972
|
});
|
|
27692
27973
|
app2.get("/lead/decisions", async (req) => {
|
|
27693
27974
|
const q = req.query;
|
|
27694
|
-
const
|
|
27975
|
+
const r = await listDecisions({
|
|
27695
27976
|
projectId: q.projectId,
|
|
27696
27977
|
specId: q.specId,
|
|
27697
27978
|
sessionId: q.sessionId,
|
|
@@ -27699,19 +27980,23 @@ async function lead_default(app2) {
|
|
|
27699
27980
|
// SPEC-485 · satu rantai dibaca lewat filter ini, urut NAIK (lihat `listDecisions`).
|
|
27700
27981
|
flowId: q.flowId,
|
|
27701
27982
|
take: q.take ? Number(q.take) : void 0,
|
|
27702
|
-
skip: q.skip ? Number(q.skip) : void 0
|
|
27983
|
+
skip: q.skip ? Number(q.skip) : void 0,
|
|
27984
|
+
page: q.page ? Number(q.page) : void 0,
|
|
27985
|
+
limit: q.limit ? Number(q.limit) : void 0
|
|
27703
27986
|
});
|
|
27704
|
-
return { items: rows.map(toDecisionView) };
|
|
27987
|
+
return { items: r.rows.map(toDecisionView), total: r.total, page: r.page, pageSize: r.pageSize };
|
|
27705
27988
|
});
|
|
27706
27989
|
app2.get("/lead/flows", async (req) => {
|
|
27707
27990
|
const q = req.query;
|
|
27708
|
-
const
|
|
27991
|
+
const r = await listFlows({
|
|
27709
27992
|
projectId: q.projectId,
|
|
27710
27993
|
status: q.status,
|
|
27711
27994
|
take: q.take ? Number(q.take) : void 0,
|
|
27712
|
-
skip: q.skip ? Number(q.skip) : void 0
|
|
27995
|
+
skip: q.skip ? Number(q.skip) : void 0,
|
|
27996
|
+
page: q.page ? Number(q.page) : void 0,
|
|
27997
|
+
limit: q.limit ? Number(q.limit) : void 0
|
|
27713
27998
|
});
|
|
27714
|
-
return { items: rows.map(toFlowView) };
|
|
27999
|
+
return { items: r.rows.map(toFlowView), total: r.total, page: r.page, pageSize: r.pageSize };
|
|
27715
28000
|
});
|
|
27716
28001
|
app2.post("/lead/flows/:id/submit", async (req, reply) => {
|
|
27717
28002
|
const { id } = req.params;
|
|
@@ -27955,9 +28240,18 @@ async function collectVersions(repoDir, fromTag, toTag) {
|
|
|
27955
28240
|
// src/services/changelog/generate.ts
|
|
27956
28241
|
init_src();
|
|
27957
28242
|
init_db();
|
|
28243
|
+
|
|
28244
|
+
// src/services/changelog/config.ts
|
|
28245
|
+
init_src();
|
|
27958
28246
|
init_settings2();
|
|
28247
|
+
async function changelogAgentDefaults() {
|
|
28248
|
+
const e = (await getSetting()).changelog;
|
|
28249
|
+
if (!e.enabled) return sessionAgentDefaults();
|
|
28250
|
+
return e.agent === "codex" ? { agent: "codex", model: e.model, effort: coerceCodexEffort(e.model, e.effort) } : { agent: "claude", model: e.model, effort: e.effort };
|
|
28251
|
+
}
|
|
27959
28252
|
|
|
27960
28253
|
// src/services/changelog/render.ts
|
|
28254
|
+
init_src2();
|
|
27961
28255
|
var MODE_LABEL = {
|
|
27962
28256
|
backlog: "backlog yang selesai dalam rentang tanggal",
|
|
27963
28257
|
commit: "riwayat perubahan repo dalam rentang yang dipilih",
|
|
@@ -28007,6 +28301,10 @@ function changelogPrompt(input, budgetMs) {
|
|
|
28007
28301
|
"- Gabungkan bahan yang bicara hal yang sama jadi satu butir. 3\u201310 butir; kurangi bila memang sedikit.",
|
|
28008
28302
|
"- Jangan mengarang perubahan yang tak ada di bahan.",
|
|
28009
28303
|
"",
|
|
28304
|
+
// SPEC-543 · ADR-0108 · dipasang di SEMUA prompt agen, bukan hanya yang menulis kode; gerbang
|
|
28305
|
+
// di baris pertama klausa yang membuatnya diam untuk narator ini.
|
|
28306
|
+
CODE_STYLE_CLAUSE,
|
|
28307
|
+
"",
|
|
28010
28308
|
"Bentuk keluaran \u2014 HANYA markdown ini, tanpa kalimat pembuka atau penutup di luarnya,",
|
|
28011
28309
|
"tanpa blok kode:",
|
|
28012
28310
|
"",
|
|
@@ -28033,7 +28331,7 @@ async function generateChangelog(projectId, req, deps = {}) {
|
|
|
28033
28331
|
if (!got.ok) return got;
|
|
28034
28332
|
const input = got.input;
|
|
28035
28333
|
const prompt = changelogPrompt(input, CHANGELOG_TIMEOUT_MS);
|
|
28036
|
-
const { agent, model, effort } = await
|
|
28334
|
+
const { agent, model, effort } = await changelogAgentDefaults();
|
|
28037
28335
|
const run2 = deps.think ?? think;
|
|
28038
28336
|
let body = "";
|
|
28039
28337
|
let generator = "agent";
|
|
@@ -28101,9 +28399,9 @@ async function changelog_default(app2) {
|
|
|
28101
28399
|
app2.get("/projects/:id/changelog", async (req, reply) => {
|
|
28102
28400
|
const { id } = req.params;
|
|
28103
28401
|
if (!await prisma.project.findUnique({ where: { id } })) return reply.code(404).send({ error: "not found" });
|
|
28104
|
-
const { page, limit } = req.query;
|
|
28402
|
+
const { page, limit, q } = req.query;
|
|
28105
28403
|
const rows = await prisma.changelog.findMany({ where: { projectId: id }, orderBy: { createdAt: "desc" } });
|
|
28106
|
-
return paginate(rows.map(view6), page, limit);
|
|
28404
|
+
return paginate(rows.filter((r) => changelogMatches(r, q ?? "")).map(view6), page, limit);
|
|
28107
28405
|
});
|
|
28108
28406
|
app2.get("/projects/:id/changelog/:cid", async (req, reply) => {
|
|
28109
28407
|
const { id, cid } = req.params;
|
|
@@ -28773,14 +29071,14 @@ async function githubIssues(app2) {
|
|
|
28773
29071
|
});
|
|
28774
29072
|
app2.get("/projects/:id/github/issues", async (req, reply) => {
|
|
28775
29073
|
const { id } = req.params;
|
|
28776
|
-
const { status } = req.query;
|
|
29074
|
+
const { status, page, limit } = req.query;
|
|
28777
29075
|
const project = await prisma.project.findUnique({ where: { id }, select: { id: true } });
|
|
28778
29076
|
if (!project) return reply.code(404).send({ error: "not found" });
|
|
28779
29077
|
const items = await prisma.githubIssue.findMany({
|
|
28780
29078
|
where: { projectId: id, ...status ? { status } : {} },
|
|
28781
29079
|
orderBy: [{ number: "desc" }]
|
|
28782
29080
|
});
|
|
28783
|
-
return reply.send(
|
|
29081
|
+
return reply.send(paginate(items.map(view8), page, limit));
|
|
28784
29082
|
});
|
|
28785
29083
|
app2.post("/github-issues/accept", async (req, reply) => {
|
|
28786
29084
|
const parsed = zAcceptMany.safeParse(req.body ?? {});
|
|
@@ -30004,6 +30302,7 @@ init_src();
|
|
|
30004
30302
|
|
|
30005
30303
|
// src/services/scheduler/governor.ts
|
|
30006
30304
|
var ALREADY_DONE_NOTE = "spec sudah selesai \u2014 tak diluncurkan";
|
|
30305
|
+
var canceledRaceNote = (sessionId2) => `dibatalkan saat sesi ${sessionId2} sudah terlanjur lahir \u2014 sesi dibiarkan hidup, tutup dari Terminal bila tak diperlukan`;
|
|
30007
30306
|
var draining = false;
|
|
30008
30307
|
async function drain(cfg, deps) {
|
|
30009
30308
|
if (draining) return;
|
|
@@ -30013,13 +30312,14 @@ async function drain(cfg, deps) {
|
|
|
30013
30312
|
if (slots <= 0) return;
|
|
30014
30313
|
for (const item of await queued2()) {
|
|
30015
30314
|
if (slots <= 0) break;
|
|
30315
|
+
if (!await isQueued(item.id)) continue;
|
|
30016
30316
|
if (await deps.isDone(item.specId)) {
|
|
30017
30317
|
await markDone(item.id, ALREADY_DONE_NOTE);
|
|
30018
30318
|
continue;
|
|
30019
30319
|
}
|
|
30020
30320
|
const blocked = await deps.blockers(item.specId);
|
|
30021
30321
|
if (blocked.length) {
|
|
30022
|
-
await
|
|
30322
|
+
await noteRow(item.id, blockedNote(blocked));
|
|
30023
30323
|
continue;
|
|
30024
30324
|
}
|
|
30025
30325
|
const liveId = deps.isLive(item.specId);
|
|
@@ -30029,7 +30329,7 @@ async function drain(cfg, deps) {
|
|
|
30029
30329
|
}
|
|
30030
30330
|
try {
|
|
30031
30331
|
const sessionId2 = await deps.launch(item, cfg.autonomy);
|
|
30032
|
-
await markLaunched(item.id, sessionId2);
|
|
30332
|
+
if (!await markLaunched(item.id, sessionId2)) await noteRow(item.id, canceledRaceNote(sessionId2));
|
|
30033
30333
|
slots--;
|
|
30034
30334
|
} catch (e) {
|
|
30035
30335
|
await markFailed(item.id, e.message);
|