@garethdaine/agentops 0.9.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,246 @@
1
+ ---
2
+ name: worklog
3
+ description: Personal work log for tracking daily contributions with timestamps, context, and professional exports
4
+ ---
5
+
6
+ You are a personal work log assistant. You help the engineer track daily contributions with timestamps, git context, and professional exports.
7
+
8
+ **Before starting, check the feature flag:**
9
+ Run: `source hooks/feature-flags.sh && agentops_enterprise_enabled "work_journal"` — if disabled, inform the user: "Work journal commands are disabled. Enable with: /agentops:flags work_journal true" and stop.
10
+
11
+ ## CRITICAL RULE: Use AskUserQuestion Tool
12
+
13
+ You MUST use the `AskUserQuestion` tool for EVERY question in this command. DO NOT print questions as plain text or numbered option lists. Call the AskUserQuestion tool which renders a proper selection UI. This is a BLOCKING REQUIREMENT.
14
+
15
+ **Read the autonomy level** from `.agentops/flags.json` (key: `autonomy_level`). Default to `guided` if not set.
16
+ - `guided` — pause at weekly summary review and export confirmation
17
+ - `supervised` — pause after every entry creation and sync operation
18
+ - `autonomous` — proceed with minimal gates
19
+
20
+ Read `templates/work-journal/conventions.md` for shared schemas, storage layout, and integration protocols.
21
+
22
+ The user's input is: $ARGUMENTS
23
+
24
+ ---
25
+
26
+ ## Phase 0: Startup
27
+
28
+ ### Step 1: Load Configuration
29
+
30
+ Check if `.agentops/journal-config.json` exists.
31
+
32
+ **If it does NOT exist**, run first-run setup per conventions.md:
33
+ 1. `AskUserQuestion`: "What is your name?" — free text
34
+ 2. `AskUserQuestion`: "What is your role?" — free text
35
+ 3. `AskUserQuestion`: "What team or organisation are you in?" — free text
36
+ 4. `AskUserQuestion`: "Would you like to add contacts for sharing? (You can add more later)" — options: "Yes, add contacts now" / "Skip for now"
37
+ 5. If adding contacts, collect id, name, role, relationship, share_categories for each
38
+ 6. Detect integrations (Step 2 below) and ask about each detected one
39
+ 7. Write config to `.agentops/journal-config.json` using the schema from conventions.md
40
+
41
+ **If it exists**, read it and proceed.
42
+
43
+ ### Step 2: Detect Integrations
44
+
45
+ Run integration detection ONCE per conventions.md protocol:
46
+ 1. If config has `integrations.cortex.enabled: true`: attempt `mcp__cortex__cortex_status`. Set CORTEX_AVAILABLE accordingly.
47
+ 2. If config has `integrations.notion.enabled: true`: check if `mcp__notion__notion_search` is available. Set NOTION_AVAILABLE accordingly.
48
+ 3. If any Linear MCP tools are available: set LINEAR_AVAILABLE accordingly.
49
+ 4. Report detected integrations once: "Integrations: Cortex {status}, Notion {status}, Linear {status}" or "Running in local-only mode."
50
+ 5. Skip silently for any unavailable integration. Never fail due to missing integration.
51
+
52
+ ### Step 3: Route by Arguments
53
+
54
+ - **If `$ARGUMENTS` is provided:** Go to Phase 1 (Quick Entry Mode)
55
+ - **If `$ARGUMENTS` is empty:** Go to Phase 2 (Interactive Mode)
56
+
57
+ ---
58
+
59
+ ## Phase 1: Quick Entry Mode
60
+
61
+ When the user provides arguments, create an entry with ZERO prompts.
62
+
63
+ 1. **Determine today's date** in UTC (YYYY-MM-DD) and current time (HH:MM)
64
+ 2. **Auto-detect git context:** run `git branch --show-current` and `git log --oneline -3` to capture branch name and recent commits
65
+ 3. **Auto-tag:** extract technology names, project names, domain concepts, and action types from the description
66
+ 4. **Create/append to daily file:**
67
+ - Path: `.agentops/journal/worklog/YYYY/MM/YYYY-MM-DD.md`
68
+ - Run `mkdir -p` on the directory before writing
69
+ - If file does not exist, create with frontmatter per conventions.md (date, entry_count: 1)
70
+ - If file exists, read it, increment entry_count in frontmatter
71
+ - Append entry section:
72
+ ```
73
+ ### HH:MM — General
74
+
75
+ **What:** {$ARGUMENTS}
76
+ **Impact:** Medium
77
+ **Collaborators:** —
78
+ **Context:** Branch: {branch}, Recent commits: {commits}
79
+ **Notes:** —
80
+ ```
81
+ 5. **Update index:** Update `.agentops/journal/worklog/index.json` using atomic write protocol from conventions.md (write to .tmp, verify, mv)
82
+ 6. **Cortex sync:** If CORTEX_AVAILABLE and enabled, write episodic memory with tags ["worklog", "general", "medium"]. On failure: log warning, continue.
83
+ 7. **Notion sync:** If NOTION_AVAILABLE and enabled, push entry to worklog database. On failure: log warning, continue.
84
+ 8. **Auto-commit:** If `preferences.auto_commit` is true in config:
85
+ - First, check whether `.agentops/journal/` is tracked by git (i.e., not excluded by `.gitignore`).
86
+ - If it is tracked, run: `git add .agentops/journal/ && git commit -m "docs(worklog): add entry — YYYY-MM-DD"`.
87
+ - If it is gitignored, skip auto-commit and warn: "Auto-commit skipped because `.agentops/journal/` is gitignored. To enable, unignore this path in `.gitignore`."
88
+ 9. **Confirm:** "Entry logged at HH:MM. {integration status if any synced}"
89
+
90
+ ---
91
+
92
+ ## Phase 2: Interactive Mode
93
+
94
+ Use `AskUserQuestion` to present the main menu:
95
+ - question: "What would you like to do?"
96
+ - header: "Work Log"
97
+ - options:
98
+ - {label: "Add entry", description: "Log a new work contribution with details"}
99
+ - {label: "Review this week", description: "See all entries from the current week"}
100
+ - {label: "Generate weekly summary", description: "Create a structured weekly summary document"}
101
+ - {label: "Export work log", description: "Export entries as PDF or DOCX"}
102
+ - {label: "Search past entries", description: "Search across all work log entries"}
103
+ - {label: "Configure settings", description: "Update identity, contacts, or integration preferences"}
104
+
105
+ Route to the appropriate section below based on the user's selection.
106
+
107
+ ---
108
+
109
+ ## Action: Add Entry
110
+
111
+ ### Step 1: Collect Details
112
+
113
+ Use `AskUserQuestion` for each field:
114
+
115
+ 1. **Description:** "What did you accomplish?" — free text
116
+ 2. **Category:** "What type of work was this?"
117
+ - options: "Feature delivery", "Architecture decision", "Bug fix", "Code review", "Documentation", "Mentoring/support", "Client interaction", "Process improvement", "Research/spike", "Other"
118
+ 3. **Impact:** "What was the impact level?"
119
+ - options: {label: "High", description: "Significant milestone, shipped feature, or critical fix"}, {label: "Medium", description: "Meaningful progress or contribution"}, {label: "Low", description: "Routine task or minor update"}
120
+ 4. **Collaborators:** "Who did you work with? (or type 'none')" — free text
121
+ 5. **Notes:** "Any additional notes for future reference? (or type 'skip')" — free text
122
+
123
+ ### Step 2: Create Entry
124
+
125
+ 1. Determine today's date and current time in UTC (YYYY-MM-DD, HH:MM)
126
+ 2. Auto-detect git context: branch and recent commits
127
+ 3. Auto-tag from description content per conventions.md
128
+ 4. Create/append to `.agentops/journal/worklog/YYYY/MM/YYYY-MM-DD.md` using the entry section format from conventions.md:
129
+ ```
130
+ ### HH:MM — {Category}
131
+
132
+ **What:** {description}
133
+ **Impact:** {impact}
134
+ **Collaborators:** {collaborators}
135
+ **Context:** Branch: {branch}, Recent commits: {commits}
136
+ **Notes:** {notes}
137
+ ```
138
+ 5. Update `.agentops/journal/worklog/index.json` using atomic write protocol
139
+ 6. Sync to Cortex (episodic, tags: ["worklog", "{category-kebab}", "{impact-lower}"]) if available
140
+ 7. Sync to Notion worklog database if available
141
+ 8. Auto-commit if `preferences.auto_commit` is true
142
+ 9. Confirm: "Entry logged at HH:MM under {category}."
143
+
144
+ ---
145
+
146
+ ## Action: Review This Week
147
+
148
+ 1. Determine the current ISO week's date range (Monday to today)
149
+ 2. Read all daily files from `.agentops/journal/worklog/YYYY/MM/` for dates in this range
150
+ 3. If no entries found, inform the user: "No entries found for this week yet."
151
+ 4. Present entries grouped by day, showing time, category, description, and impact
152
+ 5. Show summary stats: total entries, breakdown by category, high-impact items highlighted
153
+
154
+ ---
155
+
156
+ ## Action: Generate Weekly Summary
157
+
158
+ 1. Determine the ISO week number and date range
159
+ 2. Read all daily entry files for that week
160
+ 3. If no entries found, inform the user and return to menu
161
+ 4. Run `git log --oneline --since={monday} --until={sunday}` for git contribution stats
162
+ 5. Read identity from journal-config.json for the author field
163
+ 6. Generate `.agentops/journal/worklog/weekly/YYYY-Wnn.md` using the weekly summary template from conventions.md:
164
+ - Key accomplishments, metrics, decisions made, git stats, notes for next week
165
+ 7. Run `mkdir -p` on the weekly directory before writing
166
+ 8. Update index with weekly summary entry
167
+ 9. Sync to Cortex (episodic, tags: ["worklog", "weekly-summary"]) if available
168
+ 10. Sync to Notion weekly reviews database if available
169
+ 11. **If autonomy_level is `guided` or `supervised`:** Use `AskUserQuestion`: "Weekly summary generated. Would you like to review it?" — options: "Yes, show me", "No, looks good"
170
+ 12. Auto-commit if enabled
171
+
172
+ ---
173
+
174
+ ## Action: Export Work Log
175
+
176
+ 1. Use `AskUserQuestion`: "What date range would you like to export?"
177
+ - options: "This week", "This month", "Last 30 days", "This quarter", "Custom range"
178
+ 2. If "Custom range", use `AskUserQuestion` to collect start and end dates
179
+ 3. Use `AskUserQuestion`: "What format?"
180
+ - options: {label: "PDF", description: "Professional PDF document"}, {label: "DOCX", description: "Word document for editing"}, {label: "Both", description: "Generate PDF and DOCX"}
181
+ 4. Collect entries from the date range
182
+ 5. Generate a styled markdown document with identity from config, then export using the fallback chain from conventions.md:
183
+ - **Pandoc (primary):** check `which pandoc`, use `pandoc input.md -o output.{ext}`
184
+ - **Playwright (fallback):** render styled HTML, use `page.pdf()`
185
+ - **HTML (last resort):** generate styled HTML, inform user: "Install pandoc for direct PDF/DOCX export: https://pandoc.org/installing.html"
186
+ 6. Save to `.agentops/journal/exports/` with descriptive filename
187
+ 7. Confirm: "Exported to {filepath}"
188
+
189
+ ---
190
+
191
+ ## Action: Search Past Entries
192
+
193
+ 1. Use `AskUserQuestion`: "What are you searching for?" — free text
194
+ 2. Search `.agentops/journal/worklog/index.json` for matching entries (title, tags, category)
195
+ 3. Also grep daily entry files for the search term
196
+ 4. Present results grouped by date, showing category, description, and file path
197
+ 5. If results found, use `AskUserQuestion`: "Would you like to view any entry in full?" — list top results as options plus "No, I have what I need"
198
+ 6. Display selected entry if requested
199
+
200
+ ---
201
+
202
+ ## Action: Configure Settings
203
+
204
+ Use `AskUserQuestion`: "What would you like to configure?"
205
+ - options: "Update identity (name, role, team)", "Manage contacts", "Integration settings", "Toggle auto-commit", "Back to menu"
206
+
207
+ For each option, use `AskUserQuestion` to collect updated values, then write the updated config to `.agentops/journal-config.json`.
208
+
209
+ For "Toggle auto-commit": read current value from config, present current state, use `AskUserQuestion` to confirm toggle. Update `preferences.auto_commit` in config.
210
+
211
+ For "Integration settings": show current integration status, offer to enable/disable each detected integration.
212
+
213
+ ---
214
+
215
+ ## Index Management
216
+
217
+ All index operations follow conventions.md atomic write protocol:
218
+
219
+ 1. Read existing `.agentops/journal/worklog/index.json` (or initialise empty if missing)
220
+ 2. If JSON parsing fails: rebuild from markdown file frontmatter per conventions.md rebuild protocol
221
+ 3. Add/update the entry in the entries array
222
+ 4. Write to `index.json.tmp`, read back to verify valid JSON, then `mv index.json.tmp index.json`
223
+ 5. On failure: leave original index intact, log warning, continue
224
+
225
+ ---
226
+
227
+ ## Notion Sync
228
+
229
+ When NOTION_AVAILABLE and enabled in config:
230
+
231
+ 1. If `integrations.notion.worklog_database_id` is null in config:
232
+ - Use `AskUserQuestion`: "Notion is connected but no worklog database is configured. Would you like to create one?"
233
+ - options: "Yes, create worklog database", "Skip Notion sync"
234
+ - If yes: create database with schema from conventions.md (Date, Category, Impact, Description, Collaborators, Tags), store the database ID in config
235
+ 2. Push entry to the worklog database
236
+ 3. On failure: log warning "Notion sync failed — entry saved locally", continue
237
+
238
+ ---
239
+
240
+ ## Error Handling
241
+
242
+ - If daily file write fails, retry once then inform user and continue
243
+ - If index update fails, log warning but do not fail the entry creation
244
+ - Integration sync failures NEVER block local operations
245
+ - If git auto-commit fails, log warning and continue
246
+ - Never leave a partially written file — use atomic writes where possible
package/commands/flags.md CHANGED
@@ -31,6 +31,9 @@ Available flags:
31
31
  - `lockfile_audit_enabled` (bool) — Scan lockfiles for Unicode anomalies and suspicious registries
32
32
  - `enforcement_mode` ("advisory"|"blocking") — Advisory uses "ask", blocking uses "deny"
33
33
 
34
+ Dashboard flags:
35
+ - `dashboard_enabled` (bool) — Enable the Agent Office Dashboard auto-launch on session start
36
+
34
37
  Enterprise extension flags:
35
38
  - `enterprise_scaffold` (bool) — Enable project scaffolding system
36
39
  - `ai_workflows` (bool) — Enable AI-first workflow commands
@@ -32,7 +32,6 @@ fi
32
32
  FILENAME=$(basename "$FILE_PATH")
33
33
  EXTENSION="${FILENAME##*.}"
34
34
  DIR=$(dirname "$FILE_PATH")
35
- CWD=$(echo "$INPUT" | jq -r '.cwd // "."' 2>/dev/null)
36
35
 
37
36
  MESSAGES=()
38
37
 
@@ -11,7 +11,7 @@ mkdir -p "$LOG_DIR" 2>/dev/null
11
11
  TS=$(date -u +%FT%TZ)
12
12
  SESSION=$(echo "$INPUT" | jq -r '.session_id // "unknown"' 2>/dev/null) || SESSION="unknown"
13
13
  EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // "unknown"' 2>/dev/null) || EVENT="unknown"
14
- TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null) || TOOL=""
14
+ TOOL=$(echo "$INPUT" | jq -r '.tool_name // "unknown"' 2>/dev/null) || TOOL="unknown"
15
15
  TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input // {} | tostring | .[:500]' 2>/dev/null) || TOOL_INPUT="{}"
16
16
 
17
17
  # Redact secrets using canonical shared function
@@ -31,7 +31,7 @@ CODE_TRACKER="${STATE_DIR}/modified-files.txt"
31
31
  [ ! -f "$CODE_TRACKER" ] && exit 0
32
32
 
33
33
  # Count source code files only
34
- CODE_COUNT=$(sort -u "$CODE_TRACKER" 2>/dev/null | grep -cE "$SOURCE_CODE_EXTENSIONS" || echo 0)
34
+ CODE_COUNT=$(sort -u "$CODE_TRACKER" 2>/dev/null | grep -cE "$SOURCE_CODE_EXTENSIONS") || CODE_COUNT=0
35
35
 
36
36
  # After threshold source code files modified, trigger delegation
37
37
  if [ "$CODE_COUNT" -ge "$AGENTOPS_DELEGATE_THRESHOLD" ]; then
@@ -9,7 +9,6 @@ INPUT=$(cat) || exit 0
9
9
  TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null) || exit 0
10
10
 
11
11
  SENSITIVE_EXTENSIONS='\.(env|pem|key|crt|secret|p12|pfx|credential|token|password|ssh)'
12
- SENSITIVE_CONFIG='\.(json|yaml|yml|toml|cfg|ini)$'
13
12
 
14
13
  # Handle Read tool — warn when reading sensitive files
15
14
  if [ "$TOOL" = "Read" ]; then
@@ -0,0 +1,146 @@
1
+ #!/bin/bash
2
+ set -uo pipefail
3
+
4
+ # SessionStart hook: register session and auto-launch Agent Office Dashboard.
5
+ # Reads JSON on stdin: {"session_id":"...","hook_event_name":"SessionStart","cwd":"..."}
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ source "$SCRIPT_DIR/flag-utils.sh"
9
+
10
+ INPUT=$(cat) || exit 0
11
+ CWD=$(echo "$INPUT" | jq -r '.cwd // "."' 2>/dev/null) || CWD="."
12
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""' 2>/dev/null) || SESSION_ID=""
13
+ export CLAUDE_PROJECT_DIR="$CWD"
14
+
15
+ # Check dashboard_enabled flag — exit silently if disabled
16
+ agentops_dashboard_enabled || exit 0
17
+
18
+ # ── Session Registry ─────────────────────────────────────────────────────────
19
+ SAFE_HOME="${HOME:-$(cd ~ 2>/dev/null && pwd)}"
20
+ if [ -z "$SAFE_HOME" ]; then
21
+ exit 0
22
+ fi
23
+ REGISTRY_DIR="${SAFE_HOME}/.agentops"
24
+ REGISTRY_FILE="$REGISTRY_DIR/active-sessions.jsonl"
25
+ mkdir -p "$REGISTRY_DIR" 2>/dev/null
26
+
27
+ # Skip registry write if session_id is empty
28
+ [ -z "$SESSION_ID" ] && exit 0
29
+
30
+ # Append session metadata
31
+ jq -nc \
32
+ --arg sid "$SESSION_ID" \
33
+ --arg dir "$CWD" \
34
+ --arg ts "$(date -u +%FT%TZ)" \
35
+ --arg pid "$$" \
36
+ '{session_id:$sid, project_dir:$dir, started_at:$ts, pid:($pid|tonumber)}' \
37
+ >> "$REGISTRY_FILE" 2>/dev/null
38
+
39
+ # ── Dashboard Launch ─────────────────────────────────────────────────────────
40
+ STATE_DIR="$CWD/.agentops"
41
+ PID_FILE="$STATE_DIR/dashboard.pid"
42
+ PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
43
+
44
+ mkdir -p "$STATE_DIR" 2>/dev/null
45
+
46
+ # Ports used by the dashboard
47
+ RELAY_PORT=3099
48
+ NEXT_PORT=3100
49
+
50
+ # Quick check: if dashboard is already running with valid PID file and both ports LISTENING, exit
51
+ if [ -f "$PID_FILE" ] && lsof -iTCP:"$NEXT_PORT" -sTCP:LISTEN >/dev/null 2>&1 && lsof -iTCP:"$RELAY_PORT" -sTCP:LISTEN >/dev/null 2>&1; then
52
+ exit 0
53
+ fi
54
+
55
+ # Detach the full cleanup + launch sequence so the hook returns immediately.
56
+ # This avoids the hook timeout (5s) killing the launch mid-flight.
57
+ (
58
+ # Kill stale server processes on dashboard ports (only LISTEN sockets, not browser clients)
59
+ stale=false
60
+ for port in $RELAY_PORT $NEXT_PORT; do
61
+ pids=$(lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null) || true
62
+ if [ -n "$pids" ]; then
63
+ stale=true
64
+ echo "$pids" | xargs kill 2>/dev/null || true
65
+ fi
66
+ done
67
+
68
+ if [ "$stale" = true ]; then
69
+ rm -f "$PID_FILE"
70
+ # Wait for ports to actually be released (up to 5 seconds)
71
+ attempts=0
72
+ while [ $attempts -lt 10 ]; do
73
+ if ! lsof -tiTCP:"$RELAY_PORT" -sTCP:LISTEN >/dev/null 2>&1 && ! lsof -tiTCP:"$NEXT_PORT" -sTCP:LISTEN >/dev/null 2>&1; then
74
+ break
75
+ fi
76
+ sleep 0.5
77
+ attempts=$((attempts + 1))
78
+ done
79
+ # Force-kill if still hanging after graceful attempt
80
+ if [ $attempts -ge 10 ]; then
81
+ for port in $RELAY_PORT $NEXT_PORT; do
82
+ pids=$(lsof -tiTCP:"$port" -sTCP:LISTEN 2>/dev/null) || true
83
+ [ -n "$pids" ] && echo "$pids" | xargs kill -9 2>/dev/null || true
84
+ done
85
+ sleep 1
86
+ fi
87
+ fi
88
+
89
+ # Ensure both ports are free before launching (handles TIME_WAIT / slow teardown)
90
+ for port in $RELAY_PORT $NEXT_PORT; do
91
+ attempts=0
92
+ while lsof -tiTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1 && [ $attempts -lt 10 ]; do
93
+ sleep 0.5
94
+ attempts=$((attempts + 1))
95
+ done
96
+ done
97
+
98
+ # Launch relay + Next.js in background
99
+ DASHBOARD_BIN="$PLUGIN_ROOT/dashboard/node_modules/.bin"
100
+ LOG_DIR="$STATE_DIR"
101
+ DASHBOARD_DIR="$PLUGIN_ROOT/dashboard"
102
+
103
+ # Relay — must cd to CWD so relay.ts process.cwd() resolves .agentops/ correctly
104
+ nohup bash -c "cd '$CWD' && exec '$DASHBOARD_BIN/tsx' '$DASHBOARD_DIR/server/relay.ts'" > "$LOG_DIR/relay.log" 2>&1 &
105
+ RELAY_PID=$!
106
+
107
+ # Next.js — must run from dashboard dir for Turbopack to find src/app/
108
+ nohup bash -c "cd '$DASHBOARD_DIR' && exec '$DASHBOARD_BIN/next' dev --port $NEXT_PORT" > "$LOG_DIR/next.log" 2>&1 &
109
+ NEXT_PID=$!
110
+
111
+ echo "$RELAY_PID $NEXT_PID" > "$PID_FILE"
112
+
113
+ # Verify processes started — clean up if either died immediately
114
+ sleep 1
115
+ if ! kill -0 "$RELAY_PID" 2>/dev/null; then
116
+ kill "$NEXT_PID" 2>/dev/null || true
117
+ rm -f "$PID_FILE"
118
+ exit 0
119
+ fi
120
+ if ! kill -0 "$NEXT_PID" 2>/dev/null; then
121
+ kill "$RELAY_PID" 2>/dev/null || true
122
+ rm -f "$PID_FILE"
123
+ exit 0
124
+ fi
125
+
126
+ # Wait for the server to be ready, then open browser if no client is already connected
127
+ for _i in $(seq 1 30); do
128
+ if curl -s -o /dev/null "http://localhost:$NEXT_PORT" 2>/dev/null; then
129
+ # Query relay for connected client count — skip browser open if a tab is already open
130
+ CLIENT_COUNT=$(curl -s "http://localhost:$RELAY_PORT/api/clients" 2>/dev/null | jq -r '.count // 0' 2>/dev/null) || CLIENT_COUNT=0
131
+ if [ "$CLIENT_COUNT" -gt 0 ] 2>/dev/null; then
132
+ break
133
+ fi
134
+ if command -v open >/dev/null 2>&1; then
135
+ open "http://localhost:$NEXT_PORT"
136
+ elif command -v xdg-open >/dev/null 2>&1; then
137
+ xdg-open "http://localhost:$NEXT_PORT"
138
+ fi
139
+ break
140
+ fi
141
+ sleep 0.5
142
+ done
143
+ ) </dev/null >/dev/null 2>&1 &
144
+ disown 2>/dev/null || true
145
+
146
+ exit 0
@@ -29,7 +29,8 @@ agentops_enforcement_action() {
29
29
  echo "allow"
30
30
  return
31
31
  fi
32
- local MODE=$(agentops_mode)
32
+ local MODE
33
+ MODE=$(agentops_mode)
33
34
  if [ "$MODE" = "blocking" ]; then
34
35
  echo "deny"
35
36
  else
@@ -51,7 +52,8 @@ agentops_fail_closed() {
51
52
 
52
53
  # Returns "block" in blocking mode, "approve" in advisory mode (for Stop hooks).
53
54
  agentops_stop_action() {
54
- local MODE=$(agentops_mode)
55
+ local MODE
56
+ MODE=$(agentops_mode)
55
57
  if [ "$MODE" = "blocking" ]; then
56
58
  echo "block"
57
59
  else
@@ -32,6 +32,10 @@ source "${_AGENTOPS_LIB_DIR}/evolve-lib.sh"
32
32
  # Unicode anomalies, suspicious registries, and
33
33
  # malformed integrity hashes.
34
34
 
35
+ # ── Dashboard Flags ────────────────────────────────────────────────────────
36
+ # dashboard_enabled Controls auto-launch of Agent Office Dashboard
37
+ # on session start. Default: true.
38
+
35
39
  # ── Enterprise Extension Flags ──────────────────────────────────────────────
36
40
  # These flags gate the enterprise delivery framework capabilities.
37
41
  # All default to "true" via agentops_flag's default mechanism.
@@ -45,3 +45,9 @@ agentops_enterprise_enabled() {
45
45
  local FLAG="$1"
46
46
  [ "$(agentops_flag "$FLAG" "true")" = "true" ]
47
47
  }
48
+
49
+ # Dashboard flag — controls auto-launch of Agent Office Dashboard.
50
+ # Usage: agentops_dashboard_enabled && launch_dashboard
51
+ agentops_dashboard_enabled() {
52
+ [ "$(agentops_flag "dashboard_enabled" "true")" = "true" ]
53
+ }
package/hooks/hooks.json CHANGED
@@ -11,7 +11,8 @@
11
11
  { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/budget-check.sh", "timeout": 10 },
12
12
  { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/unicode-scan-session.sh", "timeout": 30 },
13
13
  { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/integrity-verify.sh", "timeout": 15 },
14
- { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/lockfile-audit.sh", "timeout": 15 }
14
+ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/lockfile-audit.sh", "timeout": 15 },
15
+ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/dashboard-launch.sh", "timeout": 5 }
15
16
  ]
16
17
  }
17
18
  ],
@@ -54,6 +55,12 @@
54
55
  "hooks": [
55
56
  { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/unicode-firewall.sh", "timeout": 5 }
56
57
  ]
58
+ },
59
+ {
60
+ "matcher": "Bash|Read|Write|Edit|Glob|Grep|WebFetch|WebSearch|mcp__.*",
61
+ "hooks": [
62
+ { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/telemetry.sh", "timeout": 10, "async": true }
63
+ ]
57
64
  }
58
65
  ],
59
66
  "PostToolUse": [
@@ -27,7 +27,7 @@ if [ "$EVENT" = "PostToolUse" ]; then
27
27
  [ -z "$HASH" ] && exit 0
28
28
 
29
29
  # Make path relative to project for portability
30
- REL_PATH="${FILE_PATH#$PROJECT_DIR/}"
30
+ REL_PATH="${FILE_PATH#"$PROJECT_DIR"/}"
31
31
 
32
32
  SESSION=$(echo "$INPUT" | jq -r '.session_id // "unknown"' 2>/dev/null)
33
33
  TS=$(date -u +%FT%TZ)
@@ -69,7 +69,7 @@ if [ "$EVENT" = "SessionStart" ]; then
69
69
 
70
70
  if [ "$ACTUAL" != "$EXPECTED" ]; then
71
71
  MISMATCH_COUNT=$((MISMATCH_COUNT + 1))
72
- REL="${ABS#$PROJECT_DIR/}"
72
+ REL="${ABS#"$PROJECT_DIR"/}"
73
73
  MISMATCHES="${MISMATCHES}\n - ${REL} (expected: ${EXPECTED:0:12}... got: ${ACTUAL:0:12}...)"
74
74
  fi
75
75
  done
@@ -46,7 +46,7 @@ done
46
46
 
47
47
  # ── Check 1: Unicode anomalies in lockfiles ─────────────────────────────────
48
48
  for LF in "${LOCKFILES[@]}"; do
49
- REL="${LF#$PROJECT_DIR/}"
49
+ REL="${LF#"$PROJECT_DIR"/}"
50
50
 
51
51
  # Invisible Unicode check
52
52
  if unicode_detect_file "$LF"; then
@@ -1,6 +1,7 @@
1
1
  #!/bin/bash
2
2
  # Shared patterns, thresholds, and protected path constants.
3
3
  # Sourced by: feature-flags.sh (facade)
4
+ # shellcheck disable=SC2034 # Shared constants file; variables may be used by sourcing scripts
4
5
 
5
6
  # ── Shared thresholds ────────────────────────────────────────────────────────
6
7
  # Number of modified files before plan/test/compliance gates trigger.
@@ -3,10 +3,33 @@ set -uo pipefail
3
3
 
4
4
  INPUT=$(cat) || exit 0
5
5
  CWD=$(echo "$INPUT" | jq -r '.cwd // "."' 2>/dev/null) || CWD="."
6
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // ""' 2>/dev/null) || SESSION_ID=""
6
7
  STATE_DIR="${CWD}/.agentops"
7
8
 
8
9
  mkdir -p "$STATE_DIR" 2>/dev/null
9
10
 
11
+ # ── Session Registry Cleanup ─────────────────────────────────────────────────
12
+ # Remove previous session entry from the global active-sessions registry.
13
+ SAFE_HOME="${HOME:-$(cd ~ 2>/dev/null && pwd)}"
14
+ REGISTRY_FILE="${SAFE_HOME}/.agentops/active-sessions.jsonl"
15
+ if [ -n "$SESSION_ID" ] && [ -f "$REGISTRY_FILE" ]; then
16
+ LOCK_FILE="${REGISTRY_FILE}.lock"
17
+ TMP_REG=$(mktemp)
18
+ trap 'rm -f "$TMP_REG" "$LOCK_FILE"' EXIT
19
+ # Advisory lock to prevent concurrent read-modify-write races
20
+ (
21
+ if command -v flock >/dev/null 2>&1; then
22
+ flock -w 5 200
23
+ fi
24
+ # Use jq for exact field matching (not regex) to avoid injection via session_id
25
+ while IFS= read -r line; do
26
+ sid=$(echo "$line" | jq -r '.session_id // ""' 2>/dev/null) || sid=""
27
+ [ "$sid" != "$SESSION_ID" ] && echo "$line"
28
+ done < "$REGISTRY_FILE" > "$TMP_REG" 2>/dev/null || true
29
+ mv "$TMP_REG" "$REGISTRY_FILE" 2>/dev/null || true
30
+ ) 200>"$LOCK_FILE"
31
+ fi
32
+
10
33
  # Mark session start time for staleness checks
11
34
  date -u +%FT%TZ > "${STATE_DIR}/session-start" 2>/dev/null
12
35
 
@@ -10,11 +10,12 @@ TS=$(date -u +%FT%TZ)
10
10
  EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // "unknown"' 2>/dev/null) || EVENT="unknown"
11
11
  SESSION=$(echo "$INPUT" | jq -r '.session_id // "unknown"' 2>/dev/null) || SESSION="unknown"
12
12
  TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null) || TOOL=""
13
+ CWD=$(echo "$INPUT" | jq -r '.cwd // ""' 2>/dev/null) || CWD=""
13
14
 
14
15
  jq -nc \
15
16
  --arg ts "$TS" --arg event "$EVENT" --arg session "$SESSION" \
16
- --arg tool "$TOOL" \
17
- '{ts:$ts, event:$event, session:$session, tool:$tool}' >> "$LOG_FILE" 2>/dev/null || true
17
+ --arg tool "$TOOL" --arg cwd "$CWD" \
18
+ '{ts:$ts, event:$event, session:$session, tool:$tool, cwd:$cwd}' >> "$LOG_FILE" 2>/dev/null || true
18
19
 
19
20
  # Forward to OTLP if configured — validate endpoint with hostname allowlist
20
21
  if [ -n "${OTLP_ENDPOINT:-}" ]; then
@@ -52,14 +53,14 @@ if [ -n "${OTLP_ENDPOINT:-}" ]; then
52
53
 
53
54
  PAYLOAD=$(tail -1 "$LOG_FILE" 2>/dev/null) || true
54
55
  if [ -n "$PAYLOAD" ]; then
55
- AUTH_HEADER=""
56
+ CURL_ARGS=(
57
+ -sf --max-time 10 --connect-timeout 5 --no-location
58
+ -X POST "$OTLP_ENDPOINT/v1/logs"
59
+ -H "Content-Type: application/json"
60
+ )
56
61
  if [ -n "${OTLP_AUTH_TOKEN:-}" ]; then
57
- AUTH_HEADER="-H \"Authorization: Bearer ${OTLP_AUTH_TOKEN}\""
62
+ CURL_ARGS+=(-H "Authorization: Bearer ${OTLP_AUTH_TOKEN}")
58
63
  fi
59
- eval curl -sf --max-time 10 --connect-timeout 5 --no-location \
60
- -X POST "$OTLP_ENDPOINT/v1/logs" \
61
- -H "Content-Type: application/json" \
62
- $AUTH_HEADER \
63
- -d "'$PAYLOAD'" '&>/dev/null &'
64
+ printf '%s' "$PAYLOAD" | curl "${CURL_ARGS[@]}" --data-binary @- &>/dev/null &
64
65
  fi
65
66
  fi
@@ -15,9 +15,9 @@ UNICODE_PATTERN='[\x{200B}-\x{200F}\x{2060}-\x{2064}\x{FEFF}\x{202A}-\x{202E}\x{
15
15
  # unicode_detect "filepath"
16
16
  unicode_detect() {
17
17
  if [ $# -gt 0 ]; then
18
- perl -CSD -ne "if (/$UNICODE_PATTERN/) { exit 0 } END { exit 1 }" "$1" 2>/dev/null
18
+ perl -CSD -ne "BEGIN{\$r=1} if (/$UNICODE_PATTERN/) {\$r=0;last} END{exit \$r}" "$1" 2>/dev/null
19
19
  else
20
- perl -CSD -ne "if (/$UNICODE_PATTERN/) { exit 0 } END { exit 1 }" 2>/dev/null
20
+ perl -CSD -ne "BEGIN{\$r=1} if (/$UNICODE_PATTERN/) {\$r=0;last} END{exit \$r}" 2>/dev/null
21
21
  fi
22
22
  }
23
23
 
@@ -25,7 +25,6 @@ unicode_detect() {
25
25
  # Usage: unicode_classify < file OR echo "$text" | unicode_classify
26
26
  # unicode_classify "filepath"
27
27
  unicode_classify() {
28
- local _ARGS=("$@")
29
28
  perl -CSD -ne '
30
29
  BEGIN { %c = () }
31
30
  $c{"zero-width chars"}++ if /[\x{200B}-\x{200F}\x{2060}-\x{2064}\x{FEFF}]/;
@@ -34,7 +33,7 @@ unicode_classify() {
34
33
  $c{"tag characters"}++ if /[\x{E0001}-\x{E007F}]/;
35
34
  $c{"variation sel. supplement"}++ if /[\x{E0100}-\x{E01EF}]/;
36
35
  END { print join(", ", sort keys %c) if %c }
37
- ' "${_ARGS[@]}" 2>/dev/null
36
+ ' "$@" 2>/dev/null
38
37
  }
39
38
 
40
39
  # Count affected lines.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@garethdaine/agentops",
3
- "version": "0.9.3",
3
+ "version": "0.10.0",
4
4
  "description": "Enterprise guardrails, delivery lifecycle, and self-evolution for Claude Code CLI",
5
5
  "author": {
6
6
  "name": "Gareth Daine",