hanoman 0.2.0 → 0.2.2
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 +411 -54
- package/dist/server.js +481 -63
- package/docs/agent-integration.md +9 -1
- package/package.json +1 -1
- package/web/assets/{index-BpV7m-Kb.js → index-BllpAcXg.js} +1547 -1547
- package/web/index.html +1 -1
package/dist/server.js
CHANGED
|
@@ -4913,6 +4913,12 @@ var init_agent = __esm({
|
|
|
4913
4913
|
// capability domain produk masing-masing; domain ini tidak memberi akses shell atau sesi.
|
|
4914
4914
|
"telegram:read",
|
|
4915
4915
|
"telegram:write",
|
|
4916
|
+
// ADR-0157 · papan Tim (`/api/tasks`, `/api/members`). Domain TERSENDIRI, bukan menumpang
|
|
4917
|
+
// `backlog`: kartu tim adalah pekerjaan MANUSIA yang `status`-nya bebas dipindah, sementara
|
|
4918
|
+
// `Spec.stage` diturunkan dari fase sesi (ADR-0008/0024). Menumpangkannya berarti satu centang
|
|
4919
|
+
// "Backlog — tulis" diam-diam membuka papan orang, dan sebaliknya.
|
|
4920
|
+
"team:read",
|
|
4921
|
+
"team:write",
|
|
4916
4922
|
// ADR-0155 · akses KETIGA: `danger`. Dipecah dari `:write` karena keempat operasi ini bukan
|
|
4917
4923
|
// "menulis lebih banyak", melainkan menjalankan sesuatu di luar proses hanoman — sesi agen di
|
|
4918
4924
|
// worktree, perintah di VPS, git yang mengubah sejarah, penghapusan artefak dokumen.
|
|
@@ -4957,6 +4963,8 @@ var init_agent = __esm({
|
|
|
4957
4963
|
{ id: "agents:write", domain: "agents", access: "write", label: "Custom agent \u2014 tulis", desc: "Buat/ubah/hapus custom agent; definisinya dipakai setiap sesi baru.", risk: "exec" },
|
|
4958
4964
|
{ id: "telegram:read", domain: "telegram", access: "read", label: "Telegram \u2014 baca", desc: "Baca status gateway, binding, memory, dan audit Telegram." },
|
|
4959
4965
|
{ id: "telegram:write", domain: "telegram", access: "write", label: "Telegram \u2014 tulis", desc: "Perbarui context/memory dan terbitkan reply sesi operator Telegram." },
|
|
4966
|
+
{ id: "team:read", domain: "team", access: "read", label: "Tim \u2014 baca", desc: "Lihat kartu papan Tim & direktori anggota." },
|
|
4967
|
+
{ id: "team:write", domain: "team", access: "write", label: "Tim \u2014 tulis", desc: "Buat/ubah/hapus kartu papan Tim & anggota; eskalasi kartu jadi backlog item." },
|
|
4960
4968
|
// ADR-0155 · akses `danger`. Tak satu pun diimplikasikan `:write` di domainnya; manusia harus
|
|
4961
4969
|
// mencentangnya sendiri di Settings → Akses AI Agent.
|
|
4962
4970
|
{ id: "sessions:spawn", domain: "sessions", access: "danger", label: "Sesi \u2014 buka sesi baru", desc: "Membuka sesi agen BARU di worktree (menjalankan claude/codex dengan izin penuh). Dipisah dari Sesi \u2014 tulis: mengendalikan sesi yang sudah ada tak lagi cukup untuk membuka yang baru.", risk: "rce" },
|
|
@@ -6520,7 +6528,15 @@ var init_api = __esm({
|
|
|
6520
6528
|
breakdown: (id, prd) => `${API}/projects/${id}/breakdown?prd=${encodeURIComponent(prd)}`,
|
|
6521
6529
|
specsBatch: `${API}/specs/batch`,
|
|
6522
6530
|
// SPEC-182 · IDE Visual
|
|
6523
|
-
|
|
6531
|
+
// `hidden` menyertakan entri .gitignore; `under` membuka satu tingkat isi sebuah direktori.
|
|
6532
|
+
ideTree: (id, ref = "", opts) => {
|
|
6533
|
+
const q = new URLSearchParams();
|
|
6534
|
+
if (ref) q.set("ref", ref);
|
|
6535
|
+
if (opts?.hidden) q.set("hidden", "1");
|
|
6536
|
+
if (opts?.under) q.set("under", opts.under);
|
|
6537
|
+
const s2 = q.toString();
|
|
6538
|
+
return `${API}/projects/${id}/tree${s2 ? `?${s2}` : ""}`;
|
|
6539
|
+
},
|
|
6524
6540
|
ideFile: (id, path, ref = "") => `${API}/projects/${id}/file${path ? `?path=${encodeURIComponent(path)}${ref ? `&ref=${encodeURIComponent(ref)}` : ""}` : ""}`,
|
|
6525
6541
|
ideGraph: (id, limit = 200, opts) => {
|
|
6526
6542
|
const p3 = new URLSearchParams({ limit: String(limit) });
|
|
@@ -7640,6 +7656,51 @@ var init_webhook = __esm({
|
|
|
7640
7656
|
}
|
|
7641
7657
|
});
|
|
7642
7658
|
|
|
7659
|
+
// ../shared/src/team.ts
|
|
7660
|
+
var TASK_STATUSES, zTaskStatus, memberId, zEmail, zName, zIso, zCreateMember, zPatchMember, zCreateTask, zPatchTask, ESCALATE_SOURCES, zEscalateTask;
|
|
7661
|
+
var init_team = __esm({
|
|
7662
|
+
"../shared/src/team.ts"() {
|
|
7663
|
+
"use strict";
|
|
7664
|
+
init_zod();
|
|
7665
|
+
init_enums();
|
|
7666
|
+
TASK_STATUSES = ["backlog", "doing", "review", "done"];
|
|
7667
|
+
zTaskStatus = external_exports.enum(TASK_STATUSES);
|
|
7668
|
+
memberId = (email) => email.trim().toLowerCase();
|
|
7669
|
+
zEmail = external_exports.string().trim().min(3).max(200).email();
|
|
7670
|
+
zName = external_exports.string().trim().min(1).max(120);
|
|
7671
|
+
zIso = external_exports.string().datetime({ offset: true });
|
|
7672
|
+
zCreateMember = external_exports.object({
|
|
7673
|
+
name: zName,
|
|
7674
|
+
email: zEmail,
|
|
7675
|
+
role: external_exports.string().trim().max(60).nullable().optional()
|
|
7676
|
+
});
|
|
7677
|
+
zPatchMember = zCreateMember.omit({ email: true }).partial().extend({
|
|
7678
|
+
active: external_exports.boolean().optional()
|
|
7679
|
+
});
|
|
7680
|
+
zCreateTask = external_exports.object({
|
|
7681
|
+
title: external_exports.string().trim().min(1).max(300),
|
|
7682
|
+
detail: external_exports.string().max(2e4).nullable().optional(),
|
|
7683
|
+
projectId: external_exports.string().max(120).nullable().optional(),
|
|
7684
|
+
status: zTaskStatus.default("backlog"),
|
|
7685
|
+
priority: zPriority.default("sedang"),
|
|
7686
|
+
memberId: external_exports.string().max(200).nullable().optional(),
|
|
7687
|
+
startDate: zIso.nullable().optional(),
|
|
7688
|
+
dueDate: zIso.nullable().optional(),
|
|
7689
|
+
order: external_exports.number().finite().optional()
|
|
7690
|
+
});
|
|
7691
|
+
zPatchTask = zCreateTask.partial();
|
|
7692
|
+
ESCALATE_SOURCES = ["brief", "qa", "audit"];
|
|
7693
|
+
zEscalateTask = external_exports.object({
|
|
7694
|
+
source: external_exports.enum(ESCALATE_SOURCES).default("brief"),
|
|
7695
|
+
priority: zPriority.default("sedang"),
|
|
7696
|
+
// Dipakai HANYA saat kartunya belum punya project (`nextSpecId` butuh repo, dan repo milik
|
|
7697
|
+
// project). Route menolak 400 bila ia menyebut project LAIN — kartu tak boleh berpindah project
|
|
7698
|
+
// sebagai efek samping yang tak diminta.
|
|
7699
|
+
projectId: external_exports.string().max(120).optional()
|
|
7700
|
+
});
|
|
7701
|
+
}
|
|
7702
|
+
});
|
|
7703
|
+
|
|
7643
7704
|
// ../shared/src/mcp-schema.ts
|
|
7644
7705
|
function obj(o) {
|
|
7645
7706
|
return {
|
|
@@ -7651,11 +7712,12 @@ function obj(o) {
|
|
|
7651
7712
|
...o.allOf ? { allOf: o.allOf } : {}
|
|
7652
7713
|
};
|
|
7653
7714
|
}
|
|
7654
|
-
var str, enumStr, bool, int, strArray, PRIORITY_ENUM, SOURCE_ENUM, STAGE_ENUM, SEVERITY_ENUM, PRIORITY, BRIEF_PAYLOAD, QA_PAYLOAD, GOAL_PAYLOAD, SPEC_PAYLOAD_ONEOF, SOURCE_PAYLOAD_ALLOF, PAGE_PARAMS, DATE_PARAMS;
|
|
7715
|
+
var str, enumStr, bool, int, strArray, PRIORITY_ENUM, SOURCE_ENUM, STAGE_ENUM, SEVERITY_ENUM, TASK_STATUS_ENUM, ESCALATE_SOURCE_ENUM, PRIORITY, BRIEF_PAYLOAD, QA_PAYLOAD, GOAL_PAYLOAD, SPEC_PAYLOAD_ONEOF, SOURCE_PAYLOAD_ALLOF, PAGE_PARAMS, DATE_PARAMS;
|
|
7655
7716
|
var init_mcp_schema = __esm({
|
|
7656
7717
|
"../shared/src/mcp-schema.ts"() {
|
|
7657
7718
|
"use strict";
|
|
7658
7719
|
init_enums();
|
|
7720
|
+
init_team();
|
|
7659
7721
|
str = (description, extra = {}) => ({ type: "string", description, ...extra });
|
|
7660
7722
|
enumStr = (values, description) => ({ type: "string", enum: values, description });
|
|
7661
7723
|
bool = (description) => ({ type: "boolean", description });
|
|
@@ -7665,6 +7727,8 @@ var init_mcp_schema = __esm({
|
|
|
7665
7727
|
SOURCE_ENUM = zSpecSource.options;
|
|
7666
7728
|
STAGE_ENUM = zStage.options;
|
|
7667
7729
|
SEVERITY_ENUM = ["critical", "major", "minor"];
|
|
7730
|
+
TASK_STATUS_ENUM = TASK_STATUSES;
|
|
7731
|
+
ESCALATE_SOURCE_ENUM = ESCALATE_SOURCES;
|
|
7668
7732
|
PRIORITY = enumStr(
|
|
7669
7733
|
PRIORITY_ENUM,
|
|
7670
7734
|
"Prioritas backlog. Nilainya bahasa Indonesia \u2014 `tinggi`, `sedang`, `rendah`. Bukan high/medium/low."
|
|
@@ -7744,7 +7808,7 @@ function paginateLocal(items, page, limit) {
|
|
|
7744
7808
|
const p3 = Math.max(1, Math.floor(page ?? 1));
|
|
7745
7809
|
return { items: items.slice((p3 - 1) * size, p3 * size), total: items.length, page: p3, pageSize: size };
|
|
7746
7810
|
}
|
|
7747
|
-
var DEFAULT_MAX_BYTES, DEFAULT_LIMIT, MAX_LIMIT, OBJECTIVE_CLIP, MARK, pick, shapeProject, shapeProjectDetail, SPEC_BASE, shapeSpecDetail, shapeSession, shapeNotification, shapeTicket, shapeGithubIssue, shapeLeadDecision;
|
|
7811
|
+
var DEFAULT_MAX_BYTES, DEFAULT_LIMIT, MAX_LIMIT, OBJECTIVE_CLIP, MARK, pick, shapeProject, shapeProjectDetail, SPEC_BASE, shapeSpecDetail, shapeSession, shapeNotification, shapeTicket, shapeGithubIssue, shapeTask, shapeMember, shapeLeadDecision;
|
|
7748
7812
|
var init_mcp_shape = __esm({
|
|
7749
7813
|
"../shared/src/mcp-shape.ts"() {
|
|
7750
7814
|
"use strict";
|
|
@@ -7806,6 +7870,25 @@ var init_mcp_shape = __esm({
|
|
|
7806
7870
|
...pick(row, ["id", "projectId", "repoSlug", "number", "title", "authorLogin", "labels", "url", "issueState", "status", "specId", "issueUpdatedAt"]),
|
|
7807
7871
|
body: clip(row.body, 500)
|
|
7808
7872
|
});
|
|
7873
|
+
shapeTask = (row) => ({
|
|
7874
|
+
...pick(row, [
|
|
7875
|
+
"id",
|
|
7876
|
+
"projectId",
|
|
7877
|
+
"title",
|
|
7878
|
+
"status",
|
|
7879
|
+
"priority",
|
|
7880
|
+
"memberId",
|
|
7881
|
+
"startDate",
|
|
7882
|
+
"dueDate",
|
|
7883
|
+
"order",
|
|
7884
|
+
"specId",
|
|
7885
|
+
"spec",
|
|
7886
|
+
"createdAt",
|
|
7887
|
+
"updatedAt"
|
|
7888
|
+
]),
|
|
7889
|
+
detail: clip(row.detail, 500)
|
|
7890
|
+
});
|
|
7891
|
+
shapeMember = (row) => pick(row, ["id", "name", "email", "role", "active"]);
|
|
7809
7892
|
shapeLeadDecision = (row) => ({
|
|
7810
7893
|
...pick(row, ["id", "projectId", "specId", "sessionId", "gate", "kind", "status", "confidence", "action", "choice", "createdAt"]),
|
|
7811
7894
|
question: clip(row.question, 300),
|
|
@@ -8815,16 +8898,25 @@ var init_ide = __esm({
|
|
|
8815
8898
|
{
|
|
8816
8899
|
name: "hanoman_ide_tree",
|
|
8817
8900
|
title: "Pohon berkas repo project",
|
|
8818
|
-
description: "Daftar berkas repo project pada sebuah ref. Tanpa `ref` ia membaca working tree apa adanya, termasuk perubahan yang belum di-commit.",
|
|
8901
|
+
description: "Daftar berkas repo project pada sebuah ref. Tanpa `ref` ia membaca working tree apa adanya, termasuk perubahan yang belum di-commit. `hidden` ikut menyertakan yang .gitignore sembunyikan \u2014 direktori yang seluruhnya diabaikan (mis. `node_modules`) diruntuhkan jadi satu entri di `dirs`; buka isinya dengan `under`, satu tingkat per panggilan.",
|
|
8819
8902
|
inputSchema: obj({
|
|
8820
|
-
properties: {
|
|
8903
|
+
properties: {
|
|
8904
|
+
project: PROJECT2,
|
|
8905
|
+
ref: str("Ref git (branch/tag/sha). Kosongkan untuk working tree."),
|
|
8906
|
+
hidden: bool("Sertakan berkas & direktori yang diabaikan .gitignore. Tak berlaku bersama `ref`."),
|
|
8907
|
+
under: str("Jalur direktori; balasannya berisi SATU tingkat isinya. Dipakai untuk membuka direktori terabaikan yang diruntuhkan.")
|
|
8908
|
+
},
|
|
8821
8909
|
required: ["project"]
|
|
8822
8910
|
}),
|
|
8823
8911
|
mode: "read",
|
|
8824
8912
|
capability: "ide:read",
|
|
8825
8913
|
samplePath: "/projects/hanoman/tree",
|
|
8826
8914
|
sampleMethod: "GET",
|
|
8827
|
-
build: (a) => ({
|
|
8915
|
+
build: (a) => ({
|
|
8916
|
+
method: "GET",
|
|
8917
|
+
path: `${p(a.project)}/tree`,
|
|
8918
|
+
query: query({ ref: s(a.ref), hidden: a.hidden ? "1" : "", under: s(a.under) })
|
|
8919
|
+
}),
|
|
8828
8920
|
shape: (raw) => raw
|
|
8829
8921
|
},
|
|
8830
8922
|
{
|
|
@@ -10907,6 +10999,311 @@ var init_lead2 = __esm({
|
|
|
10907
10999
|
}
|
|
10908
11000
|
});
|
|
10909
11001
|
|
|
11002
|
+
// ../shared/src/mcp-catalog/team.ts
|
|
11003
|
+
var TASK_ID, MEMBER_ID, nullable, CLEAR, TASK_FIELDS, taskBody, TASKS, MEMBERS, DESTRUCTIVE, TEAM_TOOLS;
|
|
11004
|
+
var init_team2 = __esm({
|
|
11005
|
+
"../shared/src/mcp-catalog/team.ts"() {
|
|
11006
|
+
"use strict";
|
|
11007
|
+
init_mcp_schema();
|
|
11008
|
+
init_mcp_shape();
|
|
11009
|
+
init_helpers();
|
|
11010
|
+
TASK_ID = str("Id kartu papan Tim, seperti muncul di hanoman_tasks_list.");
|
|
11011
|
+
MEMBER_ID = str("Id anggota \u2014 email ternormalisasi (huruf kecil), seperti muncul di hanoman_members_list.");
|
|
11012
|
+
nullable = (v) => {
|
|
11013
|
+
if (typeof v !== "string") return void 0;
|
|
11014
|
+
return v === "" ? null : v;
|
|
11015
|
+
};
|
|
11016
|
+
CLEAR = " Kirim string KOSONG untuk mengosongkannya; tak menyebutnya sama sekali berarti biarkan apa adanya.";
|
|
11017
|
+
TASK_FIELDS = {
|
|
11018
|
+
title: str("Judul kartu, satu baris."),
|
|
11019
|
+
detail: str("Isi kartu. Markdown bebas." + CLEAR),
|
|
11020
|
+
project: str("Id project pemilik kartu. Kartu BOLEH tanpa project \u2014 papan Tim tak mensyaratkannya." + CLEAR),
|
|
11021
|
+
status: enumStr(TASK_STATUS_ENUM, "Kolom papan. Milik MANUSIA dan bebas dipindah \u2014 ini BUKAN `stage` backlog, yang diturunkan dari fase sesi."),
|
|
11022
|
+
priority: PRIORITY,
|
|
11023
|
+
member: str("Id anggota yang ditugaskan (email huruf kecil), dari hanoman_members_list." + CLEAR),
|
|
11024
|
+
startDate: str("Tanggal mulai, ISO 8601 berzona mis. `2026-09-01T00:00:00.000Z`. `besok` ditolak." + CLEAR),
|
|
11025
|
+
dueDate: str("Tanggal jatuh tempo, ISO 8601 berzona. Ini tanggal KARTU, bukan estimasi backlog." + CLEAR)
|
|
11026
|
+
};
|
|
11027
|
+
taskBody = (a) => {
|
|
11028
|
+
const body = {};
|
|
11029
|
+
if (s(a.title)) body.title = s(a.title);
|
|
11030
|
+
if (s(a.status)) body.status = s(a.status);
|
|
11031
|
+
if (s(a.priority)) body.priority = s(a.priority);
|
|
11032
|
+
if (n(a.order) !== void 0) body.order = n(a.order);
|
|
11033
|
+
for (const [key, arg] of [
|
|
11034
|
+
["detail", a.detail],
|
|
11035
|
+
["projectId", a.project],
|
|
11036
|
+
["memberId", a.member],
|
|
11037
|
+
["startDate", a.startDate],
|
|
11038
|
+
["dueDate", a.dueDate]
|
|
11039
|
+
]) {
|
|
11040
|
+
const v = nullable(arg);
|
|
11041
|
+
if (v !== void 0) body[key] = v;
|
|
11042
|
+
}
|
|
11043
|
+
return body;
|
|
11044
|
+
};
|
|
11045
|
+
TASKS = [
|
|
11046
|
+
{
|
|
11047
|
+
name: "hanoman_tasks_list",
|
|
11048
|
+
title: "Kartu papan Tim",
|
|
11049
|
+
description: "Kartu kerja MANUSIA di papan Tim, terurut sesuai posisinya di kolom. Empat kolom: `backlog`, `doing`, `review`, `done` \u2014 milik manusia, tak diturunkan dari sesi agen. `specId`/`spec` terisi hanya untuk kartu yang sudah dieskalasi jadi backlog item; `specId` terisi sementara `spec` null berarti tautannya PUTUS (backlognya sudah dihapus), bukan belum dieskalasi.",
|
|
11050
|
+
inputSchema: obj({
|
|
11051
|
+
properties: {
|
|
11052
|
+
project: str("Id project. Tanpa ini, seluruh project \u2014 termasuk kartu tanpa project."),
|
|
11053
|
+
status: enumStr(TASK_STATUS_ENUM, "Kolom papan."),
|
|
11054
|
+
member: MEMBER_ID,
|
|
11055
|
+
q: str("Cari substring pada judul + detail, tak peka huruf besar-kecil. Disaring SEBELUM paginasi, jadi hasilnya tak buta terhadap kartu di luar halaman pertama."),
|
|
11056
|
+
...PAGE_PARAMS
|
|
11057
|
+
}
|
|
11058
|
+
}),
|
|
11059
|
+
mode: "read",
|
|
11060
|
+
capability: "team:read",
|
|
11061
|
+
samplePath: "/tasks",
|
|
11062
|
+
sampleMethod: "GET",
|
|
11063
|
+
build: (a) => ({
|
|
11064
|
+
method: "GET",
|
|
11065
|
+
path: "/tasks",
|
|
11066
|
+
query: query({
|
|
11067
|
+
projectId: s(a.project),
|
|
11068
|
+
status: s(a.status),
|
|
11069
|
+
memberId: s(a.member),
|
|
11070
|
+
q: s(a.q),
|
|
11071
|
+
page: n(a.page) === void 0 ? void 0 : String(n(a.page)),
|
|
11072
|
+
limit: n(a.limit) === void 0 ? void 0 : String(n(a.limit))
|
|
11073
|
+
})
|
|
11074
|
+
}),
|
|
11075
|
+
shape: (raw) => reshapePage(raw, shapeTask)
|
|
11076
|
+
},
|
|
11077
|
+
{
|
|
11078
|
+
name: "hanoman_task_create",
|
|
11079
|
+
title: "Buat kartu papan Tim",
|
|
11080
|
+
description: "Membuat kartu kerja manusia di papan Tim. Kartu BARU tak pernah tertaut backlog \u2014 tautan itu lahir dari hanoman_task_escalate, bukan dari field. Untuk pekerjaan yang akan dikerjakan AGEN, pakai hanoman_backlog_create: papan ini bukan antrean sesi.",
|
|
11081
|
+
inputSchema: obj({ properties: { ...TASK_FIELDS }, required: ["title"] }),
|
|
11082
|
+
mode: "write",
|
|
11083
|
+
capability: "team:write",
|
|
11084
|
+
samplePath: "/tasks",
|
|
11085
|
+
sampleMethod: "POST",
|
|
11086
|
+
build: (a) => ({ method: "POST", path: "/tasks", body: taskBody(a) }),
|
|
11087
|
+
shape: (raw) => shapeTask(raw)
|
|
11088
|
+
},
|
|
11089
|
+
{
|
|
11090
|
+
name: "hanoman_task_update",
|
|
11091
|
+
title: "Ubah kartu papan Tim",
|
|
11092
|
+
description: "Mengubah kartu papan Tim. Hanya field yang disebut yang ditulis. Kartu yang SUDAH tertaut backlog menolak pindah project dengan 400 \u2014 lepas tautannya dulu lewat hanoman_task_unlink. `specId` sengaja tak bisa diisi di sini.",
|
|
11093
|
+
inputSchema: obj({ properties: { task: TASK_ID, ...TASK_FIELDS }, required: ["task"] }),
|
|
11094
|
+
mode: "write",
|
|
11095
|
+
capability: "team:write",
|
|
11096
|
+
samplePath: "/tasks/t1",
|
|
11097
|
+
sampleMethod: "PATCH",
|
|
11098
|
+
build: (a) => ({ method: "PATCH", path: `/tasks/${enc(String(a.task))}`, body: taskBody(a) }),
|
|
11099
|
+
shape: (raw) => shapeTask(raw)
|
|
11100
|
+
},
|
|
11101
|
+
{
|
|
11102
|
+
name: "hanoman_task_escalate",
|
|
11103
|
+
title: "Eskalasi kartu jadi backlog item",
|
|
11104
|
+
description: "Melahirkan backlog item dari sebuah kartu papan Tim, lalu menautkan keduanya. IDEMPOTEN: kartu yang sudah tertaut menjawab 200 dengan `created: false` dan spec yang sama, bukan membuat backlog kedua. Kartu TANPA project ditolak 400 \u2014 id backlog diturunkan dari repo milik project, jadi sebutkan `project` lebih dulu. Ini TIDAK membuka sesi agen; peluncurannya tetap tindakan terpisah, dan Spec-nya lahir tanpa persetujuan peluncuran bila token pemanggil tak memegang `sessions:write`.",
|
|
11105
|
+
inputSchema: obj({
|
|
11106
|
+
properties: {
|
|
11107
|
+
task: TASK_ID,
|
|
11108
|
+
source: enumStr(ESCALATE_SOURCE_ENUM, "Bentuk backlog yang dilahirkan. Default `brief`. `goal`/`no_effort`/`help` sengaja tak tersedia: dua yang pertama menuntut kalimat yang hanya operator bisa tulis, yang terakhir milik tiket Help Center."),
|
|
11109
|
+
priority: PRIORITY,
|
|
11110
|
+
project: str("Id project tujuan. HANYA dipakai bila kartunya belum punya project \u2014 menyebut project LAIN dari milik kartu ditolak 400.")
|
|
11111
|
+
},
|
|
11112
|
+
required: ["task"]
|
|
11113
|
+
}),
|
|
11114
|
+
mode: "write",
|
|
11115
|
+
capability: "team:write",
|
|
11116
|
+
samplePath: "/tasks/t1/escalate",
|
|
11117
|
+
sampleMethod: "POST",
|
|
11118
|
+
build: (a) => ({
|
|
11119
|
+
method: "POST",
|
|
11120
|
+
path: `/tasks/${enc(String(a.task))}/escalate`,
|
|
11121
|
+
body: query({ source: s(a.source), priority: s(a.priority), projectId: s(a.project) })
|
|
11122
|
+
}),
|
|
11123
|
+
shape: (raw) => raw
|
|
11124
|
+
},
|
|
11125
|
+
{
|
|
11126
|
+
name: "hanoman_task_unlink",
|
|
11127
|
+
title: "Lepas tautan kartu dari backlog",
|
|
11128
|
+
description: "Melepas tautan kartu papan Tim dari backlog item-nya. NON-DESTRUKTIF: backlog item-nya dibiarkan hidup \u2014 hapus manual lewat hanoman_backlog_delete bila memang salah eskalasi. Idempoten: kartu yang memang tak tertaut menjawab 200 apa adanya.",
|
|
11129
|
+
inputSchema: obj({ properties: { task: TASK_ID }, required: ["task"] }),
|
|
11130
|
+
mode: "write",
|
|
11131
|
+
capability: "team:write",
|
|
11132
|
+
samplePath: "/tasks/t1/escalate",
|
|
11133
|
+
sampleMethod: "DELETE",
|
|
11134
|
+
build: (a) => ({ method: "DELETE", path: `/tasks/${enc(String(a.task))}/escalate` }),
|
|
11135
|
+
shape: (raw) => shapeTask(raw)
|
|
11136
|
+
}
|
|
11137
|
+
];
|
|
11138
|
+
MEMBERS = [
|
|
11139
|
+
{
|
|
11140
|
+
name: "hanoman_members_list",
|
|
11141
|
+
title: "Anggota tim",
|
|
11142
|
+
description: "Direktori orang untuk papan Tim. GLOBAL, bukan per project \u2014 kartu boleh tanpa project. Anggota nonaktif TETAP terdaftar (di bawah): kartu lama yang ditugaskan padanya harus tetap punya nama. `id` = email ternormalisasi, dan itu yang dipakai sebagai `member` di tool kartu.",
|
|
11143
|
+
inputSchema: obj({ properties: { activeOnly: bool("Hanya anggota aktif. Default: semua, nonaktif di urutan bawah."), ...PAGE_PARAMS } }),
|
|
11144
|
+
mode: "read",
|
|
11145
|
+
capability: "team:read",
|
|
11146
|
+
samplePath: "/members",
|
|
11147
|
+
sampleMethod: "GET",
|
|
11148
|
+
build: (a) => ({
|
|
11149
|
+
method: "GET",
|
|
11150
|
+
path: "/members",
|
|
11151
|
+
query: query({
|
|
11152
|
+
active: a.activeOnly === true ? "true" : void 0,
|
|
11153
|
+
page: n(a.page) === void 0 ? void 0 : String(n(a.page)),
|
|
11154
|
+
limit: n(a.limit) === void 0 ? void 0 : String(n(a.limit))
|
|
11155
|
+
})
|
|
11156
|
+
}),
|
|
11157
|
+
shape: (raw) => reshapePage(raw, shapeMember)
|
|
11158
|
+
},
|
|
11159
|
+
{
|
|
11160
|
+
name: "hanoman_member_create",
|
|
11161
|
+
title: "Tambah anggota tim",
|
|
11162
|
+
description: "Menambah orang ke direktori papan Tim. `id` DITURUNKAN dari email (dipangkas + huruf kecil), bukan diacak \u2014 email yang sudah terdaftar menjawab 409 berikut id-nya, bukan membuat baris kedua.",
|
|
11163
|
+
inputSchema: obj({
|
|
11164
|
+
properties: {
|
|
11165
|
+
name: str("Nama yang ditampilkan di kartu."),
|
|
11166
|
+
email: str("Email. Menjadi id anggota sesudah dipangkas & dihurufkecilkan \u2014 dan id itu TAK BISA diubah kemudian."),
|
|
11167
|
+
role: str("Peran, mis. `desainer`. Bebas teks.")
|
|
11168
|
+
},
|
|
11169
|
+
required: ["name", "email"]
|
|
11170
|
+
}),
|
|
11171
|
+
mode: "write",
|
|
11172
|
+
capability: "team:write",
|
|
11173
|
+
samplePath: "/members",
|
|
11174
|
+
sampleMethod: "POST",
|
|
11175
|
+
build: (a) => ({
|
|
11176
|
+
method: "POST",
|
|
11177
|
+
path: "/members",
|
|
11178
|
+
body: { name: String(a.name), email: String(a.email), ...s(a.role) ? { role: s(a.role) } : {} }
|
|
11179
|
+
}),
|
|
11180
|
+
shape: (raw) => shapeMember(raw)
|
|
11181
|
+
},
|
|
11182
|
+
{
|
|
11183
|
+
name: "hanoman_member_update",
|
|
11184
|
+
title: "Ubah anggota tim",
|
|
11185
|
+
description: "Mengubah nama, peran, atau status aktif seorang anggota. `email` TIDAK bisa diubah dan ditolak eksplisit 400: id diturunkan darinya dan sync tak punya operasi rename, jadi id yang berubah meninggalkan baris yatim di setiap mesin lain. Ganti email = hapus lalu buat baru. Menonaktifkan TIDAK melepas kartu-kartunya.",
|
|
11186
|
+
inputSchema: obj({
|
|
11187
|
+
properties: {
|
|
11188
|
+
member: MEMBER_ID,
|
|
11189
|
+
name: str("Nama baru."),
|
|
11190
|
+
role: str("Peran baru." + CLEAR),
|
|
11191
|
+
active: bool("false = nonaktif: tetap terdaftar dan tetap memegang kartunya, hanya turun di daftar.")
|
|
11192
|
+
},
|
|
11193
|
+
required: ["member"]
|
|
11194
|
+
}),
|
|
11195
|
+
mode: "write",
|
|
11196
|
+
capability: "team:write",
|
|
11197
|
+
samplePath: "/members/a@b.id",
|
|
11198
|
+
sampleMethod: "PATCH",
|
|
11199
|
+
build: (a) => {
|
|
11200
|
+
const body = {};
|
|
11201
|
+
if (s(a.name)) body.name = s(a.name);
|
|
11202
|
+
if (typeof a.active === "boolean") body.active = a.active;
|
|
11203
|
+
const role = nullable(a.role);
|
|
11204
|
+
if (role !== void 0) body.role = role;
|
|
11205
|
+
return { method: "PATCH", path: `/members/${enc(String(a.member))}`, body };
|
|
11206
|
+
},
|
|
11207
|
+
shape: (raw) => shapeMember(raw)
|
|
11208
|
+
}
|
|
11209
|
+
];
|
|
11210
|
+
DESTRUCTIVE = [
|
|
11211
|
+
{
|
|
11212
|
+
name: "hanoman_task_delete",
|
|
11213
|
+
title: "Hapus kartu papan Tim (BERBAHAYA)",
|
|
11214
|
+
description: "BERBAHAYA \u2014 menghapus kartu papan Tim secara permanen di SELURUH mesin yang tersync. Backlog item yang tertaut TIDAK ikut terhapus. Untuk sekadar mengarsipkan, pindahkan kartunya ke kolom `done` lewat hanoman_task_update. Hanya muncul saat tingkat `--danger` menyala.",
|
|
11215
|
+
inputSchema: obj({ properties: { task: TASK_ID }, required: ["task"] }),
|
|
11216
|
+
mode: "danger",
|
|
11217
|
+
capability: "team:write",
|
|
11218
|
+
samplePath: "/tasks/t1",
|
|
11219
|
+
sampleMethod: "DELETE",
|
|
11220
|
+
build: (a) => ({ method: "DELETE", path: `/tasks/${enc(String(a.task))}` }),
|
|
11221
|
+
shape: (raw) => raw
|
|
11222
|
+
},
|
|
11223
|
+
{
|
|
11224
|
+
name: "hanoman_member_delete",
|
|
11225
|
+
title: "Hapus anggota tim (BERBAHAYA)",
|
|
11226
|
+
description: "BERBAHAYA \u2014 menghapus anggota secara permanen di SELURUH mesin yang tersync. Kartu-kartunya TIDAK ikut terhapus: penugasannya dikosongkan (`memberId` jadi null), dan tak ada jejak siapa pemegangnya sebelum itu. Untuk orang yang sekadar berhenti aktif, pakai hanoman_member_update dengan `active: false` \u2014 ia tetap terdaftar dan kartunya tetap bernama. Hanya muncul saat tingkat `--danger` menyala.",
|
|
11227
|
+
inputSchema: obj({ properties: { member: MEMBER_ID }, required: ["member"] }),
|
|
11228
|
+
mode: "danger",
|
|
11229
|
+
capability: "team:write",
|
|
11230
|
+
samplePath: "/members/a@b.id",
|
|
11231
|
+
sampleMethod: "DELETE",
|
|
11232
|
+
build: (a) => ({ method: "DELETE", path: `/members/${enc(String(a.member))}` }),
|
|
11233
|
+
shape: (raw) => raw
|
|
11234
|
+
}
|
|
11235
|
+
];
|
|
11236
|
+
TEAM_TOOLS = [...TASKS, ...MEMBERS, ...DESTRUCTIVE];
|
|
11237
|
+
}
|
|
11238
|
+
});
|
|
11239
|
+
|
|
11240
|
+
// ../shared/src/mcp-catalog/system.ts
|
|
11241
|
+
var SYSTEM_TOOLS;
|
|
11242
|
+
var init_system = __esm({
|
|
11243
|
+
"../shared/src/mcp-catalog/system.ts"() {
|
|
11244
|
+
"use strict";
|
|
11245
|
+
init_mcp_schema();
|
|
11246
|
+
SYSTEM_TOOLS = [
|
|
11247
|
+
{
|
|
11248
|
+
name: "hanoman_limits",
|
|
11249
|
+
title: "Sisa kuota langganan Claude",
|
|
11250
|
+
description: "Sisa kuota langganan Claude milik mesin ini berikut kapan jendelanya reset. Dibaca realtime dan di-cache 30 detik di server. Dipakai untuk memutuskan APAKAH pekerjaan berat layak dimulai sekarang \u2014 bukan gerbang: hanoman tak pernah menolak membuka sesi karena angka ini. Tak menuntut capability apa pun.",
|
|
11251
|
+
inputSchema: obj({ properties: {} }),
|
|
11252
|
+
mode: "read",
|
|
11253
|
+
capability: null,
|
|
11254
|
+
samplePath: "/limits",
|
|
11255
|
+
sampleMethod: "GET",
|
|
11256
|
+
build: () => ({ method: "GET", path: "/limits" }),
|
|
11257
|
+
shape: (raw) => raw
|
|
11258
|
+
},
|
|
11259
|
+
{
|
|
11260
|
+
name: "hanoman_limits_codex",
|
|
11261
|
+
title: "Sisa kuota Codex",
|
|
11262
|
+
description: "Sisa kuota codex, dibaca dari snapshot `rate_limits` di rollout sesi codex terakhir \u2014 TANPA panggilan jaringan, jadi kesegarannya seumur sesi codex terakhir di mesin ini, bukan realtime. Endpoint terpisah dari hanoman_limits karena sumber dan semantik kesegarannya memang beda. Tak menuntut capability apa pun.",
|
|
11263
|
+
inputSchema: obj({ properties: {} }),
|
|
11264
|
+
mode: "read",
|
|
11265
|
+
capability: null,
|
|
11266
|
+
samplePath: "/limits/codex",
|
|
11267
|
+
sampleMethod: "GET",
|
|
11268
|
+
build: () => ({ method: "GET", path: "/limits/codex" }),
|
|
11269
|
+
shape: (raw) => raw
|
|
11270
|
+
},
|
|
11271
|
+
{
|
|
11272
|
+
name: "hanoman_update_status",
|
|
11273
|
+
title: "Status versi & pembaruan",
|
|
11274
|
+
description: "Versi hanoman yang berjalan, versi terbaru di registry npm, dan apakah instance ini PUNYA supervisor yang bisa memasangnya (`canApply`). Baca-saja: memasang pembaruan me-restart instance dan sengaja tetap tindakan manusia di dashboard \u2014 tak ada tool untuk itu, apa pun tingkat modenya. Tak menuntut capability apa pun.",
|
|
11275
|
+
inputSchema: obj({ properties: {} }),
|
|
11276
|
+
mode: "read",
|
|
11277
|
+
capability: null,
|
|
11278
|
+
samplePath: "/update",
|
|
11279
|
+
sampleMethod: "GET",
|
|
11280
|
+
build: () => ({ method: "GET", path: "/update" }),
|
|
11281
|
+
shape: (raw) => raw
|
|
11282
|
+
},
|
|
11283
|
+
{
|
|
11284
|
+
name: "hanoman_fs_browse",
|
|
11285
|
+
title: "Telusuri folder mesin ini",
|
|
11286
|
+
description: "Sub-folder nyata di MESIN tempat hanoman berjalan, berikut path absolutnya \u2014 dipakai untuk mengisi `repoDir` saat mengikat project ke codebase yang sudah ada (hanoman_project_binding_set), yang tanpa ini hanya bisa ditebak. Hanya direktori, berkas tak ikut; yang berawalan titik disembunyikan. Tanpa `path`, ia mulai dari home. Tak menuntut capability apa pun.",
|
|
11287
|
+
inputSchema: obj({
|
|
11288
|
+
properties: {
|
|
11289
|
+
path: str("Path absolut folder yang dibuka. Kosong = folder home pengguna yang menjalankan hanoman. Folder yang tak terbaca menjawab 400, bukan daftar kosong.")
|
|
11290
|
+
}
|
|
11291
|
+
}),
|
|
11292
|
+
mode: "read",
|
|
11293
|
+
capability: null,
|
|
11294
|
+
samplePath: "/fs/browse",
|
|
11295
|
+
sampleMethod: "GET",
|
|
11296
|
+
build: (a) => ({
|
|
11297
|
+
method: "GET",
|
|
11298
|
+
path: "/fs/browse",
|
|
11299
|
+
...typeof a.path === "string" && a.path !== "" ? { query: { path: a.path } } : {}
|
|
11300
|
+
}),
|
|
11301
|
+
shape: (raw) => raw
|
|
11302
|
+
}
|
|
11303
|
+
];
|
|
11304
|
+
}
|
|
11305
|
+
});
|
|
11306
|
+
|
|
10910
11307
|
// ../shared/src/mcp-catalog/index.ts
|
|
10911
11308
|
var MCP_TOOLS;
|
|
10912
11309
|
var init_mcp_catalog = __esm({
|
|
@@ -10927,6 +11324,8 @@ var init_mcp_catalog = __esm({
|
|
|
10927
11324
|
init_notifications();
|
|
10928
11325
|
init_support();
|
|
10929
11326
|
init_lead2();
|
|
11327
|
+
init_team2();
|
|
11328
|
+
init_system();
|
|
10930
11329
|
MCP_TOOLS = [
|
|
10931
11330
|
...ABOUT_TOOLS,
|
|
10932
11331
|
...PROJECTS_TOOLS,
|
|
@@ -10940,7 +11339,12 @@ var init_mcp_catalog = __esm({
|
|
|
10940
11339
|
...SESSIONS_TOOLS,
|
|
10941
11340
|
...NOTIFICATIONS_TOOLS,
|
|
10942
11341
|
...SUPPORT_TOOLS,
|
|
10943
|
-
...LEAD_TOOLS
|
|
11342
|
+
...LEAD_TOOLS,
|
|
11343
|
+
// ADR-0157 · papan Tim (kerja manusia) lalu status instance. Keduanya di ekor daftar dengan
|
|
11344
|
+
// sengaja: urutan di sini = urutan yang dibaca model, dan pekerjaan sehari-hari agen tetap
|
|
11345
|
+
// backlog/sesi/IDE — bukan kartu orang lain.
|
|
11346
|
+
...TEAM_TOOLS,
|
|
11347
|
+
...SYSTEM_TOOLS
|
|
10944
11348
|
];
|
|
10945
11349
|
}
|
|
10946
11350
|
});
|
|
@@ -11247,51 +11651,6 @@ var init_presence = __esm({
|
|
|
11247
11651
|
}
|
|
11248
11652
|
});
|
|
11249
11653
|
|
|
11250
|
-
// ../shared/src/team.ts
|
|
11251
|
-
var TASK_STATUSES, zTaskStatus, memberId, zEmail, zName, zIso, zCreateMember, zPatchMember, zCreateTask, zPatchTask, ESCALATE_SOURCES, zEscalateTask;
|
|
11252
|
-
var init_team = __esm({
|
|
11253
|
-
"../shared/src/team.ts"() {
|
|
11254
|
-
"use strict";
|
|
11255
|
-
init_zod();
|
|
11256
|
-
init_enums();
|
|
11257
|
-
TASK_STATUSES = ["backlog", "doing", "review", "done"];
|
|
11258
|
-
zTaskStatus = external_exports.enum(TASK_STATUSES);
|
|
11259
|
-
memberId = (email) => email.trim().toLowerCase();
|
|
11260
|
-
zEmail = external_exports.string().trim().min(3).max(200).email();
|
|
11261
|
-
zName = external_exports.string().trim().min(1).max(120);
|
|
11262
|
-
zIso = external_exports.string().datetime({ offset: true });
|
|
11263
|
-
zCreateMember = external_exports.object({
|
|
11264
|
-
name: zName,
|
|
11265
|
-
email: zEmail,
|
|
11266
|
-
role: external_exports.string().trim().max(60).nullable().optional()
|
|
11267
|
-
});
|
|
11268
|
-
zPatchMember = zCreateMember.omit({ email: true }).partial().extend({
|
|
11269
|
-
active: external_exports.boolean().optional()
|
|
11270
|
-
});
|
|
11271
|
-
zCreateTask = external_exports.object({
|
|
11272
|
-
title: external_exports.string().trim().min(1).max(300),
|
|
11273
|
-
detail: external_exports.string().max(2e4).nullable().optional(),
|
|
11274
|
-
projectId: external_exports.string().max(120).nullable().optional(),
|
|
11275
|
-
status: zTaskStatus.default("backlog"),
|
|
11276
|
-
priority: zPriority.default("sedang"),
|
|
11277
|
-
memberId: external_exports.string().max(200).nullable().optional(),
|
|
11278
|
-
startDate: zIso.nullable().optional(),
|
|
11279
|
-
dueDate: zIso.nullable().optional(),
|
|
11280
|
-
order: external_exports.number().finite().optional()
|
|
11281
|
-
});
|
|
11282
|
-
zPatchTask = zCreateTask.partial();
|
|
11283
|
-
ESCALATE_SOURCES = ["brief", "qa", "audit"];
|
|
11284
|
-
zEscalateTask = external_exports.object({
|
|
11285
|
-
source: external_exports.enum(ESCALATE_SOURCES).default("brief"),
|
|
11286
|
-
priority: zPriority.default("sedang"),
|
|
11287
|
-
// Dipakai HANYA saat kartunya belum punya project (`nextSpecId` butuh repo, dan repo milik
|
|
11288
|
-
// project). Route menolak 400 bila ia menyebut project LAIN — kartu tak boleh berpindah project
|
|
11289
|
-
// sebagai efek samping yang tak diminta.
|
|
11290
|
-
projectId: external_exports.string().max(120).optional()
|
|
11291
|
-
});
|
|
11292
|
-
}
|
|
11293
|
-
});
|
|
11294
|
-
|
|
11295
11654
|
// ../shared/src/index.ts
|
|
11296
11655
|
var init_src = __esm({
|
|
11297
11656
|
"../shared/src/index.ts"() {
|
|
@@ -26508,6 +26867,7 @@ function ownsWorktree(repoDir, cwd) {
|
|
|
26508
26867
|
import { execFile as execFile7 } from "node:child_process";
|
|
26509
26868
|
import { promisify as promisify6 } from "node:util";
|
|
26510
26869
|
import { existsSync as existsSync7 } from "node:fs";
|
|
26870
|
+
import { readdir as readdir2 } from "node:fs/promises";
|
|
26511
26871
|
import { resolve as resolve14 } from "node:path";
|
|
26512
26872
|
var exec6 = promisify6(execFile7);
|
|
26513
26873
|
var GIT4 = { maxBuffer: 1 << 24 };
|
|
@@ -26527,6 +26887,43 @@ async function listRepoTree(repoDir, ref = "") {
|
|
|
26527
26887
|
return [];
|
|
26528
26888
|
}
|
|
26529
26889
|
}
|
|
26890
|
+
async function listIgnoredEntries(repoDir) {
|
|
26891
|
+
const empty = { files: [], dirs: [] };
|
|
26892
|
+
if (!repoDir || !existsSync7(repoDir)) return empty;
|
|
26893
|
+
try {
|
|
26894
|
+
const { stdout } = await exec6(
|
|
26895
|
+
"git",
|
|
26896
|
+
["ls-files", "--others", "--ignored", "--exclude-standard", "--directory", "--no-empty-directory", "-z"],
|
|
26897
|
+
{ cwd: repoDir, ...GIT4 }
|
|
26898
|
+
);
|
|
26899
|
+
const files = [], dirs = [];
|
|
26900
|
+
for (const e of new Set(splitZ2(stdout))) {
|
|
26901
|
+
if (e.endsWith("/")) dirs.push(e.slice(0, -1));
|
|
26902
|
+
else files.push(e);
|
|
26903
|
+
}
|
|
26904
|
+
return { files: files.sort(), dirs: dirs.sort() };
|
|
26905
|
+
} catch {
|
|
26906
|
+
return empty;
|
|
26907
|
+
}
|
|
26908
|
+
}
|
|
26909
|
+
var DIR_ENTRY_MAX = 5e3;
|
|
26910
|
+
async function listDirEntries(repoDir, rel) {
|
|
26911
|
+
const empty = { files: [], dirs: [], truncated: false };
|
|
26912
|
+
if (!repoDir) return empty;
|
|
26913
|
+
const abs = repoAbsPath(repoDir, rel);
|
|
26914
|
+
let raw;
|
|
26915
|
+
try {
|
|
26916
|
+
raw = await readdir2(abs, { withFileTypes: true });
|
|
26917
|
+
} catch {
|
|
26918
|
+
return empty;
|
|
26919
|
+
}
|
|
26920
|
+
const files = [], dirs = [];
|
|
26921
|
+
for (const d of raw.slice(0, DIR_ENTRY_MAX)) {
|
|
26922
|
+
if (d.name === ".git") continue;
|
|
26923
|
+
(d.isDirectory() ? dirs : files).push(`${rel}/${d.name}`);
|
|
26924
|
+
}
|
|
26925
|
+
return { files: files.sort(), dirs: dirs.sort(), truncated: raw.length > DIR_ENTRY_MAX };
|
|
26926
|
+
}
|
|
26530
26927
|
async function readRepoFile2(repoDir, rel, ref = "") {
|
|
26531
26928
|
if (!repoDir) return null;
|
|
26532
26929
|
repoAbsPath(repoDir, rel, !!ref);
|
|
@@ -31222,7 +31619,7 @@ async function dropSpecAttachments(specId) {
|
|
|
31222
31619
|
|
|
31223
31620
|
// src/services/spec-attachment-dir.ts
|
|
31224
31621
|
init_db();
|
|
31225
|
-
import { mkdir as mkdir4, readdir as
|
|
31622
|
+
import { mkdir as mkdir4, readdir as readdir3, readFile as readFile3, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
|
|
31226
31623
|
import { join as join13 } from "node:path";
|
|
31227
31624
|
init_session_id();
|
|
31228
31625
|
init_uploads();
|
|
@@ -31287,7 +31684,7 @@ async function syncSpecAttachmentsDir(specId, projectId) {
|
|
|
31287
31684
|
}
|
|
31288
31685
|
await writeFile3(join13(dir2, INDEX), renderIndex(specId, items), { mode: 384 });
|
|
31289
31686
|
const keep = /* @__PURE__ */ new Set([INDEX, ...items.map((a) => a.filename)]);
|
|
31290
|
-
for (const name2 of await
|
|
31687
|
+
for (const name2 of await readdir3(dir2)) {
|
|
31291
31688
|
if (!keep.has(name2)) await rm4(join13(dir2, name2), { recursive: true, force: true }).catch(() => {
|
|
31292
31689
|
});
|
|
31293
31690
|
}
|
|
@@ -35153,8 +35550,25 @@ async function ide_default(app2) {
|
|
|
35153
35550
|
app2.get("/projects/:id/tree", async (req, reply) => {
|
|
35154
35551
|
const repoDir = await repoOf(req.params.id);
|
|
35155
35552
|
if (repoDir === void 0) return reply.code(404).send({ error: "not found" });
|
|
35156
|
-
const
|
|
35157
|
-
|
|
35553
|
+
const q = req.query;
|
|
35554
|
+
const ref = q.ref ?? "";
|
|
35555
|
+
const hidden = q.hidden === "1" || q.hidden === "true";
|
|
35556
|
+
if (q.under) {
|
|
35557
|
+
try {
|
|
35558
|
+
return { ref: "", ...await listDirEntries(repoDir, q.under), ignored: [] };
|
|
35559
|
+
} catch (e) {
|
|
35560
|
+
return entryError(reply, e);
|
|
35561
|
+
}
|
|
35562
|
+
}
|
|
35563
|
+
const files = await listRepoTree(repoDir, ref);
|
|
35564
|
+
if (!hidden || ref) return { ref, files, dirs: [], ignored: [] };
|
|
35565
|
+
const ign = await listIgnoredEntries(repoDir);
|
|
35566
|
+
return {
|
|
35567
|
+
ref,
|
|
35568
|
+
files: [...files, ...ign.files].sort(),
|
|
35569
|
+
dirs: ign.dirs,
|
|
35570
|
+
ignored: [...ign.files, ...ign.dirs]
|
|
35571
|
+
};
|
|
35158
35572
|
});
|
|
35159
35573
|
app2.get("/projects/:id/file", async (req, reply) => {
|
|
35160
35574
|
const id = req.params.id;
|
|
@@ -35597,7 +36011,7 @@ async function finishGraphOp(reply, id, repoDir, r, verb) {
|
|
|
35597
36011
|
}
|
|
35598
36012
|
|
|
35599
36013
|
// src/routes/fs.ts
|
|
35600
|
-
import { readdir as
|
|
36014
|
+
import { readdir as readdir4 } from "node:fs/promises";
|
|
35601
36015
|
import { homedir as homedir4 } from "node:os";
|
|
35602
36016
|
import { resolve as resolve17, dirname as dirname9, join as join16 } from "node:path";
|
|
35603
36017
|
async function fs_default(app2) {
|
|
@@ -35605,7 +36019,7 @@ async function fs_default(app2) {
|
|
|
35605
36019
|
const q = req.query.path;
|
|
35606
36020
|
const dir2 = q && q.trim() ? resolve17(q.trim()) : homedir4();
|
|
35607
36021
|
try {
|
|
35608
|
-
const ents = await
|
|
36022
|
+
const ents = await readdir4(dir2, { withFileTypes: true });
|
|
35609
36023
|
const entries3 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join16(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
|
|
35610
36024
|
const parent = dirname9(dir2);
|
|
35611
36025
|
return { path: dir2, parent: parent === dir2 ? null : parent, entries: entries3 };
|
|
@@ -39011,7 +39425,7 @@ async function getLimits() {
|
|
|
39011
39425
|
}
|
|
39012
39426
|
|
|
39013
39427
|
// src/services/codex-limits.ts
|
|
39014
|
-
import { open as open3, readdir as
|
|
39428
|
+
import { open as open3, readdir as readdir5, stat as stat2 } from "node:fs/promises";
|
|
39015
39429
|
import { homedir as homedir8 } from "node:os";
|
|
39016
39430
|
import { join as join24 } from "node:path";
|
|
39017
39431
|
var TTL_MS3 = 3e4;
|
|
@@ -39048,7 +39462,7 @@ function toWindow(slot, raw, reached) {
|
|
|
39048
39462
|
async function recentRollouts(dir2) {
|
|
39049
39463
|
let entries3;
|
|
39050
39464
|
try {
|
|
39051
|
-
entries3 = (await
|
|
39465
|
+
entries3 = (await readdir5(dir2, { recursive: true })).filter((f) => f.endsWith(".jsonl"));
|
|
39052
39466
|
} catch {
|
|
39053
39467
|
return [];
|
|
39054
39468
|
}
|
|
@@ -40764,7 +41178,7 @@ import { randomUUID as randomUUID9 } from "node:crypto";
|
|
|
40764
41178
|
init_config2();
|
|
40765
41179
|
init_src2();
|
|
40766
41180
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
40767
|
-
import { mkdir as mkdir5, writeFile as writeFile4, readFile as readFile4, unlink as unlink4, readdir as
|
|
41181
|
+
import { mkdir as mkdir5, writeFile as writeFile4, readFile as readFile4, unlink as unlink4, readdir as readdir6, stat as stat3 } from "node:fs/promises";
|
|
40768
41182
|
import { join as join25, resolve as resolve19, basename as basename6 } from "node:path";
|
|
40769
41183
|
var MAX_TRANSCRIPT_BYTES = 1024 * 1024;
|
|
40770
41184
|
function transcriptDir() {
|
|
@@ -40809,7 +41223,7 @@ async function listTranscripts() {
|
|
|
40809
41223
|
const dir2 = transcriptDir();
|
|
40810
41224
|
let names;
|
|
40811
41225
|
try {
|
|
40812
|
-
names = await
|
|
41226
|
+
names = await readdir6(dir2);
|
|
40813
41227
|
} catch (e) {
|
|
40814
41228
|
if (e.code === "ENOENT") return [];
|
|
40815
41229
|
throw e;
|
|
@@ -42878,7 +43292,10 @@ async function tasks_default(app2) {
|
|
|
42878
43292
|
projectId,
|
|
42879
43293
|
source,
|
|
42880
43294
|
priority,
|
|
42881
|
-
|
|
43295
|
+
// ADR-0157 · agen punya NAMA di sini. Tanpa cabang kedua setiap eskalasi lewat MCP menulis
|
|
43296
|
+
// `Tim · system`, dan asal-usul backlog jadi tak bisa ditelusuri persis pada jalur yang baru
|
|
43297
|
+
// saja dibuka — `launchPrincipal` di bawah sudah membedakan keduanya, penulisnya harus juga.
|
|
43298
|
+
author: req.user?.email ?? (req.agent ? `agent:${req.agent.id}` : "system"),
|
|
42882
43299
|
launchApprovedBy: launchPrincipal(req)
|
|
42883
43300
|
});
|
|
42884
43301
|
const view13 = taskView(r.task, { id: r.spec.id, stage: r.spec.stage, priority: r.spec.priority });
|
|
@@ -45092,6 +45509,7 @@ function capabilityForRoute(method, path) {
|
|
|
45092
45509
|
return rw("vps");
|
|
45093
45510
|
}
|
|
45094
45511
|
if (top === "prds") return rw("docs");
|
|
45512
|
+
if (top === "tasks" || top === "members") return rw("team");
|
|
45095
45513
|
if (top === "terminal") {
|
|
45096
45514
|
if (seg[1] === "workspace") return "COOKIE_ONLY";
|
|
45097
45515
|
if (seg[seg.length - 1] === "ws") return "sessions:write";
|