Skip to content

Web UI: keep retrying + specific errors; watcher pre-check fix - #57

Merged
sonichi merged 1 commit into
mainfrom
improve-reconnect-watcher
Mar 30, 2026
Merged

sonichi merged 1 commit into
mainfrom
improve-reconnect-watcher

Conversation

@sonichi

@sonichi sonichi commented Mar 30, 2026

Copy link
Copy Markdown
Owner

Summary

  • Web UI reconnect: Shows specific troubleshooting causes after 5 attempts (GEMINI_API_KEY, startup.sh, port) but keeps retrying forever instead of stopping
  • Watcher: Removed pgrep dedup guard that blocked the pre-check from running, causing missed tasks

Test plan

  • Disconnect voice agent — web UI keeps retrying with helpful messages
  • Create task file while watcher running — should detect
  • Create task file, restart watcher — pre-check should find it immediately

🤖 Generated with Claude Code

Web UI: After 5 failed reconnect attempts, show specific causes
(GEMINI_API_KEY, startup.sh, port) but keep retrying indefinitely.
Previously stopped after 5 attempts.

Watcher: Remove pgrep dedup guard that prevented the pre-check from
running. Pre-check now correctly finds existing .txt files before
entering fswatch wait loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sonichi
sonichi merged commit f50ae46 into main Mar 30, 2026
@sonichi
sonichi deleted the improve-reconnect-watcher branch March 30, 2026 18:02
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…ck (sonichi#57)

Web UI: After 5 failed reconnect attempts, show specific causes
(GEMINI_API_KEY, startup.sh, port) but keep retrying indefinitely.
Previously stopped after 5 attempts.

Watcher: Remove pgrep dedup guard that prevented the pre-check from
running. Pre-check now correctly finds existing .txt files before
entering fswatch wait loop.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
john-the-dev added a commit to john-the-dev/sutando that referenced this pull request Jul 7, 2026
…l body

Addresses review on sonichi#2010 (qingyun-wu):
- SKILL.md: instruct the agent to ANNOUNCE the log attachment before sending
  ("I'll attach recent diagnostic logs unless you'd rather I didn't") and honor
  --no-logs — an informed opt-out for voice/chat, matching sonichi#57's checkbox.
- report-feedback.py: add _redact() backstop over the log excerpt (Bearer
  tokens, token=/api_key=/secret= values, common key formats sk-/xoxb-/ghp_/
  AKIA, and the home-dir username). Private-Slack mirror makes this non-blocking,
  but /api/feedback can also open GitHub issues, so scrub before it leaves.
- SKILL.md access-tier: state explicitly that non-owner Discord/Slack/Telegram
  route to sandboxed codex (no cloud token) and cannot reach this skill, so a
  non-owner can't ship the owner's logs into an issue.

Also fixes a real bug found while testing: an empty --body sent body:null, which
/api/feedback rejects (400 invalid_payload, 'Expected string, received null').
Now sends a string (falls back to the title), matching the desktop form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmsnVs5ffwHXdrQsLoZUCb
qingyun-wu added a commit that referenced this pull request Jul 9, 2026
…2010)

* feat(skills): report-feedback skill (single reporting path)

The user can report a bug / feature request / feedback by asking Sutando from
ANY surface — chat, Discord, Telegram, or voice (via task delegation). One
skill, not a per-surface tool, so there's no duplication.

- skills/report-feedback/report-feedback.py posts to the cloud /api/feedback
  route (mirrors into GitHub issues) with kind/severity/title/body + a
  diagnostic context (platform + a tail of recent <workspace>/logs).
- Reads cloud-auth.json on disk (falls back to the metering env the desktop
  injects); prints NOT_SIGNED_IN + exits 2 when signed out, ERROR on API
  failure — never leaves the agent guessing.
- Owner-tier only.

Replaces the earlier voice-only report_issue inline tool (dropped) per owner:
the skill covers voice too via delegation, avoiding a duplicate capability.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Zp1T5KEdGsfNRFSLhY3fK

* fix(report-feedback): read the cloud token the desktop actually writes

The auth probe used the wrong path (state/auth/) and required a `signedIn`
field. The desktop's readCloudAuth (electron/ipc.cjs) stores the record at
<workspace>/cloud-auth.json and treats "signed in" as simply having a `token`
(no signedIn field). Match that: read <workspace>/cloud-auth.json, also probe
the packaged-app canonical workspace (~/.sutando/repo/workspace) so the skill
finds the token across the dev/packaged split, and gate on token presence.

Verified end-to-end: a signed-in submission returns 200 and lands in the
feedback funnel + mirrored GitHub issue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Zp1T5KEdGsfNRFSLhY3fK

* fix(report-feedback): announce+redact logs, harden tier note, fix null body

Addresses review on #2010 (qingyun-wu):
- SKILL.md: instruct the agent to ANNOUNCE the log attachment before sending
  ("I'll attach recent diagnostic logs unless you'd rather I didn't") and honor
  --no-logs — an informed opt-out for voice/chat, matching #57's checkbox.
- report-feedback.py: add _redact() backstop over the log excerpt (Bearer
  tokens, token=/api_key=/secret= values, common key formats sk-/xoxb-/ghp_/
  AKIA, and the home-dir username). Private-Slack mirror makes this non-blocking,
  but /api/feedback can also open GitHub issues, so scrub before it leaves.
- SKILL.md access-tier: state explicitly that non-owner Discord/Slack/Telegram
  route to sandboxed codex (no cloud token) and cannot reach this skill, so a
  non-owner can't ship the owner's logs into an issue.

Also fixes a real bug found while testing: an empty --body sent body:null, which
/api/feedback rejects (400 invalid_payload, 'Expected string, received null').
Now sends a string (falls back to the title), matching the desktop form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmsnVs5ffwHXdrQsLoZUCb

* fix(report-feedback): probe state/auth/cloud-auth.json (M1 canonical) first

Post-M1 the cloud auth record lives at <workspace>/state/auth/cloud-auth.json.
Add it as the first probe; retain the pre-M1 root path as a 30-day reader
fallback per the workspace contract. Mirror the same M1+fallback pair for the
packaged-app workspace so the skill finds the token regardless of checkout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EL2XBcV3RsWhevzTPtW4jw

* fix(report-feedback): match real AWS access keys in _redact() (no separator)

AKIA keys have no separator between the prefix and body (AKIAIOSFODNN7EXAMPLE),
so the shared [_-] pattern never matched them. Split out a dedicated
AKIA[A-Z0-9]{16} pattern that fires correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EL2XBcV3RsWhevzTPtW4jw

* fix(report-feedback): redact Slack app tokens

* test(report-feedback): cover main flow, cloud-auth branches, and logs excerpt

Raises diff coverage on report-feedback.py from 35% to 100%, clearing the
Coverage Gate on #2010. Adds tests for resolve_workspace (import + fallback),
the read_cloud_auth metering-env / dedup / malformed-file branches,
logs_excerpt (present / absent / unreadable), and main() (blank-title exit,
not-signed-in exit, successful POST with and without logs, HTTPError and
generic-error exits) via mocked urlopen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DC4oxV4ZwnKTKvE6Hb28Yo

* fix(report-feedback): redact Google API keys

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Qingyun Wu <qingyun0327@gmail.com>
john-the-dev added a commit to john-the-dev/sutando that referenced this pull request Jul 9, 2026
…onichi#2010)

* feat(skills): report-feedback skill (single reporting path)

The user can report a bug / feature request / feedback by asking Sutando from
ANY surface — chat, Discord, Telegram, or voice (via task delegation). One
skill, not a per-surface tool, so there's no duplication.

- skills/report-feedback/report-feedback.py posts to the cloud /api/feedback
  route (mirrors into GitHub issues) with kind/severity/title/body + a
  diagnostic context (platform + a tail of recent <workspace>/logs).
- Reads cloud-auth.json on disk (falls back to the metering env the desktop
  injects); prints NOT_SIGNED_IN + exits 2 when signed out, ERROR on API
  failure — never leaves the agent guessing.
- Owner-tier only.

Replaces the earlier voice-only report_issue inline tool (dropped) per owner:
the skill covers voice too via delegation, avoiding a duplicate capability.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Zp1T5KEdGsfNRFSLhY3fK

* fix(report-feedback): read the cloud token the desktop actually writes

The auth probe used the wrong path (state/auth/) and required a `signedIn`
field. The desktop's readCloudAuth (electron/ipc.cjs) stores the record at
<workspace>/cloud-auth.json and treats "signed in" as simply having a `token`
(no signedIn field). Match that: read <workspace>/cloud-auth.json, also probe
the packaged-app canonical workspace (~/.sutando/repo/workspace) so the skill
finds the token across the dev/packaged split, and gate on token presence.

Verified end-to-end: a signed-in submission returns 200 and lands in the
feedback funnel + mirrored GitHub issue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Zp1T5KEdGsfNRFSLhY3fK

* fix(report-feedback): announce+redact logs, harden tier note, fix null body

Addresses review on sonichi#2010 (qingyun-wu):
- SKILL.md: instruct the agent to ANNOUNCE the log attachment before sending
  ("I'll attach recent diagnostic logs unless you'd rather I didn't") and honor
  --no-logs — an informed opt-out for voice/chat, matching sonichi#57's checkbox.
- report-feedback.py: add _redact() backstop over the log excerpt (Bearer
  tokens, token=/api_key=/secret= values, common key formats sk-/xoxb-/ghp_/
  AKIA, and the home-dir username). Private-Slack mirror makes this non-blocking,
  but /api/feedback can also open GitHub issues, so scrub before it leaves.
- SKILL.md access-tier: state explicitly that non-owner Discord/Slack/Telegram
  route to sandboxed codex (no cloud token) and cannot reach this skill, so a
  non-owner can't ship the owner's logs into an issue.

Also fixes a real bug found while testing: an empty --body sent body:null, which
/api/feedback rejects (400 invalid_payload, 'Expected string, received null').
Now sends a string (falls back to the title), matching the desktop form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmsnVs5ffwHXdrQsLoZUCb

* fix(report-feedback): probe state/auth/cloud-auth.json (M1 canonical) first

Post-M1 the cloud auth record lives at <workspace>/state/auth/cloud-auth.json.
Add it as the first probe; retain the pre-M1 root path as a 30-day reader
fallback per the workspace contract. Mirror the same M1+fallback pair for the
packaged-app workspace so the skill finds the token regardless of checkout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EL2XBcV3RsWhevzTPtW4jw

* fix(report-feedback): match real AWS access keys in _redact() (no separator)

AKIA keys have no separator between the prefix and body (AKIAIOSFODNN7EXAMPLE),
so the shared [_-] pattern never matched them. Split out a dedicated
AKIA[A-Z0-9]{16} pattern that fires correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EL2XBcV3RsWhevzTPtW4jw

* fix(report-feedback): redact Slack app tokens

* test(report-feedback): cover main flow, cloud-auth branches, and logs excerpt

Raises diff coverage on report-feedback.py from 35% to 100%, clearing the
Coverage Gate on sonichi#2010. Adds tests for resolve_workspace (import + fallback),
the read_cloud_auth metering-env / dedup / malformed-file branches,
logs_excerpt (present / absent / unreadable), and main() (blank-title exit,
not-signed-in exit, successful POST with and without logs, HTTPError and
generic-error exits) via mocked urlopen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DC4oxV4ZwnKTKvE6Hb28Yo

* fix(report-feedback): redact Google API keys

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Qingyun Wu <qingyun0327@gmail.com>
liususan091219 added a commit that referenced this pull request Jul 13, 2026
…ezone-aware now(UTC) (#2003)

* fix(friction-detector): replace deprecated datetime.utcnow() with timezone-aware now(UTC)

datetime.utcnow() was deprecated in Python 3.12 and scheduled for removal.
Both `now` and the parsed `updatedAt` are now timezone-aware (UTC), so their
difference is still correct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QB3qZQrwponfum2JFNvHeh

* test(friction-detector): cover check_github_issues() datetime comparison for diff-coverage gate

Adds two tests that mock subprocess.run to exercise the
timezone-aware datetime comparison path without a live gh token,
satisfying the >= 95% diff-coverage gate on PR #2003.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QB3qZQrwponfum2JFNvHeh

* fix(tests): fix start-cli, startup-env-token-persist, sutando-migrate for Ubuntu CI (#2029)

* test(shell-ci): fix two ubuntu-portability failures from shell-ci-known-failures

Two tests fail on ubuntu-latest CI (wired in by #1948) due to macOS-specific
tooling; both pass on macOS today but gate CI on Ubuntu.

start-cli-claude-config-dir.test.sh (2 failures):
1. Ambient CLAUDE_CONFIG_DIR leak — the test runner session has CLAUDE_CONFIG_DIR
   set, which leaked into the subprocess and broke "not set" assertion. Fix: add
   `unset CLAUDE_CONFIG_DIR` in setup_sandbox() alongside the HOME/SUTANDO_WORKSPACE
   exports that already clear the environment.
2. /var/folders vs /private/var symlink mismatch — macOS mktemp returns /var/folders/…
   but cd+pwd -P resolves to /private/var/folders/…; sutando-config.sh uses the
   physical path, so the expected vs actual CLAUDE_CONFIG_DIR differed. Fix: resolve
   SANDBOX to its real path (cd+pwd -P) immediately after mktemp.
3. Stale expected path — expected was keyed on $SUTANDO_WORKSPACE (deprecated v0.8
   env-var, no longer read by the resolver) instead of the config-derived
   $REPO_FAKE/workspace. Fix: align expected to what sutando-config.sh actually computes.

startup-env-token-persist.test.sh (would fail on Ubuntu):
- stat -f '%Lp' (BSD mode format) and stat -f '%m' (BSD mtime) are macOS-only.
  Fix: replace both with equivalent Python one-liners (stat.S_IMODE + os.stat.st_mtime)
  that work on macOS and Linux without any extra deps.

After this PR, both suites should be removable from tests/shell-ci-known-failures.txt
once that file lands (via #1948). Closes two items from #1961.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EL2XBcV3RsWhevzTPtW4jw

* test(shell-ci): fix sutando-migrate.test.sh — wrong DEST + BSD stat + pipefail exit

Three bugs caused all 7 assertions to fail:

1. Wrong DEST — test used `SUTANDO_WORKSPACE="$DEST" --respect-env` but the
   workspace resolver ignores $SUTANDO_WORKSPACE since #1440. The backup landed
   in the real workspace instead of the temp fixture. Fix: use
   SUTANDO_MIGRATE_DEST (the documented E2E test hook symmetric with
   SUTANDO_MIGRATE_SRC_{A,B,C}).

2. Early exit via set -e — `grep -E "^sutando-migrate: backup"` exits 1 when
   the dest is empty (the backup line reads "dest empty; placeholder backup →
   …"). With `set -uo pipefail`, the INITIAL_BACKUP_ID assignment aborted the
   script before the assertion block ran. Fix: broaden the pattern to
   "migration-backup-.*\.tar\.gz" and add `|| true`.

3. BSD-only stat — `stat -f %m` is macOS-only; GNU stat uses `-c '%Y'`.
   Affects mtime comparison for notes/shared.md on Ubuntu. Fix: Python
   `os.stat().st_mtime` (portable, already used in startup-env-token-persist
   and start-cli fixes on this branch).

All 14 assertions now pass locally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EL2XBcV3RsWhevzTPtW4jw

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Qingyun Wu <qingyun0327@gmail.com>

* feat(skills): report-feedback skill (report issues from any surface) (#2010)

* feat(skills): report-feedback skill (single reporting path)

The user can report a bug / feature request / feedback by asking Sutando from
ANY surface — chat, Discord, Telegram, or voice (via task delegation). One
skill, not a per-surface tool, so there's no duplication.

- skills/report-feedback/report-feedback.py posts to the cloud /api/feedback
  route (mirrors into GitHub issues) with kind/severity/title/body + a
  diagnostic context (platform + a tail of recent <workspace>/logs).
- Reads cloud-auth.json on disk (falls back to the metering env the desktop
  injects); prints NOT_SIGNED_IN + exits 2 when signed out, ERROR on API
  failure — never leaves the agent guessing.
- Owner-tier only.

Replaces the earlier voice-only report_issue inline tool (dropped) per owner:
the skill covers voice too via delegation, avoiding a duplicate capability.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Zp1T5KEdGsfNRFSLhY3fK

* fix(report-feedback): read the cloud token the desktop actually writes

The auth probe used the wrong path (state/auth/) and required a `signedIn`
field. The desktop's readCloudAuth (electron/ipc.cjs) stores the record at
<workspace>/cloud-auth.json and treats "signed in" as simply having a `token`
(no signedIn field). Match that: read <workspace>/cloud-auth.json, also probe
the packaged-app canonical workspace (~/.sutando/repo/workspace) so the skill
finds the token across the dev/packaged split, and gate on token presence.

Verified end-to-end: a signed-in submission returns 200 and lands in the
feedback funnel + mirrored GitHub issue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Zp1T5KEdGsfNRFSLhY3fK

* fix(report-feedback): announce+redact logs, harden tier note, fix null body

Addresses review on #2010 (qingyun-wu):
- SKILL.md: instruct the agent to ANNOUNCE the log attachment before sending
  ("I'll attach recent diagnostic logs unless you'd rather I didn't") and honor
  --no-logs — an informed opt-out for voice/chat, matching #57's checkbox.
- report-feedback.py: add _redact() backstop over the log excerpt (Bearer
  tokens, token=/api_key=/secret= values, common key formats sk-/xoxb-/ghp_/
  AKIA, and the home-dir username). Private-Slack mirror makes this non-blocking,
  but /api/feedback can also open GitHub issues, so scrub before it leaves.
- SKILL.md access-tier: state explicitly that non-owner Discord/Slack/Telegram
  route to sandboxed codex (no cloud token) and cannot reach this skill, so a
  non-owner can't ship the owner's logs into an issue.

Also fixes a real bug found while testing: an empty --body sent body:null, which
/api/feedback rejects (400 invalid_payload, 'Expected string, received null').
Now sends a string (falls back to the title), matching the desktop form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmsnVs5ffwHXdrQsLoZUCb

* fix(report-feedback): probe state/auth/cloud-auth.json (M1 canonical) first

Post-M1 the cloud auth record lives at <workspace>/state/auth/cloud-auth.json.
Add it as the first probe; retain the pre-M1 root path as a 30-day reader
fallback per the workspace contract. Mirror the same M1+fallback pair for the
packaged-app workspace so the skill finds the token regardless of checkout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EL2XBcV3RsWhevzTPtW4jw

* fix(report-feedback): match real AWS access keys in _redact() (no separator)

AKIA keys have no separator between the prefix and body (AKIAIOSFODNN7EXAMPLE),
so the shared [_-] pattern never matched them. Split out a dedicated
AKIA[A-Z0-9]{16} pattern that fires correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EL2XBcV3RsWhevzTPtW4jw

* fix(report-feedback): redact Slack app tokens

* test(report-feedback): cover main flow, cloud-auth branches, and logs excerpt

Raises diff coverage on report-feedback.py from 35% to 100%, clearing the
Coverage Gate on #2010. Adds tests for resolve_workspace (import + fallback),
the read_cloud_auth metering-env / dedup / malformed-file branches,
logs_excerpt (present / absent / unreadable), and main() (blank-title exit,
not-signed-in exit, successful POST with and without logs, HTTPError and
generic-error exits) via mocked urlopen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DC4oxV4ZwnKTKvE6Hb28Yo

* fix(report-feedback): redact Google API keys

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Qingyun Wu <qingyun0327@gmail.com>

* fix(voice+phone): add explicit local-machine identity to prevent cloud fabrication (#2014)

* fix(voice): add explicit local-machine identity to prevent cloud fabrication

Addresses #2011 — when asked where Sutando runs, Gemini was defaulting
to its training-data answer ("cloud-based AI"). Add an authoritative
system-instruction line so the model answers correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUA5TZgDa7VmFcNtPka38t

* test: update voice-behavior-anchors fixture for identity instruction

Regenerate the instructions hash after adding the local-machine identity
line to buildInstructions() (deliberate prompt change for #2011).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUA5TZgDa7VmFcNtPka38t

* fix(phone): add explicit local-machine identity to prevent cloud fabrication

Same issue as voice (#2011) — phone agent also lacked an authoritative
statement about running locally. Adds the same system-instruction line
to the inbound/outbound phone session path in buildPhoneInstructions().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUA5TZgDa7VmFcNtPka38t

* test: update phone-behavior-anchors fixture for identity instruction

Regenerate hashes after adding the local-machine identity line to
buildPhoneInstructions() (deliberate prompt change for #2011).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUA5TZgDa7VmFcNtPka38t

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Qingyun Wu <qingyun0327@gmail.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Qingyun Wu <qingyun0327@gmail.com>
Co-authored-by: Susan Xueqing Liu <xliu127@stevens.edu>
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