@onlooker-community/ecosystem 0.0.2 → 0.2.1
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -5
- package/.github/workflows/release.yml +13 -5
- package/.github/workflows/test.yml +26 -0
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +40 -0
- package/README.md +14 -12
- package/config.json +4 -0
- package/hooks/hooks.json +46 -0
- package/mise.toml +11 -0
- package/package.json +10 -2
- package/scripts/common.sh +40 -0
- package/scripts/hooks/agent-spawn-tracker.sh +160 -0
- package/scripts/hooks/tool-history-tracker.sh +36 -0
- package/scripts/hooks/tool-sequence-tracker.sh +32 -0
- package/scripts/lib/onlooker-emit.sh +41 -0
- package/scripts/lib/onlooker-event.mjs +278 -0
- package/scripts/lib/onlooker-schema.sh +45 -0
- package/scripts/lib/tool-history.sh +35 -0
- package/scripts/lib/validate-path.sh +577 -0
- package/test/bats/agent-spawn-tracker.bats +29 -0
- package/test/bats/config.bats +67 -0
- package/test/bats/onlooker-emit.bats +26 -0
- package/test/bats/tool-history-tracker.bats +72 -0
- package/test/bats/tool-sequence-tracker.bats +53 -0
- package/test/bats/validate-path.bats +109 -0
- package/test/fixtures/hook-inputs/agent-tool.json +11 -0
- package/test/fixtures/hook-inputs/non-agent-tool.json +7 -0
- package/test/fixtures/hook-inputs/post-tool-use-failure-bash.json +12 -0
- package/test/fixtures/hook-inputs/post-tool-use-read.json +13 -0
- package/test/helpers/setup.bash +38 -0
- package/test/node/schema-events.test.mjs +67 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ecosystem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "TODO fill this out",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -9,9 +9,6 @@
|
|
|
9
9
|
"homepage": "https://onlooker.dev",
|
|
10
10
|
"repository": "https://github.com/onlooker-community/ecosystem",
|
|
11
11
|
"license": "MIT",
|
|
12
|
-
"skills": [
|
|
13
|
-
"./skills/",
|
|
14
|
-
"./commands/"
|
|
15
|
-
],
|
|
12
|
+
"skills": [],
|
|
16
13
|
"agents": []
|
|
17
14
|
}
|
|
@@ -30,17 +30,25 @@ jobs:
|
|
|
30
30
|
# scoped to this repo.
|
|
31
31
|
token: ${{ secrets.RELEASE_PLEASE_PAT }}
|
|
32
32
|
|
|
33
|
+
# release-please-action does not check out the repo; npm publish needs the
|
|
34
|
+
# tagged release tree (package.json, install.sh, hooks, etc.).
|
|
35
|
+
- name: Checkout release tag
|
|
36
|
+
if: ${{ steps.release.outputs.releases_created == 'true' }}
|
|
37
|
+
uses: actions/checkout@v4
|
|
38
|
+
with:
|
|
39
|
+
ref: ${{ steps.release.outputs.tag_name }}
|
|
40
|
+
|
|
33
41
|
- name: Setup Node
|
|
42
|
+
if: ${{ steps.release.outputs.releases_created == 'true' }}
|
|
34
43
|
uses: actions/setup-node@v6
|
|
35
44
|
with:
|
|
36
45
|
node-version: '22'
|
|
46
|
+
registry-url: 'https://registry.npmjs.org'
|
|
37
47
|
|
|
38
48
|
- name: Publish to npm
|
|
39
49
|
if: ${{ steps.release.outputs.releases_created == 'true' }}
|
|
40
50
|
env:
|
|
41
|
-
|
|
42
|
-
PATHS_RELEASED: ${{ steps.release.outputs.paths_released }}
|
|
51
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
43
52
|
run: |
|
|
44
|
-
|
|
45
|
-
npm
|
|
46
|
-
npm publish --access public
|
|
53
|
+
npm ci
|
|
54
|
+
npm publish --access public
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Install mise
|
|
17
|
+
uses: jdx/mise-action@v2
|
|
18
|
+
|
|
19
|
+
- name: Install tools
|
|
20
|
+
run: mise install
|
|
21
|
+
|
|
22
|
+
- name: Install Node dependencies
|
|
23
|
+
run: npm ci
|
|
24
|
+
|
|
25
|
+
- name: Run CI test suite
|
|
26
|
+
run: npm run test:ci
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.1](https://github.com/onlooker-community/ecosystem/compare/v0.2.0...v0.2.1) (2026-05-21)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **ci:** checkout release tag before npm publish :relieved: ([bc7bbdc](https://github.com/onlooker-community/ecosystem/commit/bc7bbdc7a886a55ba8f04fe09bfa60043648c766))
|
|
9
|
+
|
|
10
|
+
## [0.2.0](https://github.com/onlooker-community/ecosystem/compare/v0.1.0...v0.2.0) (2026-05-21)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **hooks:** emit canonical schema events for tool history :sparkles: ([1e49a24](https://github.com/onlooker-community/ecosystem/commit/1e49a24bfb930942fa477b594395ef352618f574))
|
|
16
|
+
* **hooks:** track tool call sequence on every PreToolUse :sparkles: ([0ad9546](https://github.com/onlooker-community/ecosystem/commit/0ad95465cc22a237e26115a67814a6e7b2951b1d))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Chores
|
|
20
|
+
|
|
21
|
+
* **deps:** use published @onlooker-community/schema from npm :relieved: ([efc92d8](https://github.com/onlooker-community/ecosystem/commit/efc92d8171592aa5a5f1c27853387e810fee612f))
|
|
22
|
+
|
|
23
|
+
## [0.1.0](https://github.com/onlooker-community/ecosystem/compare/v0.0.3...v0.1.0) (2026-05-21)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* add configuration and hooks for agent spawn tracking ([3ef4590](https://github.com/onlooker-community/ecosystem/commit/3ef459006bbbda246604bdd1ffaf9af0a59f9740))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Chores
|
|
32
|
+
|
|
33
|
+
* clean up README.md by removing outdated badge links ([42d47d6](https://github.com/onlooker-community/ecosystem/commit/42d47d602aa7b68db719874a1cf4193433d1bd68))
|
|
34
|
+
* remove skills from plugin.json to streamline configuration ([fdfd8eb](https://github.com/onlooker-community/ecosystem/commit/fdfd8eb4faa0c807eff97feb1a20961de1fe154d))
|
|
35
|
+
|
|
36
|
+
## [0.0.3](https://github.com/onlooker-community/ecosystem/compare/v0.0.2...v0.0.3) (2026-05-21)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Chores
|
|
40
|
+
|
|
41
|
+
* update release-please configuration to include custom pull request title pattern ([e860f1c](https://github.com/onlooker-community/ecosystem/commit/e860f1c5a7b58909a53ec38a3b3da89f22f0434c))
|
|
42
|
+
|
|
3
43
|
## [0.0.2](https://github.com/onlooker-community/ecosystem/compare/v0.0.1...v0.0.2) (2026-05-21)
|
|
4
44
|
|
|
5
45
|
|
package/README.md
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
# Onlooker Ecosystem
|
|
2
2
|
|
|
3
|
-
[
|
|
4
|
-
[](https://github.com/onlooker-community/ecosystem/network/members)
|
|
5
|
-
[](https://github.com/onlooker-community/ecosystem/graphs/contributors)
|
|
6
|
-
[](LICENSE)
|
|
7
|
-

|
|
8
|
-

|
|
9
|
-

|
|
10
|
-

|
|
11
|
-

|
|
12
|
-

|
|
3
|
+
Agents, skills, hooks, commands, rules, and MCP configurations that power [Onlooker](https://onlooker.dev).
|
|
13
4
|
|
|
14
5
|
---
|
|
15
6
|
|
|
16
|
-
|
|
7
|
+
## Development
|
|
17
8
|
|
|
18
|
-
|
|
9
|
+
Install tools with [mise](https://mise.jdx.dev/) (`mise install`), then install dependencies (includes [`@onlooker-community/schema`](https://www.npmjs.com/package/@onlooker-community/schema) from npm):
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm ci
|
|
13
|
+
npm test # bats + schema validation tests
|
|
14
|
+
npm run test:shellcheck
|
|
15
|
+
npm run test:ci # shellcheck + bats + schema + lint
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Hooks emit [canonical Onlooker events](https://github.com/onlooker-community/schema) via `scripts/lib/onlooker-event.mjs`. Bash helpers live in `scripts/lib/onlooker-schema.sh`.
|
|
19
|
+
|
|
20
|
+
Tests live under `test/bats/` and `test/node/` and use an isolated temp home so nothing writes to your real `~/.onlooker`.
|
|
19
21
|
|
|
20
22
|
## Quick Start
|
|
21
23
|
|
package/config.json
ADDED
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "*",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/tool-sequence-tracker.sh"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"matcher": "Agent",
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/agent-spawn-tracker.sh"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"PostToolUse": [
|
|
24
|
+
{
|
|
25
|
+
"matcher": "*",
|
|
26
|
+
"hooks": [
|
|
27
|
+
{
|
|
28
|
+
"type": "command",
|
|
29
|
+
"command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/tool-history-tracker.sh"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"PostToolUseFailure": [
|
|
35
|
+
{
|
|
36
|
+
"matcher": "*",
|
|
37
|
+
"hooks": [
|
|
38
|
+
{
|
|
39
|
+
"type": "command",
|
|
40
|
+
"command": "\"$CLAUDE_PLUGIN_ROOT\"/scripts/hooks/tool-history-tracker.sh"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|
package/mise.toml
CHANGED
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
[tools]
|
|
2
2
|
node = "26.1.0"
|
|
3
|
+
bats = "1.13.0"
|
|
4
|
+
shellcheck = "0.10.0"
|
|
5
|
+
jq = "1.7.1"
|
|
6
|
+
|
|
7
|
+
[tasks.test]
|
|
8
|
+
description = "Run bats test suite"
|
|
9
|
+
run = "bats test/bats"
|
|
10
|
+
|
|
11
|
+
[tasks."test:shellcheck"]
|
|
12
|
+
description = "Run shellcheck on shell scripts"
|
|
13
|
+
run = "shellcheck -S error -x install.sh scripts/common.sh scripts/hooks/*.sh scripts/lib/*.sh"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlooker-community/ecosystem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Agents, skills, hooks, commands, rules, and MCP configurations that power [Onlooker](https://onlooker.dev)",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Onlooker Community",
|
|
@@ -18,9 +18,17 @@
|
|
|
18
18
|
"bin": {
|
|
19
19
|
"onlooker-install": "install.sh"
|
|
20
20
|
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@onlooker-community/schema": "^1.3.0"
|
|
23
|
+
},
|
|
21
24
|
"scripts": {
|
|
22
25
|
"postinstall": "echo '\\n onlooker-ecosystem installed!\\n Run: npx onlooker-install typescript\\n Docs: https://github.com/onlooker-community/ecosystem\\n'",
|
|
23
|
-
"
|
|
26
|
+
"test": "npm run test:bats && npm run test:schema",
|
|
27
|
+
"test:bats": "bats test/bats",
|
|
28
|
+
"test:schema": "node --test test/node/*.test.mjs",
|
|
29
|
+
"test:shellcheck": "shellcheck -S error -x install.sh scripts/common.sh scripts/hooks/*.sh scripts/lib/*.sh",
|
|
30
|
+
"test:ci": "npm run test:shellcheck && npm run test:bats && npm run test:schema && npm run lint:check",
|
|
31
|
+
"lint:check": "biome check . && markdownlint '**/*.md' --ignore node_modules --ignore CHANGELOG.md",
|
|
24
32
|
"lint": "biome lint --write",
|
|
25
33
|
"format": "biome format --write",
|
|
26
34
|
"format:check": "biome format",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared helpers for Onlooker hook scripts.
|
|
3
|
+
# Requires validate-path.sh and onlooker-schema.sh.
|
|
4
|
+
|
|
5
|
+
# Emit canonical tool.agent.spawn telemetry event (schema payload).
|
|
6
|
+
# Usage: onlooker_emit_tool_agent_spawn SESSION_ID SUBAGENT_TYPE DESCRIPTION MODEL RUN_IN_BACKGROUND ISOLATION
|
|
7
|
+
onlooker_emit_tool_agent_spawn() {
|
|
8
|
+
local session_id="$1"
|
|
9
|
+
local subagent_type="$2"
|
|
10
|
+
local description="$3"
|
|
11
|
+
local _model="$4"
|
|
12
|
+
local _run_in_background="$5"
|
|
13
|
+
local _isolation="$6"
|
|
14
|
+
|
|
15
|
+
local subagent_id="${session_id}-$(date +%s)"
|
|
16
|
+
|
|
17
|
+
local params
|
|
18
|
+
params=$(jq -n \
|
|
19
|
+
--arg plugin "${ONLOOKER_PLUGIN_NAME:-onlooker}" \
|
|
20
|
+
--arg sid "$session_id" \
|
|
21
|
+
--arg subagent_id "$subagent_id" \
|
|
22
|
+
--arg agent_name "$subagent_type" \
|
|
23
|
+
--arg task_summary "$description" \
|
|
24
|
+
'{
|
|
25
|
+
plugin: $plugin,
|
|
26
|
+
session_id: $sid,
|
|
27
|
+
event_type: "tool.agent.spawn",
|
|
28
|
+
payload: {
|
|
29
|
+
subagent_id: $subagent_id,
|
|
30
|
+
agent_name: $agent_name,
|
|
31
|
+
task_summary: $task_summary
|
|
32
|
+
}
|
|
33
|
+
}')
|
|
34
|
+
|
|
35
|
+
local event
|
|
36
|
+
event=$(printf '%s' "$params" | ONLOOKER_DIR="$ONLOOKER_DIR" ONLOOKER_PLUGIN_NAME="$ONLOOKER_PLUGIN_NAME" \
|
|
37
|
+
node "${CLAUDE_PLUGIN_ROOT}/scripts/lib/onlooker-event.mjs" emit 2>/dev/null) || return 1
|
|
38
|
+
|
|
39
|
+
onlooker_append_event "$event"
|
|
40
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Onlooker Agent Spawn Tracker Script
|
|
3
|
+
# Invoked by the PreToolUse hook (via command) when an Agent tool is used.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# echo "$INPUT" | agent-spawn-tracker.sh
|
|
7
|
+
#
|
|
8
|
+
# Input:
|
|
9
|
+
# {
|
|
10
|
+
# "session_id": "123",
|
|
11
|
+
# "tool_name": "Agent",
|
|
12
|
+
# "tool_input": {
|
|
13
|
+
# "agent_id": "456"
|
|
14
|
+
# }
|
|
15
|
+
# }
|
|
16
|
+
|
|
17
|
+
set -uo pipefail # No -e: we must never exit non-zero and block the hook
|
|
18
|
+
|
|
19
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
source "$SCRIPT_DIR/../lib/validate-path.sh"
|
|
21
|
+
source "$SCRIPT_DIR/../lib/onlooker-schema.sh"
|
|
22
|
+
source "${CLAUDE_PLUGIN_ROOT:-$SCRIPT_DIR/../..}/scripts/common.sh"
|
|
23
|
+
|
|
24
|
+
hook_register "agent-spawn-tracker" "Agent Spawn Tracker" "Tracks when an agent is spawned"
|
|
25
|
+
|
|
26
|
+
INPUT=$(cat)
|
|
27
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "Unknown"')
|
|
28
|
+
|
|
29
|
+
hook_set_context "$INPUT" "PreToolUse"
|
|
30
|
+
|
|
31
|
+
json_response() {
|
|
32
|
+
jq -n --arg decision "$1" --arg reason "$2" '{ "decision": $decision, "reason": $reason }'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Only process Agent tool calls
|
|
36
|
+
if [[ "$TOOL_NAME" != "Agent" ]]; then
|
|
37
|
+
json_response "approve" "Not an Agent tool call"
|
|
38
|
+
hook_success
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# Extract agent parameters
|
|
43
|
+
SUBAGENT_TYPE=$(jq -r '.tool_input.subagent_type // "general-purpose"' <<<"$INPUT")
|
|
44
|
+
DESCRIPTION=$(jq -r '.tool_input.description // ""' <<<"$INPUT")
|
|
45
|
+
RUN_IN_BACKGROUND=$(jq -r '.tool_input.run_in_background // false' <<<"$INPUT")
|
|
46
|
+
ISOLATION=$(jq -r '.tool_input.isolation // "worktree"' <<<"$INPUT")
|
|
47
|
+
MODEL=$(jq -r '.tool_input.model // "sonnet"' <<<"$INPUT")
|
|
48
|
+
MAX_TURNS=$(jq -r '.tool_input.max_turns // 10' <<<"$INPUT")
|
|
49
|
+
TOOLS=$(jq -r '.tool_input.tools // []' <<<"$INPUT")
|
|
50
|
+
DISALLOWED_TOOLS=$(jq -r '.tool_input.disallowed_tools // []' <<<"$INPUT")
|
|
51
|
+
SKILLS=$(jq -r '.tool_input.skills // []' <<<"$INPUT")
|
|
52
|
+
MEMORY=$(jq -r '.tool_input.memory // false' <<<"$INPUT")
|
|
53
|
+
BACKGROUND=$(jq -r '.tool_input.background // false' <<<"$INPUT")
|
|
54
|
+
ISOLATION=$(jq -r '.tool_input.isolation // "worktree"' <<<"$INPUT")
|
|
55
|
+
MODEL=$(jq -r '.tool_input.model // "sonnet"' <<<"$INPUT")
|
|
56
|
+
MAX_TURNS=$(jq -r '.tool_input.max_turns // 10' <<<"$INPUT")
|
|
57
|
+
TOOLS=$(jq -r '.tool_input.tools // []' <<<"$INPUT")
|
|
58
|
+
DISALLOWED_TOOLS=$(jq -r '.tool_input.disallowed_tools // []' <<<"$INPUT")
|
|
59
|
+
SKILLS=$(jq -r '.tool_input.skills // []' <<<"$INPUT")
|
|
60
|
+
MEMORY=$(jq -r '.tool_input.memory // false' <<<"$INPUT")
|
|
61
|
+
BACKGROUND=$(jq -r '.tool_input.background // false' <<<"$INPUT")
|
|
62
|
+
|
|
63
|
+
# Track agent spawns in telemetry log
|
|
64
|
+
STATE_FILE="$ONLOOKER_DIR/agent-spawn-trackers.json"
|
|
65
|
+
LOCKFILE="$STATE_FILE.lock"
|
|
66
|
+
|
|
67
|
+
# Use flock for exclusive access
|
|
68
|
+
exec 200>"$LOCKFILE"
|
|
69
|
+
flock -w 5 200 || {
|
|
70
|
+
json_response "deny" "Failed to acquire lock"
|
|
71
|
+
hook_failure
|
|
72
|
+
exit 0
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Load or initialize state
|
|
76
|
+
if [[ -f "$STATE_FILE" ]]; then
|
|
77
|
+
STATE=$(jq '.' "$STATE_FILE" 2>/dev/null) || STATE='{}'
|
|
78
|
+
else
|
|
79
|
+
STATE='{}'
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Get Session ID for tracking
|
|
83
|
+
SESSION_ID=$(jq -r '.session_id // "unknown"' <<<"$INPUT") || SESSION_ID="unknown"
|
|
84
|
+
|
|
85
|
+
# Export turn state so envelope and payload include parent lineage
|
|
86
|
+
turn_state_export "$SESSION_ID"
|
|
87
|
+
|
|
88
|
+
# Initialize session tracking if needed
|
|
89
|
+
STATE=$(jq --arg sid "$SESSION_ID" '
|
|
90
|
+
if .sessions[$sid] == null then
|
|
91
|
+
.sessions[$sid] = {
|
|
92
|
+
spawns: 0,
|
|
93
|
+
background_spawns: 0,
|
|
94
|
+
types: {},
|
|
95
|
+
first_spawn: now | strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
96
|
+
last_spawn: null
|
|
97
|
+
}
|
|
98
|
+
else .
|
|
99
|
+
end
|
|
100
|
+
' <<<"$STATE")
|
|
101
|
+
|
|
102
|
+
# Save state
|
|
103
|
+
echo "$STATE" > "$STATE_FILE" 2>/dev/null || true
|
|
104
|
+
|
|
105
|
+
# Release lock
|
|
106
|
+
flock -u 200
|
|
107
|
+
|
|
108
|
+
# Get current session stats
|
|
109
|
+
SPAWN_COUNT=$(jq -r --arg sid "$SESSION_ID" '.sessions[$sid].spawns' <<<"$STATE")
|
|
110
|
+
BG_SPAWN_COUNT=$(jq -r --arg sid "$SESSION_ID" '.sessions[$sid].background_spawns' <<<"$STATE")
|
|
111
|
+
|
|
112
|
+
## Warn on potential issues
|
|
113
|
+
WARNING=""
|
|
114
|
+
|
|
115
|
+
# High spawn count warning
|
|
116
|
+
if (( SPAWN_COUNT > 10 )); then
|
|
117
|
+
WARNING="Note: $SPAWN_COUNT agents spawned this session. Consider if tasks could be consolidated."
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# Many background agents warning (cumulative this session)
|
|
121
|
+
if (( BG_SPAWN_COUNT > 5 )); then
|
|
122
|
+
WARNING="${WARNING:+$WARNING\n}Note: $BG_SPAWN_COUNT background agents spawned this session. Consider tracking completion."
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# Worktree isolation note (informational)
|
|
126
|
+
if [[ "$ISOLATION" == "worktree" ]]; then
|
|
127
|
+
WARNING="${WARNING:+$WARNING\n}Info: Agent using worktree isolation - changes will be in separate branch."
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
if [[ -n "$WARNING" ]]; then
|
|
131
|
+
json_response "approve" "$WARNING"
|
|
132
|
+
else
|
|
133
|
+
json_response "approve" "Agent spawn tracked (#$SPAWN_COUNT: $SUBAGENT_TYPE)"
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
PAYLOAD=$(jq -n \
|
|
137
|
+
--arg type "$SUBAGENT_TYPE" \
|
|
138
|
+
--arg desc "$DESCRIPTION" \
|
|
139
|
+
--argjson bg "$RUN_IN_BACKGROUND" \
|
|
140
|
+
--arg isolation "$ISOLATION" \
|
|
141
|
+
--arg model "$MODEL" \
|
|
142
|
+
--argjson spawn_num "$SPAWN_COUNT" \
|
|
143
|
+
--arg parent_sid "$SESSION_ID" \
|
|
144
|
+
--arg parent_turn "${ONLOOKER_TURN_NUMBER:-}" \
|
|
145
|
+
'{
|
|
146
|
+
subagent_type: $type,
|
|
147
|
+
description: $desc,
|
|
148
|
+
run_in_background: $bg,
|
|
149
|
+
isolation: $isolation,
|
|
150
|
+
model: $model,
|
|
151
|
+
session_spawn_number: $spawn_num,
|
|
152
|
+
parent_session_id: $parent_sid
|
|
153
|
+
}
|
|
154
|
+
+ (if $parent_turn != "" then {parent_turn: ($parent_turn | tonumber)} else {} end)
|
|
155
|
+
')
|
|
156
|
+
|
|
157
|
+
# Canonical event: tool.agent.spawn (@onlooker-community/schema)
|
|
158
|
+
onlooker_emit_tool_agent_spawn "$SESSION_ID" "$SUBAGENT_TYPE" "$DESCRIPTION" "$MODEL" "$RUN_IN_BACKGROUND" "$ISOLATION" 2>/dev/null && hook_success || hook_failure "Failed to emit tool.agent.spawn event"
|
|
159
|
+
|
|
160
|
+
exit 0
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Onlooker Tool History Tracker
|
|
3
|
+
# Invoked by PostToolUse and PostToolUseFailure (matcher: *) after each tool call.
|
|
4
|
+
#
|
|
5
|
+
# Appends canonical OnlookerEvent records to:
|
|
6
|
+
# ~/.onlooker/session-history/<session_id>.jsonl (per-session analysis)
|
|
7
|
+
# ~/.onlooker/logs/onlooker-events.jsonl (global telemetry)
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# echo "$INPUT" | tool-history-tracker.sh
|
|
11
|
+
|
|
12
|
+
set -uo pipefail # No -e: never block or alter tool results
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
source "$SCRIPT_DIR/../lib/validate-path.sh"
|
|
16
|
+
source "$SCRIPT_DIR/../lib/onlooker-schema.sh"
|
|
17
|
+
source "$SCRIPT_DIR/../lib/tool-history.sh"
|
|
18
|
+
|
|
19
|
+
hook_register "tool-history-tracker" "Tool History Tracker" "Records canonical tool events to session JSONL"
|
|
20
|
+
|
|
21
|
+
INPUT=$(cat)
|
|
22
|
+
|
|
23
|
+
HOOK_EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // "PostToolUse"')
|
|
24
|
+
hook_set_context "$INPUT" "$HOOK_EVENT"
|
|
25
|
+
|
|
26
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
|
|
27
|
+
turn_state_export "$SESSION_ID"
|
|
28
|
+
|
|
29
|
+
RECORD=$(tool_history_build_record "$INPUT")
|
|
30
|
+
if [[ -n "$RECORD" ]]; then
|
|
31
|
+
tool_history_append "$SESSION_ID" "$RECORD" || hook_failure "Failed to append session history"
|
|
32
|
+
onlooker_append_event "$RECORD" || hook_failure "Failed to append global event log"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
hook_success
|
|
36
|
+
exit 0
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Onlooker Tool Sequence Tracker
|
|
3
|
+
# Invoked by PreToolUse (matcher: *) before every tool call.
|
|
4
|
+
#
|
|
5
|
+
# Increments turn_tool_seq in the session tracker so downstream hooks and
|
|
6
|
+
# event emission can stamp tool_call_seq on the current turn.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# echo "$INPUT" | tool-sequence-tracker.sh
|
|
10
|
+
|
|
11
|
+
set -uo pipefail # No -e: never block the tool call
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
source "$SCRIPT_DIR/../lib/validate-path.sh"
|
|
15
|
+
|
|
16
|
+
hook_register "tool-sequence-tracker" "Tool Sequence Tracker" "Increments tool call sequence within the current turn"
|
|
17
|
+
|
|
18
|
+
INPUT=$(cat)
|
|
19
|
+
hook_set_context "$INPUT" "PreToolUse"
|
|
20
|
+
|
|
21
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
|
|
22
|
+
|
|
23
|
+
json_response() {
|
|
24
|
+
jq -n --arg decision "$1" --arg reason "$2" '{ "decision": $decision, "reason": $reason }'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
turn_state_next_tool "$SESSION_ID"
|
|
28
|
+
turn_state_export "$SESSION_ID"
|
|
29
|
+
|
|
30
|
+
json_response "approve" "Tool sequence #${ONLOOKER_TURN_TOOL_SEQ:-0} (turn ${ONLOOKER_TURN_NUMBER:-1})"
|
|
31
|
+
hook_success
|
|
32
|
+
exit 0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# onlooker-emit.sh - Legacy shim: emit canonical events via @onlooker-community/schema.
|
|
3
|
+
#
|
|
4
|
+
# Prefer onlooker_emit_from_hook / onlooker_append_event from onlooker-schema.sh.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# onlooker-emit.sh "tool.agent.spawn" '{"subagent_id":"x",...}'
|
|
8
|
+
#
|
|
9
|
+
# The first argument is treated as event_type; payload must match schema for that type.
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
# shellcheck source=validate-path.sh
|
|
14
|
+
source "$SCRIPT_DIR/validate-path.sh"
|
|
15
|
+
# shellcheck source=onlooker-schema.sh
|
|
16
|
+
source "$SCRIPT_DIR/onlooker-schema.sh"
|
|
17
|
+
|
|
18
|
+
EVENT_TYPE="$1"
|
|
19
|
+
PAYLOAD_JSON="$2"
|
|
20
|
+
|
|
21
|
+
SESSION_ID="${_HOOK_SESSION_ID:-}"
|
|
22
|
+
if [[ -z "$SESSION_ID" ]]; then
|
|
23
|
+
SESSION_ID=$(echo "$PAYLOAD_JSON" | jq -r '.session_id // "unknown"' 2>/dev/null) || SESSION_ID="unknown"
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
PARAMS=$(jq -n \
|
|
27
|
+
--arg plugin "${ONLOOKER_PLUGIN_NAME:-onlooker}" \
|
|
28
|
+
--arg sid "$SESSION_ID" \
|
|
29
|
+
--arg type "$EVENT_TYPE" \
|
|
30
|
+
--argjson payload "$PAYLOAD_JSON" \
|
|
31
|
+
'{plugin: $plugin, session_id: $sid, event_type: $type, payload: $payload}')
|
|
32
|
+
|
|
33
|
+
EVENT=$(printf '%s' "$PARAMS" | ONLOOKER_DIR="$ONLOOKER_DIR" ONLOOKER_PLUGIN_NAME="$ONLOOKER_PLUGIN_NAME" \
|
|
34
|
+
node "$_ONLOOKER_EVENT_JS" emit 2>/dev/null) || {
|
|
35
|
+
hook_failure "Failed to build canonical event"
|
|
36
|
+
exit 0
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
onlooker_append_event "$EVENT"
|
|
40
|
+
hook_success
|
|
41
|
+
exit 0
|