masamune_ai_debugger 3.9.0
masamune_ai_debugger: ^3.9.0 copied to clipboard
AI debug overlay and error reporting adapter for Masamune apps.
Masamune AI Debugger
[GitHub] | [YouTube] | [Packages] | [X] | [LinkedIn] | [mathru.net]
Plug-in packages that add functionality to the Masamune Framework.
For more information about Masamune Framework, please click here.
https://pub.dev/packages/masamune
Usage #
A floating UI will be added to the Debug build of the Masamune app that sends instructions, screenshots, and unhandled errors to SamuraiAI. Neither the UI nor the communication will be active in Release/Profile builds. The public controller's lifecycle, upload, send, incident, and log methods are also no-ops outside Debug builds.
import "package:masamune_ai_debugger/masamune_ai_debugger.dart";
final aiDebugger = AIDebuggerMasamuneAdapter(
manualModel: AIDebugModel.sol,
manualPermissionMode: AIDebugPermissionMode.plan,
errorModel: AIDebugModel.sol,
errorPermissionMode: AIDebugPermissionMode.plan,
performanceModel: AIDebugModel.sol,
performancePermissionMode: AIDebugPermissionMode.plan,
modelLoadTimeout: const Duration(seconds: 5),
indicatorTimeout: const Duration(seconds: 10),
);
void main() {
runMasamuneApp(
(ref) => MasamuneApp(
masamuneAdapters: [aiDebugger],
home: const MyHomePage(),
),
masamuneAdapters: [aiDebugger],
);
}
Create one adapter instance and pass that same instance to both MasamuneApp and runMasamuneApp. projectId is read from MASAMUNE_AI_DEBUGGER_PROJECT_ID by default. You can still pass it directly to the constructor when using a custom integration.
Do not embed the connection target and API key in the source code; specify them during debug startup.
flutter run \
--dart-define=MASAMUNE_AI_DEBUGGER_PROJECT_ID=Users-mathru-Documents-github-myapp \
--dart-define=MASAMUNE_AI_DEBUGGER_ENDPOINT=https://your-tailnet-host/__samurai \
--dart-define=MASAMUNE_AI_DEBUGGER_API_KEY=your-key \
--dart-define=MASAMUNE_AI_DEBUGGER_ACCOUNTS=admin@example.com:pass1,user@example.com:pass2
MASAMUNE_AI_DEBUGGER_ACCOUNTS is optional. When set, the debug login panel
replaces the email/password text fields with a list of selectable IDs so an AI
operator never has to type credentials. Passwords are resolved internally and
never shown in the UI. The format is id1:pass1,id2:pass2 and each entry is
split on the first colon so passwords may contain colons. When the value is
empty or unset, the panel keeps the traditional email/password form.
MASAMUNE_AI_DEBUGGER_ACCOUNTS は省略可能です。値を設定すると、デバッグ
ログインパネルはメール・パスワード入力欄の代わりに選択可能なID一覧を表示し、
AIオペレーターが機密情報を入力する必要をなくします。パスワードは内部で
解決されUIには一切表示されません。形式はid1:pass1,id2:pass2で、各エントリは
最初のコロンで分割されるためパスワードにコロンを含めることが可能です。
未設定または空の場合は従来のメール・パスワード入力式パネルになります。
APIキーは SamuraiAI の Settings で作成します。Debug APK/IPAにも値は含まれるため、配布せず、不要になったキーは無効化してください。スクリーンショットは手動操作時、未処理エラー検出時、性能閾値超過時だけ送信されます。
projectId / endpoint / apiKey をコンストラクタで省略すると、上記の dart-define が読み込まれます。既定の SamuraiAI コールバックを利用する場合は3つの値が必要です。明示的なコンストラクタ引数は dart-define より優先されます。
フローティングアイコンはタップするとそのまま開き、長押しすると現在画面のスクリーンショットを撮影してから開きます。
Authentication observations for shared automation #
authSnapshot is an optional read-only callback. authListenable notifies the
overlay when authentication or startup changes, even while the panel is closed.
The application owns and disposes the listenable. Existing login, logout,
anonymousLogin, and isLoggedIn callbacks remain compatible.
final aiDebugger = AIDebuggerMasamuneAdapter(
authListenable: authDiagnostics,
authSnapshot: () => AIDebugAuthSnapshot(
state: authDiagnostics.state,
restoreState: authDiagnostics.restoreState,
bootState: authDiagnostics.bootState,
identity: authDiagnostics.opaqueUserIdentity,
loginCount: authDiagnostics.signInAttemptCount,
),
// Keep the existing login/logout/isLoggedIn callbacks here.
);
Here authDiagnostics is application-owned instrumentation around the real
authentication and startup operations. Set restoration to completed only after
the actual restore future succeeds; do not infer it from a visible screen or a
signed-in flag. identity must be a stable non-secret opaque token derived from
the actual authenticated user ID, never the selected debug account, email,
password, or token. Count all explicit sign-in attempts since process startup
in loginCount, including automatic sign-in and failed attempts, excluding the
restore operation itself. Leave loginCount null when that guarantee is absent.
In debug builds only, independent semantics identifiers expose single labels:
Identifier suffix after ai_debugger_auth_ |
Label |
|---|---|
schema_version |
1 |
state |
unknown, initializing, authenticated, unauthenticated, error |
identity |
Real opaque user identity, none when unauthenticated, or unsupported |
restore_state |
unsupported, pending, running, completed, failed |
boot_state |
initializing, ready, error |
boot_id |
SDK-generated ID stable within a Dart isolate |
login_count |
Application-wide sign-in attempt count, or unsupported |
These observations remain accessible with the panel closed. Missing callbacks
produce unknown/unsupported, not successful restoration. Hot restart also
changes boot_id, so automation must record an actual process stop/start rather
than use this ID alone as proof of a cold restart. Verify persistence by comparing
the real identity before/after that restart, requiring a new boot ID, completed
restoration, ready startup, and a zero post-restart login count. Do not log in or
clear application data during the verification interval.
For credential-free account selection, configure AIDebugAccount with an optional
unique alias matching [A-Za-z0-9][A-Za-z0-9_-]{0,63}. It replaces the displayed
email/ID and exposes ai_debugger_auth_account_<alias>; the original ID and password
still go only to the login callback. Accounts without an alias expose stable slot IDs
account_0, account_1, and so on in their current list order. Shared automation can
refer to these slots without copying account IDs or credentials into project config.
Keep the account order stable when using slot IDs. Invalid or duplicate explicit
aliases do not receive automation identifiers.
Automation opens ai_debugger_open, then taps ai_debugger_auth_login to reveal
the account list. When signed in, that button becomes ai_debugger_auth_logout
and signs out directly. ai_debugger_close closes any panel. Observation never
performs login, logout, credential restoration, or account selection.
デバッグ認証・デバッグ課金・デバッグカメラ #
認証用の3コールバックをすべて指定すると、AI指示欄の上にログイン/ログアウトボタンが表示されます。
さらにanonymousLoginを指定すると、認証情報入力フォームに匿名ログインボタンが表示されます。
課金用の4コールバックをすべて指定すると、同じ行に課金管理ボタンが表示されます。これらのUIと
コールバックは他のAIデバッガー機能と同様にDebugビルドでのみ有効です。
AIDebugPurchaseProductはAIデバッガー専用の軽量な商品型です。masamune_purchaseには依存しないため、
アプリ側でIDを実際のPurchaseProductへ変換してください。
const debugProducts = [
AIDebugPurchaseProduct(id: "premium_monthly", label: "プレミアム(月額)"),
AIDebugPurchaseProduct(id: "premium_yearly", label: "プレミアム(年額)"),
];
final purchaseProductsById = <String, PurchaseProduct>{
monthlyProduct.productId: monthlyProduct,
yearlyProduct.productId: yearlyProduct,
};
final aiDebugger = AIDebuggerMasamuneAdapter(
login: (email, password) => appAuth.signIn(
EmailAndPasswordAuthQuery.signIn(
email: email,
password: password,
),
),
anonymousLogin: () => appAuth.signIn(AnonymouslyAuthQuery.signIn()),
logout: appAuth.signOut,
isLoggedIn: () => appAuth.isSignedIn,
purchaseProducts: () => debugProducts,
purchase: (item) => debugPurchase.forcePurchase(
purchaseProductsById[item.id]!,
),
cancelPurchase: (item) => debugPurchase.forceCancel(
purchaseProductsById[item.id]!,
),
isPurchased: (item) => debugPurchase.isPurchased(
purchaseProductsById[item.id]!,
),
);
ログイン/ログアウト/課金/解約後は、それぞれ同期getterのisLoggedIn/isPurchasedが再評価され、
UIへ即座に反映されます。コールバック内で実際のストア購入や解約を行うのではなく、アプリのデバッグ用
状態を切り替える実装を渡してください。
デバッグカメラ画像 #
カメラ用の4コールバックをすべて指定すると、AI Debuggerからカメラのデバッグ画像を
選択・解除できます。画像そのものはAI Debuggerへ渡さず、軽量な
AIDebugCameraPictureのIDをアプリ側でImageProviderへ変換します。
テスト画像はFlutter assetとしてアプリへ同梱しておくと、ネットワークや端末ギャラリーに
依存せず、Maestroから決定的に選択できます。
const debugCameraPictures = [
AIDebugCameraPicture(id: "receipt", label: "レシート"),
AIDebugCameraPicture(id: "identity_card", label: "本人確認書類"),
];
const debugCameraProviders = <String, ImageProvider>{
"receipt": AssetImage("assets/debug/receipt.png"),
"identity_card": AssetImage("assets/debug/identity_card.png"),
};
String? activeDebugCameraPictureId;
final aiDebugger = AIDebuggerMasamuneAdapter(
cameraPictures: () => debugCameraPictures,
setCameraPicture: (picture) {
camera.setDebugPicture(debugCameraProviders[picture.id]!);
activeDebugCameraPictureId = picture.id;
},
unsetCameraPicture: () {
camera.unsetDebugPicture();
activeDebugCameraPictureId = null;
},
isCameraPictureSet: (picture) =>
activeDebugCameraPictureId == picture.id,
);
画像assetはアプリのpubspec.yamlへ登録してください。対象のCameraが作り直された場合は、
activeDebugCameraPictureIdも同時にnullへ戻してください。
MaestroではOSの画像ピッカーを開かず、次のように固定Semanticsラベルと候補名を操作します。
- tapOn: "AI Debuggerを開く"
- tapOn: "AIデバッガーカメラ管理"
- tapOn: "デバッグカメラ 画像選択"
- tapOn: "レシート"
- tapOn: "デバッグカメラ画像設定実行"
# カメラを使うテストを実行
- tapOn: "AI Debuggerを開く"
- tapOn: "AIデバッガーカメラ管理"
- tapOn: "カメラ画像解除 receipt"
masamune_pickerを利用することもできますが、モバイルではOSのメディアピッカーが開き、
OS・バージョンごとの要素指定や権限処理が必要になります。任意画像を手動で試す用途には適しますが、
Maestroの標準E2Eフローには上記のassetプリセット方式を推奨します。
Maestroからは次の固定Semanticsラベルを利用できます。
AI Debuggerを開くAIデバッガー指示入力AIデバッガー認証デバッグログイン メールアドレスデバッグログイン パスワードデバッグログイン実行デバッグ匿名ログイン実行AIデバッガー課金管理デバッグ課金 商品選択デバッグ強制課金実行課金解除 <商品ID>AIデバッガーカメラ管理デバッグカメラ 画像選択デバッグカメラ画像設定実行カメラ画像解除 <画像ID>AI入力へ戻る
MaestroのpressKeyは修飾キー付きショートカットを送信できないため、フォームを直接開くショートカットは
提供していません。固定SemanticsラベルをtapOn/inputTextで操作してください。
メッセージフォーム下部のMode/Modelボタンでは、次に手動送信するセッションの
plan / bypassPermissionsとluna / terra / sol / astraを選択できます。
設定ボタンでは、未処理エラー時と計測超過時のMode/Model、およびモデル読込と
インジケーターの超過判定時間を個別に設定できます。これらの値は端末内へ保存され、
同じproject IDの次回起動時に復元されます。
手動送信と未処理エラー/性能閾値超過の自動送信には、現在のWidget構成が
Debugビルド内で自動的に添付されます。ページ名、ルート、調査に必要な状態値も
渡す場合はcontextProviderを指定してください。providerは送信の直前に呼ばれるため、
その時点の状態を返します。
final aiDebugger = AIDebuggerMasamuneAdapter(
contextProvider: () => AIDebugContextSnapshot(
pageName: "CheckoutPage",
route: "/checkout",
values: {
"cartCount": cartController.items.length,
"isSubmitting": cartController.isSubmitting,
},
),
);
valuesにはAIの調査に必要なJSON互換値だけを明示してください。任意のローカル変数を
自動収集する機能ではありません。token、API key、password、secretなどのキーと、
メールアドレスを含む文字列は送信前に秘匿され、ツリー・値の深さ、件数、文字数にも
上限が適用されます。独自AI providerの既存callbackシグネチャは変わらず、callback内では
controller.currentContextから、その送信に対応するスナップショットを参照できます。
Execution provider selection #
Configure string candidates to add a provider selector to the settings dialog:
final aiDebugger = AIDebuggerMasamuneAdapter(
providers: const ["codex", "claude", "gemini"],
initialProvider: "codex",
);
The selection applies to manual requests, error incidents, and performance
incidents, and is persisted per project on the device. Choose 自動 to let the
backend select the provider. An empty candidate list (the default) hides the
selector and preserves automatic selection. Blank candidates and duplicates are
removed. A non-null initialProvider must match a configured candidate.
If a saved candidate is removed, the selection falls back to initialProvider
or automatic selection. Existing settings without a provider use the initial
selection; an explicitly saved automatic selection remains automatic.
Candidates are strings, so custom integrations can supply their own identifiers.
The backend must support the selected identifier. SamuraiAI must include the
app-debug provider forwarding update for the selection to control execution.
Custom callbacks keep their existing signatures and can read
controller.currentProvider to obtain the selection captured for that request,
including during concurrent requests. A null value means automatic selection.
Custom AI provider #
SamuraiAI is the default provider. To connect another AI or backend, pass callbacks for each API operation. Custom callbacks receive semantic values instead of SamuraiAI-specific URLs or JSON payloads, so endpoint and apiKey are not required when all callbacks are supplied.
final customDebugger = AIDebuggerMasamuneAdapter(
projectId: "my-project",
registerRun: myRegisterRun,
heartbeat: myHeartbeat,
endRun: myEndRun,
uploadScreenshot: myUploadScreenshot,
sendRequest: mySendRequest,
reportIncident: myReportIncident,
uploadEvents: myUploadEvents,
);
The callback typedefs are AIDebugRegisterRunCallback, AIDebugHeartbeatCallback, AIDebugEndRunCallback, AIDebugUploadScreenshotCallback, AIDebugSendRequestCallback, AIDebugReportIncidentCallback, and AIDebugUploadEventsCallback. To receive the selected model and permission mode, use configuredSendRequest (AIDebugConfiguredSendRequestCallback) and configuredReportIncident (AIDebugConfiguredReportIncidentCallback). The legacy callbacks remain available for integrations that do not use session settings. The corresponding AIDebuggerMasamuneAdapter.default* static functions expose the default SamuraiAI implementations. The existing post callback remains available for replacing only the low-level HTTP transport used by those defaults.
upload() returns the provider-specific screenshot identifier, not a URL. AIDebugHttpException represents a non-2xx response returned by the AI debug API; connection and transport failures may use other exception types.
例外と性能閾値超過による自動incidentには、セッションの過剰生成を防ぐセーフティが適用されます。 同じ種別・メッセージのincidentは、同じアプリ起動中には最初の1回だけ送信します。 内容が異なるincidentも1秒以内に連続した場合は最初の1回だけを送信し、残りは破棄します。 手動で送信したAI Debuggerリクエストはこの制限の対象外です。
遅延の自動検出 #
Debugビルドでは、既存の LoggerAdapter のperformance traceを使って次を自動計測します。
- MasamuneのDocument/Collectionモデルの
load・reload・next(既定5秒) Future.showIndicator経由のインジケーター表示(既定10秒)MeasuredCircularProgressIndicator/MeasuredLinearProgressIndicatorの表示時間(既定10秒)
処理が終わらなくても閾値へ達した時点で、現在画面と直近ログをperformance incidentとして送信し、SamuraiAIにPlan Modeの調査セッションを作成します。同一処理はアプリ起動中の重複排除と時間当たり上限の対象です。閾値以下で完了した処理は duration_ms の通常ログだけを送ります。
Widgetツリーへ待機表示を直接配置する場合は、固定かつ機密情報を含まないtraceNameを付けた
MeasuredCircularProgressIndicatorまたはMeasuredLinearProgressIndicatorを使用してください。
常設の進捗率表示は標準ProgressIndicatorを使い、すでに計測済みのshowIndicatorへ計測版を渡して
二重計測してはいけません。閾値に Duration.zero を指定すると、そのカテゴリの自動incidentを無効化できます。
想定内/想定外エラーのcatchと報告 #
自動で設定される runZonedGuarded・FlutterError.onError・PlatformDispatcher.onError の3つは、いずれも
誰にもハンドリングされなかった例外専用のフックです。Dartの仕様上 try-catch でキャッチされた例外は
これらに一切到達しないため、握り潰すとAI Debuggerからは完全に不可視になります。
想定内エラーは型付きの on XxxException catch で個別に処理し、そのtryの最後に想定外エラー用の型指定なしcatchを置きます。型指定なしcatchで処理を継続する場合は Logger.error で明示的に報告してください。アプリテンプレートが生成する appLogger を使えば ref や BuildContext なしでどこからでも呼び出せます。
try {
await something();
} on ValidationException catch (e) {
handleValidationError(e);
} catch (e, stackTrace) {
await appLogger.error(e, stackTrace);
handleUnexpectedError();
}
これによりスクリーンショットとスタックトレース付きの exception incidentが送信されます。
LoggerAdapter が一つも設定されていない場合は何もしないため、想定外エラーを処理する際に常に呼び出しても安全です。
masamune_lints の masamune_expected_error_should_have_unexpected_catch が最終catchの不足を、masamune_caught_error_should_report が想定外エラーの報告漏れを警告します。rethrow や throw で伝播させている場合はグローバルハンドラーが報告するため対象外です。同じエラーをローカル報告してから再送出すると二重報告になるため避けてください。
lintはエラーの業務上の意味を推論しません。想定内エラーは型付きcatchで明示し、型なしcatch内の型判定で振り分けないでください。
握り潰した例外をincident化せずパンくずログ(severity error)だけに留めたい場合は
reportHandledErrors に false を指定します。
AIDebuggerMasamuneAdapter(
reportHandledErrors: false,
);
GitHub Sponsors #
Sponsors are always welcome. Thank you for your support!