hanoman 0.1.46 → 0.1.50

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/README.md CHANGED
@@ -84,13 +84,55 @@ Sesudah `hanoman update`, instance yang berjalan perlu di-restart (mis. `systemc
84
84
 
85
85
  | Env | Default | Untuk apa |
86
86
  |---|---|---|
87
- | `HANOMAN_HOME` | `~/.hanoman` | DB SQLite, key SSH, transkrip sesi |
87
+ | `HANOMAN_HOME` | `~/.hanoman` | akar seluruh state hanoman lihat [Isi `$HANOMAN_HOME`](#isi-hanoman_home) |
88
88
  | `HANOMAN_DATABASE_URL` | — | berkas DB khusus hanoman; hanya URL `file:` (nilai lain **melempar**) |
89
89
  | `DATABASE_URL` | `file:$HANOMAN_HOME/hanoman.db` | dipakai bila ber-`file:`; nilai lain **diabaikan** dengan peringatan |
90
+ | `HANOMAN_TRANSCRIPT_DIR` | `$HANOMAN_HOME/transcripts` | transkrip sesi yang sudah ditutup |
91
+ | `HANOMAN_UPLOAD_DIR` | `$HANOMAN_HOME/uploads` | lampiran tiket & lampiran gambar sesi terminal |
92
+ | `HANOMAN_SSH_KEY_DIR` | `$HANOMAN_HOME` | keypair identitas hanoman untuk VPS |
90
93
  | `PORT` / `HOST` | `8787` / `127.0.0.1` | alamat bind |
91
94
  | `HANOMAN_CLAUDE_BIN` / `HANOMAN_CODEX_BIN` | `claude` / `codex` | biner agen |
92
95
  | `HANOMAN_TMUX_SOCKET` | `hanoman` | socket tmux terpisah dari milikmu |
93
96
 
97
+ <a id="isi-hanoman_home"></a>
98
+
99
+ ## Isi `$HANOMAN_HOME` — satu batas backup/restore
100
+
101
+ Tanpa override, **seluruh** state hanoman hidup di bawah satu direktori. Itulah batas yang harus
102
+ disalin: memindahkan DB saja meninggalkan metadata yang menunjuk byte yang tak ada (SPEC-846).
103
+
104
+ | Path | Isi | Hilang berarti |
105
+ |---|---|---|
106
+ | `hanoman.db` (+ `-wal`/`-shm`) | seluruh state aplikasi | semuanya |
107
+ | `secret.key` | kunci AES untuk RuntimeConfig & webhook secret | credential terenkripsi tak bisa dibuka |
108
+ | `id_ed25519` + `.pub` | identitas SSH hanoman ke VPS | akses VPS mati; `authorized_keys` tak lagi cocok |
109
+ | `transcripts/` | transkrip sesi yang ditutup | `hasTranscript` benar, isinya tak terbaca |
110
+ | `uploads/` | lampiran tiket, byte source-map lama | lampiran 404 walau metadata utuh |
111
+ | `uploads/terminal/<sessionId>/` | lampiran gambar sesi terminal | path di prompt agen menunjuk berkas hilang |
112
+ | `setup.token` | token bootstrap admin pertama (one-time, 15 menit) | hanya relevan sebelum admin pertama |
113
+ | `agent-token` | agent token yang dibaca `hanoman mcp` | klien MCP lokal kehilangan autentikasi |
114
+
115
+ ```bash
116
+ # Backup — SQLite disalin konsisten, sisanya berkas biasa.
117
+ sqlite3 "$HANOMAN_HOME/hanoman.db" ".backup '/backup/hanoman.db'"
118
+ tar -C "$HANOMAN_HOME" -czf /backup/hanoman-files.tgz \
119
+ secret.key id_ed25519 id_ed25519.pub transcripts uploads
120
+
121
+ # Restore di host lain — umask dulu, isinya memuat kunci privat.
122
+ umask 077
123
+ install -d -m 0700 "$HANOMAN_HOME"
124
+ cp /backup/hanoman.db "$HANOMAN_HOME/hanoman.db"
125
+ tar -C "$HANOMAN_HOME" -xzf /backup/hanoman-files.tgz
126
+ hanoman doctor # memverifikasi tiap path data efektif + izin tulisnya
127
+ ```
128
+
129
+ `setup.token` dan `agent-token` sengaja **tidak** ikut: yang pertama one-time dan lahir ulang
130
+ sendiri bila DB belum punya user, yang kedua sebaiknya diterbitkan ulang di host baru.
131
+
132
+ Mengeset `HANOMAN_TRANSCRIPT_DIR`, `HANOMAN_UPLOAD_DIR`, atau `HANOMAN_SSH_KEY_DIR` memindahkan
133
+ bagian itu ke luar batas ini — sah, tapi backup-nya menjadi tanggung jawabmu. `hanoman doctor`
134
+ mencetak path efektifnya supaya tak perlu ditebak.
135
+
94
136
  ## Bind & TLS
95
137
 
96
138
  Default `127.0.0.1:8787`. hanoman punya auth, tapi cookie sesinya `Secure` — set
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.46",
3
- "sha": "03155cdb",
4
- "builtAt": "2026-08-18T01:48:20.610Z"
2
+ "version": "0.1.50",
3
+ "sha": "30fe53e3",
4
+ "builtAt": "2026-08-20T04:25:22.099Z"
5
5
  }
package/dist/cli.js CHANGED
@@ -5022,7 +5022,7 @@ var init_cron_expr = __esm({
5022
5022
  });
5023
5023
 
5024
5024
  // ../shared/src/dto.ts
5025
- var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, zMarkSpecDone, 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, USER_ROLES, zUserRole, zLogin, zSignup, zSetup, zChangePassword, zCreateClientAccount, zUpdateClientAccount, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, UPDATE_RESTART_EXIT, zUpdateApplyBody, zTicketView, zTicketAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus, zCronExpr, zCreateCron, zPatchCron, zSchedulerCron, zSchedulerCronRun;
5025
+ var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, zMarkSpecDone, 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, USER_ROLES, zUserRole, zLogin, zSignup, zSetup, zChangePassword, zCreateClientAccount, zUpdateClientAccount, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, UPDATE_RESTART_EXIT, zUpdateApplyBody, zTicketView, zTicketAttachmentView, zSpecAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus, zCronExpr, zCreateCron, zPatchCron, zSchedulerCron, zSchedulerCronRun;
5026
5026
  var init_dto = __esm({
5027
5027
  "../shared/src/dto.ts"() {
5028
5028
  "use strict";
@@ -5064,6 +5064,10 @@ var init_dto = __esm({
5064
5064
  cwd: external_exports.string(),
5065
5065
  startedAt: external_exports.string(),
5066
5066
  endedAt: external_exports.string().nullable(),
5067
+ // SPEC-844 · ADR-0125 · `string` longgar, bukan enum: baris lama null dan nilai asing dari
5068
+ // instance lebih baru tak boleh melempar di boundary — `sessionOutcome()` yang menafsirkannya.
5069
+ endedReason: external_exports.string().nullable(),
5070
+ reconciledAt: external_exports.string().nullable(),
5067
5071
  exitCode: external_exports.number().nullable(),
5068
5072
  transcriptBytes: external_exports.number().nullable()
5069
5073
  });
@@ -5505,6 +5509,13 @@ var init_dto = __esm({
5505
5509
  mimeType: external_exports.string(),
5506
5510
  size: external_exports.number().int()
5507
5511
  });
5512
+ zSpecAttachmentView = external_exports.object({
5513
+ id: external_exports.string(),
5514
+ filename: external_exports.string(),
5515
+ mimeType: external_exports.string(),
5516
+ size: external_exports.number().int(),
5517
+ createdAt: external_exports.string()
5518
+ });
5508
5519
  zTicketDetail = zTicketView.extend({
5509
5520
  detail: external_exports.string(),
5510
5521
  attachments: external_exports.array(zTicketAttachmentView),
@@ -5606,6 +5617,9 @@ var init_api = __esm({
5606
5617
  specSource: (id) => `${API}/specs/${id}/source`,
5607
5618
  // SPEC-804 · ADR-0120 · tandai item selesai manual (operasi khusus, bukan field PATCH).
5608
5619
  specDone: (id) => `${API}/specs/${id}/done`,
5620
+ // SPEC-843 · ADR-0124 · lampiran backlog item (multi-berkas per request).
5621
+ specAttachments: (id) => `${API}/specs/${id}/attachments`,
5622
+ specAttachment: (id, attId) => `${API}/specs/${id}/attachments/${attId}`,
5609
5623
  specReview: (id) => `${API}/specs/${id}/review`,
5610
5624
  specReviewFile: (id, path) => `${API}/specs/${id}/review/${path}`,
5611
5625
  settings: `${API}/settings`,
@@ -5908,6 +5922,13 @@ var init_session_kind = __esm({
5908
5922
  }
5909
5923
  });
5910
5924
 
5925
+ // ../shared/src/session-end.ts
5926
+ var init_session_end = __esm({
5927
+ "../shared/src/session-end.ts"() {
5928
+ "use strict";
5929
+ }
5930
+ });
5931
+
5911
5932
  // ../shared/src/config.ts
5912
5933
  var zHanomanConfig;
5913
5934
  var init_config = __esm({
@@ -6281,6 +6302,8 @@ var init_webhook = __esm({
6281
6302
  "branch",
6282
6303
  "startedAt",
6283
6304
  "endedAt",
6305
+ "endedReason",
6306
+ "reconciledAt",
6284
6307
  "exitCode"
6285
6308
  ],
6286
6309
  projectIdField: "projectId",
@@ -6289,9 +6312,9 @@ var init_webhook = __esm({
6289
6312
  },
6290
6313
  derived: [{
6291
6314
  type: "session.ended",
6292
- label: "Sesi selesai atau gagal",
6315
+ label: "Sesi selesai, gagal, atau terputus",
6293
6316
  changed: ["endedAt"],
6294
- when: "Sesi ditutup dan endedAt terisi. exitCode bukan 0 berarti pane mati gagal; null berarti tak terbaca, misalnya tmux mati di luar hanoman."
6317
+ when: 'Sesi berakhir dan endedAt terisi. endedReason memberi tahu CARANYA: "closed" = hanoman menutupnya, jadi exitCode berlaku (null = agen masih hidup saat ditutup, 0 = keluar bersih, selain itu gagal); "reconciled" = panenya sudah lenyap saat hanoman menyala lagi (reboot, kill-server, host mati) sehingga hasilnya TAK DIKETAHUI, exitCode selalu null, dan reconciledAt menyebut kapan hanoman menemukannya \u2014 endedAt di kasus itu batas BAWAH, bukan waktu berakhir sebenarnya. null = baris lahir sebelum SPEC-844.'
6295
6318
  }],
6296
6319
  sample: {
6297
6320
  id: "6f0c1c1e-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
@@ -6307,6 +6330,8 @@ var init_webhook = __esm({
6307
6330
  branch: "hanoman/spec-481",
6308
6331
  startedAt: "2026-08-01T02:12:31.000Z",
6309
6332
  endedAt: "2026-08-01T09:44:02.000Z",
6333
+ endedReason: "closed",
6334
+ reconciledAt: null,
6310
6335
  exitCode: 0
6311
6336
  }
6312
6337
  },
@@ -7241,6 +7266,7 @@ var init_src = __esm({
7241
7266
  init_portal();
7242
7267
  init_prd_status();
7243
7268
  init_session_kind();
7269
+ init_session_end();
7244
7270
  init_config();
7245
7271
  init_config_registry();
7246
7272
  init_semver();
@@ -7433,6 +7459,16 @@ function resolveHome(env = process.env, home = homedir()) {
7433
7459
  const v = env.HANOMAN_HOME?.trim();
7434
7460
  return v ? v : join(home, ".hanoman");
7435
7461
  }
7462
+ function resolveDataDirs(env = process.env, home = homedir()) {
7463
+ const root = resolveHome(env, home);
7464
+ const dir = (key, name) => env[key]?.trim() || join(root, name);
7465
+ return {
7466
+ home: root,
7467
+ transcripts: dir("HANOMAN_TRANSCRIPT_DIR", "transcripts"),
7468
+ uploads: dir("HANOMAN_UPLOAD_DIR", "uploads"),
7469
+ sshKeys: env.HANOMAN_SSH_KEY_DIR?.trim() || root
7470
+ };
7471
+ }
7436
7472
  function schemeOf(raw) {
7437
7473
  return raw.split("://")[0] ?? "?";
7438
7474
  }
@@ -7691,10 +7727,10 @@ function ensureSpawnHelpersExecutable(paths2, ops) {
7691
7727
  }
7692
7728
  return fixed;
7693
7729
  }
7694
- function repairSpawnHelper(resolve4, notify) {
7730
+ function repairSpawnHelper(resolve5, notify) {
7695
7731
  let ptyDir;
7696
7732
  try {
7697
- ptyDir = dirname3(resolve4("node-pty/package.json"));
7733
+ ptyDir = dirname3(resolve5("node-pty/package.json"));
7698
7734
  } catch {
7699
7735
  return [];
7700
7736
  }
@@ -8096,7 +8132,7 @@ __export(doctor_exports, {
8096
8132
  });
8097
8133
  import { execFileSync as execFileSync3 } from "node:child_process";
8098
8134
  import { accessSync, constants, existsSync as existsSync4 } from "node:fs";
8099
- import { dirname as dirname5 } from "node:path";
8135
+ import { dirname as dirname5, resolve as resolve4 } from "node:path";
8100
8136
  function doctorReport(p) {
8101
8137
  const major = Number(/^v?(\d+)/.exec(p.node)?.[1] ?? 0);
8102
8138
  const rows = [
@@ -8111,7 +8147,13 @@ function doctorReport(p) {
8111
8147
  text: p.gh ? `gh ${p.gh}` : "gh \u2014 tak ada (tarik issue akan lewat HTTP + GITHUB_TOKEN)",
8112
8148
  fatal: false
8113
8149
  },
8114
- { mark: p.homeWritable ? "\u2713" : "\u2717", text: `data dir ${p.homeWritable ? "bisa ditulis" : "TAK bisa ditulis"}`, fatal: !p.homeWritable },
8150
+ // Direktori turunan lahir saat dipakai, jadi izin tulis yang belum ada di situ adalah
8151
+ // peringatan — bukan alasan menyatakan hanoman tak bisa menjalankan sesi. Home tetap fatal.
8152
+ ...p.dirs.map((d) => ({
8153
+ mark: d.writable ? "\u2713" : d.fatal ? "\u2717" : "!",
8154
+ text: `${d.label} ${d.path}${d.writable ? "" : " \u2014 TAK bisa ditulis"}`,
8155
+ fatal: d.fatal && !d.writable
8156
+ })),
8115
8157
  { mark: p.web ? "\u2713" : "!", text: p.web ? "aset dashboard ada" : "aset dashboard tak ada \u2014 API jalan, dashboard tidak", fatal: false },
8116
8158
  { mark: "\xB7", text: `db ${p.db}`, fatal: false },
8117
8159
  {
@@ -8134,6 +8176,21 @@ function doctorReport(p) {
8134
8176
  }
8135
8177
  return { lines: rows.map((r) => ` ${r.mark} ${r.text}`), ok: !rows.some((r) => r.fatal) };
8136
8178
  }
8179
+ function writable(path) {
8180
+ for (let p = resolve4(path); ; ) {
8181
+ if (existsSync4(p)) {
8182
+ try {
8183
+ accessSync(p, constants.W_OK);
8184
+ return true;
8185
+ } catch {
8186
+ return false;
8187
+ }
8188
+ }
8189
+ const up = dirname5(p);
8190
+ if (up === p) return false;
8191
+ p = up;
8192
+ }
8193
+ }
8137
8194
  function version(bin, args) {
8138
8195
  try {
8139
8196
  return execFileSync3(bin, args, { encoding: "utf8", timeout: 1e4, stdio: ["ignore", "pipe", "ignore"] }).trim().split("\n")[0] ?? null;
@@ -8152,13 +8209,13 @@ async function doctor(_argv, ctx) {
8152
8209
  `);
8153
8210
  return 1;
8154
8211
  }
8155
- const home = resolveHome(ctx.env);
8156
- let homeWritable = false;
8157
- try {
8158
- accessSync(existsSync4(home) ? home : dirname5(home), constants.W_OK);
8159
- homeWritable = true;
8160
- } catch {
8161
- }
8212
+ const d = resolveDataDirs(ctx.env);
8213
+ const dirs = [
8214
+ { label: "data dir", path: d.home, writable: writable(d.home), fatal: true },
8215
+ { label: "transkrip", path: d.transcripts, writable: writable(d.transcripts), fatal: false },
8216
+ { label: "upload", path: d.uploads, writable: writable(d.uploads), fatal: false },
8217
+ { label: "key SSH", path: d.sshKeys, writable: writable(d.sshKeys), fatal: false }
8218
+ ];
8162
8219
  const claude = version(ctx.env.HANOMAN_CLAUDE_BIN ?? "claude", ["--version"]);
8163
8220
  const codex = version(ctx.env.HANOMAN_CODEX_BIN ?? "codex", ["--version"]);
8164
8221
  const method = METHODS[DEFAULT_METHOD];
@@ -8188,7 +8245,7 @@ async function doctor(_argv, ctx) {
8188
8245
  claude,
8189
8246
  codex,
8190
8247
  gh: version(ctx.env.HANOMAN_GH_BIN ?? "gh", ["--version"]),
8191
- homeWritable,
8248
+ dirs,
8192
8249
  web: layout.web !== null,
8193
8250
  db,
8194
8251
  methods,
@@ -17578,14 +17635,14 @@ function inputRequiredRoundsExceededMessage(method, maxRounds) {
17578
17635
  return `Multi-round-trip request '${method}' still required input after ${maxRounds} rounds (inputRequired.maxRounds)`;
17579
17636
  }
17580
17637
  function sleep(ms, signal) {
17581
- return new Promise((resolve4, reject) => {
17638
+ return new Promise((resolve5, reject) => {
17582
17639
  if (signal?.aborted) {
17583
17640
  reject(signal.reason instanceof SdkError ? signal.reason : new SdkError(SdkErrorCode.RequestTimeout, String(signal.reason)));
17584
17641
  return;
17585
17642
  }
17586
17643
  const timer = setTimeout(() => {
17587
17644
  signal?.removeEventListener("abort", onAbort);
17588
- resolve4();
17645
+ resolve5();
17589
17646
  }, ms);
17590
17647
  const onAbort = () => {
17591
17648
  clearTimeout(timer);
@@ -19396,7 +19453,7 @@ var init_src_CX2iR2pK = __esm({
19396
19453
  const flowStartedAt = Date.now();
19397
19454
  let onAbort;
19398
19455
  let cleanupMessageId;
19399
- return new Promise((resolve4, reject) => {
19456
+ return new Promise((resolve5, reject) => {
19400
19457
  const earlyReject = (error2) => {
19401
19458
  reject(error2);
19402
19459
  };
@@ -19464,7 +19521,7 @@ var init_src_CX2iR2pK = __esm({
19464
19521
  }
19465
19522
  if (decoded.kind === "invalid") return reject(decoded.error);
19466
19523
  if (decoded.kind === "input_required") {
19467
- if (options?.allowInputRequired === true) return resolve4(manualInputRequiredValue(decoded));
19524
+ if (options?.allowInputRequired === true) return resolve5(manualInputRequiredValue(decoded));
19468
19525
  const flow = {
19469
19526
  codec,
19470
19527
  request,
@@ -19476,11 +19533,11 @@ var init_src_CX2iR2pK = __esm({
19476
19533
  params
19477
19534
  }, resultSchema, legOptions)
19478
19535
  };
19479
- return resolve4(this._resolveNonCompleteResult(decoded, flow));
19536
+ return resolve5(this._resolveNonCompleteResult(decoded, flow));
19480
19537
  }
19481
19538
  const result = decoded.result;
19482
19539
  validateStandardSchema(resultSchema, result).then((parseResult) => {
19483
- if (parseResult.success) resolve4(parseResult.data);
19540
+ if (parseResult.success) resolve5(parseResult.data);
19484
19541
  else reject(new SdkError(SdkErrorCode.InvalidResult, `Invalid result for ${request.method}: ${parseResult.error}`));
19485
19542
  }, reject);
19486
19543
  });
@@ -22296,7 +22353,7 @@ var init_ajvProvider_CEoC_sr = __esm({
22296
22353
  ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, ref);
22297
22354
  const schOrFunc = root.refs[ref];
22298
22355
  if (schOrFunc) return schOrFunc;
22299
- let _sch = resolve4.call(this, root, ref);
22356
+ let _sch = resolve5.call(this, root, ref);
22300
22357
  if (_sch === void 0) {
22301
22358
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
22302
22359
  const { schemaId } = this.opts;
@@ -22322,7 +22379,7 @@ var init_ajvProvider_CEoC_sr = __esm({
22322
22379
  function sameSchemaEnv(s1, s2) {
22323
22380
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
22324
22381
  }
22325
- function resolve4(root, ref) {
22382
+ function resolve5(root, ref) {
22326
22383
  let sch;
22327
22384
  while (typeof (sch = this.refs[ref]) == "string") ref = sch;
22328
22385
  return sch || this.schemas[ref] || resolveSchema.call(this, root, ref);
@@ -22772,7 +22829,7 @@ var init_ajvProvider_CEoC_sr = __esm({
22772
22829
  else if (typeof uri === "object") uri = parse4(serialize(uri, options), options);
22773
22830
  return uri;
22774
22831
  }
22775
- function resolve4(baseURI, relativeURI, options) {
22832
+ function resolve5(baseURI, relativeURI, options) {
22776
22833
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
22777
22834
  const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
22778
22835
  schemelessOptions.skipEscape = true;
@@ -22946,7 +23003,7 @@ var init_ajvProvider_CEoC_sr = __esm({
22946
23003
  const fastUri = {
22947
23004
  SCHEMES,
22948
23005
  normalize,
22949
- resolve: resolve4,
23006
+ resolve: resolve5,
22950
23007
  resolveComponent,
22951
23008
  equal,
22952
23009
  serialize,
@@ -28638,7 +28695,7 @@ var init_stdio = __esm({
28638
28695
  }
28639
28696
  send(message) {
28640
28697
  if (this._closed) return Promise.reject(/* @__PURE__ */ new Error("StdioServerTransport is closed"));
28641
- return new Promise((resolve4, reject) => {
28698
+ return new Promise((resolve5, reject) => {
28642
28699
  const json = serializeMessage(message);
28643
28700
  let settled = false;
28644
28701
  const onError = (error2) => {
@@ -28653,14 +28710,14 @@ var init_stdio = __esm({
28653
28710
  settled = true;
28654
28711
  this._stdout.off("error", onError);
28655
28712
  this._stdout.off("drain", onDrain);
28656
- resolve4();
28713
+ resolve5();
28657
28714
  };
28658
28715
  this._stdout.once("error", onError);
28659
28716
  if (this._stdout.write(json)) {
28660
28717
  if (settled) return;
28661
28718
  settled = true;
28662
28719
  this._stdout.off("error", onError);
28663
- resolve4();
28720
+ resolve5();
28664
28721
  } else if (!settled) this._stdout.once("drain", onDrain);
28665
28722
  });
28666
28723
  }
@@ -28714,14 +28771,14 @@ var init_stdio = __esm({
28714
28771
  */
28715
28772
  async whenRequestsAnswered(timeoutMs) {
28716
28773
  if (this._closed || this._pendingRequests.size === 0) return true;
28717
- return await new Promise((resolve4) => {
28774
+ return await new Promise((resolve5) => {
28718
28775
  const waiter = () => {
28719
28776
  clearTimeout(timer);
28720
- resolve4(true);
28777
+ resolve5(true);
28721
28778
  };
28722
28779
  const timer = setTimeout(() => {
28723
28780
  this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
28724
- resolve4(false);
28781
+ resolve5(false);
28725
28782
  }, timeoutMs);
28726
28783
  this._drainWaiters.push(waiter);
28727
28784
  });
@@ -28998,10 +29055,10 @@ var init_server = __esm({
28998
29055
  // src/commands/mcp.ts
28999
29056
  var mcp_exports = {};
29000
29057
  __export(mcp_exports, {
29058
+ agentTokenPath: () => agentTokenPath,
29001
29059
  default: () => mcp
29002
29060
  });
29003
29061
  import { readFileSync as readFileSync2 } from "node:fs";
29004
- import { homedir as homedir3 } from "node:os";
29005
29062
  import { join as join6 } from "node:path";
29006
29063
  async function mcp(argv, ctx) {
29007
29064
  const cfg = resolveMcpConfig(argv, ctx.env, () => tokenFile(ctx.env));
@@ -29016,26 +29073,27 @@ async function mcp(argv, ctx) {
29016
29073
  await handle.close();
29017
29074
  return 0;
29018
29075
  }
29019
- await new Promise((resolve4) => {
29020
- process.stdin.once("close", resolve4);
29021
- process.stdin.once("end", resolve4);
29076
+ await new Promise((resolve5) => {
29077
+ process.stdin.once("close", resolve5);
29078
+ process.stdin.once("end", resolve5);
29022
29079
  });
29023
29080
  return 0;
29024
29081
  }
29025
- var tokenFile;
29082
+ var agentTokenPath, tokenFile;
29026
29083
  var init_mcp2 = __esm({
29027
29084
  "src/commands/mcp.ts"() {
29028
29085
  "use strict";
29086
+ init_src2();
29029
29087
  init_stdio();
29030
29088
  init_router();
29031
29089
  init_config2();
29032
29090
  init_client();
29033
29091
  init_server();
29034
29092
  init_redact();
29093
+ agentTokenPath = (env) => join6(resolveHome(env), "agent-token");
29035
29094
  tokenFile = (env) => {
29036
- const home = env.HANOMAN_HOME ?? join6(homedir3(), ".hanoman");
29037
29095
  try {
29038
- return readFileSync2(join6(home, "agent-token"), "utf8");
29096
+ return readFileSync2(agentTokenPath(env), "utf8");
29039
29097
  } catch {
29040
29098
  return null;
29041
29099
  }
@@ -29249,6 +29307,10 @@ var init_migrate_pg = __esm({
29249
29307
  "TelegramAudit",
29250
29308
  "Ticket",
29251
29309
  "TicketAttachment",
29310
+ // SPEC-843 · ADR-0124 · SpecAttachment WAJIB sesudah Spec (FK specId, cascade). Tabel ini
29311
+ // LOCAL-only dan lazimnya TIDAK ada di sumber Postgres lama — jalur 42P01 memperlakukannya
29312
+ // sebagai nol baris, cermin Changelog.
29313
+ "SpecAttachment",
29252
29314
  // SPEC-471 · ADR-0095 · GithubIssue sesudah Project (FK projectId) DAN sesudah Spec: `specId`
29253
29315
  // memang tanpa FK, tapi memindahkannya lebih awal membuat urutan tabel tak lagi mencerminkan
29254
29316
  // arah tautannya bagi pembaca berikutnya.