Story 2.12: UI-Initiated Session Configuration Collection

Epic: Epic 2 - Session Management & Async Interaction
Story Key: 2-12-ui-session-config
Created: 2026-02-02
Priority: High

Story

As a developer,
I want sessions created in OpenCode UI to collect configuration lazily based on actual needs,
So that I can start exploratory sessions with zero friction and only provide builder/Slack config when required.

Acceptance Criteria

AC1: Session Creation Without Config

Given User creates session in OpenCode UI with title “Add rate limiting”
When Session is created
Then Session is registered with minimal state (id, title, directory)
And No builder or Slack configuration is collected yet
And Session type is “exploratory”

AC2: Exploratory Work Without Prompts

Given Exploratory session is active
When User asks questions and agent reads code
Then Work proceeds without any configuration prompts
And Agent uses Read, Grep, Glob tools freely

AC3: Write Operation Triggers Builder Config

Given User attempts first write operation (Edit, Write, Bash with file modification)
When Plugin intercepts write tool execution
Then Execution is paused
And User is prompted for builder configuration

AC4: OpenCode Modal for Builder Config (When in Web UI)

Given User is in OpenCode web UI when write is attempted
When Builder config prompt is needed
Then OpenCode modal appears with fields:

  • Project: [Dropdown of projects from .ai/projectlist.md or “Create New”]
  • Workspace Name: [Auto-filled: {project-id}-{task-slug}]
  • Repositories: [Multi-select, pre-selected based on detected repos]
  • Category: [Select, inferred from project]
    And Optional section: “Setup Slack notifications now?” (unchecked by default)

AC5: Slack Form for Builder Config (When in Slack)

Given User is in Slack when write is attempted
When Builder config prompt is needed
Then Slack form appears in existing thread or DM with same fields
And Optional section: “Setup Slack notifications?” (checked by default)

AC6: Builder Initialization on Config Submission

Given Builder config is provided
When User submits form
Then Gateway invokes task builder:init BUILDER_NAME={workspace_name} REPOS={repos}
And Builder workspace is created at .builders/{workspace_name}/
And Session is moved to builder workspace
And Session type changes to “work”
And Write operation proceeds

AC7: Optional Bundled Slack Config

Given Builder config form includes optional Slack section
When User enables Slack notifications and submits
Then Both builder and Slack configs are saved
And Slack thread is created in specified channel
And Future notifications route to Slack

AC8: Going Offline Triggers Slack Config

Given User is in OpenCode web UI working
When User goes offline (presence detection: no web activity >10min)
Then Next notification triggers Slack config collection
And Slack form appears: “Where should I post updates for ‘{session.title}’?”
And Fields: Channel (inferred), Priority (medium default)

AC9: Slack Config Creates Thread

Given Slack config is provided via form
When User submits
Then Slack thread is created in chosen channel
And Pending notification is posted to thread
And Mapping session_id → thread_ts is saved to .session-state.yaml

AC10: Independent Config Collection Order

Given User creates session, goes offline, then starts building
When Both configs are eventually needed
Then Slack config collected first (when going offline)
And Builder config collected second (when attempting write in Slack)
And Both configs can be collected in either order

AC11: Project ID Inference

Given Project inference runs on session title “Add auth to OpenCode Slack”
When System matches against .ai/projectlist.md
Then Project 0012 (OpenCode Slack Integration) is suggested
And Workspace name defaults to “0012-add-auth-to-opencode-slack”
And Repositories default to [“ai-dev”] (from project metadata)
And Category defaults to “ai-dev”

AC12: Project Selection Fallback

Given Project inference cannot determine project with confidence
When Multiple projects match or none match
Then Dropdown shows all active projects
And “Create New Project” option is available
And If selected, next available project ID (e.g., 0014) is assigned

AC13: Builder Config State Persistence

Given Session with builder config exists
When Gateway restarts
Then .session-state.yaml is loaded from PVC
And Builder config (project_id, workspace_name, repos, category) is restored
And Session can resume work without re-prompting

AC14: Slack Config State Persistence

Given Session with Slack config exists
When Gateway restarts
Then Slack thread mapping is loaded from state file
And Future notifications route to correct thread
And No duplicate threads are created

AC15: Single-Form Both Configs

Given User opts to provide both configs at once
When Builder config form shows optional Slack section
Then User can check “Setup Slack notifications now”
And Single form submission provides both configs
And No second prompt occurs later

Tasks/Subtasks

Task 1: Gateway - Session Config Manager

  • 1.1: Create SessionConfigManager class in services/gateway/services/session_config.py
  • 1.2: Implement ensure_builder_config(session_id) method with presence detection
  • 1.3: Implement ensure_slack_config(session_id) method
  • 1.4: Add project ID inference from .ai/projectlist.md
  • 1.5: Add workspace name generation: {project-id}-{slugified-title}
  • 1.6: Add repository/category inference from git remotes and paths

Task 2: Gateway - Builder Initialization

  • 2.1: Create builder initialization service in services/gateway/services/builder.py
  • 2.2: Implement init_builder(workspace_name, repositories) calling task builder:init
  • 2.3: Handle builder creation success/failure responses
  • 2.4: Update session state with builder config and workspace path

Task 3: Gateway - Slack Form Prompts

  • 3.1: Create Slack form builder in services/gateway/services/slack_forms.py
  • 3.2: Implement builder config form (with optional Slack section)
  • 3.3: Implement Slack config-only form
  • 3.4: Handle form submissions and extract config data
  • 3.5: Create Slack threads and store thread_ts mapping

Task 4: Bridge Plugin - Write Detection

  • 4.1: Add tool.beforeExecute hook in plugin
  • 4.2: Detect write operations: mcp_edit, mcp_write, bash with file modifications
  • 4.3: Check session for builder config before allowing write
  • 4.4: Send builder config request to Gateway if missing
  • 4.5: Pause tool execution until config provided

Task 5: Bridge Plugin - OpenCode Modal (Optional - depends on SDK capability)

  • 5.1: Research OpenCode SDK modal/form capabilities
  • 5.2: If supported: Implement builder config modal prompt
  • 5.3: If not supported: Skip (use Slack form fallback)

Task 6: Gateway - Presence Detection

  • 6.1: Track last_web_activity timestamp per user
  • 6.2: Update timestamp on web UI interactions (WebSocket messages from Bridge)
  • 6.3: Implement presence check: is_user_in_web_ui() (activity within 10min)
  • 6.4: Route config prompts to OpenCode vs Slack based on presence

Task 7: Gateway - State Persistence

  • 7.1: Extend .session-state.yaml schema with builder_config and slack_config
  • 7.2: Save builder config to state on initialization
  • 7.3: Save Slack config to state on thread creation
  • 7.4: Load both configs on Gateway restart
  • 7.5: Restore session mappings (session_id → thread_ts)

Task 8: Integration Testing

  • 8.1: Test exploratory session (no config prompts)
  • 8.2: Test write triggers builder config prompt
  • 8.3: Test builder initialization and workspace creation
  • 8.4: Test going offline triggers Slack config
  • 8.5: Test both configs collected in different orders
  • 8.6: Test project inference (match/no match)
  • 8.7: Test state recovery after Gateway restart
  • 8.8: Test optional bundled config submission

Dev Notes

Architecture Context

Refer to .ai/projects/ai-dev/opencode-slack-integration/architecture.md Decision 15 for complete architectural details.

Key Implementation Points

  1. Two Independent Configs: Builder config and Slack config are orthogonal - can be collected in any order
  2. Lazy Collection: Only prompt when actually needed (write for builder, offline for Slack)
  3. Smart Inference: Pre-fill forms with detected values from session context
  4. Presence-Aware Routing: Prompt in OpenCode if user present, Slack if offline/unknown
  5. Optional Bundling: Single form can collect both configs to minimize interruptions

Component Files

  • services/gateway/services/session_config.py - Config manager
  • services/gateway/services/builder.py - Builder initialization
  • services/gateway/services/slack_forms.py - Slack form builders
  • services/gateway/models/session.py - Session state schema extensions
  • plugins/opencode-bridge/src/handlers/write-detection.ts - Write interception

State Schema Extension

session:
  id: ses_123
  title: "Add rate limiting"
  type: exploratory | work
  builder_config:
    project_id: "0012"
    workspace_name: "0012-add-rate-limiting"
    repositories: ["ai-dev", "k8s-lab"]
    category: "ai-dev"
    workspace_path: ".builders/0012-add-rate-limiting"
  slack_config:
    channel: "#ai-dev-builds"
    thread_ts: "1234567890.123456"
    priority: "high"

Test Data

  • Sample project list: Use .ai/projectlist.md (projects 0001-0013)
  • Test session titles that match/don’t match projects
  • Test repo detection in various workspace structures

Dev Agent Record

Debug Log

Completion Notes

File List

Change Log

  • 2026-02-02: Story created from Epic 2 Story 2.12

Status

in-progress