hanoman 0.2.1 → 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 +390 -50
- package/dist/server.js +395 -49
- package/docs/agent-integration.md +9 -1
- package/package.json +1 -1
- package/web/assets/{index-CRbdeYxO.js → index-BllpAcXg.js} +1546 -1546
- 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" },
|
|
@@ -7648,6 +7656,51 @@ var init_webhook = __esm({
|
|
|
7648
7656
|
}
|
|
7649
7657
|
});
|
|
7650
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
|
+
|
|
7651
7704
|
// ../shared/src/mcp-schema.ts
|
|
7652
7705
|
function obj(o) {
|
|
7653
7706
|
return {
|
|
@@ -7659,11 +7712,12 @@ function obj(o) {
|
|
|
7659
7712
|
...o.allOf ? { allOf: o.allOf } : {}
|
|
7660
7713
|
};
|
|
7661
7714
|
}
|
|
7662
|
-
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;
|
|
7663
7716
|
var init_mcp_schema = __esm({
|
|
7664
7717
|
"../shared/src/mcp-schema.ts"() {
|
|
7665
7718
|
"use strict";
|
|
7666
7719
|
init_enums();
|
|
7720
|
+
init_team();
|
|
7667
7721
|
str = (description, extra = {}) => ({ type: "string", description, ...extra });
|
|
7668
7722
|
enumStr = (values, description) => ({ type: "string", enum: values, description });
|
|
7669
7723
|
bool = (description) => ({ type: "boolean", description });
|
|
@@ -7673,6 +7727,8 @@ var init_mcp_schema = __esm({
|
|
|
7673
7727
|
SOURCE_ENUM = zSpecSource.options;
|
|
7674
7728
|
STAGE_ENUM = zStage.options;
|
|
7675
7729
|
SEVERITY_ENUM = ["critical", "major", "minor"];
|
|
7730
|
+
TASK_STATUS_ENUM = TASK_STATUSES;
|
|
7731
|
+
ESCALATE_SOURCE_ENUM = ESCALATE_SOURCES;
|
|
7676
7732
|
PRIORITY = enumStr(
|
|
7677
7733
|
PRIORITY_ENUM,
|
|
7678
7734
|
"Prioritas backlog. Nilainya bahasa Indonesia \u2014 `tinggi`, `sedang`, `rendah`. Bukan high/medium/low."
|
|
@@ -7752,7 +7808,7 @@ function paginateLocal(items, page, limit) {
|
|
|
7752
7808
|
const p3 = Math.max(1, Math.floor(page ?? 1));
|
|
7753
7809
|
return { items: items.slice((p3 - 1) * size, p3 * size), total: items.length, page: p3, pageSize: size };
|
|
7754
7810
|
}
|
|
7755
|
-
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;
|
|
7756
7812
|
var init_mcp_shape = __esm({
|
|
7757
7813
|
"../shared/src/mcp-shape.ts"() {
|
|
7758
7814
|
"use strict";
|
|
@@ -7814,6 +7870,25 @@ var init_mcp_shape = __esm({
|
|
|
7814
7870
|
...pick(row, ["id", "projectId", "repoSlug", "number", "title", "authorLogin", "labels", "url", "issueState", "status", "specId", "issueUpdatedAt"]),
|
|
7815
7871
|
body: clip(row.body, 500)
|
|
7816
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"]);
|
|
7817
7892
|
shapeLeadDecision = (row) => ({
|
|
7818
7893
|
...pick(row, ["id", "projectId", "specId", "sessionId", "gate", "kind", "status", "confidence", "action", "choice", "createdAt"]),
|
|
7819
7894
|
question: clip(row.question, 300),
|
|
@@ -10924,6 +10999,311 @@ var init_lead2 = __esm({
|
|
|
10924
10999
|
}
|
|
10925
11000
|
});
|
|
10926
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
|
+
|
|
10927
11307
|
// ../shared/src/mcp-catalog/index.ts
|
|
10928
11308
|
var MCP_TOOLS;
|
|
10929
11309
|
var init_mcp_catalog = __esm({
|
|
@@ -10944,6 +11324,8 @@ var init_mcp_catalog = __esm({
|
|
|
10944
11324
|
init_notifications();
|
|
10945
11325
|
init_support();
|
|
10946
11326
|
init_lead2();
|
|
11327
|
+
init_team2();
|
|
11328
|
+
init_system();
|
|
10947
11329
|
MCP_TOOLS = [
|
|
10948
11330
|
...ABOUT_TOOLS,
|
|
10949
11331
|
...PROJECTS_TOOLS,
|
|
@@ -10957,7 +11339,12 @@ var init_mcp_catalog = __esm({
|
|
|
10957
11339
|
...SESSIONS_TOOLS,
|
|
10958
11340
|
...NOTIFICATIONS_TOOLS,
|
|
10959
11341
|
...SUPPORT_TOOLS,
|
|
10960
|
-
...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
|
|
10961
11348
|
];
|
|
10962
11349
|
}
|
|
10963
11350
|
});
|
|
@@ -11264,51 +11651,6 @@ var init_presence = __esm({
|
|
|
11264
11651
|
}
|
|
11265
11652
|
});
|
|
11266
11653
|
|
|
11267
|
-
// ../shared/src/team.ts
|
|
11268
|
-
var TASK_STATUSES, zTaskStatus, memberId, zEmail, zName, zIso, zCreateMember, zPatchMember, zCreateTask, zPatchTask, ESCALATE_SOURCES, zEscalateTask;
|
|
11269
|
-
var init_team = __esm({
|
|
11270
|
-
"../shared/src/team.ts"() {
|
|
11271
|
-
"use strict";
|
|
11272
|
-
init_zod();
|
|
11273
|
-
init_enums();
|
|
11274
|
-
TASK_STATUSES = ["backlog", "doing", "review", "done"];
|
|
11275
|
-
zTaskStatus = external_exports.enum(TASK_STATUSES);
|
|
11276
|
-
memberId = (email) => email.trim().toLowerCase();
|
|
11277
|
-
zEmail = external_exports.string().trim().min(3).max(200).email();
|
|
11278
|
-
zName = external_exports.string().trim().min(1).max(120);
|
|
11279
|
-
zIso = external_exports.string().datetime({ offset: true });
|
|
11280
|
-
zCreateMember = external_exports.object({
|
|
11281
|
-
name: zName,
|
|
11282
|
-
email: zEmail,
|
|
11283
|
-
role: external_exports.string().trim().max(60).nullable().optional()
|
|
11284
|
-
});
|
|
11285
|
-
zPatchMember = zCreateMember.omit({ email: true }).partial().extend({
|
|
11286
|
-
active: external_exports.boolean().optional()
|
|
11287
|
-
});
|
|
11288
|
-
zCreateTask = external_exports.object({
|
|
11289
|
-
title: external_exports.string().trim().min(1).max(300),
|
|
11290
|
-
detail: external_exports.string().max(2e4).nullable().optional(),
|
|
11291
|
-
projectId: external_exports.string().max(120).nullable().optional(),
|
|
11292
|
-
status: zTaskStatus.default("backlog"),
|
|
11293
|
-
priority: zPriority.default("sedang"),
|
|
11294
|
-
memberId: external_exports.string().max(200).nullable().optional(),
|
|
11295
|
-
startDate: zIso.nullable().optional(),
|
|
11296
|
-
dueDate: zIso.nullable().optional(),
|
|
11297
|
-
order: external_exports.number().finite().optional()
|
|
11298
|
-
});
|
|
11299
|
-
zPatchTask = zCreateTask.partial();
|
|
11300
|
-
ESCALATE_SOURCES = ["brief", "qa", "audit"];
|
|
11301
|
-
zEscalateTask = external_exports.object({
|
|
11302
|
-
source: external_exports.enum(ESCALATE_SOURCES).default("brief"),
|
|
11303
|
-
priority: zPriority.default("sedang"),
|
|
11304
|
-
// Dipakai HANYA saat kartunya belum punya project (`nextSpecId` butuh repo, dan repo milik
|
|
11305
|
-
// project). Route menolak 400 bila ia menyebut project LAIN — kartu tak boleh berpindah project
|
|
11306
|
-
// sebagai efek samping yang tak diminta.
|
|
11307
|
-
projectId: external_exports.string().max(120).optional()
|
|
11308
|
-
});
|
|
11309
|
-
}
|
|
11310
|
-
});
|
|
11311
|
-
|
|
11312
11654
|
// ../shared/src/index.ts
|
|
11313
11655
|
var init_src = __esm({
|
|
11314
11656
|
"../shared/src/index.ts"() {
|
|
@@ -42950,7 +43292,10 @@ async function tasks_default(app2) {
|
|
|
42950
43292
|
projectId,
|
|
42951
43293
|
source,
|
|
42952
43294
|
priority,
|
|
42953
|
-
|
|
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"),
|
|
42954
43299
|
launchApprovedBy: launchPrincipal(req)
|
|
42955
43300
|
});
|
|
42956
43301
|
const view13 = taskView(r.task, { id: r.spec.id, stage: r.spec.stage, priority: r.spec.priority });
|
|
@@ -45164,6 +45509,7 @@ function capabilityForRoute(method, path) {
|
|
|
45164
45509
|
return rw("vps");
|
|
45165
45510
|
}
|
|
45166
45511
|
if (top === "prds") return rw("docs");
|
|
45512
|
+
if (top === "tasks" || top === "members") return rw("team");
|
|
45167
45513
|
if (top === "terminal") {
|
|
45168
45514
|
if (seg[1] === "workspace") return "COOKIE_ONLY";
|
|
45169
45515
|
if (seg[seg.length - 1] === "ws") return "sessions:write";
|
|
@@ -91,7 +91,8 @@ host-mu benar dan masalahnya ada pada token atau master switch.
|
|
|
91
91
|
## 3. Capability
|
|
92
92
|
|
|
93
93
|
Capability berformat `"<domain>:<access>"`, `access ∈ {read, write}`, dan **write meng-implikasikan
|
|
94
|
-
read** pada domain yang sama. Ada **
|
|
94
|
+
read** pada domain yang sama. Ada **13 domain**; empat di antaranya punya akses KETIGA `danger`
|
|
95
|
+
(ADR-0155) yang **tak diimplikasikan `:write`**, sehingga totalnya **30 capability**. Katalog resmi (dengan label &
|
|
95
96
|
deskripsi) tampil di panel **Settings → Akses AI Agent** saat manusia membuat token; endpoint
|
|
96
97
|
katalognya (`GET /api/agent-tokens/capabilities`) bersifat **cookie-only** (lihat §5) — kamu tak
|
|
97
98
|
perlu mengambilnya, cukup rujuk tabel di bawah:
|
|
@@ -110,6 +111,7 @@ perlu mengambilnya, cukup rujuk tabel di bawah:
|
|
|
110
111
|
| `lead` | `/api/lead*` | minta putusan ke hanoman-lead & baca jejaknya — **`lead:write` bisa menggerakkan sesi** (ADR-0091) |
|
|
111
112
|
| `agents` | `/api/custom-agents*` | katalog custom agent global & per project — **`agents:write` mengubah apa yang dilihat SETIAP sesi baru** (ADR-0094) |
|
|
112
113
|
| `telegram` | `/api/telegram*` kecuali sub-path kredensial | context/memory/reply/audit kanal operator Telegram (ADR-0096) |
|
|
114
|
+
| `team` | `/api/tasks*`, `/api/members*` | papan **Tim**: kartu kerja MANUSIA & direktori anggota (ADR-0157). `status` kartu milik manusia — ia **bukan** `stage` backlog. `POST /api/tasks/:id/escalate` melahirkan backlog item dan tetap `team:write` (cermin `POST /api/tickets/:id/accept`) |
|
|
113
115
|
|
|
114
116
|
Aturan pemetaan **deterministik** (`server/src/services/agent-capabilities.ts`): `GET`/`HEAD` →
|
|
115
117
|
`:read`, metode lain → `:write`. Itu berlaku untuk domain `lead` juga — **`POST /api/lead/decisions`
|
|
@@ -118,6 +120,12 @@ permanen dan keputusannya bisa menggerakkan sesi. Sub-path `/api/projects/:id/{d
|
|
|
118
120
|
domain **`docs`**; sub-path IDE/git di atas dihitung domain **`ide`**; WebSocket terminal butuh
|
|
119
121
|
**`sessions:write`**.
|
|
120
122
|
|
|
123
|
+
Empat endpoint STATUS tak menuntut capability sama sekali — token sah mana pun boleh membacanya
|
|
124
|
+
(`GLOBAL_READ`, ADR-0157): `GET /api/limits`, `GET /api/limits/codex`, `GET /api/update`, dan
|
|
125
|
+
`GET /api/fs/browse` (menelusuri folder mesin untuk mengisi `repoDir`). Hanya method BACA:
|
|
126
|
+
`POST /api/update/apply` me-restart instance dan tetap **403** untuk agent token, apa pun
|
|
127
|
+
capability-nya (SPEC-405/ADR-0088) — prefix yang sama tak menurunkan gerbangnya.
|
|
128
|
+
|
|
121
129
|
## 4. Aturan gate & kode status
|
|
122
130
|
|
|
123
131
|
Gate `onRequest` yang sama menegakkan semuanya:
|