@dreb/coding-agent 2.34.3 → 2.36.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.
- package/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/dist/cli/args.d.ts +3 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +3 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +30 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +172 -130
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/export-html/template.js +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +5 -102
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/git-branch.d.ts +19 -0
- package/dist/core/git-branch.d.ts.map +1 -0
- package/dist/core/git-branch.js +109 -0
- package/dist/core/git-branch.js.map +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +21 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +117 -42
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +8 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/{modes/interactive → core}/tab-title.d.ts +12 -3
- package/dist/core/tab-title.d.ts.map +1 -0
- package/dist/{modes/interactive → core}/tab-title.js +58 -16
- package/dist/core/tab-title.js.map +1 -0
- package/dist/core/tools/search.d.ts.map +1 -1
- package/dist/core/tools/search.js +4 -1
- package/dist/core/tools/search.js.map +1 -1
- package/dist/core/tools/subagent.d.ts +63 -2
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +459 -73
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/core/tools/web.d.ts +1 -1
- package/dist/core/tools/web.d.ts.map +1 -1
- package/dist/core/tools/web.js +60 -16
- package/dist/core/tools/web.js.map +1 -1
- package/dist/main.d.ts +10 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +54 -4
- package/dist/main.js.map +1 -1
- package/dist/modes/index.d.ts +2 -2
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +4 -40
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +2 -2
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/index.d.ts +2 -2
- package/dist/modes/rpc/index.d.ts.map +1 -1
- package/dist/modes/rpc/index.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +114 -6
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +140 -2
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +97 -0
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +656 -27
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +312 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/dashboard.md +254 -0
- package/docs/development.md +8 -5
- package/docs/rpc.md +652 -5
- package/examples/sdk/12-full-control.ts +1 -1
- package/package.json +1 -1
- package/dist/modes/interactive/tab-title.d.ts.map +0 -1
- 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,254 @@
|
|
|
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
|
+
# remote over Tailscale with HTTPS (PWA + notifications on mobile)
|
|
23
|
+
dreb dashboard --remote --allow you@example.com \
|
|
24
|
+
--https --cert /path/cert.pem --key /path/key.pem
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If `@dreb/dashboard` is not installed, `dreb dashboard` fails loudly with
|
|
28
|
+
install instructions (`npm install -g @dreb/dashboard`).
|
|
29
|
+
|
|
30
|
+
Open `http://127.0.0.1:5343` on the same machine.
|
|
31
|
+
|
|
32
|
+
## Local vs remote — exactly two modes
|
|
33
|
+
|
|
34
|
+
**Local-only (default).** The server binds `127.0.0.1` exclusively. Machines
|
|
35
|
+
on your LAN cannot reach it — packets never arrive at the process. No login or
|
|
36
|
+
pairing. Works without Tailscale installed. This is the right mode for
|
|
37
|
+
same-machine use, including work environments.
|
|
38
|
+
|
|
39
|
+
Requests are additionally validated for loopback `Host`/`Origin` headers, so a
|
|
40
|
+
malicious website cannot drive the dashboard API through DNS rebinding.
|
|
41
|
+
|
|
42
|
+
**Remote (opt-in).** For any access from another device — phone, laptop, even
|
|
43
|
+
on the same LAN — the path is [Tailscale](https://tailscale.com):
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
dreb dashboard --remote --allow you@example.com --allow teammate@example.com
|
|
47
|
+
# or: dreb-dashboard --remote --allow you@example.com --allow teammate@example.com
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Layers, in order, all fail-closed (any auth-subsystem error denies):
|
|
51
|
+
|
|
52
|
+
1. **Tailscale reachability** — the peer address must resolve to a tailnet
|
|
53
|
+
identity via `tailscale status --json`. No Tailscale, no access.
|
|
54
|
+
2. **Identity allowlist** — `--allow` login names. An empty allowlist denies
|
|
55
|
+
everyone. Rejected identities see a denial page naming the identity.
|
|
56
|
+
3. **Pairing code** — first login from a new device requires the current
|
|
57
|
+
6-digit rotating code. The code is shown live in settings → devices on the
|
|
58
|
+
host/local dashboard and rotates every 30 seconds (the current code is also
|
|
59
|
+
printed at server start as a headless fallback). It proves the person can
|
|
60
|
+
see the host machine's local dashboard, so a stolen allowlist identity can't
|
|
61
|
+
quietly gain access.
|
|
62
|
+
4. **Device cookie** — successful pairing sets a signed HttpOnly cookie
|
|
63
|
+
(30-day expiry). Paired devices are listed in settings → devices and can be
|
|
64
|
+
unpaired at any time.
|
|
65
|
+
|
|
66
|
+
**There is no LAN mode.** The server never listens on a LAN-reachable
|
|
67
|
+
interface without Tailscale identity enforcement.
|
|
68
|
+
|
|
69
|
+
### What a paired device can do
|
|
70
|
+
|
|
71
|
+
Pairing grants the same power as sitting at the terminal: chatting with
|
|
72
|
+
agents, running commands through them, browsing the whole host filesystem
|
|
73
|
+
(anywhere the dreb process can read), and uploading/downloading files. The
|
|
74
|
+
pairing screen states this before the PIN is entered. Every file operation is
|
|
75
|
+
logged server-side.
|
|
76
|
+
|
|
77
|
+
## Screens
|
|
78
|
+
|
|
79
|
+
| Screen | What it does |
|
|
80
|
+
|---|---|
|
|
81
|
+
| **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. |
|
|
82
|
+
| **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. |
|
|
83
|
+
| **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. |
|
|
84
|
+
| **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. |
|
|
85
|
+
| **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. |
|
|
86
|
+
| **Pairing** | Remote first-login: identity echo, rotating-code entry, and the security copy explaining what pairing grants. |
|
|
87
|
+
|
|
88
|
+
### Composer modes
|
|
89
|
+
|
|
90
|
+
While the agent is streaming, the send button becomes mode-aware:
|
|
91
|
+
|
|
92
|
+
- **steer** — deliver now: injected into the running turn after the current
|
|
93
|
+
tool call completes.
|
|
94
|
+
- **follow-up** — queued; delivered after the agent finishes the current work.
|
|
95
|
+
- **■ stop** — abort the current turn. Only visible while streaming.
|
|
96
|
+
|
|
97
|
+
When the agent is idle, send is a plain prompt.
|
|
98
|
+
|
|
99
|
+
## Notifications
|
|
100
|
+
|
|
101
|
+
Needs-attention notifications are delivered through a **service worker**
|
|
102
|
+
(`registration.showNotification()`), not the page-context `Notification`
|
|
103
|
+
constructor — the constructor was removed from Android Chrome (throws
|
|
104
|
+
`Illegal constructor`) and is absent from iOS Safari entirely. The service
|
|
105
|
+
worker handles `notificationclick`: it focuses an open dashboard client and
|
|
106
|
+
navigates to the session that needs attention, or opens one. All browsers still
|
|
107
|
+
get a `◆` tab-title badge fallback when the tab is hidden.
|
|
108
|
+
|
|
109
|
+
The settings tab exposes a browser-local permission toggle. Gating is unchanged:
|
|
110
|
+
notifications fire only when permission is granted **and** the tab is hidden.
|
|
111
|
+
|
|
112
|
+
**iOS:** notifications exist only in the **installed PWA** (Add to Home Screen,
|
|
113
|
+
iOS 16.4+) — a plain Safari tab has no Notification API regardless of HTTPS.
|
|
114
|
+
The settings copy explains the install prerequisite when it detects an
|
|
115
|
+
un-installed iOS Safari session. (Note: iOS 17.4+ in the EU dropped standalone
|
|
116
|
+
PWA support — installed PWAs open as Safari tabs and push is unavailable there.)
|
|
117
|
+
|
|
118
|
+
## Installable PWA + secure context
|
|
119
|
+
|
|
120
|
+
The dashboard ships a web app manifest (`display: standalone`, theme/background
|
|
121
|
+
colors, icon set), an apple-touch-icon, and service worker registration, so it
|
|
122
|
+
is **installable to the home screen** on Android Chrome and iOS Safari 16.4+ —
|
|
123
|
+
no URL bar, app-like presence, and (on iOS) the only context where
|
|
124
|
+
notifications work.
|
|
125
|
+
|
|
126
|
+
Service workers and the Notifications API require a **secure context**: HTTPS,
|
|
127
|
+
or `localhost`/`127.0.0.1`. Local mode (`http://127.0.0.1:<port>`) already
|
|
128
|
+
qualifies — install and notifications work with no TLS setup. **Remote mode
|
|
129
|
+
over the tailnet is plain HTTP**, which is not a secure context, so the service
|
|
130
|
+
worker will not register and notifications are unavailable until you enable
|
|
131
|
+
HTTPS. See [Native TLS](#native-tls-remote-https) below.
|
|
132
|
+
|
|
133
|
+
## Native TLS (remote HTTPS)
|
|
134
|
+
|
|
135
|
+
For PWA install + notifications from a phone over the tailnet, the dashboard
|
|
136
|
+
terminates TLS itself using certificate files from
|
|
137
|
+
[`tailscale cert`](https://tailscale.com/docs/how-to/set-up-https-certificates)
|
|
138
|
+
(no reverse proxy, **no auth-model change**):
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
dreb dashboard --remote --allow you@example.com \
|
|
142
|
+
--https --cert /etc/dreb/cert.pem --key /etc/dreb/key.pem
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Because the dashboard terminates TLS directly, `req.socket.remoteAddress` is
|
|
146
|
+
still the phone's real tailnet IP — Tailscale identity resolution, the
|
|
147
|
+
allowlist, and pairing all keep working exactly as in plain-HTTP remote mode.
|
|
148
|
+
There is no header trust, no proxy, no weakening of the auth model.
|
|
149
|
+
|
|
150
|
+
### One-time cert setup with `tailscale cert`
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Enable HTTPS certificates in the Tailscale admin console (DNS → HTTPS) first.
|
|
154
|
+
sudo tailscale cert \
|
|
155
|
+
--cert-file=/etc/dreb/cert.pem \
|
|
156
|
+
--key-file=/etc/dreb/key.pem \
|
|
157
|
+
hostname.tailXXXX.ts.net
|
|
158
|
+
sudo chown dreb:dreb /etc/dreb/cert.pem /etc/dreb/key.pem
|
|
159
|
+
sudo chmod 644 /etc/dreb/cert.pem && sudo chmod 600 /etc/dreb/key.pem
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Renewal is **manual** — `tailscale cert` certs are Let's Encrypt, 90-day
|
|
163
|
+
lifetime. The dashboard hot-reloads the cert files on change
|
|
164
|
+
(`setSecureContext`), so a renewal that rewrites the files is picked up with
|
|
165
|
+
zero downtime. A daily systemd timer with `--min-validity=720h` (only renews
|
|
166
|
+
when within 30 days of expiry) is the recommended cadence:
|
|
167
|
+
|
|
168
|
+
```ini
|
|
169
|
+
# /etc/systemd/system/dreb-cert.service
|
|
170
|
+
[Service]
|
|
171
|
+
Type=oneshot
|
|
172
|
+
ExecStart=/usr/bin/tailscale cert --cert-file=/etc/dreb/cert.pem \
|
|
173
|
+
--key-file=/etc/dreb/key.pem --min-validity=720h hostname.tailXXXX.ts.net
|
|
174
|
+
ExecStartPost=/bin/chown dreb:dreb /etc/dreb/cert.pem /etc/dreb/key.pem
|
|
175
|
+
|
|
176
|
+
# /etc/systemd/system/dreb-cert.timer
|
|
177
|
+
[Timer]
|
|
178
|
+
OnCalendar=daily
|
|
179
|
+
RandomizedDelaySec=3600
|
|
180
|
+
[Install]
|
|
181
|
+
WantedBy=timers.target
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Then open `https://hostname.tailXXXX.ts.net:<port>` on the phone.
|
|
185
|
+
|
|
186
|
+
> **Hostname note (important):** the `tailscale cert` certificate is issued
|
|
187
|
+
> for your machine's tailnet name (`hostname.tailXXXX.ts.net`) **only** — not
|
|
188
|
+
> `127.0.0.1`, not a raw tailnet IP. When `--https` is enabled the server
|
|
189
|
+
> speaks TLS on every address it binds, so on the host itself:
|
|
190
|
+
>
|
|
191
|
+
> - `https://hostname.tailXXXX.ts.net:<port>` — works, cert validates (resolves
|
|
192
|
+
> to your tailnet IP). But it's a *remote* request: you go through the full
|
|
193
|
+
> Tailscale allowlist + pairing flow, not instant loopback local mode.
|
|
194
|
+
> - `https://127.0.0.1:<port>` — the server answers, but the browser rejects
|
|
195
|
+
> the cert (no `127.0.0.1` SAN) with a scary warning.
|
|
196
|
+
> - `http://127.0.0.1:<port>` — **dead**: the server only speaks TLS now.
|
|
197
|
+
>
|
|
198
|
+
> If you want the host dashboard tab to stay instant (loopback local mode, no
|
|
199
|
+
> pairing, no warning), run a **second** dashboard process without `--https` on
|
|
200
|
+
> a different port for local-only use, and keep the TLS-enabled one for remote.
|
|
201
|
+
> `--https` is primarily for the `--remote` path; pure-local setups don't need
|
|
202
|
+
> it (`127.0.0.1` is already a secure context).
|
|
203
|
+
|
|
204
|
+
## Subagent observability
|
|
205
|
+
|
|
206
|
+
Background subagents are first-class:
|
|
207
|
+
|
|
208
|
+
- Fleet cards show running/done counts and live agent lines.
|
|
209
|
+
- The session view shows a chip strip — one chip per background agent; click
|
|
210
|
+
to drill into its live transcript.
|
|
211
|
+
- The drill-in view streams the child's events in real time via the
|
|
212
|
+
`background_agent_event` relay (see [RPC events](rpc.md#event-types)) and
|
|
213
|
+
hydrates from the agent's on-disk session log on mount, so transcripts
|
|
214
|
+
survive browser reloads and remain viewable after the agent finishes.
|
|
215
|
+
|
|
216
|
+
## Responsive behavior
|
|
217
|
+
|
|
218
|
+
Single breakpoint at 700px. On mobile: fleet cards stack, the session view
|
|
219
|
+
prioritizes read-and-steer (model/thinking switchers collapse into ⋯, tasks
|
|
220
|
+
default collapsed), file table shows name + download only. Composer modes,
|
|
221
|
+
abort, and needs-attention affordances are never reduced away — steering a
|
|
222
|
+
running agent from a phone is the primary remote use case.
|
|
223
|
+
|
|
224
|
+
## Architecture
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
Browser dashboard (SolidJS + Vite, tokens.css design system)
|
|
228
|
+
⇄ Express server: fail-closed auth, REST, SSE fanout, file API
|
|
229
|
+
⇄ RpcClient pool — one `dreb --mode rpc --ui dashboard` child per session
|
|
230
|
+
⇄ sessions on disk (~/.dreb/agent/sessions), settings, models
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
- **SSE catch-up**: events carry sequence IDs; reconnects replay from
|
|
234
|
+
`Last-Event-ID` against a bounded buffer, falling back to a full state
|
|
235
|
+
refetch when the gap is too old. Slow clients whose server-side write
|
|
236
|
+
buffer exceeds a bound are disconnected (loudly logged) and recover via
|
|
237
|
+
the same reconnect path. Deleting a runtime publishes `runtime_removed`
|
|
238
|
+
so browsers evict that session's transcript state.
|
|
239
|
+
- **ctx%** comes from the session itself (`get_state.contextUsage` — the same
|
|
240
|
+
numbers the TUI footer shows), never client-side estimates.
|
|
241
|
+
- **Auto-naming** runs in the shared `AgentSession` layer, so dashboard-created
|
|
242
|
+
RPC sessions get the same LLM-generated session names as the TUI and update
|
|
243
|
+
live via `session_name_changed`.
|
|
244
|
+
- **Visual language**: `tokens.css` (`packages/dashboard/src/client/styles/`),
|
|
245
|
+
the dashboard's design system — IBM Plex Mono, light + dark via
|
|
246
|
+
`prefers-color-scheme`.
|
|
247
|
+
|
|
248
|
+
## Limitations (deliberate, sequenced later)
|
|
249
|
+
|
|
250
|
+
- No session tree screen yet (fork-from-message covers the go-back-and-re-edit
|
|
251
|
+
loop; the tree design and RPC are ready).
|
|
252
|
+
- No shell passthrough from the browser.
|
|
253
|
+
- No subagent steering (the drill-in view is read-only).
|
|
254
|
+
- Fixed light/dark via `prefers-color-scheme` — no TUI-theme following.
|
package/docs/development.md
CHANGED
|
@@ -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/
|
|
32
|
-
agent/
|
|
33
|
-
tui/
|
|
34
|
-
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`
|
|
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
|
|