SpyBara
Go Premium

plugins/mods/api.md 2026-11-10 21:57 UTC to 2026-11-11 22:59 UTC

This page contains 89 additions and 89 deletions.

2026
Mon 2 23:57 Wed 11 22:59

Gunakan mods API

Panggil mods API dari mod Claude Code untuk menambahkan perintah dan tool, memanggil model, menjalankan pekerjaan pada timer, mengirim pesan ke sesi lain, dan mengakses file serta jaringan.

Mods API adalah kumpulan metode yang dipanggil mod untuk bertindak: menambahkan perintah dan alat, memanggil model, menjalankan pekerjaan antar peristiwa, dan mengakses sistem file, proses, dan jaringan. Setiap hook menerimanya sebagai argumen pertamanya, $, dengan metode yang dikelompokkan dalam namespace seperti $.ui dan $.fs. Events menentukan kapan hook berjalan, dan mods API adalah apa yang dipanggil hook setelah berjalan.

Bangun mod pertama Anda sebelum Anda mulai di sini. Untuk setiap metode, lihat mods API methods atau baca tipe untuk build Anda.

Tambahkan perintah atau alat

Mod dapat menambahkan perintah untuk dijalankan pengguna dan alat untuk dipanggil Claude. Daftarkan keduanya dalam hook session.start. Claude Code menunggu hook itu sebelum prompt pertama, jadi apa yang Anda daftarkan tersedia dari giliran pertama.

Tambahkan perintah

Perintah adalah untuk pengguna. Daftarkan, kemudian tangani command.run untuk namanya. Contoh ini menambahkan perintah /standup yang mengambil jumlah hari opsional:

on('session.start', async ($, e, next) => {
  // Add /standup to the command list, with the description the user sees there
  await $.command.register({ name: 'standup', description: 'Summarize what changed today', argumentHint: '[days]' })
  return next(e)
})

// The matcher limits the hook to /standup, so other commands don't reach it
on('command.run', { command: 'standup' }, async ($, e) => {
  // e.args is the text typed after the command name, or an empty string
  return { text: 'Summary for the last ' + (e.args || '1') + ' day(s): ...' }
})

Setelah sesi dimulai, /standup muncul dengan deskripsinya dalam daftar yang Anda lihat saat mengetik /. argumentHint ditampilkan dalam prompt setelah Anda mengetik perintah dan spasi, seperti /standup [days]. Saat Anda menjalankan /standup 3, hook kedua mengembalikan Summary for the last 3 day(s): ..., dan transkrip menunjukkan teks itu setelah nama plugin. Hook tidak pernah memanggil next, karena perintah tidak memiliki perilaku selain milik Anda.

text yang Anda kembalikan dicetak dalam transkrip dan Claude membacanya. Untuk tidak mencetak apa pun, seperti perintah yang hanya membuka pane, kembalikan {}. Untuk membiarkan perintah berjalan saat Claude sedang bekerja, tambahkan immediate: true ke pendaftaran.

Pilih nama yang tidak digunakan oleh perintah bawaan. Ketik / dalam sesi untuk melihatnya. $.command.register melempar untuk nama yang diambil, dengan pesan seperti "/focus" refused: it is the built-in /focus. Hook yang melempar dilewati, jadi sisa hook session.start Anda tidak berjalan juga. Daftarkan perintah terakhir dalam hook itu, atau bungkus panggilan dalam try dan catch.

Tambahkan alat

Alat adalah untuk Claude. Daftarkan dengan nama, deskripsi yang dibaca Claude, dan JSON Schema untuk inputnya. Claude melihatnya dengan nama yang lebih panjang yang terdiri dari mcp__, nama plugin Anda, dua garis bawah, dan nama yang Anda daftarkan. Anda menangani panggilannya dalam hook tool.call yang disaring ke nama lengkap itu. Contoh ini, dari plugin bernama my-mod, mendaftarkan ticket, jadi nama lengkapnya adalah mcp__my-mod__ticket. Ini memberi Claude alat yang mencari tiket dalam pelacak masalah:

on('session.start', async ($, e, next) => {
  await $.tool.register({
    name: 'ticket',
    // Claude decides when to call the tool from this description
    description: 'Look up a ticket by its id and return its title and status',
    // The arguments Claude has to send: one required string named id
    inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
  })
  return next(e)
})

// The full tool name is mcp__, the plugin's name, and the registered name
on('tool.call', { tool: 'mcp__my-mod__ticket' }, async ($, e) => {
  // The tool's arguments are fields of e, so the id is e.id
  const response = await $.http.fetch('https://tickets.example.com/api/' + encodeURIComponent(e.id))
  // Return a result either way, so Claude learns when the lookup failed
  return { result: response.ok ? response.text : 'Lookup failed with status ' + response.status }
})

Saat Anda bertanya tentang tiket, Claude dapat memanggil mcp__my-mod__ticket dengan id-nya. Hook kedua mengambil tiket dan mengembalikan badan respons, yang dibaca Claude sebagai hasil alat. Saat server menjawab dengan status kesalahan, Claude membaca Lookup failed with status dan nomornya.

[!TIP]

Saat MCP tool search menunda tool yang terdaftar, Claude melihat namanya tetapi tidak melihat deskripsinya sampai Claude mencarinya. Jika Claude perlu mempertimbangkan tool tersebut di setiap giliran, tambahkan isDeferred: false ke pendaftaran untuk memuat tool lengkap sejak awal. Field ini memerlukan Claude Code v2.1.293 atau yang lebih baru, dan versi sebelumnya mengabaikannya.

Panggil model

Mod dapat mengirim permintaannya sendiri ke model untuk pekerjaan kecil seperti mengklasifikasikan atau merangkum teks. $.model.complete mengirim prompt Anda secara tersendiri, dan $.model.fork({ prompt }) mengirim percakapan saat ini dengan prompt Anda di bagian akhir.

Tabel ini membandingkan isi setiap permintaan:

Dalam permintaan $.model.complete $.model.fork
Model model yang Anda berikan Model sesi
System prompt Blok atribusi singkat, lalu system Anda jika Anda memberikannya System prompt sesi
Pesan Satu pesan pengguna, yaitu prompt Anda Percakapan sejauh ini, lalu prompt Anda sebagai pesan pengguna
CLAUDE.md dan konteks proyek lainnya Tidak disertakan Disertakan, seperti pada permintaan terakhir percakapan
Tool Tidak ada Tool Claude, yang tidak dapat dipanggil oleh model

Fork mengulang permintaan terakhir percakapan, sehingga Claude API melayani sebagian besarnya dari cache prompt selama percakapan masih berada di cache.

Kedua panggilan menggunakan kredensial sesi, sehingga tagihannya dibebankan ke paket, kunci API, atau penyedia cloud pengguna. Tipe untuk build Anda mendokumentasikan setiap metode $.model.

Kirim satu prompt

Berikan model dan prompt ke $.model.complete. prompt menjadi pesan pengguna. Untuk memberikan instruksi kepada model, seperti peran atau format output, berikan juga system, yang menjadi system prompt.

Hook ini menjawab perintah /triage, didaftarkan sebagai perintah, dengan menanyakan model kecil untuk memberi label pada teks yang diketik setelahnya:

on('command.run', { command: 'triage' }, async ($, e) => {
  const r = await $.model.complete({
    model: 'haiku',
    // The system prompt sets the job, and the prompt carries the text to label
    system: 'Reply with one word: bug, feature, or question.',
    prompt: e.args,
    // One word needs few tokens, and the call gives up after 15 seconds
    maxTokens: 20,
    timeoutMs: 15000,
  })
  // r.text exists only when the model answered, so check r.isAnswered first
  const label = r.isAnswered ? r.text.trim() : 'unknown'
  return { text: 'Label: ' + label }
})

Saat Anda menjalankan /triage the export button does nothing, mod mengirim teks itu ke model dan mencetak jawabannya, seperti Label: bug. Saat model tidak menjawab, labelnya adalah unknown.

Kegagalan Claude API tidak menolak panggilan, jadi periksa r.isAnswered, dan baca r.reason saat nilainya false. Panggilan ditolak untuk permintaan yang tidak akan dikirim Claude Code, seperti model yang diblokir organisasi Anda.

Tipe untuk build Anda mencantumkan opsi lain, seperti effort, dan batas memberikan default maxTokens.

Gunakan prompt caching

$.model.complete mendukung prompt caching Claude API. API menyimpan ke cache bagian awal permintaan, yang disebut prefiks, hingga cache breakpoint yang Anda tetapkan. Saat setiap panggilan dimulai dengan konten statis panjang yang sama, seperti instruksi atau materi referensi, tetapkan breakpoint di akhir konten tersebut. Panggilan berikutnya kemudian membacanya dari cache alih-alih membayar harga input penuh untuk konten itu.

Untuk menetapkan breakpoint, berikan prompt sebagai array blok { text } alih-alih string, dan tambahkan cache: true ke blok terakhir dari konten statis. Claude Code mengirim blok tersebut dengan field cache_control milik API. system menerima bentuk array yang sama. Untuk memilih di antara keduanya, lihat Memilih antara prompt dan system.

[!NOTE]

Array blok memerlukan Claude Code v2.1.292 atau lebih baru. Versi sebelumnya menolak array dalam prompt dengan error yang diakhiri dengan takes { model, prompt } (host check), dan tidak menyertakan array dalam system ke dalam permintaan.

Versi hook /triage ini mengirim serangkaian aturan pelabelan yang panjang sebelum teks yang akan diberi label, dengan breakpoint setelah aturan tersebut. RULES adalah string milik Anda sendiri:

on('command.run', { command: 'triage' }, async ($, e) => {
  const r = await $.model.complete({
    model: 'haiku',
    prompt: [
      // Identical on every call, so it forms the cached prefix
      { text: RULES, cache: true },
      // Changes on every call, so it goes after the breakpoint
      { text: e.args },
    ],
  })
  return { text: 'Label: ' + (r.isAnswered ? r.text.trim() : 'unknown') }
})

TTL dan jumlah breakpoint memiliki batas berikut:

  • TTL: entri cache bertahan lima menit setelah penggunaan terakhirnya. TTL berasal dari pengaturan Claude Code pengguna, bukan dari panggilan. Untuk satu jam, atur subagentPromptCacheTtl ke 1h.
  • Breakpoint per permintaan: API menerima hingga empat, dan satu lagi akan dikembalikan sebagai api-error dalam r.reason

Memilih antara prompt dan system

Letakkan konten statis yang digunakan bersama oleh panggilan Anda di awal prompt, kecuali Anda tahu bahwa permintaan Anda langsung menuju Claude API:

  • Langsung ke Claude API, dengan kunci API atau langganan Claude: kedua field dapat digunakan
  • Melalui Amazon Bedrock, Claude Platform on AWS, Google Cloud's Agent Platform, Microsoft Foundry, atau LLM gateway: gunakan prompt. Claude Code memulai system prompt dengan blok atribusi yang fingerprint-nya berasal dari awal pesan pengguna. Endpoint api.anthropic.com menghapus blok tersebut sebelum caching. Endpoint lain menerimanya sebagai bagian dari prompt, sehingga breakpoint dalam system dapat meleset saat prompt dimulai secara berbeda.
  • Dalam mod yang dijalankan orang lain: gunakan prompt, karena Anda tidak memilih penyedia mereka

system muncul sebelum prompt dalam prefiks, sehingga breakpoint dalam prompt juga mencakup system, dan panggilan dengan system yang berbeda akan meleset dari cache.

Memeriksa cache hit

Hasil dari $.model.complete memiliki objek usage dengan field cache milik API. usage.cache_creation_input_tokens menghitung token yang ditulis panggilan ke cache, dan usage.cache_read_input_tokens menghitung token yang dibaca panggilan dari cache. Harapkan penulisan pada panggilan pertama dan pembacaan pada panggilan berikutnya dalam TTL.

Jika setiap panggilan menulis dan tidak ada yang membaca, prefiks berbeda antar panggilan atau jarak antar panggilan lebih lama dari TTL. Untuk prefiks yang berbeda, lihat Memilih antara prompt dan system.

Jika kedua field tetap bernilai nol pada panggilan yang dijawab model, tidak ada yang disimpan ke cache. Periksa setiap penyebab berikut:

Apa yang diterima hook model.complete

Jika Anda memasang hook pada event model.complete untuk memeriksa atau mengubah permintaan mod lain, baca teks dari field berikut:

  • e.prompt: selalu berupa string. Saat pemanggil memberikan array, nilainya adalah teks blok-blok yang digabungkan secara berurutan.
  • e.system: string yang dibangun dengan cara yang sama, atau tidak ada saat pemanggil tidak memberikan system
  • e.promptBlocks dan e.systemBlocks: array milik pemanggil, masing-masing ada saat pemanggil memberikan array untuk field tersebut

Claude Code mengirim string yang diteruskan hook Anda ke next dan menggunakan array yang Anda berikan bersamanya untuk menempatkan cache breakpoint. Claude Code mempertahankan blok-blok awal yang masih cocok dengan awal string, beserta breakpoint-nya, dan mengirim sisa string tanpa breakpoint. Misalnya, next({ ...e, prompt: e.prompt + NOTE }) mempertahankan breakpoint pemanggil, sedangkan hook yang mengubah awal prompt menghapusnya.

Menjalankan pekerjaan di latar belakang

Pekerjaan yang berlangsung lebih lama dari satu event, seperti memeriksa sesuatu sekali per menit, berjalan pada timer yang Anda mulai dari session.start. Sebuah hook sendiri berjalan untuk satu event dan memiliki batas waktu pada waktu eksekusinya sendiri. Waktu yang dihabiskan untuk menunggu next atau panggilan API mods tidak dihitung, kecuali $.clock.sleep. $.clock.every dan $.clock.after menggantikan setInterval dan setTimeout, dengan jeda dalam milidetik sebagai argumen pertama: $.clock.after(5000, fn) memanggil fn sekali, lima detik dari sekarang. Masing-masing mengembalikan timer dengan metode cancel(), dan await $.clock.now() memberikan waktu dalam milidetik.

Hook ini memeriksa status checks sebuah pull request sekali per menit dan menampilkan hasilnya di bawah prompt. summarize adalah fungsi milik Anda sendiri yang mengubah output JSON perintah menjadi beberapa kata:

on('session.start', async ($, e, next) => {
  // Call the function every 60,000 milliseconds, starting one minute from now
  $.clock.every(60_000, async () => {
    const status = await $.process.run(['gh', 'pr', 'checks', '--json', 'state'])
    // Replace the line under the prompt with the latest summary
    $.ui.status('checks: ' + summarize(status.stdout))
  })
  // Return without waiting for the timer, so the session starts right away
  return next(e)
})

Sesi dimulai seperti biasa. Satu menit kemudian, sebuah baris muncul di bawah prompt dengan ⚠, nama mod, lalu checks: dan ringkasan Anda. Baris tersebut diganti sekali per menit setelahnya. Callback timer berjalan di luar event apa pun, sehingga terus berjalan di antara giliran dan tidak memulai giliran. Jika callback melempar error, error tersebut dikirim ke log debug dan timer berjalan lagi pada interval berikutnya.

Menampilkan sesuatu tanpa memulai giliran

Pekerjaan latar belakang dapat menampilkan sesuatu kepada pengguna tanpa memulai giliran. Masing-masing panggilan berikut menempatkan teks di tempat yang berbeda:

Panggilan Apa yang dilihat pengguna
$.ui.status(text) Satu baris di bawah prompt yang tetap ada sampai Anda mengubahnya. Baris ini diawali dengan ⚠ dan nama mod, seperti pada ⚠ my-mod: checks: 3 passing.
$.ui.toast(text) Notifikasi toast dengan nama mod yang menghilang setelah beberapa detik. Notifikasi ini berupa kotak di kanan atas dalam rendering layar penuh, dan satu baris di sebelah kanan di bawah prompt dalam renderer klasik.
$.ui.log(text) Baris redup dalam transkrip yang tidak dibaca Claude. Baris ini diawali dengan ● dan nama mod, seperti pada ● my-mod: build finished.
$.ui.notify(text) Notifikasi dari terminal pengguna, di luar Claude Code, yang dikirim sesuai pengaturan preferredNotifChannel mereka. Nama mod menjadi judulnya kecuali Anda meneruskan { title } yang tidak kosong, yang ditampilkan persis seperti yang Anda berikan.

$.ui.notify adalah satu-satunya di antara panggilan ini yang memberikan jawaban. Panggilan ini di-resolve ke { isSent: true, channel } ketika channel pengguna menulis notifikasi ke terminal mereka. channel adalah nilai pengaturan yang mengirimkannya, seperti kitty atau terminal_bell. Channel terminal_bell membunyikan bel dan tidak menampilkan teks maupun judul Anda.

Jika tidak, panggilan di-resolve ke { isSent: false, reason }:

reason Arti
disabled Pengguna menetapkan preferredNotifChannel ke notifications_disabled
no-channel Pengaturannya adalah auto dan terminal pengguna tidak memiliki notifikasi yang dapat dikirim Claude Code
no-surface Sesi tidak memiliki terminal, seperti pada run -p, Agent SDK, aplikasi desktop, ekstensi VS Code, atau sesi cloud. Juga merupakan jawaban untuk panggilan yang dilakukan sebelum sesi dimulai, saat mod Anda sedang dimuat, dan untuk panggilan yang dilakukan saat Claude Code sedang keluar
refused Hook mod lain pada ui.notify menjawab menggantikan Claude Code tanpa mengirim

Panggilan ditolak (reject) alih-alih di-resolve ketika text bukan string, ketika hook mod lain pada ui.notify mengembalikan { deny }, atau ketika penulisan ke terminal gagal.

Mod Anda tidak dapat memilih channel, dan Claude Code tidak membatasi seberapa sering sebuah mod mengirim notifikasi. Pengaturan pengguna adalah satu-satunya pengendali.

Ketika panggilan mencapai sesi yang sedang berjalan, hook Notification milik pengguna berjalan dengan tipe plugin_notification. Hook tersebut berjalan bahkan ketika jawabannya disabled, dan dalam sesi tanpa terminal, di mana jawabannya no-surface. Tidak ada hook yang berjalan untuk panggilan yang dilakukan sebelum sesi dimulai atau saat Claude Code sedang keluar. Hook tempat mod Anda memanggil $.ui.notify dikecualikan dari run tersebut, sehingga hook classic.Notification yang mengirim notifikasi tidak dipanggil lagi untuk notifikasinya sendiri.

Terminal tidak melaporkan apakah notifikasi ditampilkan, sehingga isSent: true berarti notifikasi telah dikirim, bukan berarti pengguna telah melihatnya.

Contoh ini mengirim notifikasi ketika giliran percakapan utama berakhir tanpa interupsi, dan menggunakan toast sebagai fallback ketika tidak ada notifikasi berisi teks yang terkirim. Contoh ini tidak menunggu notifikasi sebelum meneruskan event, karena hook Notification milik pengguna berjalan terlebih dahulu dan dapat memakan waktu:

on('turn.complete', ($, e, next) => {
  const isMainTurn = e.agentId === undefined && !e.isAborted
  const fallBack = () => $.ui.toast('Turn finished')
  if (isMainTurn) {
    $.ui.notify('Turn finished').then(sent => {
      const hasText = sent.isSent && sent.channel !== 'terminal_bell'
      if (!hasText) fallBack()
    }, fallBack)
  }
  return next(e)
})

Di ekstensi VS Code, baris status dan toast ditampilkan di atas kotak prompt, dan baris log ditampilkan dalam percakapan.

Memulai giliran dari pekerjaan latar belakang

Ketika pekerjaan latar belakang menemukan sesuatu yang memerlukan perhatian Claude, pekerjaan tersebut dapat memulai giliran dengan mengirimkan prompt menggunakan $.prompt.submit({ text }). Claude membaca teks tersebut setelah sebuah kalimat yang menyebut mod Anda sebagai pengirim. Untuk mengirimkannya sebagai kata-kata pengguna sendiri, tanpa kalimat tersebut, tambahkan asUser: true. Panggilan ini menunggu hingga sesi idle lalu memulai giliran baru. Panggilan di-resolve ketika giliran tersebut dimulai, jadi jangan gunakan await padanya dalam handler yang berjalan saat Claude sedang bekerja.

Menghentikan pekerjaan latar belakang

Timer berhenti setelah modul dimuat ulang. Untuk pekerjaan yang berjalan lama di dalam hook, next.signal adalah AbortSignal yang dibatalkan ketika event yang ditangani hook Anda ditinggalkan, misalnya ketika pengguna melakukan interupsi. Teruskan signal ini ke apa pun yang berjalan lama.

Panggilan dan pekerjaan latar belakang setelah pengguna menginterupsi giliran

Alasan the session was stopped and nobody has sent input since dalam sebuah hasil atau error berarti pengguna menghentikan giliran yang sedang berjalan, misalnya dengan Esc atau Stop. Setelah interupsi tersebut, panggilan yang memulai pekerjaan baru dalam sesi ditolak hingga input pengguna berikutnya, seperti pesan di prompt.

Selain $.prompt.submit dan $.command.run, penanganan panggilan dan pekerjaan latar belakang setelah interupsi ini memerlukan Claude Code v2.1.304 atau yang lebih baru.

Mencoba ulang panggilan yang ditolak atau dibuang

Lakukan lagi panggilan yang ditolak atau dibuang dari hook turn.complete. Panggilan tetap ditolak hingga input pengguna berikutnya, dan tidak ada yang mengantrekan atau mengulanginya untuk Anda.

Ketika hasil atau error sebuah panggilan berisi salah satu string berikut, simpan argumennya untuk retry nanti:

  • the session was stopped and nobody has sent input since: panggilan ditolak
  • was removed from the queue before it ran: prompt atau perintah keluar dari antrean tanpa dijalankan dan dibuang, misalnya ketika pengguna menghentikan giliran

Dalam contoh ini, panggil submitPrompt dari pekerjaan latar belakang Anda sebagai pengganti $.prompt.submit. Letakkan variabel pendingText dan fungsi submitPrompt di atas register, dan hook turn.complete di dalamnya, karena validasi menolak $ yang diteruskan ke fungsi yang dideklarasikan di dalam register. Ketika hasil atau error membawa salah satu string tersebut, submitPrompt menyimpan teks di pendingText agar hook turn.complete mengirimkannya lagi:

// The text of the last refused or dropped prompt, or null
let pendingText = null

async function submitPrompt($, text) {
  let reason
  try {
    const result = await $.prompt.submit({ text })
    reason = result?.drop ?? ''
  } catch (error) {
    reason = String(error?.message ?? error)
  }
  if (reason.includes('the session was stopped and nobody has sent input since') ||
      reason.includes('was removed from the queue before it ran')) {
    pendingText = text
  }
  // Any other result or error isn't retried
}

on('turn.complete', async ($, e, next) => {
  if (pendingText !== null) {
    const text = pendingText
    // Empty pendingText before retrying, so that a second run of this hook
    // doesn't submit the same text.
    pendingText = null
    // Not awaited: the hook would stay open until the new turn starts
    submitPrompt($, text)
  }
  return next(e)
})

Sebelum input pengguna berikutnya, turn.complete dapat dipicu untuk giliran yang diinterupsi itu sendiri dan untuk giliran subagent, dan panggilan yang dilakukan saat itu ditolak lagi. Dalam kasus tersebut, submitPrompt menyimpan teks untuk turn.complete berikutnya.

Untuk panggilan yang ditolak (reject), seperti $.command.run, tangkap error-nya dan uji pesannya dengan cara yang sama seperti submitPrompt menguji reason.

Apa yang dikembalikan panggilan yang ditolak

Dalam hasil, <reason> mewakili alasan panggilan ditolak. Setelah interupsi, alasannya adalah the session was stopped and nobody has sent input since. Untuk alasan lain, lihat Panggilan yang ditolak dalam run claude -p dan sesi Agent SDK.

Beberapa panggilan yang ditolak Hasil
$.prompt.submit Di-resolve dengan { drop: '<reason>' } dan tidak mengantrekan prompt
$.command.run Ditolak (reject) dengan error yang pesannya diakhiri dengan <reason>
$.agent.spawn Di-resolve dengan { deny: '<reason>' } dan tidak memulai subagent
$.session.send ke subagent yang sudah selesai Di-resolve dengan { isDelivered: false, reason: 'Agent "<id>" was not resumed: <reason>.' }, di mana <id> adalah id subagent. Subagent yang masih berjalan menerima pesan tersebut.
$.session.send ke rekan satu tim dalam tim agent Di-resolve dengan { isDelivered: false, reason: 'Teammate "<name>" was not messaged: <reason>.' }, di mana <name> adalah nama rekan satu tim tersebut
$.tool.call dari Bash dengan run_in_background: true, Monitor, atau CronCreate Di-resolve dengan { deny: '<tool> was not run: it starts background work, a schedule or a goal, and <reason>' }, di mana <tool> adalah nama tool

Ketika salah satu panggilan $.session.send atau $.tool.call ini ditolak setelah interupsi dalam sesi interaktif, pengguna dapat melihat toast Stopped:.

Pekerjaan yang dimulai mod Anda sebelum interupsi

Setelah pengguna menghentikan giliran yang sedang berjalan, misalnya dengan Esc, sebagian pekerjaan yang sudah dimulai mod Anda dibuang dan sebagian lainnya menunggu input pengguna berikutnya.

Pekerjaan Apa yang terjadi hingga input pengguna berikutnya Apa yang harus dilakukan
Prompt dari $.prompt.submit yang masih dalam antrean Dibuang. Panggilan di-resolve dengan { drop: 'the prompt was removed from the queue before it ran' }. Kirimkan lagi seperti yang ditunjukkan di Mencoba ulang panggilan yang ditolak atau dibuang
Perintah dari $.command.run yang masih dalam antrean Dibuang. Panggilan ditolak (reject) dengan error yang pesannya berisi the command was removed from the queue before it ran. Jalankan lagi seperti yang ditunjukkan di Mencoba ulang panggilan yang ditolak atau dibuang
Pekerjaan satu kali yang dijadwalkan dengan tool seperti CronCreate yang jatuh tempo sebelum input pengguna berikutnya Tidak memulai giliran saat itu, dan berjalan setelah input tersebut Tidak perlu melakukan apa pun
Pekerjaan berulang yang dijadwalkan dengan tool seperti CronCreate yang jatuh tempo sebelum input pengguna berikutnya Tidak memulai giliran saat itu. Pekerjaan kehilangan run tersebut dan mempertahankan jadwalnya. Tidak perlu melakukan apa pun
Event dari monitor yang dimulai mod Anda Monitor terus berjalan. Event yang dimunculkan monitor sebelum input pengguna berikutnya dibuang, kecuali pemberitahuan bahwa monitor berakhir, yang tiba setelah input tersebut. Tidak ada yang dapat dilakukan
Subagent yang di-spawn mod Anda dan kemudian dilanjutkan dengan $.session.send yang selesai sebelum input pengguna berikutnya Pemberitahuan selesainya menunggu, dan mencapai percakapan setelah input tersebut Tidak perlu melakukan apa pun
Perintah Bash atau PowerShell foreground dari $.tool.call yang sudah berjalan ketika pengguna menghentikan giliran dan mencapai timeout-nya sebelum input berikutnya Dihentikan pada timeout-nya alih-alih dipindahkan ke latar belakang. Panggilan di-resolve dengan isError ditetapkan, dan output-nya berisi baris Ended instead of moving to the background: plugins' input is held, by a Stop or by the host's limits. Jalankan lagi setelah input pengguna berikutnya, seperti yang ditunjukkan di Mencoba ulang panggilan yang ditolak atau dibuang

Ketika pekerjaan terjadwal jatuh tempo atau monitor memunculkan event sebelum input pengguna berikutnya, pengguna dapat melihat toast Stopped:.

Panggilan yang ditolak dalam run claude -p dan sesi Agent SDK

Dalam run claude -p atau sesi Agent SDK, hasil seperti { drop: 'the run reached its --max-turns limit' } berarti run tersebut menolak panggilan mod Anda.

$.prompt.submit yang ditolak di-resolve dengan { drop: '<reason>' }. Panggilan lain yang ditolak, seperti $.command.run dan $.agent.spawn, memberikan hasil yang ditunjukkan di Apa yang dikembalikan panggilan yang ditolak dengan alasan menggantikan <reason>. Temukan <reason> Anda di bawah ini untuk mengetahui apa yang harus dilakukan:

  • the run reached its --max-turns limit: giliran dari prompt mod Anda dan dari subagent yang di-spawn-nya dihitung terhadap --max-turns atau batas giliran Agent SDK. Untuk mendapatkan lebih banyak giliran, mulai run dengan batas yang lebih tinggi. Jika tidak bisa, buat mod Anda berhenti melakukan panggilan yang ditolak. Jika run menerima prompt lain dari pengguna atau aplikasi SDK, jumlah giliran direset dan run menerima panggilan lagi.
  • the run is past its --max-budget-usd limit: mulai run dengan --max-budget-usd yang lebih tinggi, atau naikkan batas pengeluaran Agent SDK. Jika tidak bisa, buat mod Anda berhenti melakukan panggilan yang ditolak. Batas ini berlaku untuk total biaya run sejauh ini. Total tersebut terus bertambah ketika run menerima prompt lain, sehingga retry mengembalikan alasan yang sama.
  • the host has ended its input: misalnya, aplikasi SDK mengakhiri input streaming-nya, atau stdin ditutup pada run yang dimulai dengan --input-format stream-json. Run sedang berakhir dan tidak menerima input lagi, sehingga retry mengembalikan alasan yang sama. Buat mod Anda berhenti melakukan panggilan yang ditolak. Jika Anda sendiri yang memulai run, lain kali biarkan input tersebut tetap terbuka hingga mod Anda tidak memiliki panggilan lagi untuk dilakukan.
  • the session was stopped and nobody has sent input since: run diinterupsi, misalnya dengan interrupt() dari Agent SDK. Untuk melakukan panggilan lagi setelah pesan berikutnya dari pengguna atau aplikasi SDK, ikuti Mencoba ulang panggilan yang ditolak atau dibuang.

Run claude -p dengan mod Anda tidak pernah keluar

Jika mod Anda mengirimkan prompt di akhir setiap giliran, setiap prompt memulai giliran lain. Tanpa --max-turns atau --max-budget-usd, run claude -p yang memuat mod tersebut terus berlanjut hingga Anda menghentikannya sendiri.

Agar run berakhir dengan sendirinya, tambahkan salah satu flag tersebut ke perintah di shell Anda. Jika orang lain yang memulai run, buat mod Anda berhenti mengirimkan prompt setelah pekerjaannya selesai. Perintah ini menambahkan --max-turns:

claude -p "find the TODOs" --max-turns 5

Setelah run mencapai batas tersebut, prompt berikutnya dari mod ditolak alih-alih memulai giliran, dan run berakhir.

Jika run masih melewati batas, jalankan claude --version di shell Anda, dan perbarui Claude Code jika versi Anda lebih lama dari v2.1.304. Prompt mod ditolak pada batas tersebut mulai dari v2.1.299. Jika mod Anda men-spawn subagent, giliran subagent tersebut dihitung terhadap --max-turns mulai dari v2.1.304.

Mengirim dan menerima pesan antar sesi

Sebuah mod dapat mengirim pesan teks biasa ke sesi Anda yang lain, ke salah satu subagent milik sesi ini, atau ke rekan tim dalam tim agent miliknya. Mod juga dapat mengamati pesan yang masuk dan keluar.

Untuk mengirim satu pesan, panggil $.session.send({ to, text }), yang melakukan pengiriman yang sama seperti yang dilakukan tool SendMessage. Atur to berdasarkan siapa yang menerima pesan:

  • Sesi Anda yang lain: { sessionId }
  • Subagent atau rekan tim: { agentId }, dengan id dari $.agent.list()
  • Pengirim pesan yang Anda terima: alamat string asal pesan tersebut

Panggilan ini selesai setelah pesan masuk antrean, dengan { isDelivered: true }. Ketika tidak ada yang tersampaikan, panggilan selesai dengan { isDelivered: false, reason }, dan reason menjelaskan alasannya.

Hook ini menjawab perintah /ping, yang didaftarkan sebagai perintah, dengan menanyakan status kepada sesi yang id-nya Anda ketik setelah perintah tersebut:

on('command.run', { command: 'ping' }, async ($, e) => {
  // e.args is the session id typed after /ping
  const sent = await $.session.send({ to: { sessionId: e.args }, text: 'Status? One line.' })
  // The call resolves either way, so check isDelivered to learn what happened
  if (!sent.isDelivered) $.ui.toast('Not delivered: ' + sent.reason)
  // An empty result prints nothing in this session's transcript
  return {}
})

Ketika pesan masuk antrean, tidak ada yang muncul di sesi Anda, dan Claude di sesi lain membaca Status? One line. Ketika tidak ada yang tersampaikan, notifikasi toast menampilkan alasannya.

session.receive dan session.send memungkinkan mod mengamati pesan. Kembalikan next(e) dari keduanya untuk meneruskan setiap pesan tanpa perubahan:

Event Dipicu ketika Field yang berguna
session.receive Sebuah pesan tiba untuk sesi ini, sebelum Claude membacanya e.text, dan e.origin.kind, seperti peer atau peer-send-message untuk sesi atau agent lain, task-notification, atau scheduled-trigger. Kembalikan { consumed: reason } agar pesan tidak sampai ke Claude.
session.send Sebuah pesan akan keluar, dari tool SendMessage atau dari mod e.to, e.text, dan e.origin.kind, yang bernilai model atau plugin

Sesi yang diatur untuk menolak pesan masuk menolak pesan sebelum session.receive dipicu, sehingga hook tidak pernah melihatnya. Pesan yang ditahan untuk menunggu persetujuan Anda sampai ke hook terlebih dahulu, sehingga mod dapat membaca pesan yang belum Anda setujui. next(e) milik hook akan ditolak (reject) ketika pesan tidak tersampaikan.

Nama pengirim pada pesan yang diterima adalah apa pun yang ditulis oleh pengirim, jadi jangan mendasarkan keputusan padanya.

Menjangkau file, proses, dan jaringan

Sebuah mod menjangkau sistem file, proses, dan jaringan melalui mods API, dengan izin yang sama seperti pengguna yang menjalankan Claude Code. Modul hook itu sendiri tidak memiliki API Node.js, tidak memiliki global timer seperti setTimeout, dan tidak memiliki akses jaringan atau file sendiri. API JavaScript standar dan API web seperti URL, TextEncoder, AbortController, dan crypto.subtle tersedia. Setiap namespace di bawah ini mencakup satu jenis akses:

Namespace Fungsinya
$.fs read(path), write(path, text), exists(path), stat(path), dan list(path) bekerja pada file dan direktori
$.process run(['git', 'status']) memulai sebuah perintah dan di-resolve saat perintah tersebut keluar. spawn mengalirkan output dari perintah yang berjalan lama.
$.http fetch(url, init) melalui http atau https. Ini di-resolve ke { status, ok, headers, text } setelah body dibaca.
$.store Penyimpanan key-value JSON milik plugin Anda sendiri, yang dipertahankan antar sesi
$.env get dan set environment variable. Tulis namanya sebagai string literal.
$.settings read() di-resolve ke semua pengaturan, yang digabungkan menjadi satu objek. Dengan { source } atau { key }, ini di-resolve ke pengaturan satu sumber atau nilai satu key sebagai gantinya.
$.session messages() mengembalikan transkrip sebagai daftar { role, text, toolUses }. Juga direktori kerja, model, dan lainnya. usage() mengembalikan penggunaan context window dan batas paket.
$.mcp call sebuah tool pada server MCP yang terhubung

File, proses, dan permintaan memiliki beberapa aturan tersendiri:

  • Path: path relatif di-resolve terhadap direktori kerja sesi, atau direktori kerja subagent yang event-nya sedang ditangani oleh hook
  • $.fs.list: mengembalikan entri dari satu direktori sebagai { name, kind, size, isLink } dan tidak rekursif
  • $.process.run: menerima daftar argumen dan tidak menggunakan shell. Ini di-resolve ke { exitCode, stdout, stderr } apa pun exit code-nya. Ini ditolak (reject) jika program tidak dapat dimulai atau masih berjalan saat timeout, yang secara default adalah 30 detik, jadi bungkus dengan try dan catch.
  • $.http.fetch: mengikuti hingga lima redirect. Pada redirect ke origin yang berbeda, ini hanya mempertahankan header permintaan accept, accept-language, content-type, dan user-agent yang Anda tetapkan dan membuang sisanya, sehingga permintaan yang bergantung pada header lain, seperti Authorization, dapat gagal setelah redirect tersebut. Batasan menjelaskan timeout dan ukuran body-nya.

Setiap panggilan ini sendiri merupakan sebuah event, yang dinamai sesuai namespace dan metodenya tanpa $., seperti fs.read untuk $.fs.read. Sebuah mod yang lebih awal dalam rantai dapat mengamati, menulis ulang, atau menolak panggilan Anda, dan dengan cara inilah sebuah organisasi membatasi apa yang dapat dijangkau oleh mod.

Sebuah mod dapat menolak panggilan $.process.spawn Anda setelah perintah menghasilkan output atau keluar, dan tidak ada yang dilakukan perintah tersebut yang dibatalkan. Panggilan tersebut kemudian ditolak (reject) dengan pesan yang diakhiri dengan salah satu string berikut dan alasan dari mod yang menolak:

  • $.process.spawn started, and a plugin withheld its result:: mod yang menolak belum membaca output perintah hingga akhir. Claude Code menghentikan perintah tersebut jika masih berjalan.
  • $.process.spawn ran, and a plugin withheld its result:: mod yang menolak telah membaca output perintah hingga akhir, sehingga perintah tersebut sudah keluar

Membaca semua pengaturan, satu sumber, atau satu key

$.settings.read() di-resolve ke satu objek yang berisi pengaturan dari semua sumber yang digunakan, digabungkan berdasarkan prioritas. Berikan { source } untuk membaca satu sumber saja, { key } untuk membaca satu key, atau keduanya:

Panggilan Di-resolve ke
$.settings.read() Pengaturan yang digabungkan sebagai satu objek. Key tingkat atasnya adalah key dari file settings.json, seperti permissions dan hooks.
$.settings.read({ source: 'policy' }) Pengaturan dari satu sumber tersebut sebagai satu objek, atau {} jika sumber tersebut tidak berisi pengaturan apa pun
$.settings.read({ key: 'spinnerTipsEnabled' }) Nilai key tersebut dalam pengaturan yang digabungkan, atau undefined jika key tersebut tidak ditetapkan
$.settings.read({ key: 'spinnerTipsEnabled', source: 'user' }) Nilai key tersebut dalam satu sumber tersebut, atau undefined jika sumber tersebut tidak menetapkannya

Untuk source, berikan nama satu sumber pengaturan yang akan dibaca:

  • user: ~/.claude/settings.json
  • project: .claude/settings.json
  • local: .claude/settings.local.json
  • flag: apa yang Anda berikan dengan --settings, dan pengaturan inline dari SDK
  • policy: pengaturan terkelola

Untuk key, berikan nama persis dari satu key tingkat atas, bukan path bertitik ke dalam objek bersarang. Argumen key memerlukan Claude Code v2.1.304 atau yang lebih baru.

Langkah berikutnya