@dreb/coding-agent 2.34.3 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +1 -1
  3. package/dist/cli/args.d.ts +3 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +3 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +30 -2
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +172 -130
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/export-html/template.js +1 -1
  12. package/dist/core/footer-data-provider.d.ts.map +1 -1
  13. package/dist/core/footer-data-provider.js +5 -102
  14. package/dist/core/footer-data-provider.js.map +1 -1
  15. package/dist/core/git-branch.d.ts +19 -0
  16. package/dist/core/git-branch.d.ts.map +1 -0
  17. package/dist/core/git-branch.js +109 -0
  18. package/dist/core/git-branch.js.map +1 -0
  19. package/dist/core/sdk.d.ts.map +1 -1
  20. package/dist/core/sdk.js +1 -1
  21. package/dist/core/sdk.js.map +1 -1
  22. package/dist/core/session-manager.d.ts +21 -0
  23. package/dist/core/session-manager.d.ts.map +1 -1
  24. package/dist/core/session-manager.js +117 -42
  25. package/dist/core/session-manager.js.map +1 -1
  26. package/dist/core/settings-manager.d.ts +6 -0
  27. package/dist/core/settings-manager.d.ts.map +1 -1
  28. package/dist/core/settings-manager.js +8 -0
  29. package/dist/core/settings-manager.js.map +1 -1
  30. package/dist/{modes/interactive → core}/tab-title.d.ts +12 -3
  31. package/dist/core/tab-title.d.ts.map +1 -0
  32. package/dist/{modes/interactive → core}/tab-title.js +58 -16
  33. package/dist/core/tab-title.js.map +1 -0
  34. package/dist/core/tools/search.d.ts.map +1 -1
  35. package/dist/core/tools/search.js +4 -1
  36. package/dist/core/tools/search.js.map +1 -1
  37. package/dist/core/tools/subagent.d.ts +63 -2
  38. package/dist/core/tools/subagent.d.ts.map +1 -1
  39. package/dist/core/tools/subagent.js +459 -73
  40. package/dist/core/tools/subagent.js.map +1 -1
  41. package/dist/core/tools/web.d.ts +1 -1
  42. package/dist/core/tools/web.d.ts.map +1 -1
  43. package/dist/core/tools/web.js +60 -16
  44. package/dist/core/tools/web.js.map +1 -1
  45. package/dist/main.d.ts +10 -0
  46. package/dist/main.d.ts.map +1 -1
  47. package/dist/main.js +54 -4
  48. package/dist/main.js.map +1 -1
  49. package/dist/modes/index.d.ts +2 -2
  50. package/dist/modes/index.d.ts.map +1 -1
  51. package/dist/modes/index.js +1 -1
  52. package/dist/modes/index.js.map +1 -1
  53. package/dist/modes/interactive/components/session-selector.d.ts +1 -1
  54. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  55. package/dist/modes/interactive/components/session-selector.js +4 -40
  56. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  57. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  58. package/dist/modes/interactive/interactive-mode.js +2 -2
  59. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  60. package/dist/modes/rpc/index.d.ts +2 -2
  61. package/dist/modes/rpc/index.d.ts.map +1 -1
  62. package/dist/modes/rpc/index.js.map +1 -1
  63. package/dist/modes/rpc/rpc-client.d.ts +114 -6
  64. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  65. package/dist/modes/rpc/rpc-client.js +140 -2
  66. package/dist/modes/rpc/rpc-client.js.map +1 -1
  67. package/dist/modes/rpc/rpc-mode.d.ts +97 -0
  68. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  69. package/dist/modes/rpc/rpc-mode.js +656 -27
  70. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  71. package/dist/modes/rpc/rpc-types.d.ts +312 -1
  72. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  73. package/dist/modes/rpc/rpc-types.js.map +1 -1
  74. package/docs/dashboard.md +152 -0
  75. package/docs/development.md +8 -5
  76. package/docs/rpc.md +652 -5
  77. package/examples/sdk/12-full-control.ts +1 -1
  78. package/package.json +1 -1
  79. package/dist/modes/interactive/tab-title.d.ts.map +0 -1
  80. package/dist/modes/interactive/tab-title.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"rpc-types.js","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG","sourcesContent":["/**\n * RPC protocol types for headless operation.\n *\n * Commands are sent as JSON lines on stdin.\n * Responses and events are emitted as JSON lines on stdout.\n */\n\nimport type { AgentMessage, ThinkingLevel } from \"@dreb/agent-core\";\nimport type { ImageContent, Model } from \"@dreb/ai\";\nimport type { SessionStats } from \"../../core/agent-session.js\";\nimport type { BashResult } from \"../../core/bash-executor.js\";\nimport type { CompactionResult } from \"../../core/compaction/index.js\";\nimport type { SourceInfo } from \"../../core/source-info.js\";\n\n// ============================================================================\n// RPC Commands (stdin)\n// ============================================================================\n\nexport type RpcCommand =\n\t// Prompting\n\t| { id?: string; type: \"prompt\"; message: string; images?: ImageContent[]; streamingBehavior?: \"steer\" | \"followUp\" }\n\t| { id?: string; type: \"steer\"; message: string; images?: ImageContent[] }\n\t| { id?: string; type: \"follow_up\"; message: string; images?: ImageContent[] }\n\t| { id?: string; type: \"abort\" }\n\t| { id?: string; type: \"new_session\"; parentSession?: string }\n\n\t// State\n\t| { id?: string; type: \"get_state\" }\n\n\t// Model\n\t| { id?: string; type: \"set_model\"; provider: string; modelId: string }\n\t| { id?: string; type: \"resolve_model\"; pattern: string }\n\t| { id?: string; type: \"cycle_model\" }\n\t| { id?: string; type: \"get_available_models\" }\n\n\t// Buddy — hatch/reroll run inside the agent process so API keys never leave\n\t| { id?: string; type: \"buddy_hatch\" }\n\t| { id?: string; type: \"buddy_reroll\" }\n\n\t// Thinking\n\t| { id?: string; type: \"set_thinking_level\"; level: ThinkingLevel }\n\t| { id?: string; type: \"cycle_thinking_level\" }\n\n\t// Queue modes\n\t| { id?: string; type: \"set_steering_mode\"; mode: \"all\" | \"one-at-a-time\" }\n\t| { id?: string; type: \"set_follow_up_mode\"; mode: \"all\" | \"one-at-a-time\" }\n\n\t// Compaction\n\t| { id?: string; type: \"compact\"; customInstructions?: string }\n\t| { id?: string; type: \"set_auto_compaction\"; enabled: boolean }\n\n\t// Retry\n\t| { id?: string; type: \"set_auto_retry\"; enabled: boolean }\n\t| { id?: string; type: \"abort_retry\" }\n\n\t// Bash\n\t| { id?: string; type: \"bash\"; command: string }\n\t| { id?: string; type: \"abort_bash\" }\n\n\t// Session\n\t| { id?: string; type: \"get_session_stats\" }\n\t| { id?: string; type: \"get_performance_stats\" }\n\t| { id?: string; type: \"export_html\"; outputPath?: string }\n\t| { id?: string; type: \"switch_session\"; sessionPath: string }\n\t| { id?: string; type: \"fork\"; entryId: string }\n\t| { id?: string; type: \"get_fork_messages\" }\n\t| { id?: string; type: \"get_last_assistant_text\" }\n\t| { id?: string; type: \"set_session_name\"; name: string }\n\n\t// Messages\n\t| { id?: string; type: \"get_messages\" }\n\n\t// Commands (available for invocation via prompt)\n\t| { id?: string; type: \"get_commands\" }\n\n\t// Session listing\n\t| { id?: string; type: \"list_sessions\" }\n\n\t// Version\n\t| { id?: string; type: \"get_version\" };\n\n// ============================================================================\n// RPC Slash Command (for get_commands response)\n// ============================================================================\n\n/** A command available for invocation via prompt */\nexport interface RpcSlashCommand {\n\t/** Command name (without leading slash) */\n\tname: string;\n\t/** Human-readable description */\n\tdescription?: string;\n\t/** What kind of command this is */\n\tsource: \"extension\" | \"prompt\" | \"skill\";\n\t/** Source metadata for the owning resource */\n\tsourceInfo: SourceInfo;\n}\n\n// ============================================================================\n// RPC State\n// ============================================================================\n\nexport interface RpcSessionState {\n\tmodel?: Model<any>;\n\tthinkingLevel: ThinkingLevel;\n\tisStreaming: boolean;\n\tisCompacting: boolean;\n\tsteeringMode: \"all\" | \"one-at-a-time\";\n\tfollowUpMode: \"all\" | \"one-at-a-time\";\n\tsessionFile?: string;\n\tsessionId: string;\n\tsessionName?: string;\n\tautoCompactionEnabled: boolean;\n\tmessageCount: number;\n\tpendingMessageCount: number;\n\t/** Non-empty when the model was changed from the user's saved preference\n\t * (e.g. saved model unavailable after restart). */\n\tmodelFallbackMessage?: string;\n}\n\n// ============================================================================\n// RPC Responses (stdout)\n// ============================================================================\n\n// Success responses with data\nexport type RpcResponse =\n\t// Prompting (async - events follow)\n\t| { id?: string; type: \"response\"; command: \"prompt\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"steer\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"follow_up\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"abort\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"new_session\"; success: true; data: { cancelled: boolean } }\n\n\t// State\n\t| { id?: string; type: \"response\"; command: \"get_state\"; success: true; data: RpcSessionState }\n\n\t// Model\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"set_model\";\n\t\t\tsuccess: true;\n\t\t\tdata: Model<any>;\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"resolve_model\";\n\t\t\tsuccess: true;\n\t\t\tdata: { model: Model<any>; warning?: string } | null;\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"cycle_model\";\n\t\t\tsuccess: true;\n\t\t\tdata: { model: Model<any>; thinkingLevel: ThinkingLevel; isScoped: boolean } | null;\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_available_models\";\n\t\t\tsuccess: true;\n\t\t\tdata: { models: Model<any>[] };\n\t }\n\n\t// Buddy\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"buddy_hatch\";\n\t\t\tsuccess: true;\n\t\t\tdata: { state: import(\"../../core/buddy/buddy-types.js\").BuddyState };\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"buddy_reroll\";\n\t\t\tsuccess: true;\n\t\t\tdata: { state: import(\"../../core/buddy/buddy-types.js\").BuddyState };\n\t }\n\n\t// Thinking\n\t| { id?: string; type: \"response\"; command: \"set_thinking_level\"; success: true }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"cycle_thinking_level\";\n\t\t\tsuccess: true;\n\t\t\tdata: { level: ThinkingLevel } | null;\n\t }\n\n\t// Queue modes\n\t| { id?: string; type: \"response\"; command: \"set_steering_mode\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"set_follow_up_mode\"; success: true }\n\n\t// Compaction\n\t| { id?: string; type: \"response\"; command: \"compact\"; success: true; data: CompactionResult }\n\t| { id?: string; type: \"response\"; command: \"set_auto_compaction\"; success: true }\n\n\t// Retry\n\t| { id?: string; type: \"response\"; command: \"set_auto_retry\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"abort_retry\"; success: true }\n\n\t// Bash\n\t| { id?: string; type: \"response\"; command: \"bash\"; success: true; data: BashResult }\n\t| { id?: string; type: \"response\"; command: \"abort_bash\"; success: true }\n\n\t// Session\n\t| { id?: string; type: \"response\"; command: \"get_session_stats\"; success: true; data: SessionStats }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_performance_stats\";\n\t\t\tsuccess: true;\n\t\t\tdata: { models: Array<{ provider: string; modelId: string; median: number; mean: number; count: number }> };\n\t }\n\t| { id?: string; type: \"response\"; command: \"export_html\"; success: true; data: { path: string } }\n\t| { id?: string; type: \"response\"; command: \"switch_session\"; success: true; data: { cancelled: boolean } }\n\t| { id?: string; type: \"response\"; command: \"fork\"; success: true; data: { text: string; cancelled: boolean } }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_fork_messages\";\n\t\t\tsuccess: true;\n\t\t\tdata: { messages: Array<{ entryId: string; text: string }> };\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_last_assistant_text\";\n\t\t\tsuccess: true;\n\t\t\tdata: { text: string | null };\n\t }\n\t| { id?: string; type: \"response\"; command: \"set_session_name\"; success: true }\n\n\t// Messages\n\t| { id?: string; type: \"response\"; command: \"get_messages\"; success: true; data: { messages: AgentMessage[] } }\n\n\t// Commands\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_commands\";\n\t\t\tsuccess: true;\n\t\t\tdata: { commands: RpcSlashCommand[] };\n\t }\n\n\t// Session listing\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"list_sessions\";\n\t\t\tsuccess: true;\n\t\t\tdata: { sessions: RpcSessionInfo[] };\n\t }\n\n\t// Version\n\t| { id?: string; type: \"response\"; command: \"get_version\"; success: true; data: { version: string } }\n\n\t// Error response (any command can fail)\n\t| { id?: string; type: \"response\"; command: string; success: false; error: string };\n\n// ============================================================================\n// Session Info (for list_sessions response)\n// ============================================================================\n\n/** Session metadata returned by list_sessions */\nexport interface RpcSessionInfo {\n\t/** Full path to the session JSONL file */\n\tpath: string;\n\t/** Session UUID */\n\tid: string;\n\t/** Working directory where the session was started */\n\tcwd: string;\n\t/** User-defined display name */\n\tname?: string;\n\t/** ISO timestamp of session creation */\n\tcreated: string;\n\t/** ISO timestamp of last modification */\n\tmodified: string;\n\t/** Number of messages in the session */\n\tmessageCount: number;\n\t/** First user message text */\n\tfirstMessage: string;\n}\n\n// ============================================================================\n// Extension UI Events (stdout)\n// ============================================================================\n\n/** Emitted when an extension needs user input */\nexport type RpcExtensionUIRequest =\n\t| { type: \"extension_ui_request\"; id: string; method: \"select\"; title: string; options: string[]; timeout?: number }\n\t| { type: \"extension_ui_request\"; id: string; method: \"confirm\"; title: string; message: string; timeout?: number }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"input\";\n\t\t\ttitle: string;\n\t\t\tplaceholder?: string;\n\t\t\ttimeout?: number;\n\t }\n\t| { type: \"extension_ui_request\"; id: string; method: \"editor\"; title: string; prefill?: string }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"notify\";\n\t\t\tmessage: string;\n\t\t\tnotifyType?: \"info\" | \"warning\" | \"error\";\n\t }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"setStatus\";\n\t\t\tstatusKey: string;\n\t\t\tstatusText: string | undefined;\n\t }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"setWidget\";\n\t\t\twidgetKey: string;\n\t\t\twidgetLines: string[] | undefined;\n\t\t\twidgetPlacement?: \"aboveEditor\" | \"belowEditor\";\n\t }\n\t| { type: \"extension_ui_request\"; id: string; method: \"setTitle\"; title: string }\n\t| { type: \"extension_ui_request\"; id: string; method: \"set_editor_text\"; text: string };\n\n// ============================================================================\n// Extension UI Commands (stdin)\n// ============================================================================\n\n/** Response to an extension UI request */\nexport type RpcExtensionUIResponse =\n\t| { type: \"extension_ui_response\"; id: string; value: string }\n\t| { type: \"extension_ui_response\"; id: string; confirmed: boolean }\n\t| { type: \"extension_ui_response\"; id: string; cancelled: true };\n\n// ============================================================================\n// Helper type for extracting command types\n// ============================================================================\n\nexport type RpcCommandType = RpcCommand[\"type\"];\n"]}
1
+ {"version":3,"file":"rpc-types.js","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG","sourcesContent":["/**\n * RPC protocol types for headless operation.\n *\n * Commands are sent as JSON lines on stdin.\n * Responses and events are emitted as JSON lines on stdout.\n */\n\nimport type { AgentMessage, ThinkingLevel } from \"@dreb/agent-core\";\nimport type { ImageContent, Model, Transport } from \"@dreb/ai\";\nimport type { SessionStats } from \"../../core/agent-session.js\";\nimport type { BashResult } from \"../../core/bash-executor.js\";\nimport type { CompactionResult } from \"../../core/compaction/index.js\";\nimport type { ContextUsage } from \"../../core/extensions/types.js\";\nimport type { SessionEntry } from \"../../core/session-manager.js\";\nimport type { SourceInfo } from \"../../core/source-info.js\";\n\n// ============================================================================\n// RPC Commands (stdin)\n// ============================================================================\n\nexport type RpcCommand =\n\t// Prompting\n\t| { id?: string; type: \"prompt\"; message: string; images?: ImageContent[]; streamingBehavior?: \"steer\" | \"followUp\" }\n\t| { id?: string; type: \"steer\"; message: string; images?: ImageContent[] }\n\t| { id?: string; type: \"follow_up\"; message: string; images?: ImageContent[] }\n\t| { id?: string; type: \"abort\" }\n\t| { id?: string; type: \"new_session\"; parentSession?: string }\n\n\t// State\n\t| { id?: string; type: \"get_state\" }\n\t| { id?: string; type: \"get_resources\" }\n\t| { id?: string; type: \"get_git_branch\" }\n\t| { id?: string; type: \"get_daily_cost\" }\n\n\t// Model\n\t| { id?: string; type: \"set_model\"; provider: string; modelId: string }\n\t| { id?: string; type: \"resolve_model\"; pattern: string }\n\t| { id?: string; type: \"cycle_model\" }\n\t| { id?: string; type: \"get_available_models\" }\n\n\t// Buddy — hatch/reroll run inside the agent process so API keys never leave\n\t| { id?: string; type: \"buddy_hatch\" }\n\t| { id?: string; type: \"buddy_reroll\" }\n\n\t// Thinking\n\t| { id?: string; type: \"set_thinking_level\"; level: ThinkingLevel }\n\t| { id?: string; type: \"cycle_thinking_level\" }\n\n\t// Queue modes\n\t| { id?: string; type: \"set_steering_mode\"; mode: \"all\" | \"one-at-a-time\" }\n\t| { id?: string; type: \"set_follow_up_mode\"; mode: \"all\" | \"one-at-a-time\" }\n\t| { id?: string; type: \"get_pending_messages\" }\n\t| { id?: string; type: \"clear_pending_messages\" }\n\n\t// Compaction\n\t| { id?: string; type: \"compact\"; customInstructions?: string }\n\t| { id?: string; type: \"set_auto_compaction\"; enabled: boolean }\n\t| { id?: string; type: \"abort_compaction\" }\n\n\t// Retry\n\t| { id?: string; type: \"set_auto_retry\"; enabled: boolean }\n\t| { id?: string; type: \"abort_retry\" }\n\n\t// Bash\n\t| { id?: string; type: \"bash\"; command: string }\n\t| { id?: string; type: \"abort_bash\" }\n\n\t// Session\n\t| { id?: string; type: \"get_session_stats\" }\n\t| { id?: string; type: \"get_performance_stats\" }\n\t| { id?: string; type: \"export_html\"; outputPath?: string }\n\t| { id?: string; type: \"switch_session\"; sessionPath: string }\n\t| { id?: string; type: \"delete_session\"; sessionPath: string }\n\t| { id?: string; type: \"fork\"; entryId: string }\n\t| { id?: string; type: \"get_fork_messages\" }\n\t| { id?: string; type: \"get_tree\" }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"navigate_tree\";\n\t\t\ttargetId: string;\n\t\t\tsummarize?: boolean;\n\t\t\tcustomInstructions?: string;\n\t\t\treplaceInstructions?: boolean;\n\t\t\tlabel?: string;\n\t }\n\t| { id?: string; type: \"get_last_assistant_text\" }\n\t| { id?: string; type: \"set_session_name\"; name: string }\n\n\t// Messages\n\t| { id?: string; type: \"get_messages\" }\n\n\t// Commands (available for invocation via prompt)\n\t| { id?: string; type: \"get_commands\" }\n\n\t// Session listing\n\t| { id?: string; type: \"list_sessions\" }\n\t| { id?: string; type: \"list_all_sessions\" }\n\n\t// Background agents\n\t| { id?: string; type: \"list_background_agents\" }\n\t| { id?: string; type: \"list_agent_types\" }\n\n\t// Settings (persistent defaults)\n\t| { id?: string; type: \"get_settings\" }\n\t| { id?: string; type: \"set_settings\"; settings: RpcSettingsUpdate }\n\n\t// Version\n\t| { id?: string; type: \"get_version\" };\n\n// ============================================================================\n// RPC Slash Command (for get_commands response)\n// ============================================================================\n\n/** A command available for invocation via prompt */\nexport interface RpcSlashCommand {\n\t/** Command name (without leading slash) */\n\tname: string;\n\t/** Human-readable description */\n\tdescription?: string;\n\t/** What kind of command this is */\n\tsource: \"extension\" | \"prompt\" | \"skill\";\n\t/** Source metadata for the owning resource */\n\tsourceInfo: SourceInfo;\n}\n\nexport interface RpcScopedModel {\n\tprovider: string;\n\tid: string;\n\tname?: string;\n\treasoning?: boolean;\n\tthinkingLevel?: string;\n}\n\nexport interface RpcResources {\n\tcontextFiles: Array<{ path: string }>;\n\tskills: Array<{ name: string; description: string }>;\n\textensions: Array<{ name?: string; path: string }>;\n\tpromptTemplates: Array<{ name: string; description?: string }>;\n\tsystemPromptPresent: boolean;\n}\n\nexport interface RpcQueuedMessage {\n\ttext: string;\n\timages?: ImageContent[];\n}\n\nexport interface RpcPendingMessages {\n\t/** Text-only compatibility view for existing clients. */\n\tsteering: string[];\n\t/** Text-only compatibility view for existing clients. */\n\tfollowUp: string[];\n\t/** Full queued payloads, including inline image attachments. */\n\tsteeringMessages?: RpcQueuedMessage[];\n\t/** Full queued payloads, including inline image attachments. */\n\tfollowUpMessages?: RpcQueuedMessage[];\n}\n\n// ============================================================================\n// RPC State\n// ============================================================================\n\nexport interface RpcSessionState {\n\tmodel?: Model<any>;\n\tscopedModels: RpcScopedModel[];\n\tusingSubscription: boolean;\n\tthinkingLevel: ThinkingLevel;\n\tisStreaming: boolean;\n\tisCompacting: boolean;\n\tsteeringMode: \"all\" | \"one-at-a-time\";\n\tfollowUpMode: \"all\" | \"one-at-a-time\";\n\tsessionFile?: string;\n\tsessionId: string;\n\tsessionName?: string;\n\tautoCompactionEnabled: boolean;\n\tmessageCount: number;\n\tpendingMessageCount: number;\n\t/**\n\t * Context window usage computed by the session — the exact numbers the TUI footer\n\t * renders (AgentSession.getContextUsage()). `tokens`/`percent` are null when usage\n\t * is unknown (e.g. right after compaction, before the next LLM response). Undefined\n\t * when no model is set or the model has no context window.\n\t */\n\tcontextUsage?: ContextUsage;\n\t/** Non-empty when the model was changed from the user's saved preference\n\t * (e.g. saved model unavailable after restart). */\n\tmodelFallbackMessage?: string;\n}\n\n// ============================================================================\n// RPC Responses (stdout)\n// ============================================================================\n\n// Success responses with data\nexport type RpcResponse =\n\t// Prompting (async - events follow)\n\t| { id?: string; type: \"response\"; command: \"prompt\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"steer\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"follow_up\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"abort\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"new_session\"; success: true; data: { cancelled: boolean } }\n\n\t// State\n\t| { id?: string; type: \"response\"; command: \"get_state\"; success: true; data: RpcSessionState }\n\t| { id?: string; type: \"response\"; command: \"get_resources\"; success: true; data: RpcResources }\n\t| { id?: string; type: \"response\"; command: \"get_git_branch\"; success: true; data: { branch: string | null } }\n\t| { id?: string; type: \"response\"; command: \"get_daily_cost\"; success: true; data: { cost: number } }\n\n\t// Model\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"set_model\";\n\t\t\tsuccess: true;\n\t\t\tdata: Model<any>;\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"resolve_model\";\n\t\t\tsuccess: true;\n\t\t\tdata: { model: Model<any>; warning?: string } | null;\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"cycle_model\";\n\t\t\tsuccess: true;\n\t\t\tdata: { model: Model<any>; thinkingLevel: ThinkingLevel; isScoped: boolean } | null;\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_available_models\";\n\t\t\tsuccess: true;\n\t\t\tdata: { models: Model<any>[] };\n\t }\n\n\t// Buddy\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"buddy_hatch\";\n\t\t\tsuccess: true;\n\t\t\tdata: { state: import(\"../../core/buddy/buddy-types.js\").BuddyState };\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"buddy_reroll\";\n\t\t\tsuccess: true;\n\t\t\tdata: { state: import(\"../../core/buddy/buddy-types.js\").BuddyState };\n\t }\n\n\t// Thinking\n\t| { id?: string; type: \"response\"; command: \"set_thinking_level\"; success: true }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"cycle_thinking_level\";\n\t\t\tsuccess: true;\n\t\t\tdata: { level: ThinkingLevel } | null;\n\t }\n\n\t// Queue modes\n\t| { id?: string; type: \"response\"; command: \"set_steering_mode\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"set_follow_up_mode\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"get_pending_messages\"; success: true; data: RpcPendingMessages }\n\t| { id?: string; type: \"response\"; command: \"clear_pending_messages\"; success: true; data: RpcPendingMessages }\n\n\t// Compaction\n\t| { id?: string; type: \"response\"; command: \"compact\"; success: true; data: CompactionResult }\n\t| { id?: string; type: \"response\"; command: \"set_auto_compaction\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"abort_compaction\"; success: true }\n\n\t// Retry\n\t| { id?: string; type: \"response\"; command: \"set_auto_retry\"; success: true }\n\t| { id?: string; type: \"response\"; command: \"abort_retry\"; success: true }\n\n\t// Bash\n\t| { id?: string; type: \"response\"; command: \"bash\"; success: true; data: BashResult }\n\t| { id?: string; type: \"response\"; command: \"abort_bash\"; success: true }\n\n\t// Session\n\t| { id?: string; type: \"response\"; command: \"get_session_stats\"; success: true; data: SessionStats }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_performance_stats\";\n\t\t\tsuccess: true;\n\t\t\tdata: { models: Array<{ provider: string; modelId: string; median: number; mean: number; count: number }> };\n\t }\n\t| { id?: string; type: \"response\"; command: \"export_html\"; success: true; data: { path: string } }\n\t| { id?: string; type: \"response\"; command: \"switch_session\"; success: true; data: { cancelled: boolean } }\n\t| { id?: string; type: \"response\"; command: \"delete_session\"; success: true; data: { method: \"trash\" | \"unlink\" } }\n\t| { id?: string; type: \"response\"; command: \"fork\"; success: true; data: { text: string; cancelled: boolean } }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_fork_messages\";\n\t\t\tsuccess: true;\n\t\t\tdata: { messages: Array<{ entryId: string; text: string }> };\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_tree\";\n\t\t\tsuccess: true;\n\t\t\tdata: { roots: RpcTreeNode[]; leafId: string | null };\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"navigate_tree\";\n\t\t\tsuccess: true;\n\t\t\tdata: { cancelled: boolean; editorText?: string };\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_last_assistant_text\";\n\t\t\tsuccess: true;\n\t\t\tdata: { text: string | null };\n\t }\n\t| { id?: string; type: \"response\"; command: \"set_session_name\"; success: true }\n\n\t// Messages\n\t| { id?: string; type: \"response\"; command: \"get_messages\"; success: true; data: { messages: AgentMessage[] } }\n\n\t// Commands\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"get_commands\";\n\t\t\tsuccess: true;\n\t\t\tdata: { commands: RpcSlashCommand[] };\n\t }\n\n\t// Session listing\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"list_sessions\";\n\t\t\tsuccess: true;\n\t\t\tdata: { sessions: RpcSessionInfo[] };\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"list_all_sessions\";\n\t\t\tsuccess: true;\n\t\t\tdata: { sessions: RpcSessionInfo[] };\n\t }\n\n\t// Background agents\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"list_background_agents\";\n\t\t\tsuccess: true;\n\t\t\tdata: { agents: RpcBackgroundAgentInfo[] };\n\t }\n\t| {\n\t\t\tid?: string;\n\t\t\ttype: \"response\";\n\t\t\tcommand: \"list_agent_types\";\n\t\t\tsuccess: true;\n\t\t\tdata: { agentTypes: RpcAgentTypeInfo[] };\n\t }\n\n\t// Settings\n\t| { id?: string; type: \"response\"; command: \"get_settings\"; success: true; data: RpcSettingsSnapshot }\n\t| { id?: string; type: \"response\"; command: \"set_settings\"; success: true; data: RpcSettingsSetResult }\n\n\t// Version\n\t| { id?: string; type: \"response\"; command: \"get_version\"; success: true; data: { version: string } }\n\n\t// Error response (any command can fail)\n\t| { id?: string; type: \"response\"; command: string; success: false; error: string };\n\n// ============================================================================\n// Session Info (for list_sessions response)\n// ============================================================================\n\n/** Session metadata returned by list_sessions */\nexport interface RpcSessionInfo {\n\t/** Full path to the session JSONL file */\n\tpath: string;\n\t/** Session UUID */\n\tid: string;\n\t/** Working directory where the session was started */\n\tcwd: string;\n\t/** User-defined display name */\n\tname?: string;\n\t/** ISO timestamp of session creation */\n\tcreated: string;\n\t/** ISO timestamp of last modification */\n\tmodified: string;\n\t/** Number of messages in the session */\n\tmessageCount: number;\n\t/** First user message text */\n\tfirstMessage: string;\n}\n\n/** Background agent metadata returned by list_background_agents */\nexport interface RpcBackgroundAgentInfo {\n\t/** Registry ID (hex) used in background_agent_* events */\n\tagentId: string;\n\t/** Agent type name (e.g. \"Explore\") */\n\tagentType: string;\n\t/** Short human-readable task label */\n\ttaskSummary: string;\n\t/** ISO timestamp of launch */\n\tstartedAt: string;\n\t/** Lifecycle status */\n\tstatus: \"running\" | \"completed\" | \"failed\";\n\t/** Directory containing the agent's session JSONL file (known at spawn time) */\n\tsessionDir?: string;\n\t/** Path to the agent's session JSONL file (available after the child exits) */\n\tsessionFile?: string;\n\t/** Working directory the agent runs in */\n\tcwd?: string;\n}\n\n/** Agent type metadata returned by list_agent_types */\nexport interface RpcAgentTypeInfo {\n\t/** Agent type name, e.g. \"Explore\" */\n\tname: string;\n\t/** Human-readable description from the agent frontmatter */\n\tdescription: string;\n}\n\n/** Serializable session tree node returned by get_tree. Stable DTO — no raw entry payloads. */\nexport interface RpcTreeNode {\n\t/** Entry id */\n\tid: string;\n\t/**\n\t * Parent entry id, or null for a root. Orphaned roots (broken parent chains) keep their\n\t * original non-null parentId, which references an entry not present in the tree — prefer\n\t * the nested `children` structure over parentId when reconstructing hierarchy.\n\t */\n\tparentId: string | null;\n\t/** Session entry type */\n\ttype: SessionEntry[\"type\"];\n\t/** Message role, present only when type === \"message\" (user, assistant, toolResult, bashExecution, ...) */\n\trole?: string;\n\t/** Short single-line content preview (whitespace-collapsed, max 200 chars) */\n\tpreview: string;\n\t/** ISO timestamp of the entry */\n\ttimestamp: string;\n\t/** Resolved label, if any */\n\tlabel?: string;\n\t/** Child nodes, oldest first */\n\tchildren: RpcTreeNode[];\n}\n\n// ============================================================================\n// Settings (persistent defaults)\n// ============================================================================\n\n/**\n * Snapshot of persistent default settings returned by `get_settings` and `set_settings`.\n *\n * These are the values persisted via SettingsManager (merged global + project view) that\n * seed fresh runtimes — NOT the live session state. For the current runtime state\n * (active model, effective thinking level, modes in effect), use `get_state`.\n */\nexport interface RpcSettingsSnapshot {\n\t/** Default provider used at startup (absent if never set) */\n\tdefaultProvider?: string;\n\t/** Default model id used at startup (absent if never set) */\n\tdefaultModel?: string;\n\t/** Default thinking level applied at startup (absent if never set) */\n\tdefaultThinkingLevel?: ThinkingLevel;\n\t/** How queued steering messages are delivered */\n\tsteeringMode: \"all\" | \"one-at-a-time\";\n\t/** How queued follow-up messages are delivered */\n\tfollowUpMode: \"all\" | \"one-at-a-time\";\n\t/** Whether automatic context compaction is enabled */\n\tcompactionEnabled: boolean;\n\t/** Whether automatic retry on transient errors is enabled */\n\tretryEnabled: boolean;\n\t/** Whether image inputs are automatically resized before sending to providers */\n\timageAutoResize?: boolean;\n\t/** Whether image inputs are blocked from being sent to providers */\n\tblockImages?: boolean;\n\t/** Whether skills are registered as slash commands */\n\tenableSkillCommands?: boolean;\n\t/** Whether nested AGENTS.md/CLAUDE.md context auto-loads when tools enter subdirectories */\n\tautoLoadNestedContext?: boolean;\n\t/** Preferred model transport */\n\ttransport?: Transport;\n\t/** Whether raw thinking blocks are hidden in rendered transcripts */\n\thideThinkingBlock?: boolean;\n\t/** Per-agent model fallback lists, merged global + project with project entries winning */\n\tagentModels?: Record<string, string[]>;\n}\n\n/** Settings snapshot returned by `set_settings`; warnings are present for loud shadowing notices. */\nexport type RpcSettingsSetResult = RpcSettingsSnapshot & { warnings?: string[] };\n\n/**\n * Partial update payload for `set_settings`. All fields optional, but at least one\n * must be present. `defaultProvider` and `defaultModel` must be supplied together.\n * Writes persistent defaults only — never touches live session state.\n */\nexport interface RpcSettingsUpdate {\n\tdefaultProvider?: string;\n\tdefaultModel?: string;\n\tdefaultThinkingLevel?: ThinkingLevel;\n\tsteeringMode?: \"all\" | \"one-at-a-time\";\n\tfollowUpMode?: \"all\" | \"one-at-a-time\";\n\tcompactionEnabled?: boolean;\n\tretryEnabled?: boolean;\n\timageAutoResize?: boolean;\n\tblockImages?: boolean;\n\tenableSkillCommands?: boolean;\n\tautoLoadNestedContext?: boolean;\n\ttransport?: Transport;\n\thideThinkingBlock?: boolean;\n\tagentModels?: Record<string, string[]>;\n}\n\n// ============================================================================\n// Extension UI Events (stdout)\n// ============================================================================\n\n/** Emitted when an extension needs user input */\nexport type RpcExtensionUIRequest =\n\t| { type: \"extension_ui_request\"; id: string; method: \"select\"; title: string; options: string[]; timeout?: number }\n\t| { type: \"extension_ui_request\"; id: string; method: \"confirm\"; title: string; message: string; timeout?: number }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"input\";\n\t\t\ttitle: string;\n\t\t\tplaceholder?: string;\n\t\t\ttimeout?: number;\n\t }\n\t| { type: \"extension_ui_request\"; id: string; method: \"editor\"; title: string; prefill?: string }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"notify\";\n\t\t\tmessage: string;\n\t\t\tnotifyType?: \"info\" | \"warning\" | \"error\";\n\t }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"setStatus\";\n\t\t\tstatusKey: string;\n\t\t\tstatusText: string | undefined;\n\t }\n\t| {\n\t\t\ttype: \"extension_ui_request\";\n\t\t\tid: string;\n\t\t\tmethod: \"setWidget\";\n\t\t\twidgetKey: string;\n\t\t\twidgetLines: string[] | undefined;\n\t\t\twidgetPlacement?: \"aboveEditor\" | \"belowEditor\";\n\t }\n\t| { type: \"extension_ui_request\"; id: string; method: \"setTitle\"; title: string }\n\t| { type: \"extension_ui_request\"; id: string; method: \"set_editor_text\"; text: string };\n\n// ============================================================================\n// Extension UI Commands (stdin)\n// ============================================================================\n\n/** Response to an extension UI request */\nexport type RpcExtensionUIResponse =\n\t| { type: \"extension_ui_response\"; id: string; value: string }\n\t| { type: \"extension_ui_response\"; id: string; confirmed: boolean }\n\t| { type: \"extension_ui_response\"; id: string; cancelled: true };\n\n// ============================================================================\n// Helper type for extracting command types\n// ============================================================================\n\nexport type RpcCommandType = RpcCommand[\"type\"];\n"]}
@@ -0,0 +1,152 @@
1
+ # Web Dashboard
2
+
3
+ `dreb dashboard` launches a browser UI for dreb: a fleet overview of sessions
4
+ across projects, a full-parity chat view, live background-subagent
5
+ observability, host file browsing, and settings — usable from desktop and
6
+ mobile browsers.
7
+
8
+ The dashboard lives in the `@dreb/dashboard` package. Live agent control goes
9
+ over [RPC mode](rpc.md): the server maintains a pool of `dreb --mode rpc`
10
+ child processes, one per live session. The server uses dreb's public session
11
+ APIs for on-disk inventory/delete and serves its own host file API.
12
+
13
+ ## Launching
14
+
15
+ ```bash
16
+ # via the dreb CLI (requires @dreb/dashboard to be installed)
17
+ dreb dashboard [--port 5343]
18
+
19
+ # or directly
20
+ dreb-dashboard [--port 5343]
21
+ ```
22
+
23
+ If `@dreb/dashboard` is not installed, `dreb dashboard` fails loudly with
24
+ install instructions (`npm install -g @dreb/dashboard`).
25
+
26
+ Open `http://127.0.0.1:5343` on the same machine.
27
+
28
+ ## Local vs remote — exactly two modes
29
+
30
+ **Local-only (default).** The server binds `127.0.0.1` exclusively. Machines
31
+ on your LAN cannot reach it — packets never arrive at the process. No login or
32
+ pairing. Works without Tailscale installed. This is the right mode for
33
+ same-machine use, including work environments.
34
+
35
+ Requests are additionally validated for loopback `Host`/`Origin` headers, so a
36
+ malicious website cannot drive the dashboard API through DNS rebinding.
37
+
38
+ **Remote (opt-in).** For any access from another device — phone, laptop, even
39
+ on the same LAN — the path is [Tailscale](https://tailscale.com):
40
+
41
+ ```bash
42
+ dreb dashboard --remote --allow you@example.com --allow teammate@example.com
43
+ # or: dreb-dashboard --remote --allow you@example.com --allow teammate@example.com
44
+ ```
45
+
46
+ Layers, in order, all fail-closed (any auth-subsystem error denies):
47
+
48
+ 1. **Tailscale reachability** — the peer address must resolve to a tailnet
49
+ identity via `tailscale status --json`. No Tailscale, no access.
50
+ 2. **Identity allowlist** — `--allow` login names. An empty allowlist denies
51
+ everyone. Rejected identities see a denial page naming the identity.
52
+ 3. **Pairing code** — first login from a new device requires the current
53
+ 6-digit rotating code. The code is shown live in settings → devices on the
54
+ host/local dashboard and rotates every 30 seconds (the current code is also
55
+ printed at server start as a headless fallback). It proves the person can
56
+ see the host machine's local dashboard, so a stolen allowlist identity can't
57
+ quietly gain access.
58
+ 4. **Device cookie** — successful pairing sets a signed HttpOnly cookie
59
+ (30-day expiry). Paired devices are listed in settings → devices and can be
60
+ unpaired at any time.
61
+
62
+ **There is no LAN mode.** The server never listens on a LAN-reachable
63
+ interface without Tailscale identity enforcement.
64
+
65
+ ### What a paired device can do
66
+
67
+ Pairing grants the same power as sitting at the terminal: chatting with
68
+ agents, running commands through them, browsing the whole host filesystem
69
+ (anywhere the dreb process can read), and uploading/downloading files. The
70
+ pairing screen states this before the PIN is entered. Every file operation is
71
+ logged server-side.
72
+
73
+ ## Screens
74
+
75
+ | Screen | What it does |
76
+ |---|---|
77
+ | **Fleet** | Home. Live-first: one grid of every live session at the top — status chip (● running / ◆ needs-attention / ○ idle / ✕ error), project path, activity line, live subagent lines, tasks progress, ctx%, model, last activity. Live cards keep a deterministic order by project path, then session start time; needs-attention cards badge the browser tab without jumping around. Below the grid: past sessions grouped by project, three compact rows per group with an "all N on disk" expander, resume and delete. |
78
+ | **Session view** | Full chat drill-in. Markdown streaming transcript (text, thinking blocks with expand preference, agent-result cards, tool cards with bespoke read/write/edit/bash bodies plus full expandable inputs and markdown-rendered results for markdown-contract tools like subagent/skill/web_fetch/suggest_next, compaction/branch summaries, custom messages), per-message copy, tasks panel, subagent strip, status line with elapsed time plus ■ stop and compaction/retry aborts, and an info bar with cwd, branch, session name, token breakdown, cost/(sub)/daily rollup, ctx%, median tok/s, and a stats popover. Composer supports auto-grow, history, `/` autocomplete from `get_commands`, image attach/paste, queued-message chips with restore-all, steer/follow-up modes, and suggest-next. The ⋯ menu covers export HTML, compact, rename, fork-from-message, loaded context, and tool expand/collapse. Session names update live from manual rename or auto-naming. Extension UI requests (select/confirm/input/editor) render as modals; notifications as toasts. |
79
+ | **Subagent view** | Read-only transcript of a background agent: live events via the RPC relay, hydrated from the agent's on-disk session log (`/subagents/:agentId/messages`) so the view survives browser reloads. Shows the task, streaming output, and tool activity. No composer — subagents can't be steered yet; the parent session controls them. |
80
+ | **Files** | Host-wide browser with places shortcuts (home, /tmp, project roots), breadcrumbs to `/`, new-folder, download, drop-zone/picker upload with explicit collision prompts, and "new session here" on any directory. |
81
+ | **Settings** | Persistent defaults (default model, thinking level, steering/follow-up queue modes, auto-compaction, auto-retry) via `get_settings`/`set_settings` — validation errors are shown verbatim. Dashboard-local preferences (always expand thinking, needs-attention notification permission) live in the browser. Shows the current rotating pairing code on the host/local dashboard, plus the paired-devices list with unpair. |
82
+ | **Pairing** | Remote first-login: identity echo, rotating-code entry, and the security copy explaining what pairing grants. |
83
+
84
+ ### Composer modes
85
+
86
+ While the agent is streaming, the send button becomes mode-aware:
87
+
88
+ - **steer** — deliver now: injected into the running turn after the current
89
+ tool call completes.
90
+ - **follow-up** — queued; delivered after the agent finishes the current work.
91
+ - **■ stop** — abort the current turn. Only visible while streaming.
92
+
93
+ When the agent is idle, send is a plain prompt.
94
+
95
+ ## Notifications
96
+
97
+ The settings tab exposes a browser-local permission toggle for needs-attention
98
+ notifications. When permission is granted, a hidden dashboard tab sends a
99
+ browser notification when a session newly needs input; all browsers still get a
100
+ `◆` tab-title badge fallback.
101
+
102
+ ## Subagent observability
103
+
104
+ Background subagents are first-class:
105
+
106
+ - Fleet cards show running/done counts and live agent lines.
107
+ - The session view shows a chip strip — one chip per background agent; click
108
+ to drill into its live transcript.
109
+ - The drill-in view streams the child's events in real time via the
110
+ `background_agent_event` relay (see [RPC events](rpc.md#event-types)) and
111
+ hydrates from the agent's on-disk session log on mount, so transcripts
112
+ survive browser reloads and remain viewable after the agent finishes.
113
+
114
+ ## Responsive behavior
115
+
116
+ Single breakpoint at 700px. On mobile: fleet cards stack, the session view
117
+ prioritizes read-and-steer (model/thinking switchers collapse into ⋯, tasks
118
+ default collapsed), file table shows name + download only. Composer modes,
119
+ abort, and needs-attention affordances are never reduced away — steering a
120
+ running agent from a phone is the primary remote use case.
121
+
122
+ ## Architecture
123
+
124
+ ```
125
+ Browser dashboard (SolidJS + Vite, tokens.css design system)
126
+ ⇄ Express server: fail-closed auth, REST, SSE fanout, file API
127
+ ⇄ RpcClient pool — one `dreb --mode rpc --ui dashboard` child per session
128
+ ⇄ sessions on disk (~/.dreb/agent/sessions), settings, models
129
+ ```
130
+
131
+ - **SSE catch-up**: events carry sequence IDs; reconnects replay from
132
+ `Last-Event-ID` against a bounded buffer, falling back to a full state
133
+ refetch when the gap is too old. Slow clients whose server-side write
134
+ buffer exceeds a bound are disconnected (loudly logged) and recover via
135
+ the same reconnect path. Deleting a runtime publishes `runtime_removed`
136
+ so browsers evict that session's transcript state.
137
+ - **ctx%** comes from the session itself (`get_state.contextUsage` — the same
138
+ numbers the TUI footer shows), never client-side estimates.
139
+ - **Auto-naming** runs in the shared `AgentSession` layer, so dashboard-created
140
+ RPC sessions get the same LLM-generated session names as the TUI and update
141
+ live via `session_name_changed`.
142
+ - **Visual language**: `tokens.css` (`packages/dashboard/src/client/styles/`),
143
+ the dashboard's design system — IBM Plex Mono, light + dark via
144
+ `prefers-color-scheme`.
145
+
146
+ ## Limitations (deliberate, sequenced later)
147
+
148
+ - No session tree screen yet (fork-from-message covers the go-back-and-re-edit
149
+ loop; the tree design and RPC are ready).
150
+ - No shell passthrough from the browser.
151
+ - No subagent steering (the drill-in view is read-only).
152
+ - Fixed light/dark via `prefers-color-scheme` — no TUI-theme following.
@@ -28,13 +28,16 @@ The script can be run from any directory. dreb keeps the caller's current workin
28
28
 
29
29
  ```
30
30
  packages/
31
- ai/ # Model registry, provider APIs, types (@dreb/ai)
32
- agent/ # Core agent loop, event system, types (@dreb/agent-core)
33
- tui/ # Terminal UI components (@dreb/tui)
34
- coding-agent/ # CLI, tools, interactive mode, TUI app (@dreb/coding-agent)
31
+ ai/ # Model registry, provider APIs, types (@dreb/ai)
32
+ agent/ # Core agent loop, event system, types (@dreb/agent-core)
33
+ tui/ # Terminal UI components (@dreb/tui)
34
+ coding-agent/ # CLI, tools, interactive mode, TUI app (@dreb/coding-agent)
35
+ semantic-search/ # Semantic code search library + MCP server (@dreb/semantic-search)
36
+ telegram/ # Telegram bot frontend over RPC (@dreb/telegram)
37
+ dashboard/ # Web dashboard server/client over RPC (@dreb/dashboard)
35
38
  ```
36
39
 
37
- Dependencies flow one way: `coding-agent` `agent` `ai`, and `coding-agent` `tui`. Changes to a dependency require rebuilding downstream packages — `npm run build` handles this automatically in the correct order.
40
+ Dependencies flow one way through the workspace: `coding-agent` depends on `agent`, `ai`, and `tui`; frontend packages such as `telegram`, `dashboard`, and `semantic-search` build on the published workspace APIs instead of creating reverse dependencies. Changes to a dependency require rebuilding downstream packages — `npm run build` handles this automatically in the correct order.
38
41
 
39
42
  ## Code style
40
43