From e77b9f4bef2ba2101f16de14b068cd0a9870e3c4 Mon Sep 17 00:00:00 2001 From: Bruno Bornsztein Date: Tue, 9 Jun 2026 10:00:12 -0500 Subject: [PATCH] feat: proactively offer push notifications to existing GMs (plugin 1.4.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Existing GMs had no way to discover the task-event channel — they'd only get it if the user happened to run /gm-doctor. Ship a plugin SessionStart hook so the GM itself raises it. - hooks/hooks.json + hooks/channel-nudge.sh: a SessionStart hook (delivered by the plugin, so it reaches GMs that already exist once they update). It is scoped to taskyou-os GM dirs and self-disabling — it injects an additionalContext note only when the channel ISN'T deployed, prompting the GM to offer '/gm-doctor' to the user. Silent otherwise. - plugin.json: register hooks + bump 1.3.0 -> 1.4.0; marketplace.json synced. - README: document enabling marketplace auto-update (off by default for third-party) and the proactive offer. - qa harness: assert the nudge fires on a channel-less GM and stays silent on a channel-equipped GM / outside GM dirs. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 5 ++-- README.md | 4 ++++ hooks/channel-nudge.sh | 41 +++++++++++++++++++++++++++++++++ hooks/hooks.json | 14 +++++++++++ qa/run-qa.sh | 13 +++++++++++ 6 files changed, 76 insertions(+), 3 deletions(-) create mode 100755 hooks/channel-nudge.sh create mode 100644 hooks/hooks.json diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 65fa6a2..a61bcb0 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -11,7 +11,7 @@ { "name": "taskyou-os", "description": "Launch and manage AI agent teams on remote servers. Interactive setup walks you through everything — server provisioning, configuration, and deployment.", - "version": "1.3.0", + "version": "1.4.0", "author": { "name": "TaskYou" }, diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 7de9a1c..c0dbf57 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "taskyou-os", "description": "Launch and manage AI agent teams on remote servers. Interactive setup walks you through everything — server provisioning, configuration, and deployment.", - "version": "1.3.0", + "version": "1.4.0", "author": { "name": "TaskYou" }, @@ -9,5 +9,6 @@ "repository": "https://github.com/taskyou/taskyou-os", "license": "MIT", "keywords": ["agents", "automation", "taskyou", "remote", "productivity"], - "commands": "./.claude/commands" + "commands": "./.claude/commands", + "hooks": "./hooks/hooks.json" } diff --git a/README.md b/README.md index ed98aab..a22d29a 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,10 @@ The taskyou-os plugin updates through the Claude Code marketplace. Run: This pulls the latest version. Restart Claude Code afterward for changes to take effect. +**Tip: enable auto-update** so you get new features without checking manually. Auto-update is off by default for third-party marketplaces — turn it on via `/plugin` → **Marketplaces** → taskyou-os → *Enable auto-update*. Once on, Claude Code pulls new plugin versions for you and prompts you to `/reload-plugins`. + +When a new plugin version adds a feature your existing GM doesn't have yet (e.g. the task-event channel), the GM will **proactively offer to enable it** at launch — just say yes, and it runs `/gm-doctor` for you. No need to track releases. + ### Update a GM Installation Run `/taskyou-os:doctor` from any directory. It checks and updates everything automatically: diff --git a/hooks/channel-nudge.sh b/hooks/channel-nudge.sh new file mode 100755 index 0000000..942c8c9 --- /dev/null +++ b/hooks/channel-nudge.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# taskyou-os SessionStart hook (shipped by the plugin, so it reaches GMs that +# already exist once they update the plugin). +# +# Nudges a GM that predates the task-event channel to run /gm-doctor. It is: +# - SCOPED: only acts inside a rendered taskyou-os GM directory. +# - SELF-DISABLING: silent once the channel is deployed. +# Output: an `additionalContext` JSON object (exit 0) ONLY when the nudge +# applies; otherwise no output. The GM reads the note and surfaces it to the +# user conversationally. + +set -uo pipefail + +# SessionStart delivers JSON on stdin including `cwd`; prefer it, fall back to $PWD. +input="$(cat 2>/dev/null || true)" +cwd="$PWD" +if [ -n "$input" ] && command -v python3 >/dev/null 2>&1; then + parsed="$(printf '%s' "$input" | python3 -c 'import sys,json +try: print(json.load(sys.stdin).get("cwd","")) +except Exception: print("")' 2>/dev/null)" + [ -n "$parsed" ] && cwd="$parsed" +fi + +# Only act in a taskyou-os GM directory (rendered config + wrapper present). +[ -f "$cwd/config.env" ] || exit 0 +grep -q '^GM_ALIAS=' "$cwd/config.env" 2>/dev/null || exit 0 +[ -f "$cwd/bin/ty-remote" ] || exit 0 + +# Channel already deployed? Stay silent. +if [ -f "$cwd/channel/taskyou-channel.ts" ] && [ -f "$cwd/.mcp.json" ]; then + exit 0 +fi + +note='This taskyou-os GM does not have the task-event channel installed yet. Push notifications — task completed/blocked events that arrive automatically in this session instead of via a polling agent — are available in this plugin version. Briefly let the user know, once, that they can enable it by running /gm-doctor (it deploys the channel and updates the launch alias; the GM must then be restarted). If the user declines, do not bring it up again this session.' + +if command -v python3 >/dev/null 2>&1; then + printf '%s' "$note" | python3 -c 'import sys,json; print(json.dumps({"additionalContext": sys.stdin.read()}))' +else + printf '%s\n' "$note" +fi +exit 0 diff --git a/hooks/hooks.json b/hooks/hooks.json new file mode 100644 index 0000000..3fe8085 --- /dev/null +++ b/hooks/hooks.json @@ -0,0 +1,14 @@ +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/channel-nudge.sh" + } + ] + } + ] + } +} diff --git a/qa/run-qa.sh b/qa/run-qa.sh index d6bfd13..4102b45 100755 --- a/qa/run-qa.sh +++ b/qa/run-qa.sh @@ -97,6 +97,19 @@ grep -q 'The agents live on a remote server' "$GM_DIR/CLAUDE.md" && fail "CLAUDE grep -q 'daemon is running on this machine' "$SANDBOX/local.log" && pass "checklist shows local daemon step" || fail "checklist not local-aware" grep -q 'claude login' "$SANDBOX/local.log" && fail "checklist still tells you to 'claude login' on a server" || pass "checklist drops server-login steps" +# ── 1c. Plugin SessionStart nudge hook (existing-GM onboarding) ────────────── +echo; echo "[1c] plugin nudge hook — scoped + self-disabling" +NUDGE="$SRC/hooks/channel-nudge.sh" +# rendered GM HAS the channel → hook must stay silent +out=$(printf '{"cwd":"%s","source":"startup"}' "$GM_DIR" | bash "$NUDGE" 2>/dev/null) +[ -z "$out" ] && pass "nudge silent on a GM that already has the channel (self-disabling)" || fail "nudge fired on a channel-equipped GM" +# synthetic GM missing the channel → hook must nudge toward /gm-doctor +OLD="$SANDBOX/gm-old"; mkdir -p "$OLD/bin"; printf 'GM_ALIAS="x"\n' > "$OLD/config.env"; touch "$OLD/bin/ty-remote" +printf '{"cwd":"%s","source":"startup"}' "$OLD" | bash "$NUDGE" 2>/dev/null | grep -q 'gm-doctor' && pass "nudge fires on a GM missing the channel" || fail "nudge did not fire on a channel-less GM" +# non-GM dir → silent +out=$(printf '{"cwd":"%s","source":"startup"}' "$SANDBOX" | bash "$NUDGE" 2>/dev/null) +[ -z "$out" ] && pass "nudge silent outside a GM dir" || fail "nudge fired outside a GM dir" + # ── 2. Channel smoke test (PR #28's own test) ──────────────────────────────── echo; echo "[2] channel smoke-test (handshake + capabilities + tools)" cp "$SRC/templates/channel/smoke-test.ts" "$GM_DIR/channel/smoke-test.ts"