Skip to content

feat: DutyBound hibernation — branding, stabilisation, on-demand launcher#55

Merged
DarlingtonDeveloper merged 3 commits into
mainfrom
feature/dutybound-hibernation
Feb 11, 2026
Merged

feat: DutyBound hibernation — branding, stabilisation, on-demand launcher#55
DarlingtonDeveloper merged 3 commits into
mainfrom
feature/dutybound-hibernation

Conversation

@DarlingtonDeveloper
Copy link
Copy Markdown
Collaborator

@DarlingtonDeveloper DarlingtonDeveloper commented Feb 11, 2026

Summary

  • Stream 0 (Branding): README rebranded as DutyBound, /dutybound route added
  • Stream 1 (Stabilise): CI fix (--validate-only exits cleanly without .mission/), full README rewrite, --force banned from Kai's commands, dashboard status/token/audit fixes
  • Stream 2 (On-Demand): mc launcher reverse proxy with wake-on-request, auto-shutdown after idle timeout, mc wake CLI command, migration TODOs updated

Test plan

  • make build succeeds with new launcher/wake commands
  • mc launcher --port 8080 --backend-port 8081 starts and responds to /api/health
  • mc wake triggers startup and polls until ready
  • Idle timeout shuts down backend after configured duration
  • CI passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed dashboard view issues including incorrect status labels and improved audit filter accuracy.
  • New Features

    • Wake-on-request API and CLI command to activate services on demand.
    • Auto-shutdown of services after an idle timeout (default 30 minutes).
    • Health/status page with loading indicators and readiness display.
    • Added --auto-mode option to enable automatic gate approval.
  • Documentation

    • Added onboarding guidance and a response completion protocol for setup flows.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

Adds a wake-on-request launcher and CLI, a new AutoMode config flag and onboarding prompt text. The launcher proxies to a child MC serve process, exposes /api/wake and /api/health, auto-starts on demand, and auto-shuts down after an idle timeout.

Changes

Cohort / File(s) Summary
Migration notes
MIGRATIONTODOS.md
Fixes dashboard status semantics (uses "done" vs "complete"), switches tokenWebSocket handling to adaptTokens(), and derives audit filters from actual data.
Launcher server
cmd/mc/launcher.go
New stateful HTTP launcher: /api/wake and /api/health, reverse-proxy to backend, lifecycle state machine (sleeping→starting→ready), starts/stops child mc serve, idle-timeout auto-shutdown, and CLI command registration. Review process management, timers, and error branches.
Wake CLI
cmd/mc/wake.go
New mc wake command: POST /api/wake, polls /api/health until ready with retries/timeouts. Check polling logic and user messaging.
Init config changes
cmd/mc/init.go
Adds exported AutoMode bool to config and a --auto-mode flag; writes auto_mode to config.json. Verify config schema and downstream consumers.
Prompts / onboarding
cmd/mc/prompts.go
Adds an "Onboarding Mode" flow and a "Response Completion Protocol" requiring appended structured bash blocks to .mission/conversation.md. Review for workflow and content correctness.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client as Client\n(requestor)
  participant Launcher as Launcher\n(proxy + lifecycle)
  participant Child as MC Serve\n(child process)
  participant Backend as Backend API\n(local HTTP)

  rect rgba(200,220,255,0.5)
    Client->>Launcher: HTTP request (any)
    alt Launcher sleeping
      Client->>Launcher: GET /api/health or POST /api/wake
      Launcher-->>Client: 503 + Retry-After (triggers wake)
      Launcher->>Launcher: triggerWake (async)
      Launcher->>Child: start mc serve (fork child process)
      Child->>Backend: bind and serve on backend port
      Launcher->>Backend: poll /api/health until healthy
      Backend-->>Launcher: status: ready
      Launcher->>Launcher: transition to ready, start idle timer
      Client->>Launcher: retry request
      Launcher->>Backend: reverse-proxy request
      Backend-->>Client: proxied response
    else Launcher ready
      Client->>Launcher: request
      Launcher->>Backend: reverse-proxy request
      Backend-->>Client: response
    end
  end

  rect rgba(255,220,200,0.5)
    note right of Launcher: idle timer expires → stopServices\n(signal child, wait, kill if needed)\ntransition to sleeping
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

I'm a rabbit in the launcher's code, hopping light and spry,
I nudge the sleeping server with a soft and gentle cry.
I proxy, poll, then tuck it in when idle time is fed,
I wake on request and nap again — a tiny thread's homestead.
🐇🚀---END---

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: DutyBound hibernation — branding, stabilisation, on-demand launcher' accurately captures the three main streams of changes: branding (DutyBound rebranding), stabilization (CI/dashboard fixes), and the new on-demand launcher feature with wake/idle timeout.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/dutybound-hibernation

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
cmd/mc/launcher.go (2)

53-62: Consider handling the url.Parse error.

The error from url.Parse is discarded. While the hardcoded format is unlikely to fail, ignoring errors can mask issues if the function signature or inputs change.

Suggested improvement
 func newLauncher(port, backendPort int, idleTimeout time.Duration) *launcher {
-	target, _ := url.Parse(fmt.Sprintf("http://localhost:%d", backendPort))
+	target, err := url.Parse(fmt.Sprintf("http://localhost:%d", backendPort))
+	if err != nil {
+		// This should never happen with a hardcoded format, but log defensively
+		log.Fatalf("launcher: invalid backend URL: %v", err)
+	}
 	return &launcher{

176-188: Potential race between child exit detection and ready transition.

There's a small window between waitForHealthy() returning and setting state = stateReady where the background goroutine (lines 156-173) could detect that cmd.Wait() completed and set the state back to stateSleeping. Line 183 would then overwrite it to stateReady even though the process has exited.

Consider checking if serveCmd is still set before transitioning to ready:

Suggested improvement
 	l.mu.Lock()
+	if l.serveCmd == nil {
+		// Process died during health check wait
+		l.mu.Unlock()
+		log.Println("launcher: mc serve exited during startup")
+		return
+	}
 	l.state = stateReady
 	l.mu.Unlock()

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@cmd/mc/launcher.go`:
- Around line 230-260: The stopServices method currently calls Process.Wait a
second time and ignores errors from Signal/Kill; add a waitCh chan error to the
launcher struct, have the goroutine that runs cmd.Wait() (the one started when
serveCmd is launched) send its error into waitCh, then in stopServices() read
from waitCh with a 5s timeout instead of calling cmd.Process.Wait() again; also
capture and handle errors returned by cmd.Process.Signal(...) and
cmd.Process.Kill(...) (logging or returning them as appropriate) and ensure you
still nil out serveCmd and stop idleTimer and set stateSleeping as done now.

In `@cmd/mc/wake.go`:
- Around line 21-47: The POST and GET responses in the wake flow currently only
check transport errors and ignore non-2xx status codes and JSON decode errors;
update the client.Post call in the wake command to check resp.StatusCode (treat
any non-2xx as an error), defer resp.Body.Close() immediately after ensuring
resp != nil, and do the same for client.Get in the polling loop (defer or ensure
close on every loop iteration). After client.Get, validate resp.StatusCode
before decoding, then capture the error returned by
json.NewDecoder(resp.Body).Decode(&health) and treat decode errors as failures
(log/continue or return an error instead of silently continuing). Reference:
baseURL, client.Post, client.Get, the local health struct and
json.NewDecoder(...).Decode to locate where to add status-code checks, proper
resp.Body.Close() defers, and decode error handling.

Comment thread cmd/mc/launcher.go
Comment on lines +230 to +260
func (l *launcher) stopServices() {
l.mu.Lock()
if l.idleTimer != nil {
l.idleTimer.Stop()
l.idleTimer = nil
}
cmd := l.serveCmd
l.serveCmd = nil
l.state = stateSleeping
l.mu.Unlock()

// Kill mc serve child process
if cmd != nil && cmd.Process != nil {
log.Println("launcher: stopping mc serve")
cmd.Process.Signal(os.Interrupt)
// Give it a moment to shut down gracefully
done := make(chan struct{})
go func() {
cmd.Process.Wait()
close(done)
}()
select {
case <-done:
case <-time.After(5 * time.Second):
log.Println("launcher: mc serve did not exit, killing")
cmd.Process.Kill()
}
}

log.Println("launcher: services stopped, sleeping")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd . && find . -name "launcher.go" -path "*/cmd/mc/*" | head -1

Repository: DarlingtonDeveloper/MissionControl

Length of output: 98


🏁 Script executed:

cat -n cmd/mc/launcher.go | head -300

Repository: DarlingtonDeveloper/MissionControl

Length of output: 9312


🏁 Script executed:

gofmt -l cmd/mc/launcher.go

Repository: DarlingtonDeveloper/MissionControl

Length of output: 60


🏁 Script executed:

gofmt -d cmd/mc/launcher.go | head -50

Repository: DarlingtonDeveloper/MissionControl

Length of output: 60


Fix double-wait on child process and add error checking.

Line 244 ignores the error from cmd.Process.Signal, and line 248 calls cmd.Process.Wait() even though cmd.Wait() is already running in a goroutine at line 156. In Go, Process.Wait() can only be called once; the second call will fail with "already waited" and skip the kill path, causing errcheck CI failures. Additionally, line 255 ignores the error from cmd.Process.Kill().

Use a single wait path via a stored channel instead of calling Process.Wait() twice, and always check errors from Signal() and Kill():

Suggested fix

Add waitCh chan error field to the launcher struct, modify the wait goroutine at line 156 to send the error to waitCh, and update stopServices() to receive from waitCh with a timeout instead of calling Process.Wait() again. Check all signal/kill errors:

 type launcher struct {
 	mu          sync.Mutex
 	state       launcherState
 	port        int
 	backendPort int
 	idleTimeout time.Duration
 
 	idleTimer *time.Timer
 	serveCmd  *exec.Cmd
+	waitCh    chan error
 	proxy     *httputil.ReverseProxy
 }

At line 156, wrap cmd.Wait() and send result to channel:

-	go func() {
-		if err := cmd.Wait(); err != nil {
+	waitCh := make(chan error, 1)
+	go func() {
+		err := cmd.Wait()
+		waitCh <- err
+		close(waitCh)
+		if err != nil {

At lines 236–237, save the wait channel:

 	cmd := l.serveCmd
+	waitCh := l.waitCh
 	l.serveCmd = nil
+	l.waitCh = nil

At lines 244–256, use the channel and check errors:

-		cmd.Process.Signal(os.Interrupt)
+		if err := cmd.Process.Signal(os.Interrupt); err != nil {
+			log.Printf("launcher: failed to signal mc serve: %v", err)
+		}
 		done := make(chan struct{})
 		go func() {
-			cmd.Process.Wait()
-			close(done)
+			if waitCh != nil {
+				<-waitCh
+			}
+			close(done)

Or simpler, use the wait channel directly with timeout:

-		done := make(chan struct{})
-		go func() {
-			cmd.Process.Wait()
-			close(done)
-		}()
-		select {
-		case <-done:
+		if waitCh != nil {
+			select {
+			case <-waitCh:
+			case <-time.After(5 * time.Second):
+				log.Println("launcher: mc serve did not exit, killing")
+				if err := cmd.Process.Kill(); err != nil {
+					log.Printf("launcher: failed to kill mc serve: %v", err)
+				}
+			}
 		}
🧰 Tools
🪛 GitHub Actions: CI

[error] 244-244: Error return value of cmd.Process.Signal is not checked (errcheck)

🪛 GitHub Check: build-and-test

[failure] 248-248:
Error return value of cmd.Process.Wait is not checked (errcheck)


[failure] 244-244:
Error return value of cmd.Process.Signal is not checked (errcheck)

🤖 Prompt for AI Agents
In `@cmd/mc/launcher.go` around lines 230 - 260, The stopServices method currently
calls Process.Wait a second time and ignores errors from Signal/Kill; add a
waitCh chan error to the launcher struct, have the goroutine that runs
cmd.Wait() (the one started when serveCmd is launched) send its error into
waitCh, then in stopServices() read from waitCh with a 5s timeout instead of
calling cmd.Process.Wait() again; also capture and handle errors returned by
cmd.Process.Signal(...) and cmd.Process.Kill(...) (logging or returning them as
appropriate) and ensure you still nil out serveCmd and stop idleTimer and set
stateSleeping as done now.

Comment thread cmd/mc/wake.go
Comment on lines +21 to +47
// Send wake request
fmt.Println("Waking DutyBound services...")
resp, err := client.Post(baseURL+"/api/wake", "application/json", nil)
if err != nil {
return fmt.Errorf("could not reach launcher at %s: %w", baseURL, err)
}
resp.Body.Close()

// Poll until ready
timeout := time.After(90 * time.Second)
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()

for {
select {
case <-timeout:
return fmt.Errorf("timed out waiting for services to become ready")
case <-ticker.C:
resp, err := client.Get(baseURL + "/api/health")
if err != nil {
continue
}
var health struct {
Status string `json:"status"`
}
json.NewDecoder(resp.Body).Decode(&health)
resp.Body.Close()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cd /tmp && find . -name "wake.go" -type f 2>/dev/null | head -5

Repository: DarlingtonDeveloper/MissionControl

Length of output: 60


🏁 Script executed:

cat -n cmd/mc/wake.go | sed -n '21,47p'

Repository: DarlingtonDeveloper/MissionControl

Length of output: 1013


🏁 Script executed:

ls -la cmd/mc/wake.go 2>/dev/null || echo "File not found in expected location"
git ls-files | grep -i wake.go

Repository: DarlingtonDeveloper/MissionControl

Length of output: 156


🏁 Script executed:

gofmt -l cmd/mc/wake.go

Repository: DarlingtonDeveloper/MissionControl

Length of output: 60


🏁 Script executed:

cat -n cmd/mc/wake.go

Repository: DarlingtonDeveloper/MissionControl

Length of output: 2336


Handle HTTP response status codes and JSON decode errors to avoid silent timeouts.

Line 23 doesn't check the POST response status code, and Line 39 doesn't check the GET response status code. Both requests only handle network errors, allowing 4xx/5xx responses to proceed as if successful. Additionally, Line 46 ignores JSON decode errors—if the response body is malformed, health.Status silently remains empty and polling continues until timeout with no actionable feedback.

Check both response status codes (treat non-2xx as errors), and validate the JSON decode result before proceeding. Use defer for resp.Body.Close() to ensure cleanup even on errors.

🔧 Suggested fix
 		// Send wake request
 		fmt.Println("Waking DutyBound services...")
 		resp, err := client.Post(baseURL+"/api/wake", "application/json", nil)
 		if err != nil {
 			return fmt.Errorf("could not reach launcher at %s: %w", baseURL, err)
 		}
+		if resp.StatusCode/100 != 2 {
+			resp.Body.Close()
+			return fmt.Errorf("wake request failed: %s", resp.Status)
+		}
 		resp.Body.Close()

@@
 			case <-ticker.C:
 				resp, err := client.Get(baseURL + "/api/health")
 				if err != nil {
 					continue
 				}
+				if resp.StatusCode != http.StatusOK {
+					resp.Body.Close()
+					continue
+				}
 				var health struct {
 					Status string `json:"status"`
 				}
-				json.NewDecoder(resp.Body).Decode(&health)
+				if err := json.NewDecoder(resp.Body).Decode(&health); err != nil {
+					resp.Body.Close()
+					continue
+				}
 				resp.Body.Close()
🤖 Prompt for AI Agents
In `@cmd/mc/wake.go` around lines 21 - 47, The POST and GET responses in the wake
flow currently only check transport errors and ignore non-2xx status codes and
JSON decode errors; update the client.Post call in the wake command to check
resp.StatusCode (treat any non-2xx as an error), defer resp.Body.Close()
immediately after ensuring resp != nil, and do the same for client.Get in the
polling loop (defer or ensure close on every loop iteration). After client.Get,
validate resp.StatusCode before decoding, then capture the error returned by
json.NewDecoder(resp.Body).Decode(&health) and treat decode errors as failures
(log/continue or return an error instead of silently continuing). Reference:
baseURL, client.Post, client.Get, the local health struct and
json.NewDecoder(...).Decode to locate where to add status-code checks, proper
resp.Body.Close() defers, and decode error handling.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
cmd/mc/init.go (1)

53-56: ⚠️ Potential issue | 🟡 Minor

Ignored error from os.UserHomeDir() can produce malformed paths.

If UserHomeDir fails (e.g., $HOME unset), home is empty and the resulting path becomes invalid (e.g., /rest/of/path). Consider returning an error or logging a warning.

Suggested fix
 	if strings.HasPrefix(workDir, "~") {
-		home, _ := os.UserHomeDir()
+		home, err := os.UserHomeDir()
+		if err != nil {
+			return fmt.Errorf("failed to expand ~: %w", err)
+		}
 		workDir = filepath.Join(home, workDir[1:])
 	}
cmd/mc/prompts.go (1)

154-176: ⚠️ Potential issue | 🟠 Major

Heredoc quoting prevents timestamp expansion.

The single-quoted delimiter << 'EXCHANGE' disables command substitution, so $(date ...) will be written literally instead of generating an actual timestamp. This breaks the intended ISO 8601 timestamp requirement.

Suggested fix — compute date before heredoc
 ` + "```" + `bash
+TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
-cat >> .mission/conversation.md << 'EXCHANGE'
+cat >> .mission/conversation.md << EXCHANGE

-## Assistant [$(date -u +%Y-%m-%dT%H:%M:%SZ)]
+## Assistant [$TIMESTAMP]

 <your complete response here>

 ---END---
 EXCHANGE
 ` + "```" + `

Note: Using an unquoted delimiter means any $ or backticks in the response content could be interpreted by the shell. If responses may contain such characters, consider escaping them or using a different approach (e.g., printf or echo with proper quoting).

🧹 Nitpick comments (1)
cmd/mc/init.go (1)

200-203: Audit log omits the new auto_mode setting.

For traceability, consider including initAutoMode in the audit payload alongside openclaw.

Suggested fix
 	writeAuditLog(missionDir, AuditProjectInitialized, "cli", map[string]interface{}{
 		"path":     workDir,
 		"openclaw": initOpenClaw,
+		"auto_mode": initAutoMode,
 	})

DarlingtonDeveloper and others added 2 commits February 11, 2026 20:17
Implements Stream 2 of the hibernation plan:
- `mc launcher` — lightweight reverse proxy that starts/stops mc serve on demand
- `mc wake` — CLI command to wake services via the launcher
- Auto-shutdown after configurable idle timeout (default 30m)
- Health endpoint for frontend polling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add conversational onboarding section to OpenClaw prompt so Kai guides
new users through project setup (clone, init, gate preferences). Add
--auto-mode flag to mc init for automatic gate approval.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@DarlingtonDeveloper DarlingtonDeveloper force-pushed the feature/dutybound-hibernation branch from ab43720 to c47f5ef Compare February 11, 2026 20:17
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@cmd/mc/prompts.go`:
- Around line 129-145: Update the "Onboarding Mode" prompt text to align with
the OpenClaw persona and the gate-approval policy by removing or clarifying the
hard name "you're Kai" (e.g., call out it's an optional alias or use "OpenClaw /
your coordinator") and change the `--auto-mode` mention to require explicit
opt-in phrasing (e.g., "enable auto-mode only if user explicitly agrees").
Ensure the gate approval question explicitly states that automatic approvals are
disabled by default and that the user must opt in, and keep the conversational
tone and step sequence intact (reference the "Onboarding Mode" section, the
`--auto-mode` flag, and the gate approval wording).

Comment thread cmd/mc/prompts.go
Comment on lines +129 to +145
## Onboarding Mode

When a user first connects and no project is active (mc status shows no .mission/),
guide them through setup naturally:

1. Greet them — you're Kai, their development coordinator
2. Ask what they'd like to build, or if they have an existing repo
3. Existing repo: clone it with ` + "`" + `git clone <url> /workspace/<name>` + "`" + `
4. New project: create dir, optionally ` + "`" + `git init` + "`" + `
5. Bootstrap: ` + "`" + `mc init --path <project-path> [--auto-mode]` + "`" + `
6. Ask preferences:
- Gate approval: "Should I handle gates automatically, or do you want to approve each?"
- Zones: "What areas — frontend, backend, database, infra?"
7. Register: ` + "`" + `mc project register <name> <path>` + "`" + `
8. Confirm setup, suggest starting Discovery stage

Keep it conversational — you're a colleague, not a rigid wizard.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Align onboarding wording with the OpenClaw persona and gate-approval policy.
Line 134 says “you’re Kai” and Line 140 suggests --auto-mode, but earlier the prompt defines OpenClaw and the “Important” section says “don’t auto-approve.” This creates mixed guidance that can steer the coordinator inconsistently. Consider clarifying that “Kai” is an alias (if intended) and that auto-mode is only enabled after explicit opt‑in.

✏️ Suggested wording tweak (within this segment)
-1. Greet them — you're Kai, their development coordinator
+1. Greet them — you're OpenClaw (Kai), their development coordinator
 ...
-6. Ask preferences:
-   - Gate approval: "Should I handle gates automatically, or do you want to approve each?"
+6. Ask preferences:
+   - Gate approval: "Do you want to opt into auto-mode, or approve each gate manually?"
🤖 Prompt for AI Agents
In `@cmd/mc/prompts.go` around lines 129 - 145, Update the "Onboarding Mode"
prompt text to align with the OpenClaw persona and the gate-approval policy by
removing or clarifying the hard name "you're Kai" (e.g., call out it's an
optional alias or use "OpenClaw / your coordinator") and change the
`--auto-mode` mention to require explicit opt-in phrasing (e.g., "enable
auto-mode only if user explicitly agrees"). Ensure the gate approval question
explicitly states that automatic approvals are disabled by default and that the
user must opt in, and keep the conversational tone and step sequence intact
(reference the "Onboarding Mode" section, the `--auto-mode` flag, and the gate
approval wording).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@DarlingtonDeveloper DarlingtonDeveloper merged commit 248e7bd into main Feb 11, 2026
3 checks passed
@DarlingtonDeveloper DarlingtonDeveloper deleted the feature/dutybound-hibernation branch February 11, 2026 20:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant