رویدادهای هوک

‏Claude Code در لحظه‌های مشخصی از هر کار، دستورهای هوک را اجرا می‌کند: یک پیام فرستاده شد، ابزاری دارد اجرا می‌شود، ابزاری تمام شد، کار تمام شد و غیره. aipager به همهٔ این لحظه‌ها هوک می‌گذارد و آن‌ها را به اتفاق‌هایی در تلگرام تبدیل می‌کند: کارت در حال کار، درخواست‌های اجازه، جواب‌ها، اعلان‌ها و لاگ ممیزی.

سورس در GitHub ↖این صفحه ترجمهٔ صفحهٔ انگلیسی است؛ اگر جایی با هم فرق داشتند، صفحهٔ انگلیسی درست است. انگلیسی

چطور وصل می‌شود#

‏aipager config (یا aipager setup) ورودی‌های aipager را به ‎~/.claude/settings.json اضافه می‌کند، و aipager در هر شروع آن‌ها را چک می‌کند:

  • یک دستور aipager-hook برای هر کدام از ۱۶ رویداد پایین، با matcher برابر * روی رویدادهای ابزار (PreToolUse،‏ PostToolUse،‏ PermissionRequest)؛
  • مهلت ۵ ثانیه روی PreModelSwitch، و از aipager config و aipager setup مهلت ۳۰ ثانیه روی PermissionRequest (خود هوک حداکثر ۲۰ ثانیه منتظر می‌ماند، پایین‌تر را ببینید)؛ بقیه از پیش‌فرض Claude Code استفاده می‌کنند؛
  • دستور statusLine، یعنی aipager-statusline.

‏aipager uninstall این ورودی‌ها را دوباره برمی‌دارد (فرمان‌ها را ببینید).

‏Claude Code برای هر رویداد یک پروسهٔ جدید aipager-hook (aipager.dtach.notify_hook:main) اجرا می‌کند. این پروسه JSON رویداد را از stdin می‌خواند، دو فیلد (پایین) به آن اضافه می‌کند و آن را به‌شکل یک datagram روی یک سوکت یونیکس برای aipager می‌فرستد. سوکت به این ترتیب انتخاب می‌شود: ‎$AIPAGER_INSTANCE_DIR/aipager.sock برای یک نسخهٔ جدای تست، ‎$AIPAGER_SOCKET_PATH،‏ ‎$XDG_RUNTIME_DIR/aipager.sock، یا ‎/tmp/aipager.sock. ارسال هیچ‌وقت منتظر نمی‌ماند: اگر aipager گوش ندهد، رویداد دور ریخته می‌شود و ناظر سشن‌ها از روی فایل‌های سشن عقب‌ماندگی را جبران می‌کند.

بیشتر رویدادها برای Claude Code حدود ۲۰ تا ۲۵ میلی‌ثانیه زمان شروع پروسه هزینه دارند. چندتایی کد قانون‌های aipager را هم لود می‌کنند: قبل از اجرای یک ابزار (بررسی ایمنی، PreToolUse را ببینید) حدود ۷۰ میلی‌ثانیه، در پایان یک کار (برای پاک کردن علامت باز بودن کار) حدود ۵۵ میلی‌ثانیه، و UserPromptSubmit (که قانون‌های کار را می‌نویسد). PermissionRequest عمداً تا ۲۰ ثانیه منتظر زدن دکمهٔ شما می‌ماند.

هوک حافظهٔ خودش را به ۱ گیگابایت محدود می‌کند. هوکی که به این سقف برسد با خطا خارج می‌شود (برای PreToolUse، با کد خروجی که Claude Code آن را رد کردن می‌خواند) و یک datagram hook_memory_cap_hit برای aipager می‌فرستد که در چت به اعلانی تبدیل می‌شود، با دکمهٔ «🐞 گزارشش کن» برای مالک این نصب (گزارش مشکل را ببینید). AIPAGER_DEBUG=1 را در محیط سشن بگذارید تا هوک و خط وضعیت اطلاعات عیب‌یابی‌شان را روی stderr چاپ کنند.

aipager چه چیزی می‌گیرد#

‏datagram همان JSON هوک خود Claude Code است، با فیلدهایی که aipager اضافه کرده (چند رویداد چیزهای بیشتری دارند که زیر هر رویداد آمده):

فیلدمعنی
hook_event_nameرویداد (فیلد خود Claude Code؛‏ aipager بر اساس آن کار را پخش می‌کند).
session_id،‏ transcript_path،‏ cwd، ...فیلدهای خود Claude Code، همان‌طور که فرستاده. session_id شناسهٔ گفتگو در Claude Code است.
sessionاضافه‌شده توسط aipager: اسمی که aipager روی سشن گذاشته، claude-<name>‎، از متغیر CLAUDE_DTACH_SESSION که aipager موقع شروع سشن تنظیم می‌کند. رویدادهای سشن Claude Code که aipager شروعش نکرده اسمی ندارند و aipager نادیده‌شان می‌گیرد.
sl_tokensاضافه‌شده توسط aipager، روی هر رویدادی جز MessageDisplay، وقتی فایل خط وضعیت سشن وجود داشته باشد: context_pct،‏ total_output،‏ total_input،‏ current_output،‏ lines_added و lines_removed.

رویدادهای ابزار tool_name و tool_input را هم دارند. هر رویدادی که داخل یک ساب‌ایجنت رخ دهد agent_id دارد (و رویدادهای ساب‌ایجنت agent_type هم)؛ رویدادهای خود حلقهٔ اصلی ندارند، و aipager با همین، اجرای ابزارهای یک ایجنت را روی ردیف خود آن ایجنت می‌گذارد.

مرجع رویدادها#

گرداننده‌های aipager در aipager/dtach/hook_receiver.py هستند؛ کار خود هوک در aipager/dtach/notify_hook.py است.

SessionStart#

یک سشن شروع شد، ادامه داده شد یا گفتگویش کامپکت شد. سشنی که aipager هنوز دنبالش نمی‌کند ثبت می‌شود. بعد از کامپکت، کارت می‌گوید 📦 name · گفتگو کامپکت شد: ۸۲٪ ← ۴٪ (PreCompact را ببینید). برای یک گفتگوی جدید، هوک علامت باز بودن کار قبلی را هم پاک می‌کند.

SessionEnd#

سشن بسته شد. به‌عنوان GONE علامت می‌خورد: از نوار وضعیت سنجاق‌شده بیرون می‌رود و به سشن‌های بسته‌شده می‌رود، جایی که ‎/resume می‌تواند برش گرداند. aipager session <name>‎ یا ‎/new <name>‎ یک سشن جدید شروع می‌کند.

UserPromptSubmit#

‏Claude یک پیام را برداشت، چه در ترمینال تایپ شده باشد و چه از تلگرام آمده باشد.

هوک اول پیام را با یادداشت‌های اجازهٔ سشن (‎/tmp/claude-notes-<session>/‎، یکی برای هر پیام تلگرامی که aipager تایپ کرده) جور می‌کند و قانون‌های کار را در ‎/tmp/claude-policy-<session>.json می‌نویسد که بررسی PreToolUse آن را می‌خواند (مدل امنیتی ← اجرای قانون‌ها در استفادهٔ تیمی را ببینید). قبل از اینکه رویداد را بفرستد، به aipager می‌گوید Claude کدام پیام‌ها را برداشت (یک datagram queue_pickup). بعد اگر چیزی باشد، متن اضافه‌ای برای Claude چاپ می‌کند: سبک جوابی که در ‎/settings انتخاب شده، و برای جواب به یک پیام قدیمی‌تر، اینکه آن پیام چه می‌گفت و کی بود. پیامی از نوع ‎<task-notification>‎ (Claude که وقتی یک ایجنت یا دستور پس‌زمینه تمام می‌شود خودش را بیدار می‌کند) ادامهٔ همان کار است: با هیچ یادداشتی جور نمی‌شود و قانون‌های کار را نگه می‌دارد.

‏aipager سشن را در حال کار علامت می‌زند، اگر کارتی باز نباشد کارت در حال کار را باز می‌کند (پیامی که از تلگرام فرستاده شده از قبل کارت دارد) و 👀 پیام‌هایی را که Claude برداشته به 👍 تبدیل می‌کند. پیامی که وسط یک کار فرستاده شده وقتی 👍 می‌گیرد که Claude آن را وارد همان کار کند یا کار بعدی را با آن شروع کند.

PreToolUse#

ابزاری دارد اجرا می‌شود. بررسی ایمنی خود aipager اینجا، داخل هوک، قبل از اینکه Claude Code به قانون‌های اجازهٔ خودش نگاه کند، اجرا می‌شود:

  • کاری که از تلگرام شروع شده به قانون‌های نقش فرستنده‌اش و به کف ایمنی (مسیرهای محافظت‌شده، الگوهای دستور) محدود می‌شود که فقط نقش owner از آن معاف است؛ کاری که aipager نمی‌تواند به کسی نسبتش دهد کف ایمنی را می‌گیرد؛ پیامی که خودتان در ترمینال تایپ کرده‌اید محدود نمی‌شود. قانون‌ها در استفادهٔ تیمی و مدل امنیتی آمده‌اند.
  • وقتی قانونی جلوی اجرا را بگیرد، هوک تصمیم deny را چاپ می‌کند، Claude Code بدون پرسیدن ابزار را رد می‌کند و aipager خبردار می‌شود (safety_blocked): کار فوراً با 🛑 x1 · قانون ایمنی جلویش را گرفت: ... (متوقف شد) متوقف می‌شود و همهٔ ابزارهای بعدی همان کار هم رد می‌شوند.
  • یک فراخوانی SendUserFile (فرستادن فایل به چت توسط Claude) برای هر کاری چک می‌شود، کار owner و پیامی که در ترمینال تایپ شده هم: کف ارسال، چک‌های فایل و قانون‌های خود کار (امنیت). فراخوانی ردشده یک deny بدون نشان «aipager safety policy» می‌گیرد: Claude دلیلش را می‌خواند و به شما می‌گوید، کار ادامه پیدا می‌کند، و هوک برای لاگ ممیزی send_files_refused را با اسم هر فایل برای aipager می‌فرستد.
  • بررسی‌ای که خودش با خطا روبه‌رو شود رد می‌کند، مگر اینکه قانون‌های سشن خوانده شوند و معافیت مالک را بدهند.

در سمت aipager، اجرای ابزار به یک ردیف روی کارت در حال کار تبدیل می‌شود (اجرای ابزار یک ساب‌ایجنت روی ردیف همان ایجنت نشان داده می‌شود)، و وقتی ‎/settings ← پیش‌نمایش diff روشن باشد (پیش‌فرض خاموش است)، Write یا Edit یک پیش‌نمایش diff نشان می‌دهد. یک فراخوانی AskUserQuestion به سؤال با یک دکمه برای هر گزینه تبدیل می‌شود؛ سؤالی که یک ایجنت کمکی Claude Code وقتی هیچ کاری در جریان نیست می‌پرسد نشان داده نمی‌شود (Claude نیست که از شما می‌پرسد). فراخوانی Agent خود Claude،‏ subagent_type و description را برای ایجنتی که شروع می‌کند به جا می‌گذارد، و اجرای ابزار یک ساب‌ایجنت، آخرین فعالیت همان ایجنت است: هر دو، تا وقتی ایجنت در پس‌زمینه کار می‌کند، در وضعیت زیر جواب دیده می‌شوند.

PermissionRequest#

‏Claude Code می‌خواهد اجازه بگیرد. کارت در حال کار به درخواست اجازه تبدیل می‌شود، با دستور یا مسیر واقعی زیر توضیح خود Claude (فرمان‌ها ← درخواست‌های اجازه را ببینید): اجازه بده، رد کن، توقف، و «همیشه اجازه بده» فقط وقتی Claude Code یک قانون واقعی برای به خاطر سپردن پیشنهاد کند.

هوک هر وقت بتواند خودش به درخواست جواب می‌دهد. یک سوکت جواب کوتاه‌عمر باز می‌کند، آدرسش را با رویداد می‌فرستد و تا ۲۰ ثانیه منتظر دکمهٔ شما می‌ماند. دکمه‌ای که به‌موقع زده شود به‌شکل یک تصمیم PermissionRequest برمی‌گردد (اجازه، اجازه با قانون، یا رد)، پس Claude Code هیچ‌وقت پنجرهٔ سؤال خودش را نشان نمی‌دهد. اگر دکمه به‌موقع زده نشود، هوک به aipager می‌گوید انتظار تمام شد (permission_reply_timeout) و چیزی چاپ نمی‌کند؛ وقتی aipager در دسترس نباشد یا هر چیزی در این مسیر خراب شود هم چیزی چاپ نمی‌کند. آن‌وقت Claude Code پنجرهٔ سؤال خودش را در ترمینال نشان می‌دهد و دکمه‌ای که بعداً زده شود با تایپ کلیدها جوابش را می‌دهد. لاگ ممیزی (‎~/.claude/aipager-audit.jsonl) ثبت می‌کند هر دکمه از کدام راه جواب داده: ‎"via": "hook_decision"‎ یا ‎"via": "keystroke_fallback"‎.

اگر وقتی درخواست دیگری باز است یک PermissionRequest برسد، آن هم نشان داده می‌شود، به‌شکل پیام جداگانه زیر کارت در حال کار. همان پنجرهٔ سؤال اگر دوباره برسد (همان درخواست‌کننده، همان ابزار و همان اجرا، یا همان ورودی) فقط یک بار نشان داده می‌شود. Claude Code این هوک را برای AskUserQuestion هم اجرا می‌کند؛ آن سؤال از قبل از طریق PreToolUse خودش نشان داده شده، پس هیچ‌وقت دوباره نشان داده نمی‌شود. پنجرهٔ سؤال یک ایجنت پس‌زمینه فقط بعد از تمام شدن انتظار هوکش در ترمینال ظاهر می‌شود، زیر هر پنجره‌ای که آنجا باز است؛ برای همین دکمه فقط در درخواستی کلید تایپ می‌کند که Claude Code اول نشان می‌دهد (فرمان‌ها ← درخواست‌های اجازه را ببینید).

PostToolUse / PostToolUseFailure#

یک ابزار تمام شد یا شکست خورد. ردیفش روی کارت در حال کار ثابت می‌شود (شکست روی ردیف علامت می‌خورد)، و جوابی که به یک درخواست باز داده شده دیده می‌شود: یک «اجازه بده» یا «رد کن» قدیمی برای آن، بعد از این چیزی تایپ نمی‌کند. سشن ۲۰۰ ردیف آخر ابزارها را نگه می‌دارد.

فراخوانی Bash که Claude Code به پس‌زمینه برده، یک backgroundTaskId گزارش می‌دهد. aipager از آن به بعد آن را به‌عنوان یک دستور پس‌زمینه دنبال می‌کند (فقط مال حلقهٔ اصلی: فراخوانی‌ای که agent_id دارد مال همان ساب‌ایجنت است) تا پایانش دیده شود: یک ‎<task-notification>‎ با ‎<status>‎ (در پیامی که Claude را بیدار می‌کند، یا در گفتگوی ذخیره‌شده وقتی وسط کار برسد)، یک TaskStop یا KillShell با شناسهٔ آن، بسته شدن سشن، یا دو ساعت (AIPAGER_BG_SHELL_MAX_TRACK، به ثانیه، پیش‌فرض 7200). برای ردیفش روی کارت، فرمان‌ها را ببینید.

اجرای Agent (یا Task) که داخل یک ساب‌ایجنت انجام شود (رویدادش agent_id دارد) شناسهٔ ایجنتی را که شروع کرده گزارش می‌دهد. aipager یادش می‌ماند کدام ایجنت کدام را شروع کرده، و این‌طور ایجنتی را که برای منتظر ماندنِ ایجنت خودش متوقف شده از ایجنتی که تمام شده جدا می‌کند (پایین‌تر SubagentStart /‏ SubagentStop را ببینید).

فایل‌های یک SendUserFile که اجرا شده را aipager فوراً کپی می‌کند، برای هر فراخوانی یک بار و فقط در کاری که با یک پرامپت شروع شده، از روی فهرستی که PreToolUse آن نشان داد (هیچ‌وقت از نتیجهٔ ابزار)، و هر کدام را دوباره چک می‌کند؛ کپی‌ها بعد از جواب کار فرستاده می‌شوند، وقتی فراخوانی در گفتگوی ذخیره‌شده‌ای که aipager برای آن کار یادداشت کرده پیدا شد و در همان کار نوشته شده بود (امنیت، فرمان‌ها). کاری که Claude Code بدون hook شروع کند (پیامی که بعد از کار قبلی برمی‌دارد، بازخورد یک hook از نوع Stop) به محض این‌که گفتگوی ذخیره‌شده شروعش را نشان بدهد حساب می‌شود.

Notification#

‏aipager notification_type خود Claude Code را می‌خواند:

  • ‏permission_prompt: نشانهٔ کندتر و قدیمی‌تر یک درخواست اجازه. فقط وقتی درخواست را نشان می‌دهد که هیچ درخواستی باز نباشد: اگر یکی باز باشد، این همان پنجره‌ای است که PermissionRequest از قبل نشان داده.
  • ‏idle_prompt وقتی یک درخواست اجازه یا یک سؤال منتظر است: سشن منتظر می‌ماند و چت، یک بار در هر انتظار، ⬆️ jim · هنوز بالاتر منتظر جواب شماست را می‌گیرد.
  • ‏idle_prompt در بقیهٔ موارد: مثل Stop (پایین) با آن رفتار می‌شود. کاری را می‌گیرد که Stop آن گم شده؛ جوابی که قبلاً فرستاده شده هیچ‌وقت دوباره فرستاده نمی‌شود.

Stop#

کار تمام شد. سشن آماده می‌شود، کارت در حال کار همان‌طور که چیدمان شما می‌گوید ثابت می‌شود و جواب Claude فرستاده می‌شود (از last_assistant_message رویداد، وگرنه از گفتگوی ذخیره‌شده). پیام‌هایی که تا تمام شدن کار نگه داشته شده بودند بعد از آن ارسال می‌شوند. ایجنت‌ها و دستورهای پس‌زمینه‌ای که هنوز اجرا می‌شوند کار را باز نگه می‌دارند: کارت به‌عنوان وضعیت آن کار می‌ماند و جواب با یک وضعیت زنده از آن کار پس‌زمینه تمام می‌شود: هر ایجنت چه می‌کند و چند دستور پس‌زمینهٔ همین کار در حال اجراست (فرمان‌ها ← ایجنت‌هایی که هنوز کار می‌کنند را ببینید). هوک اول علامت باز بودن کار را پاک می‌کند، مگر اینکه Claude هنوز پیامی در صف داشته باشد که فوراً کار بعدی می‌شود.

StopFailure#

کار با یک خطای API تمام شد. سشن آماده می‌شود و جواب، اگر باشد، مثل Stop فرستاده می‌شود؛ خطای شناخته‌شده (شلوغی سرور، محدودیت سرعت، ...) به زبان ساده نشان داده می‌شود.

SubagentStart / SubagentStop#

یک ساب‌ایجنت شروع شد یا تمام شد. هر کدام ردیف خودش را روی کارت در حال کار دارد، ‎🤖 <type> · <activity> · <elapsed>‎، با اجرای ابزارهای خودش جمع‌شده زیر آن، و وقتی تمام می‌شود به ✅ 🤖 <type> · N ابزار اجرا کرد · <elapsed> ثابت می‌شود. SubagentStart نمی‌گوید کدام ایجنت آن را شروع کرده: aipager آن را به اجرای Agent (PreToolUse) هم‌نوعش که در انتظار است وصل می‌کند. اگر همهٔ این اجراها از یک ایجنت آمده باشند، ایجنت جدید همان لحظه به آن ایجنت وصل می‌شود (ردیفش روی کارت می‌شود ‎🤖 pipeline-runner › ship-tester و در وضعیت زیر جواب، زیر همان ایجنت می‌آید)؛ اگر از ایجنت‌های مختلف آمده باشند، تا وقتی PostToolUse خود ایجنتِ شروع‌کننده اسمش را نیاورد، به هیچ‌کدام وصل نمی‌شود. توضیح یک اجرا فقط وقتی به کار می‌رود که دقیقاً یک اجرا از همان نوع در انتظار باشد. ساب‌ایجنتی که SubagentStop آن هیچ‌وقت نمی‌رسد، وقتی ۳۰ دقیقه بی‌خبر بماند کنار گذاشته می‌شود (AIPAGER_SUBAGENT_SILENCE، به ثانیه؛ هیچ رویدادی از هیچ نوع با شناسهٔ آن نرسد). ایجنتی که کار می‌کند هر چند ثانیه رویداد ابزار می‌فرستد، پس این فقط ایجنتی را جمع می‌کند که پایانش دیده نشده (یک ری‌استارت، یک datagram گم‌شده)؛ یک اجرای ابزار ۳۰ دقیقه‌ای بدون هیچ رویدادی با شناسهٔ آن ایجنت هم آن را جمع می‌کند. AIPAGER_SUBAGENT_TTL به‌عنوان اسم قدیمی همین تنظیم پذیرفته می‌شود. رویدادهای SubagentStop بدون نوع و با شناسهٔ ناشناخته زیاد می‌رسند و نادیده گرفته می‌شوند.

ایجنت پس‌زمینه‌ای که وقتی ایجنتی که خودش شروع کرده هنوز اجرا می‌شود متوقف شود هم یک SubagentStop می‌گیرد. aipager این را مکث حساب می‌کند، نه پایان: ایجنت در حال اجرا می‌ماند (ردیفش، کار، وضعیت زیر جواب‌ها، نوار سنجاق‌شده). SubagentStart بعدی‌اش با همان شناسه یعنی ادامه داده و ردیف دومی اضافه نمی‌کند؛ هر ابزاری که خودش اجرا کند هم ادامه حساب می‌شود. وقتی تمام می‌شود که متوقف شود و هیچ‌کدام از ایجنت‌های خودش دیگر اجرا نشود. اگر دو دقیقه بعد از تمام شدن آخرینِ آن‌ها ادامه نداده باشد، دیگر در حال اجرا حساب نمی‌شود، اما هیچ‌وقت تمام‌شده نشان داده نمی‌شود. ایجنت‌های در حال اجرا، از جمله آن‌هایی که منتظرند، با ری‌استارت aipager نگه داشته می‌شوند.

PreCompact / PostCompact#

‏Claude دارد کانتکستش را کامپکت می‌کند (trigger برابر auto یا manual). کارت در حال کار می‌گوید 🔄 name · در حال کامپکت کردن گفتگو (اگر کارتی باز نباشد، یک پیام جدید)، تا این مکث شبیه کرش به نظر نرسد. PostCompact این وضعیت را تمام می‌کند؛ خط 📦 name · گفتگو کامپکت شد: X٪ ← Y٪ از SessionStart بعدی می‌آید، یا اگر آن جا بماند از خط وضعیت. خط وضعیت هشدار ⚠️ name · کانتکست ۸۲٪ پر شده - به‌زودی خودکار کامپکت می‌شود را هم می‌دهد (با یک دکمهٔ کامپکت)، در هر گفتگو یک بار وقتی کانتکست به ۸۰٪ برسد: کامپکت کردن آن را تکرار نمی‌کند و یک گفتگوی جدید (مثلاً بعد از ‎/clear) دوباره آن را می‌گیرد.

MessageDisplay#

متن خود Claude همان لحظه که به صفحه می‌رسد. گفتگوی ذخیره‌شده فقط بعد از هر دور اجرای ابزارها نوشته می‌شود، پس این تنها منبع لحظه‌ای از چیزی است که Claude می‌گوید، و همان چیزی است که توضیح‌هایش را به کارت در حال کار می‌رساند. متن یک ساب‌ایجنت (هر رویدادی که agent_id آن را دارد) هیچ‌وقت متن خود Claude حساب نمی‌شود: هیچ‌وقت به توضیح‌های کارت در حال کار، Mini App یا لاگ کار نمی‌رسد، و آخرین فعالیت همان ایجنت در وضعیت زیر جواب می‌شود. هوک به این رویداد دادهٔ خط وضعیت اضافه نمی‌کند: Claude Code موقع کشیدن صفحه منتظرش می‌ماند و برای هر پیام چند بار رخ می‌دهد.

PreModelSwitch#

‏Claude Code از نسخهٔ 2.1.251 به بعد این را قبل از عوض کردن مدل می‌فرستد. محتوایش from_model،‏ to_model،‏ requested_model و source است (command برای ‎/model تایپ‌شده، یا picker یا sdk). Claude Code معمولاً قبل از تعویض مدل می‌پرسد «Switch model?»، چون جواب بعدی باید کل گفتگو را دوباره بخواند. هوکی که permissionDecision: "allow"‎ جواب بدهد، اجازه می‌دهد تعویض بدون این سؤال انجام شود.

‏aipager-hook فقط برای تعویضی که خود aipager تایپ کرده allow جواب می‌دهد، از کنترل مدل در Mini App یا کیبورد مدل‌ها. هر تعویض دیگری جوابی نمی‌گیرد، پس Claude Code مثل همیشه می‌پرسد. این شامل ‎/model خود شما در ترمینال، انتخاب‌گر مدل و تعویض از SDK است.

‏aipager تعویض‌های خودش را با یک فایل علامت می‌شناسد. درست قبل از اینکه ‎/model <name>‎ را تایپ کند، aipager فایل aipager-modelswitch-<session>.json را می‌نویسد:

  • در پوشه‌ای که سوکت کنترل در آن است (معمولاً ‎$XDG_RUNTIME_DIR)؛
  • به‌شکل اتمی، با حالت دسترسی 0600؛
  • با اسم مدل و شناسهٔ سشن Claude؛
  • که بعد از ۳۰ ثانیه منقضی می‌شود.

هوک فقط وقتی allow جواب می‌دهد که همهٔ این‌ها برقرار باشد:

  • فایل علامت مال خود شما باشد و symlink نباشد؛
  • منقضی نشده باشد؛
  • برای همان سشن aipager و همان شناسهٔ سشن Claude باشد؛
  • مدلش، بدون توجه به حروف بزرگ و کوچک، با requested_model یا to_model برابر باشد؛
  • ‏source برابر command باشد.

بعد هوک فایل علامت را با یک تغییر اسم اتمی برمی‌دارد، تا دقیقاً یک بار استفاده شود. این بررسی فقط یک فایل محلی را می‌خواند، بدون سوکت و بدون انتظار. این رویداد برای aipager فرستاده نمی‌شود.

‏Claude Code از 2.1.101 به بعد رویدادهای هوکی را که نمی‌شناسد نادیده می‌گیرد، پس نسخه‌های قدیمی‌تری که PreModelSwitch ندارند مشکلی پیدا نمی‌کنند. یک حالت هنوز می‌تواند سؤال را نشان دهد: تعویض به opusplan یا از آن ممکن است باعث شود Claude Code دو بار از هوک بپرسد، و فقط پرسش اول فایل علامت را پیدا می‌کند، پس Claude Code در ترمینال «Switch model?» را می‌پرسد.

خط وضعیت#

رویداد هوک نیست: Claude Code هر بار که خط وضعیتش را از نو می‌کشد، دستور statusLine، یعنی aipager-statusline (aipager.dtach.statusline_notify:main) را اجرا می‌کند. این دستور:

  • ‏JSON وضعیت Claude Code را در ‎/tmp/claude-status-<session>.json می‌نویسد (که هوک برای sl_tokens،‏ aipager status و خود aipager می‌خوانند)؛
  • یک datagram statusline با مدل، درصد کانتکست، هزینه، تعداد توکن‌ها و خط‌های اضافه و حذف‌شده برای aipager می‌فرستد که به کارت در حال کار، ‎/status،‏ Mini App، بررسی انجام شدن تعویض مدل و هشدار کانتکست می‌رسد؛
  • خط وضعیت ترمینال را چاپ می‌کند: ‎[name] Opus 5.5 | 42% ctx | $1.23.

datagramهای خود aipager#

جدا از رویدادهای هوک، این ابزارهای کمکی چند پیام مخصوص خودشان را روی همان سوکت برای aipager می‌فرستند:

پیامفرستندهکی
queue_pickupهوکدر UserPromptSubmit:‏ Claude کدام پیام‌های تلگرام را برداشت.
safety_blockedهوکدر PreToolUse، وقتی بررسی ایمنی اجرا را رد کرد.
send_files_refusedهوکدر PreToolUse، وقتی فایل‌هایی که Claude می‌خواست بفرستد رد شدند: هر فایل و دلیلش، برای لاگ ممیزی.
permission_reply_timeoutهوکانتظار یک PermissionRequest بدون زدن دکمه تمام شد.
statuslineخط وضعیتدر هر بار کشیدن دوبارهٔ خط وضعیت.
hook_memory_cap_hitهوک، خط وضعیتپروسه به سقف حافظه‌اش رسید.
report_errorهوک، دستورهای aipagerبه خطایی خورد: فقط نوع خطا و جایش در کد aipager، هیچ‌وقت محتوای رویداد، برای گزارش مشکل.

مسیر یک درخواست اجازه#

text
claude              aipager-hook             aipager daemon          Telegram
  |                      |                         |                     |
  | PermissionRequest    |                         |                     |
  |--------------------->| opens a reply socket    |                     |
  |                      |------------------------>| busy card becomes   |
  |                      |                         | the prompt          |
  |                      |                         |-------------------->|
  |                      |                         |   you tap Allow     |
  |                      |                         |<--------------------|
  |                      |   allow (reply socket)  | audit log           |
  |                      |<------------------------|                     |
  |  decision: allow     |                         |                     |
  |<---------------------|                         |                     |
  | tool runs            |                         |                     |

اگر تا ۲۰ ثانیه دکمه‌ای زده نشود، هوک چیزی چاپ نمی‌کند و Claude Code پنجرهٔ سؤال خودش را نشان می‌دهد؛ دکمه‌ای که بعداً زده شود جواب را از طریق ترمینال سشن (dtach -p) در همان پنجره تایپ می‌کند. در هر دو حالت، دکمه در لاگ ممیزی ثبت می‌شود و زیر کارت نوشته می‌شود (✅ jim · اجازه داده شد · @alice · Bash: ls -la /tmp).

این‌ها را هم ببینید#