Skip to content

Desktop widget + PTT + task cancellation - #38

Closed
ncrispino wants to merge 6 commits into
mainfrom
feature/widget
Closed

ncrispino wants to merge 6 commits into
mainfrom
feature/widget

Conversation

@ncrispino

@ncrispino ncrispino commented Mar 29, 2026 •

Copy link
Copy Markdown

Summary

Adds a native macOS desktop widget and several cross-cutting features.

Desktop widget (widget.swift)

  • SwiftUI floating panel — always-on-top, translucent, all-spaces
  • Task display, voice status indicator, conversation view, text input
  • Global push-to-talk hotkey (Ctrl+Space, configurable via SUTANDO_PTT_KEY)
  • Core status polling (shows proactive loop activity)
  • Auto-built and launched from startup.sh (gracefully skips if Xcode CLT not installed)

Push-to-talk (PTT)

  • Widget: global hotkey via Carbon Events (with Accessibility event tap fallback)
  • Web client: Space bar when PTT mode enabled, PTT toggle button
  • Both sync mute state to voice agent via new HTTP control server (port 9901)

Task cancellation

  • New /task/cancel endpoint in agent-api
  • Cancel buttons in both widget and web client
  • Result polling stops properly on cancel/error (with 5-min timeout)

Web client improvements

  • Auto-connect overlay (respects browser autoplay policy)
  • Better microphone error messages (specific to permission, device, etc.)
  • setMuteState refactor — widget/PTT can control mute remotely
  • Live transcript publishing to control server for widget pickup
  • End All button + Escape shortcut
  • Fixed stale task cleanup (no flicker on agent-api restart)

Voice agent

  • HTTP control server on port 9901 (state, mute, PTT, live transcript)
  • Writes voice-conversation.json for widget conversation display

Utility scripts

  • kill-all.sh — reliably stop all Sutando processes
  • clean-state.sh — remove stale task/result files

Review feedback addressed

  • ✅ Poll timeout added (5 min) + stop on error/cancelled states
  • ✅ PTT hotkey: return eventNotHandledErr when event not matched
  • ✅ kill-all.sh: kill watch-tasks instead of fswatch
  • ✅ Startup gracefully handles missing Xcode CLT
  • ✅ Port 9901 (voice control server) added to health-check.py

Open

  • PTT: further testing across apps (consider deferring to v2 as optional mode)

Test plan

  • Build widget: swiftc -o sutando-widget src/widget.swift -framework Cocoa -framework SwiftUI -framework CoreGraphics -O
  • Widget floats on all spaces, shows tasks and voice status
  • PTT works globally (Ctrl+Space) and in web client (Space)
  • Task cancel from widget and web client
  • Text input: goes through WS when voice connected, task API when not
  • Auto-connect overlay activates voice on click
  • health-check shows voice-control (port 9901) status

🤖 Generated with Claude Code

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Review — WIP: Desktop widget + PTT + task cancellation

Great additions. Here's my assessment:

Widget (widget.swift) ✅

  • SwiftUI floating panel, always-on-top, all-spaces — solid UX
  • Core status polling (reads core-status.json) — integrates with the status system we built today
  • Task display, conversation view, text input — all useful
  • 1346 lines but well-structured

Task cancellation ✅

  • /task/cancel API endpoint — fills a gap (only had inline cancel before)
  • Cancel buttons in widget + web client
  • Proper result polling stop on cancel

PTT (Push-to-talk) ⚠️

  • Owner suggests PTT may be unnecessary for v1 — hotkey connect + voice "bye" disconnect is simpler
  • Could defer to v2 as optional mode
  • Carbon Events + Accessibility fallback adds complexity

Web client improvements ✅

  • Auto-connect overlay — good UX
  • Better mic error messages — addresses real user pain
  • setMuteState refactor — enables remote control from widget

Utility scripts ✅

  • kill-all.sh, clean-state.sh — useful maintenance tools

Suggestions:

  1. Consider making PTT optional/deferrrable
  2. The widget auto-build in startup.sh needs Xcode CLT — add a check
  3. Port 9901 (control server) should be in health-check.py

Overall: strong PR, widget is a differentiator. Merge when TODOs are resolved.

Addresses reviewer suggestion to include the widget/PTT control server
in the health check alongside other optional services.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vyvy3n vyvy3n changed the title WIP: Desktop widget + PTT + task cancellation Desktop widget + PTT + task cancellation Mar 29, 2026
@vyvy3n
vyvy3n marked this pull request as ready for review March 29, 2026 22:18
@sonichi

sonichi commented Apr 3, 2026

Copy link
Copy Markdown
Owner

Reviewed the diff — the features look solid (task cancel, PTT, widget, kill-all/clean-state scripts). However, the PR has merge conflicts against current main (startup.sh, voice-agent.ts, web-client.ts, .gitignore have all changed significantly since March 29).

Note: the native Sutando menu bar app (PR #74-76) now handles some of what the widget covers (global hotkeys, voice toggle, mute). The widget adds PTT and a floating panel which are complementary, but the startup.sh and voice-agent.ts changes will need careful rebasing to avoid conflicts with the new inline tools, tab aliases, and stand-identity.json loading.

@ncrispino could you rebase against main when you get a chance?

@ncrispino

Copy link
Copy Markdown
Author

@sonichi I think we can just close this for now, I can put more targeted features against main when I have time.

@ncrispino ncrispino closed this Apr 3, 2026
liususan091219 added a commit that referenced this pull request Apr 13, 2026
"ts.net" in url matches spoofed hostnames like evil-ts.net.attacker.com.
Parse URL and check hostname endswith(".ts.net") instead.
POC: scripts/poc-codeql-38-url-substring.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
sonichi pushed a commit that referenced this pull request Apr 14, 2026
* fix: validate display param is numeric (CodeQL #33)

Unsanitized display query param flowed into subprocess.run args.
Add isdigit() check so only numeric values reach screencapture.
Includes POC script: scripts/poc-codeql-33-cmd-injection.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: escape backslashes before quotes in AppleScript strings (CodeQL #42, #39)

The .replace(/"/g, '\\"') only escaped quotes but not backslashes.
A backslash before a quote (\\") would become \\" which closes the
AppleScript string context. Fix: escape backslashes first, then quotes.
Includes POC: scripts/poc-codeql-42-39-string-escaping.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: POC confirms path injection guards work (CodeQL #16-23, #28-31, #35-36)

Existing resolve() + is_relative_to() + regex guards in agent-api.py
and dashboard.py properly block all path traversal attempts. CodeQL
flags these as false positives because it can't track taint through
the validation logic. POC: scripts/poc-codeql-path-injection.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use hostname endswith instead of substring check (CodeQL #38)

"ts.net" in url matches spoofed hostnames like evil-ts.net.attacker.com.
Parse URL and check hostname endswith(".ts.net") instead.
POC: scripts/poc-codeql-38-url-substring.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: reduce phone number exposure in logs (CodeQL #37)

Extract masked phone to separate variable and reduce visible digits
from 6 (2+4) to 4 (2+2). Breaks CodeQL taint flow from env var
to console.log.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
liususan091219 added a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…ichi#39, sonichi#42) (sonichi#320)

* fix: validate display param is numeric (CodeQL sonichi#33)

Unsanitized display query param flowed into subprocess.run args.
Add isdigit() check so only numeric values reach screencapture.
Includes POC script: scripts/poc-codeql-33-cmd-injection.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: escape backslashes before quotes in AppleScript strings (CodeQL sonichi#42, sonichi#39)

The .replace(/"/g, '\\"') only escaped quotes but not backslashes.
A backslash before a quote (\\") would become \\" which closes the
AppleScript string context. Fix: escape backslashes first, then quotes.
Includes POC: scripts/poc-codeql-42-39-string-escaping.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: POC confirms path injection guards work (CodeQL sonichi#16-23, sonichi#28-31, sonichi#35-36)

Existing resolve() + is_relative_to() + regex guards in agent-api.py
and dashboard.py properly block all path traversal attempts. CodeQL
flags these as false positives because it can't track taint through
the validation logic. POC: scripts/poc-codeql-path-injection.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: use hostname endswith instead of substring check (CodeQL sonichi#38)

"ts.net" in url matches spoofed hostnames like evil-ts.net.attacker.com.
Parse URL and check hostname endswith(".ts.net") instead.
POC: scripts/poc-codeql-38-url-substring.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: reduce phone number exposure in logs (CodeQL sonichi#37)

Extract masked phone to separate variable and reduce visible digits
from 6 (2+4) to 4 (2+2). Breaks CodeQL taint flow from env var
to console.log.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

3 participants