תיעוד 80
שימוש ב-mods API
קראו ל-mods API מתוך mod של Claude Code כדי להוסיף פקודות וכלים, לקרוא למודל, להריץ עבודה בטיימר, לשלוח הודעות לסשנים אחרים, ולהגיע לקבצים ולרשת.
ה-mods API הוא קבוצת המתודות ש-mod קורא להן כדי לפעול: להוסיף פקודות וכלים, לקרוא למודל, להריץ עבודה בין אירועים, ולהגיע למערכת הקבצים, לתהליכים ולרשת. כל הוק מקבל אותו כארגומנט הראשון, $, והמתודות מקובצות במרחבי שמות כגון $.ui ו-$.fs. אירועים קובעים מתי הוק רץ, וה-mods API הוא מה שההוק קורא לו אחרי שהוא רץ.
בנו את ה-mod הראשון לפני שמתחילים כאן. לכל מתודה, ראו מתודות של mods API או קראו את הטיפוסים לבילד שלכם.
#הוספת פקודה או כלי
mod יכול להוסיף פקודה שהמשתמש מריץ וכלי ש-Claude קורא לו. רשמו את שניהם בהוק session.start. Claude Code ממתין להוק הזה לפני הפרומפט הראשון, כך שמה שנרשם זמין מהתור הראשון.
#הוספת פקודה
פקודה מיועדת למשתמש. רשמו אותה, ואז טפלו ב-command.run עבור השם שלה. הדוגמה הזו מוסיפה פקודת /standup שמקבלת מספר ימים אופציונלי:
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): ...' }
})אחרי שהסשן מתחיל, /standup מופיעה עם התיאור שלה ברשימה שרואים כשמקלידים /. ה-argumentHint מוצג בפרומפט אחרי שמקלידים את הפקודה ורווח, כמו ב-/standup [days]. כשמריצים /standup 3, ההוק השני מחזיר Summary for the last 3 day(s): ..., והתמלול מציג את הטקסט הזה אחרי שם התוסף. ההוק לא קורא ל-next, כי לפקודה אין התנהגות מלבד שלכם.
ה-text שמחזירים מודפס בתמלול ו-Claude קורא אותו. כדי לא להדפיס כלום, כמו פקודה שרק פותחת חלונית, החזירו {}. כדי לאפשר לפקודה לרוץ בזמן ש-Claude עובד, הוסיפו immediate: true לרישום.
בחרו שם שאף פקודה מובנית לא משתמשת בו. הקלידו / בסשן כדי לראות אותן. $.command.register זורק עבור שם תפוס, עם הודעה כגון "/focus" refused: it is the built-in /focus. הוק שזורק מדלגים עליו, ולכן גם שאר הוק session.start שלכם לא רץ. רשמו פקודות אחרונות בהוק הזה, או עטפו את הקריאה ב-try וב-catch.
#הוספת כלי
כלי מיועד ל-Claude. רשמו אותו עם שם, תיאור ש-Claude קורא, ו-JSON Schema עבור הקלט שלו. Claude רואה אותו תחת שם ארוך יותר שמורכב מ-mcp__, שם התוסף שלכם, שני קווים תחתונים, והשם שרשמתם. מטפלים בקריאות אליו בהוק tool.call שמסונן לשם המלא הזה. הדוגמה הזו, מתוסף בשם my-mod, רושמת ticket, כך שהשם המלא הוא mcp__my-mod__ticket. היא נותנת ל-Claude כלי שמאתר כרטיס במעקב תקלות:
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 }
})כששואלים על כרטיס, Claude יכול לקרוא ל-mcp__my-mod__ticket עם ה-id שלו. ההוק השני מביא את הכרטיס ומחזיר את גוף התשובה, ש-Claude קורא כתוצאת הכלי. כשהשרת עונה עם סטטוס שגיאה, Claude קורא Lookup failed with status ואת המספר.
[!TIP]
כשחיפוש כלי MCP דוחה כלי רשום, Claude רואה את השם שלו אבל לא את התיאור עד שהוא מחפש אותו. אם Claude צריך לשקול את הכלי בכל תור, הוסיפו
isDeferred: falseלרישום כדי לטעון את הכלי המלא מראש. השדה דורש Claude Code v2.1.293 או גרסה מאוחרת יותר, וגרסאות קודמות מתעלמות ממנו.
#קריאה למודל
mod יכול לשלוח בקשות משלו למודל עבור משימה קטנה כגון סיווג או סיכום טקסט. $.model.complete שולח את הפרומפט שלכם בפני עצמו, ו-$.model.fork({ prompt }) שולח את השיחה הנוכחית עם הפרומפט שלכם בסוף.
הטבלה הזו משווה מה כל בקשה מכילה:
| בבקשה | $.model.complete | $.model.fork |
|---|---|---|
| מודל | ה-model שמעבירים | המודל של הסשן |
| פרומפט מערכת | בלוק ייחוס קצר, ואז ה-system שלכם אם מעבירים אחד | פרומפט המערכת של הסשן |
| הודעות | הודעת משתמש אחת, ה-prompt שלכם | השיחה עד כה, ואז ה-prompt שלכם כהודעת משתמש |
| CLAUDE.md והקשר פרויקט אחר | לא כלול | כלול, כמו בבקשה האחרונה של השיחה |
| כלים | אין | הכלים של Claude, שהמודל לא יכול לקרוא להם |
fork חוזר על הבקשה האחרונה של השיחה, כך ש-Claude API מגיש את רובה ממטמון הפרומפט כל עוד השיחה עדיין במטמון.
שתי הקריאות משתמשות באישורים של הסשן, ולכן הן מחויבות לתוכנית של המשתמש, למפתח API, או לספק הענן. הטיפוסים לבילד שלכם מתעדים כל מתודה של $.model.
#שליחת פרומפט אחד
העבירו model ו-prompt אל $.model.complete. prompt הופך להודעת המשתמש. כדי לתת למודל הוראות, כגון תפקיד או פורמט פלט, העבירו גם system, שהופך לפרומפט המערכת.
ההוק הזה עונה לפקודת /triage, שנרשמה כפקודה, על ידי בקשה ממודל קטן לתייג את הטקסט שהוקלד אחריה:
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 }
})כשמריצים /triage the export button does nothing, ה-mod שולח את הטקסט הזה למודל ומדפיס את התשובה שלו, כגון Label: bug. כשהמודל לא עונה, התווית היא unknown.
כשל של Claude API לא דוחה את הקריאה, אז בדקו את r.isAnswered, וקראו את r.reason כשהוא false. הקריאה נדחית עבור בקשה ש-Claude Code לא ישלח, כגון מודל שהארגון שלכם חוסם.
הטיפוסים לבילד שלכם מפרטים את האפשרויות האחרות, כגון effort, והמגבלות נותנות את ברירת המחדל של maxTokens.
#שימוש במטמון פרומפט
$.model.complete תומך במטמון פרומפט של Claude API. ה-API שומר במטמון את תחילת הבקשה, שנקראת הקידומת, עד נקודת עצירה של מטמון שמגדירים. כשכל קריאה מתחילה באותו תוכן סטטי ארוך, כגון הוראות או חומר עזר, הגדירו נקודת עצירה בסוף התוכן הזה. קריאות מאוחרות יותר קוראות אותו מהמטמון במקום לשלם עבורו את מחיר הקלט המלא.
כדי להגדיר נקודת עצירה, העבירו את prompt כמערך של בלוקי { text } במקום מחרוזת, והוסיפו cache: true לבלוק האחרון של התוכן הסטטי. Claude Code שולח את הבלוק הזה עם שדה cache_control של ה-API. system מקבל את אותה צורת מערך. כדי להחליט ביניהם, ראו בחירה בין prompt ל-system.
[!NOTE]
מערכים של בלוקים דורשים Claude Code v2.1.292 או גרסה מאוחרת יותר. גרסאות קודמות דוחות מערך ב-
promptעם שגיאה שמסתיימת ב-takes { model, prompt } (host check), והן משאירות מערך ב-systemמחוץ לבקשה.
הגרסה הזו של הוק /triage שולחת קבוצה ארוכה של כללי תיוג לפני הטקסט לתיוג, עם נקודת עצירה אחרי הכללים. RULES היא מחרוזת משלכם:
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 ולמספר נקודות העצירה יש את המגבלות האלה:
- TTL: רשומת מטמון נמשכת חמש דקות אחרי השימוש האחרון בה. ה-TTL מגיע מהגדרות Claude Code של המשתמש, לא מהקריאה. לשעה אחת, הגדירו
subagentPromptCacheTtlל-1h. - נקודות עצירה לבקשה: ה-API מקבל עד ארבע, ועוד אחת חוזרת כ-
api-errorב-r.reason
#בחירה בין prompt ל-system
שימו את התוכן הסטטי שהקריאות שלכם חולקות בתחילת prompt, אלא אם ידוע שהבקשות שלכם הולכות ישירות ל-Claude API:
- ישירות ל-Claude API, עם מפתח API או מנוי Claude: כל אחד מהשדות עובד
- דרך Amazon Bedrock, Claude Platform on AWS, Agent Platform של Google Cloud, Microsoft Foundry, או LLM gateway: השתמשו ב-
prompt. Claude Code מתחיל את פרומפט המערכת בבלוק ייחוס שהטביעת האצבע שלו מגיעה מתחילת הודעת המשתמש. נקודת הקצהapi.anthropic.comמסירה את הבלוק הזה לפני השמירה במטמון. נקודות קצה אחרות מקבלות אותו כחלק מהפרומפט, כך שנקודת עצירה ב-systemיכולה להחמיץ כש-promptמתחיל אחרת. - ב-mod שאנשים אחרים מריצים: השתמשו ב-
prompt, כי לא בוחרים את הספק שלהם
system בא לפני prompt בקידומת, כך שנקודת עצירה ב-prompt מכסה גם את system, וקריאה עם system שונה מחמיצה את המטמון.
#בדיקה של פגיעות מטמון
לתוצאה של $.model.complete יש אובייקט usage עם שדות המטמון של ה-API. usage.cache_creation_input_tokens סופר את הטוקנים שהקריאה כתבה למטמון, ו-usage.cache_read_input_tokens סופר את הטוקנים שהיא קראה מהמטמון. צפו לכתיבה בקריאה הראשונה ולקריאות בקריאות מאוחרות יותר בתוך ה-TTL.
אם כל קריאה כותבת ואף אחת לא קוראת, הקידומת שונה בין קריאות או שהקריאות רחוקות זו מזו יותר מה-TTL. עבור קידומת ששונה, ראו בחירה בין prompt ל-system.
אם שני השדות נשארים באפס בקריאות שהמודל ענה עליהן, שום דבר לא נשמר במטמון. בדקו כל אחת מהסיבות האלה:
- הקידומת קצרה מדי: ה-API לא שומר במטמון קידומת מתחת לאורך המינימלי של המודל, והוא לא מחזיר שגיאה
- מטמון פרומפט מושבת: כשמשתנה
DISABLE_PROMPT_CACHINGחל על המודל, Claude Code מסיר את נקודות העצירה ושולח את הטקסט בלי מטמון - ה-gateway שלכם מסיר את
cache_control: gateway יכול להסיר את השדה ועדיין להחזיר הצלחה - mod אחר משכתב את תחילת הטקסט: Claude Code אז שולח אותו בלי נקודות עצירה
#מה הוק model.complete מקבל
אם מחברים את אירוע model.complete כדי לבדוק או לשנות בקשות של mods אחרים, קראו את הטקסט מהשדות האלה:
e.prompt: תמיד מחרוזת. כשהקורא העביר מערך, זו הטקסט של הבלוקים מחובר לפי הסדר.e.system: מחרוזת שנבנית באותו אופן, או חסרה כשהקורא לא העבירsysteme.promptBlocksו-e.systemBlocks: המערכים של הקורא, כל אחד נוכח כשהקורא העביר מערך עבור השדה הזה
Claude Code שולח את המחרוזות שההוק שלכם מעביר ל-next ומשתמש במערכים שמעבירים איתן כדי למקם נקודות עצירה של מטמון. הוא שומר את הבלוקים המובילים שעדיין תואמים את תחילת המחרוזת, עם נקודות העצירה שלהם, ושולח את שאר המחרוזת בלי נקודת עצירה. לדוגמה, next({ ...e, prompt: e.prompt + NOTE }) שומר את נקודות העצירה של הקורא, והוק שמשנה את תחילת prompt מסיר אותן.
#הרצת עבודה ברקע
עבודה שנמשכת מעבר לאירוע אחד, למשל בדיקה של משהו פעם בדקה, רצה על טיימר שמתחילים מ-session.start. hook עצמו רץ לאירוע אחד ויש לו מגבלת זמן על זמן הריצה שלו. זמן שמבלים בהמתנה ל-next או לקריאת mods API לא נספר, חוץ מ-$.clock.sleep. $.clock.every ו-$.clock.after מחליפים את setInterval ו-setTimeout, כשההשהיה במילישניות קודם: $.clock.after(5000, fn) קורא ל-fn פעם אחת, חמש שניות מעכשיו. כל אחד מחזיר טיימר עם מתודה cancel(), ו-await $.clock.now() נותן את הזמן במילישניות.
ה-hook הזה בודק את ה-checks של pull request פעם בדקה ומציג את התוצאה מתחת ל-prompt. summarize היא פונקציה משלך שהופכת את פלט ה-JSON של הפקודה לכמה מילים:
on('session.start', async ($, e, next) => {
// קריאה לפונקציה כל 60,000 מילישניות, החל מדקה מעכשיו
$.clock.every(60_000, async () => {
const status = await $.process.run(['gh', 'pr', 'checks', '--json', 'state'])
// החלפת השורה מתחת ל prompt בסיכום העדכני
$.ui.status('checks: ' + summarize(status.stdout))
})
// החזרה בלי לחכות לטיימר, כדי שה session יתחיל מיד
return next(e)
})ה-session מתחיל כרגיל. דקה אחר כך מופיעה שורה מתחת ל-prompt עם ⚠, שם ה-mod, ואז checks: והסיכום שלך. היא מוחלפת פעם בדקה אחרי זה. ה-callback של הטיימר רץ מחוץ לכל אירוע, ולכן הוא ממשיך לרוץ בין תורות ולא מתחיל תור. אם ה-callback זורק שגיאה, השגיאה הולכת ל-יומן הדיבוג והטיימר רץ שוב במרווח הבא.
#הצגת משהו בלי להתחיל תור
עבודת רקע יכולה להראות למשתמש משהו בלי להתחיל תור. כל אחת מהקריאות האלה שמה טקסט במקום אחר:
| קריאה | מה המשתמש רואה |
|---|---|
$.ui.status(text) | שורה אחת מתחת ל-prompt שנשארת עד שמשנים אותה. היא מתחילה ב-⚠ ובשם ה-mod, כמו ב-⚠ my-mod: checks: 3 passing. |
$.ui.toast(text) | התראת toast עם שם ה-mod שנעלמת אחרי כמה שניות. זו תיבה בפינה הימנית העליונה ב-רינדור מסך מלא, ושורה אחת מימין מתחת ל-prompt ב-renderer הקלאסי. |
$.ui.log(text) | שורה עמומה ב-transcript ש-Claude לא קורא. היא מתחילה ב-● ובשם ה-mod, כמו ב-● my-mod: build finished. |
$.ui.notify(text) | התראה מה-terminal שלהם, מחוץ ל-Claude Code, שנשלחת לפי מה שההגדרה preferredNotifChannel שלהם אומרת. שם ה-mod הוא הכותרת שלה אלא אם מעבירים { title } שאינו ריק, שמוצג כמו שנותנים אותו. |
$.ui.notify היא היחידה מבין אלה שעונה. היא מסתיימת ב-{ isSent: true, channel } כשהערוץ של המשתמש כתב את ההתראה ל-terminal שלהם. channel הוא ערך ההגדרה ששלח אותה, למשל kitty או terminal_bell. הערוץ terminal_bell מצלצל בפעמון ולא מציג את הטקסט ולא את הכותרת.
אחרת הקריאה מסתיימת ב-{ isSent: false, reason }:
reason | משמעות |
|---|---|
disabled | המשתמש הגדיר את preferredNotifChannel ל-notifications_disabled |
no-channel | ההגדרה היא auto ול-terminal שלהם אין התראה ש-Claude Code יכול לשלוח |
no-surface | ל-session אין terminal, כמו בהרצה עם -p, ב-Agent SDK, באפליקציית שולחן העבודה, בהרחבת VS Code, או ב-session בענן. גם התשובה לקריאה שנעשתה לפני שה-session מתחיל, בזמן שה-mod נטען, ולקריאה שנעשתה בזמן ש-Claude Code יוצא |
refused | hook של mod אחר על ui.notify ענה במקום Claude Code בלי לשלוח |
הקריאה נדחית במקום להסתיים כש-text אינו מחרוזת, כש-hook של mod אחר על ui.notify מחזיר { deny }, או כשכתיבה ל-terminal נכשלת.
ה-mod שלך לא יכול לבחור את הערוץ, ו-Claude Code לא מגביל כמה פעמים mod מודיע. ההגדרה של המשתמש היא המתג היחיד.
כשהקריאה מגיעה ל-session שרץ, ה-Notification hooks של המשתמש רצים עם הטיפוס plugin_notification. הם רצים גם כשהתשובה היא disabled, וב-session בלי terminal, שבו התשובה היא no-surface. אף hook לא רץ לקריאה שנעשתה לפני שה-session מתחיל או בזמן ש-Claude Code יוצא. ה-hook שממנו ה-mod שלך קרא ל-$.ui.notify מושמט מהריצה הזו, כך ש-hook מסוג classic.Notification שמודיע לא נקרא שוב להתראה שלו עצמו.
terminal לא מדווח אם הוא הציג את ההתראה, ולכן isSent: true אומר שהיא נשלחה. זה לא אומר שהמשתמש ראה אותה.
הדוגמה הזו מודיעה כשתור השיחה הראשית נגמר בלי הפרעה, ועוברת ל-toast כגיבוי כשאף התראה עם טקסט לא יצאה. היא לא מחכה להתראה לפני שהיא מעבירה את האירוע הלאה, כי ה-Notification hooks של המשתמש רצים קודם ויכולים לקחת זמן:
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)
})ב-הרחבת VS Code, שורות status ו-toasts מוצגות מעל תיבת ה-prompt במקום זאת, ושורות log בשיחה.
#התחלת תור מעבודת רקע
כשעבודת רקע מוצאת משהו שצריך את תשומת הלב של Claude, היא יכולה להתחיל תור על ידי שליחת prompt עם $.prompt.submit({ text }). Claude קורא את הטקסט אחרי משפט שמציין את ה-mod שלך כשולח. כדי לשלוח אותו כמילים של המשתמש עצמו, בלי המשפט הזה, מוסיפים asUser: true. הקריאה מחכה עד שה-session פנוי ואז מתחילה תור חדש. היא מסתיימת כשהתור הזה מתחיל, אז אל תעשה await לה ב-handler שרץ בזמן ש-Claude עובד.
#עצירת עבודת רקע
טיימרים נעצרים אחרי שהמודול נטען מחדש. לעבודה ארוכה בתוך hook, next.signal הוא AbortSignal שמבטל כשהאירוע שה-hook שלך מטפל בו ננטש, למשל כשהמשתמש מפריע. מעבירים אותו לכל דבר שרץ זמן רב.
#קריאות ועבודת רקע אחרי שהמשתמש מפריע לתור
הסיבה the session was stopped and nobody has sent input since בתוצאה או בשגיאה אומרת שהמשתמש עצר תור שרץ, למשל עם Esc או Stop. אחרי ההפרעה הזו, קריאות שמתחילות עבודה חדשה ב-session נדחות עד הקלט הבא של המשתמש, למשל הודעה ב-prompt.
מלבד $.prompt.submit ו-$.command.run, הטיפול הזה בקריאות ובעבודת רקע אחרי הפרעה דורש Claude Code v2.1.304 או חדש יותר.
#ניסיון חוזר לקריאה שנדחתה או שנזרקה
עושים שוב קריאה שנדחתה או שנזרקה מ-hook של turn.complete. קריאות נשארות דחויות עד הקלט הבא של המשתמש, ושום דבר לא מכניס אותן לתור או חוזר עליהן בשבילך.
כשתוצאה או שגיאה של קריאה מכילה אחת מהמחרוזות האלה, שומרים את הארגומנטים שלה לניסיון חוזר מאוחר יותר:
the session was stopped and nobody has sent input since: הקריאה נדחתהwas removed from the queue before it ran: ה-prompt או הפקודה יצאו מהתור בלי לרוץ ונזרקו, למשל כשהמשתמש עצר את התור
בדוגמה הזו, קוראים ל-submitPrompt מעבודת הרקע במקום $.prompt.submit. שמים את המשתנה pendingText ואת הפונקציה submitPrompt מעל register, ואת ה-hook של turn.complete בתוכו, כי אימות דוחה $ שמועבר לפונקציה שמוצהרת בתוך register. כשהתוצאה או השגיאה נושאת אחת מהמחרוזות האלה, submitPrompt שומר את הטקסט ב-pendingText כדי שה-hook של turn.complete ישלח אותו שוב:
// הטקסט של ה prompt האחרון שנדחה או שנזרק, או 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
}
// כל תוצאה או שגיאה אחרת לא מנוסה שוב
}
on('turn.complete', async ($, e, next) => {
if (pendingText !== null) {
const text = pendingText
// ריקון pendingText לפני הניסיון החוזר, כדי שהרצה שנייה של ה hook
// לא תשלח את אותו טקסט.
pendingText = null
// בלי await: ה hook יישאר פתוח עד שהתור החדש מתחיל
submitPrompt($, text)
}
return next(e)
})לפני הקלט הבא של המשתמש, turn.complete יכול לרוץ עבור התור שהופרע עצמו ועבור תור של subagent, וקריאות שנעשות אז נדחות שוב. במקרה הזה submitPrompt שומר את הטקסט ל-turn.complete מאוחר יותר.
לקריאה שנדחית, כמו $.command.run, תופסים את השגיאה ובודקים את ההודעה שלה כמו ש-submitPrompt בודק את reason.
#מה קריאות שנדחו מחזירות
בתוצאות, <reason> מייצג את הסיבה שהקריאה נדחתה. אחרי הפרעה, זו the session was stopped and nobody has sent input since. לסיבות אחרות, ראו קריאות שנדחות בהרצות claude -p וב-sessions של Agent SDK.
| חלק מהקריאות שנדחו | תוצאה |
|---|---|
$.prompt.submit | מסתיימת עם { drop: '<reason>' } ולא מכניסה prompt לתור |
$.command.run | נדחית עם שגיאה שההודעה שלה נגמרת ב-<reason> |
$.agent.spawn | מסתיימת עם { deny: '<reason>' } ולא מתחילה subagent |
$.session.send ל-subagent שסיים | מסתיימת עם { isDelivered: false, reason: 'Agent "<id>" was not resumed: <reason>.' }, כאשר <id> הוא ה-id של ה-subagent. subagent שעדיין רץ מקבל את ההודעה. |
$.session.send ל-teammate ב-צוות סוכנים | מסתיימת עם { isDelivered: false, reason: 'Teammate "<name>" was not messaged: <reason>.' }, כאשר <name> הוא שם ה-teammate |
$.tool.call של Bash עם run_in_background: true, Monitor, או CronCreate | מסתיימת עם { deny: '<tool> was not run: it starts background work, a schedule or a goal, and <reason>' }, כאשר <tool> הוא שם הכלי |
כשאחת מהקריאות האלה של $.session.send או $.tool.call נדחית אחרי הפרעה ב-session אינטראקטיבי, המשתמש יכול לראות את ה-toast של Stopped:.
#עבודה שה-mod שלך התחיל לפני ההפרעה
אחרי שהמשתמש עוצר תור שרץ, למשל עם Esc, חלק מהעבודה שה-mod שלך כבר התחיל נזרק וחלק מחכה לקלט הבא של המשתמש.
| עבודה | מה קורה עד הקלט הבא של המשתמש | מה לעשות |
|---|---|---|
prompt מ-$.prompt.submit שעדיין בתור | נזרק. הקריאה מסתיימת עם { drop: 'the prompt was removed from the queue before it ran' }. | שולחים אותו שוב כמו ש-ניסיון חוזר לקריאה שנדחתה או שנזרקה מראה |
פקודה מ-$.command.run שעדיין בתור | נזרקת. הקריאה נדחית עם שגיאה שההודעה שלה כוללת the command was removed from the queue before it ran. | מריצים אותה שוב כמו ש-ניסיון חוזר לקריאה שנדחתה או שנזרקה מראה |
| עבודה חד פעמית שתוזמנה עם כלי כמו CronCreate שמגיע מועדה לפני הקלט הבא של המשתמש | לא מתחילה תור אז, ורצה אחרי הקלט הזה | אין צורך בפעולה |
| עבודה חוזרת שתוזמנה עם כלי כמו CronCreate שמגיע מועדה לפני הקלט הבא של המשתמש | לא מתחילה תור אז. העבודה מפסידה את הריצה הזו ושומרת על לוח הזמנים שלה. | אין צורך בפעולה |
| אירוע מ-monitor שה-mod שלך התחיל | ה-monitor ממשיך לרוץ. אירוע שה-monitor מעלה לפני הקלט הבא של המשתמש נזרק, חוץ מההודעה שה-monitor נגמר, שמגיעה אחרי הקלט הזה. | אי אפשר לעשות דבר |
subagent שה-mod שלך יצר ואחר כך חידש עם $.session.send שמסיים לפני הקלט הבא של המשתמש | הודעת הסיום שלו מחכה, ומגיעה לשיחה אחרי הקלט הזה | אין צורך בפעולה |
פקודת Bash או PowerShell בחזית מ-$.tool.call שכבר רצה כשהמשתמש עצר את התור ומגיעה ל-timeout שלה לפני הקלט הבא שלהם | נעצרת ב-timeout שלה במקום מעבר לרקע. הקריאה מסתיימת עם isError מוגדר, והפלט כולל את השורה Ended instead of moving to the background: plugins' input is held, by a Stop or by the host's limits. | מריצים אותה שוב אחרי הקלט הבא של המשתמש, כמו ש-ניסיון חוזר לקריאה שנדחתה או שנזרקה מראה |
כשעבודה מתוזמנת מגיעה למועד או monitor מעלה אירוע לפני הקלט הבא של המשתמש, המשתמש יכול לראות את ה-toast של Stopped:.
#קריאות שנדחות בהרצות claude -p וב-sessions של Agent SDK
בהרצת claude -p או ב-session של Agent SDK, תוצאה כמו { drop: 'the run reached its --max-turns limit' } אומרת שההרצה דחתה את הקריאה של ה-mod שלך.
$.prompt.submit שנדחה מסתיים עם { drop: '<reason>' }. קריאות דחויות אחרות, כמו $.command.run ו-$.agent.spawn, נותנות את התוצאות שמוצגות ב-מה קריאות שנדחו מחזירות עם הסיבה במקום <reason>. מצאו את <reason> שלכם למטה למה לעשות לגביו:
the run reached its --max-turns limit: תורות מ-prompts של ה-mod שלך ומ-subagents שהוא יוצר נספרים ל---max-turnsאו ל-מגבלת התורות של Agent SDK. ליותר תורות, מתחילים את ההרצה עם מגבלה גבוהה יותר. אם אי אפשר, גורמים ל-mod להפסיק לבצע את הקריאה שנדחתה. אם ההרצה מקבלת prompt נוסף מהמשתמש או מיישום ה-SDK, ספירת התורות מתאפסת וההרצה מקבלת קריאות שוב.the run is past its --max-budget-usd limit: מתחילים את ההרצה עם--max-budget-usdגבוה יותר, או מעלים את מגבלת ההוצאה של Agent SDK. אם אי אפשר, גורמים ל-mod להפסיק לבצע את הקריאה שנדחתה. המגבלה חלה על העלות הכוללת של ההרצה עד כה. הסכום הזה ממשיך להצטבר כשההרצה מקבלת prompt נוסף, ולכן ניסיון חוזר מחזיר את אותה סיבה.the host has ended its input: למשל, יישום ה-SDK סיים את קלט ה-streaming שלו, או ש-stdin נסגר בהרצה שהתחילה עם--input-format stream-json. ההרצה נגמרת ולא מקבלת עוד קלט, ולכן ניסיון חוזר מחזיר את אותה סיבה. גורמים ל-mod להפסיק לבצע את הקריאה שנדחתה. אם מתחילים את ההרצה בעצמכם, בפעם הבאה משאירים את הקלט הזה פתוח עד של-mod אין עוד קריאות לבצע.the session was stopped and nobody has sent input since: ההרצה הופרעה, למשל עםinterrupt()של Agent SDK. כדי לבצע את הקריאה שוב אחרי ההודעה הבאה מהמשתמש או מיישום ה-SDK, עוקבים אחרי ניסיון חוזר לקריאה שנדחתה או שנזרקה.
#הרצת claude -p עם ה-mod שלך אף פעם לא יוצאת
אם ה-mod שלך שולח prompt בסוף כל תור, כל prompt מתחיל תור נוסף. בלי --max-turns או --max-budget-usd, הרצת claude -p שטוענת את ה-mod ממשיכה עד שעוצרים אותה בעצמכם.
כדי שההרצה תסתיים מעצמה, מוסיפים אחד משני הדגלים לפקודה ב-shell. אם אנשים אחרים מתחילים את ההרצה, גורמים ל-mod להפסיק לשלוח prompts ברגע שהעבודה שלו נגמרה. הפקודה הזו מוסיפה --max-turns:
claude -p "find the TODOs" --max-turns 5ברגע שההרצה מגיעה למגבלה הזו, ה-prompt הבא של ה-mod נדחה במקום להתחיל תור, וההרצה נגמרת.
אם ההרצה עדיין עוברת את המגבלה, מריצים claude --version ב-shell, ו-מעדכנים את Claude Code אם הגרסה שלך מוקדמת מ-v2.1.304. ה-prompts של ה-mod נדחים במגבלה מ-v2.1.299. אם ה-mod שלך יוצר subagents, התורות שלהם נספרים ל---max-turns מ-v2.1.304.
#שליחה וקבלה של הודעות בין סשנים
מוד יכול לשלוח הודעת טקסט פשוט לסשן אחר שלך, לסוכן משנה של הסשן הזה, או לחבר צוות בצוות הסוכנים שלו. הוא יכול גם לצפות בהודעות שמגיעות ויוצאות.
כדי לשלוח הודעה, קרא ל-$.session.send({ to, text }), שמבצע את אותה מסירה שהכלי SendMessage מבצע. הגדר את to לפי מי שמקבל את ההודעה:
- סשן אחר שלך:
{ sessionId } - סוכן משנה או חבר צוות:
{ agentId }, עם מזהה מ-$.agent.list() - השולח של הודעה שקיבלת: מחרוזת הכתובת שממנה ההודעה הגיעה
הקריאה נפתרת ברגע שההודעה נכנסת לתור, עם { isDelivered: true }. כשדבר לא נמסר, היא נפתרת עם { isDelivered: false, reason }, ו-reason אומר למה.
ההוק הזה עונה לפקודה /ping, שנרשמה כפקודה, בכך שהוא מבקש סטטוס מהסשן שהמזהה שלו מוקלד אחריה:
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 {}
})כשההודעה נכנסת לתור, דבר לא מופיע בסשן שלך, וקלוד של הסשן האחר קורא את Status? One line. כשדבר לא נמסר, התראת toast נותנת את הסיבה.
session.receive ו-session.send מאפשרים למוד לצפות בהודעות. החזר next(e) משניהם כדי להעביר כל הודעה ללא שינוי:
| אירוע | מתי הוא מופעל | שדות שימושיים |
|---|---|---|
session.receive | הודעה מגיעה לסשן הזה, לפני שקלוד קורא אותה | e.text, ו-e.origin.kind, כגון peer או peer-send-message עבור סשן או סוכן אחר, task-notification, או scheduled-trigger. החזר { consumed: reason } כדי למנוע אותה מקלוד. |
session.send | הודעה עומדת לצאת, מהכלי SendMessage או ממוד | e.to, e.text, ו-e.origin.kind, שהוא model או plugin |
סשן שמוגדר לסרב להודעות נכנסות מסרב להודעה לפני ש-session.receive מופעל, ולכן הוק אף פעם לא רואה אותה. הודעה שמוחזקת לאישור שלך מגיעה להוק קודם, כך שמוד יכול לקרוא הודעה שעדיין לא אישרת. ה-next(e) של ההוק נדחה כשההודעה לא נמסרת.
השם של השולח על הודעה שהתקבלה הוא מה שהשולח כתב, אז אל תבסס החלטה עליו.
#גישה לקבצים, לתהליכים ולרשת
מוד מגיע למערכת הקבצים, לתהליכים ולרשת דרך ה-API של המודים, עם אותן הרשאות כמו למשתמש שמריץ את Claude Code. למודול ההוקים עצמו אין ממשקי API של Node.js, אין גלובלים של טיימר כמו setTimeout, ואין גישה משלו לרשת או לקבצים. ממשקי API סטנדרטיים של JavaScript ו-Web, כמו URL, TextEncoder, AbortController ו-crypto.subtle, זמינים. כל מרחב שמות להלן מכסה סוג אחד של גישה:
| מרחב שמות | מה הוא עושה |
|---|---|
$.fs | read(path), write(path, text), exists(path), stat(path) ו-list(path) עובדים על קבצים ותיקיות |
$.process | run(['git', 'status']) מפעיל פקודה ונפתר כשהיא יוצאת. spawn מזרים את הפלט של פקודה שרצה זמן רב. |
$.http | fetch(url, init) מעל http או https. הוא נפתר ל-{ status, ok, headers, text } אחרי שגוף התגובה נקרא. |
$.store | מאגר מפתח-ערך ב-JSON של התוסף עצמו, שנשמר בין סשנים |
$.env | get ו-set של משתני סביבה. כתוב את השם כליטרל מחרוזת. |
$.settings | read() נפתר לכל ההגדרות, ממוזגות לאובייקט אחד. עם { source } או { key }, הוא נפתר להגדרות של מקור אחד או לערך של מפתח אחד במקום. |
$.session | messages() מחזיר את התמליל כרשימה של { role, text, toolUses }. גם את ספריית העבודה, את המודל ועוד. usage() מחזיר את השימוש בחלון ההקשר ואת מגבלות התוכנית. |
$.mcp | call של כלי בשרת MCP מחובר |
לקבצים, לתהליכים ולבקשות יש כמה כללים משלהם:
- נתיבים: נתיב יחסי נפתר מול ספריית העבודה של הסשן, או של סוכן המשנה שההוק מטפל באירוע שלו
$.fs.list: מחזיר את הרשומות של תיקייה אחת כ-{ name, kind, size, isLink }ואינו רקורסיבי$.process.run: מקבל רשימת ארגומנטים ואינו משתמש ב-shell. הוא נפתר ל-{ exitCode, stdout, stderr }יהיה קוד היציאה אשר יהיה. הוא נדחה אם התוכנית לא יכולה להתחיל או עדיין רצה בזמן ה-timeout, שהוא 30 שניות כברירת מחדל, אז עטוף אותו ב-tryוב-catch.$.http.fetch: עוקב אחרי עד חמש הפניות. בהפניה ל-origin אחר, הוא שומר רק את כותרות הבקשהaccept,accept-language,content-typeו-user-agentשהגדרת ומשמיט את השאר, כך שבקשה שתלויה בכותרת אחרת, כמוAuthorization, יכולה להיכשל אחרי ההפניה הזו. המגבלות נותנות את ה-timeout ואת גודלי הגוף שלו.
כל אחת מהקריאות האלה היא בעצמה אירוע, שנקרא על שם מרחב השמות והמתודה שלו בלי ה-$., כמו fs.read עבור $.fs.read. מוד מוקדם יותר בשרשרת יכול לצפות בקריאה שלך, לשכתב אותה או לסרב לה, וכך ארגון מגביל את מה שהמודים מגיעים אליו.
מוד יכול לסרב לקריאת $.process.spawn שלך אחרי שהפקודה הפיקה פלט או יצאה, ודבר ממה שהפקודה עשתה לא מבוטל. הקריאה נדחית אז עם הודעה שמסתיימת באחת מהמחרוזות האלה ובסיבה של המוד שסירב:
$.process.spawn started, and a plugin withheld its result:: המוד שסירב לא קרא את פלט הפקודה עד הסוף. Claude Code עוצר את הפקודה אם היא עדיין רצה.$.process.spawn ran, and a plugin withheld its result:: המוד שסירב קרא את פלט הפקודה עד הסוף, כך שהפקודה יצאה
#קריאה של כל ההגדרות, מקור אחד, או מפתח אחד
$.settings.read() נפתר לאובייקט אחד שמחזיק את ההגדרות מכל המקורות שבשימוש, ממוזגות לפי קדימות. העבר { source } כדי לקרוא מקור אחד במקום, { key } כדי לקרוא מפתח אחד, או את שניהם:
| קריאה | נפתר ל |
|---|---|
$.settings.read() | ההגדרות הממוזגות כאובייקט אחד. המפתחות ברמה העליונה שלו הם אלה של קובץ settings.json, כמו permissions ו-hooks. |
$.settings.read({ source: 'policy' }) | ההגדרות של המקור האחד הזה כאובייקט אחד, או {} כשהמקור הזה לא מחזיק הגדרות |
$.settings.read({ key: 'spinnerTipsEnabled' }) | הערך של המפתח הזה בהגדרות הממוזגות, או undefined כשהמפתח לא מוגדר |
$.settings.read({ key: 'spinnerTipsEnabled', source: 'user' }) | הערך של המפתח הזה במקור האחד הזה, או undefined כשהמקור הזה לא מגדיר אותו |
עבור source, העבר את השם של מקור ההגדרות האחד לקריאה:
user:~/.claude/settings.jsonproject:.claude/settings.jsonlocal:.claude/settings.local.jsonflag: מה שאתה מעביר עם--settings, וההגדרות המוטמעות של ה-SDKpolicy: הגדרות מנוהלות
עבור key, העבר את השם המדויק של מפתח אחד ברמה העליונה, לא נתיב עם נקודות לתוך אובייקט מקונן. הארגומנט key דורש את Claude Code v2.1.304 או מאוחר יותר.
#הצעדים הבאים
- תגובה לאירועים: חיבור של קריאות כלים, הנחיות ותורות
- ציור בממשק: הצגה של מה שהמוד אוסף בחלונית או מעל ההנחיה
- בדיקת מוד: stub של כל אחת מהקריאות האלה בבדיקה
- הפניה למודים: אירועים, מתודות של ה-API של המודים, ומגבלות