@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.
- package/README.md +71 -9
- package/commands/enterprise/idea.md +188 -0
- package/commands/enterprise/search.md +236 -0
- package/commands/enterprise/worklog.md +246 -0
- package/commands/flags.md +3 -0
- package/hooks/ai-guardrails.sh +0 -1
- package/hooks/audit-log.sh +1 -1
- package/hooks/auto-delegate.sh +1 -1
- package/hooks/credential-redact.sh +0 -1
- package/hooks/dashboard-launch.sh +146 -0
- package/hooks/enforcement-lib.sh +4 -2
- package/hooks/feature-flags.sh +4 -0
- package/hooks/flag-utils.sh +6 -0
- package/hooks/hooks.json +8 -1
- package/hooks/integrity-verify.sh +2 -2
- package/hooks/lockfile-audit.sh +1 -1
- package/hooks/patterns-lib.sh +1 -0
- package/hooks/session-cleanup.sh +23 -0
- package/hooks/telemetry.sh +10 -9
- package/hooks/unicode-lib.sh +3 -4
- package/package.json +1 -1
- package/templates/work-journal/conventions.md +423 -0
|
@@ -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
|
package/hooks/ai-guardrails.sh
CHANGED
package/hooks/audit-log.sh
CHANGED
|
@@ -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
|
package/hooks/auto-delegate.sh
CHANGED
|
@@ -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" ||
|
|
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
|
package/hooks/enforcement-lib.sh
CHANGED
|
@@ -29,7 +29,8 @@ agentops_enforcement_action() {
|
|
|
29
29
|
echo "allow"
|
|
30
30
|
return
|
|
31
31
|
fi
|
|
32
|
-
local 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
|
|
55
|
+
local MODE
|
|
56
|
+
MODE=$(agentops_mode)
|
|
55
57
|
if [ "$MODE" = "blocking" ]; then
|
|
56
58
|
echo "block"
|
|
57
59
|
else
|
package/hooks/feature-flags.sh
CHANGED
|
@@ -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.
|
package/hooks/flag-utils.sh
CHANGED
|
@@ -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
|
|
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
|
|
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
|
package/hooks/lockfile-audit.sh
CHANGED
package/hooks/patterns-lib.sh
CHANGED
|
@@ -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.
|
package/hooks/session-cleanup.sh
CHANGED
|
@@ -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
|
|
package/hooks/telemetry.sh
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
62
|
+
CURL_ARGS+=(-H "Authorization: Bearer ${OTLP_AUTH_TOKEN}")
|
|
58
63
|
fi
|
|
59
|
-
|
|
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
|
package/hooks/unicode-lib.sh
CHANGED
|
@@ -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/) {
|
|
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/) {
|
|
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
|
-
' "
|
|
36
|
+
' "$@" 2>/dev/null
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
# Count affected lines.
|