anolisa-tokenless 0.7.14 → 0.8.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.
Files changed (30) hide show
  1. package/README.md +206 -79
  2. package/adapters/tokenless/claude-code/.claude-plugin/plugin.json +1 -1
  3. package/adapters/tokenless/claude-code/hooks/run-hook.sh +62 -0
  4. package/adapters/tokenless/codex/.codex-plugin/plugin.json +1 -1
  5. package/adapters/tokenless/common/cosh-extension.json +4 -4
  6. package/adapters/tokenless/common/hooks/compress_response_hook.py +180 -96
  7. package/adapters/tokenless/common/hooks/compress_schema_hook.py +19 -31
  8. package/adapters/tokenless/common/hooks/hook_utils.py +240 -70
  9. package/adapters/tokenless/common/hooks/rewrite_hook.py +53 -169
  10. package/adapters/tokenless/dsh/dist/index.js +353 -264
  11. package/adapters/tokenless/dsh/package.json +2 -2
  12. package/adapters/tokenless/hermes/__init__.py +191 -357
  13. package/adapters/tokenless/hermes/plugin.yaml +2 -2
  14. package/adapters/tokenless/manifest.json +17 -1
  15. package/adapters/tokenless/openclaw/dist/index.d.ts +4 -16
  16. package/adapters/tokenless/openclaw/dist/index.js +291 -507
  17. package/adapters/tokenless/openclaw/index.ts +408 -628
  18. package/adapters/tokenless/openclaw/openclaw.plugin.json +4 -20
  19. package/adapters/tokenless/openclaw/package.json +6 -4
  20. package/adapters/tokenless/qoder/.qoder-plugin/plugin.json +1 -1
  21. package/adapters/tokenless/qwencode/hooks/run-hook.sh +62 -0
  22. package/adapters/tokenless/qwencode/qwen-extension.json +4 -4
  23. package/adapters/tokenless/qwenpaw/plugin.json +17 -0
  24. package/adapters/tokenless/qwenpaw/plugin.py +390 -0
  25. package/adapters/tokenless/qwenpaw/requirements.txt +6 -0
  26. package/adapters/tokenless/qwenpaw/scripts/detect.sh +131 -0
  27. package/adapters/tokenless/qwenpaw/scripts/install.sh +98 -0
  28. package/adapters/tokenless/qwenpaw/scripts/uninstall.sh +61 -0
  29. package/package.json +5 -5
  30. package/adapters/tokenless/common/hooks/compress_toon_hook.py +0 -174
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env bash
2
+ # detect.sh — Inspect tokenless QwenPaw integration. Read-only.
3
+ #
4
+ # Reports qwenpaw CLI, QwenPaw working directory, tokenless plugin install
5
+ # state, and adapter resource availability. Exit codes:
6
+ # 0 = installed and ready
7
+ # 1 = not installed but installable
8
+ # 2 = missing prerequisites
9
+ set -euo pipefail
10
+
11
+ AGENT="${ANOLISA_TARGET:-qwenpaw}"
12
+ COMPONENT="${ANOLISA_COMPONENT:-tokenless}"
13
+ ADAPTER_DIR="${ANOLISA_ADAPTER_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}"
14
+ # Resolve the working directory like QwenPaw: QWENPAW_WORKING_DIR, else
15
+ # COPAW_WORKING_DIR, else a legacy ~/.copaw, else ~/.qwenpaw.
16
+ PLUGIN_ID="tokenless"
17
+ QWENPAW_WORKING_DIR="${QWENPAW_WORKING_DIR:-${COPAW_WORKING_DIR:-}}"
18
+ if [ -z "$QWENPAW_WORKING_DIR" ]; then
19
+ if [ -d "$HOME/.copaw" ]; then QWENPAW_WORKING_DIR="$HOME/.copaw"; other="$HOME/.qwenpaw"; else QWENPAW_WORKING_DIR="$HOME/.qwenpaw"; other="$HOME/.copaw"; fi
20
+ # ~/.copaw may have appeared or vanished since the plugin was installed:
21
+ # prefer the default location that actually holds it.
22
+ if [ ! -d "$QWENPAW_WORKING_DIR/plugins/$PLUGIN_ID" ] && [ -d "$other/plugins/$PLUGIN_ID" ]; then
23
+ QWENPAW_WORKING_DIR="$other"
24
+ fi
25
+ fi
26
+ QWENPAW_BIN="${QWENPAW_BIN:-}"
27
+ export PATH="${QWENPAW_HOME:-$HOME/.qwenpaw}/bin:$HOME/.local/bin:/usr/local/bin:$PATH"
28
+
29
+ PLUGIN_SRC="$ADAPTER_DIR/qwenpaw"
30
+
31
+ # QwenPaw installs requirements.txt with its own interpreter (the CLI's
32
+ # shebang). On a platform without a matching wheel pip installs nothing and
33
+ # still exits 0, and a released wheel may predate the SDK surface the plugin
34
+ # imports, so prove the SDK imports there. QWENPAW_PYTHON overrides the
35
+ # interpreter; a CLI without a Python shebang (frozen build) is left unverified.
36
+ check_sdk() {
37
+ local python="${QWENPAW_PYTHON:-}"
38
+ if [ -z "$python" ]; then
39
+ python="$(sed -n '1s/^#![[:space:]]*//p' "$QWENPAW_BIN")"
40
+ case "$python" in
41
+ /usr/bin/env\ *) python="$(command -v "${python#/usr/bin/env }" 2>/dev/null || true)" ;;
42
+ esac
43
+ fi
44
+ if [ -z "$python" ] || [ ! -x "$python" ] || ! "$python" --version 2>&1 | grep -q '^Python '; then
45
+ return 2
46
+ fi
47
+ "$python" - <<'PY'
48
+ import sys
49
+ try:
50
+ import anolisa_tokenless as sdk
51
+ except ImportError as error:
52
+ sys.exit(f"anolisa_tokenless is not importable by {sys.executable}: {error}")
53
+ if not hasattr(sdk, "RecoveryMethod"):
54
+ sys.exit(f"anolisa_tokenless {sdk.__version__} predates the SDK surface the plugin needs")
55
+ print(sdk.__version__)
56
+ PY
57
+ }
58
+
59
+ line() { printf '[%s] %s\n' "$COMPONENT" "$*"; }
60
+ field() { printf '[%s] %-26s %s\n' "$COMPONENT" "$1" "$2"; }
61
+
62
+ PREREQ_MISSING=()
63
+ INSTALL_MISSING=()
64
+ note_prereq_missing() { PREREQ_MISSING+=("$1"); }
65
+ note_install_missing() { INSTALL_MISSING+=("$1"); }
66
+
67
+ if [ -z "$QWENPAW_BIN" ]; then
68
+ QWENPAW_BIN="$(command -v qwenpaw 2>/dev/null || true)"
69
+ fi
70
+
71
+ line "${AGENT} detect"
72
+ if [ -n "$QWENPAW_BIN" ] && [ -x "$QWENPAW_BIN" ]; then
73
+ field "qwenpaw CLI" "present (${QWENPAW_BIN})"
74
+ else
75
+ field "qwenpaw CLI" "missing"
76
+ note_prereq_missing "qwenpaw CLI"
77
+ fi
78
+
79
+ if [ -d "$QWENPAW_WORKING_DIR" ]; then
80
+ field "qwenpaw working dir" "present (${QWENPAW_WORKING_DIR})"
81
+ else
82
+ field "qwenpaw working dir" "not initialized (${QWENPAW_WORKING_DIR})"
83
+ note_install_missing "qwenpaw working dir"
84
+ fi
85
+
86
+ if [ -f "$PLUGIN_SRC/plugin.json" ] && [ -f "$PLUGIN_SRC/plugin.py" ] && [ -f "$PLUGIN_SRC/requirements.txt" ]; then
87
+ field "plugin resource" "present (${PLUGIN_SRC})"
88
+ else
89
+ field "plugin resource" "missing (${PLUGIN_SRC})"
90
+ note_prereq_missing "plugin resource"
91
+ fi
92
+
93
+ plugin_dst="${QWENPAW_WORKING_DIR%/}/plugins/${PLUGIN_ID}"
94
+ if [ -f "$plugin_dst/plugin.json" ]; then
95
+ stale=""
96
+ for file in plugin.json plugin.py requirements.txt; do
97
+ cmp -s "$PLUGIN_SRC/$file" "$plugin_dst/$file" || stale="$file"
98
+ done
99
+ if [ -n "$stale" ]; then
100
+ field "${PLUGIN_ID} plugin" "stale (${plugin_dst}/${stale} differs from ${PLUGIN_SRC})"
101
+ note_install_missing "${PLUGIN_ID} plugin"
102
+ else
103
+ field "${PLUGIN_ID} plugin" "installed (${plugin_dst})"
104
+ fi
105
+ else
106
+ field "${PLUGIN_ID} plugin" "missing (${plugin_dst})"
107
+ note_install_missing "${PLUGIN_ID} plugin"
108
+ fi
109
+
110
+ if [ -n "$QWENPAW_BIN" ] && [ -x "$QWENPAW_BIN" ]; then
111
+ sdk_version="$(check_sdk 2>/dev/null)" && sdk_status=0 || sdk_status=$?
112
+ case "$sdk_status" in
113
+ 0) field "anolisa_tokenless SDK" "importable (${sdk_version})" ;;
114
+ 2) field "anolisa_tokenless SDK" "unverified (no Python shebang in ${QWENPAW_BIN})" ;;
115
+ *)
116
+ field "anolisa_tokenless SDK" "not importable by QwenPaw's Python (${sdk_version})"
117
+ note_install_missing "anolisa_tokenless SDK"
118
+ ;;
119
+ esac
120
+ fi
121
+
122
+ if [ ${#PREREQ_MISSING[@]} -gt 0 ]; then
123
+ line "${AGENT}: missing prerequisites (${PREREQ_MISSING[*]})"
124
+ exit 2
125
+ fi
126
+ if [ ${#INSTALL_MISSING[@]} -gt 0 ]; then
127
+ line "${AGENT}: not installed (ready to install)"
128
+ exit 1
129
+ fi
130
+ line "${AGENT}: ready"
131
+ exit 0
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env bash
2
+ # install.sh — Install the tokenless plugin into QwenPaw through its own CLI.
3
+ #
4
+ # `qwenpaw plugin install` copies the bundle into the QwenPaw working
5
+ # directory, validates plugin.py, installs requirements.txt into QwenPaw's
6
+ # Python environment, and hot-loads when QwenPaw is running. It exits 0 even
7
+ # when it fails, so the installed files are compared with the source afterwards.
8
+ set -euo pipefail
9
+
10
+ AGENT="${ANOLISA_TARGET:-qwenpaw}"
11
+ COMPONENT="${ANOLISA_COMPONENT:-tokenless}"
12
+ ADAPTER_DIR="${ANOLISA_ADAPTER_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}"
13
+ # Resolve the working directory like QwenPaw: QWENPAW_WORKING_DIR, else
14
+ # COPAW_WORKING_DIR, else a legacy ~/.copaw, else ~/.qwenpaw.
15
+ QWENPAW_WORKING_DIR="${QWENPAW_WORKING_DIR:-${COPAW_WORKING_DIR:-}}"
16
+ if [ -z "$QWENPAW_WORKING_DIR" ]; then
17
+ if [ -d "$HOME/.copaw" ]; then QWENPAW_WORKING_DIR="$HOME/.copaw"; else QWENPAW_WORKING_DIR="$HOME/.qwenpaw"; fi
18
+ fi
19
+ QWENPAW_BIN="${QWENPAW_BIN:-}"
20
+ DRY_RUN="${ANOLISA_DRY_RUN:-0}"
21
+ export PATH="${QWENPAW_HOME:-$HOME/.qwenpaw}/bin:$HOME/.local/bin:/usr/local/bin:$PATH"
22
+
23
+ PLUGIN_ID="tokenless"
24
+ PLUGIN_SRC="$ADAPTER_DIR/qwenpaw"
25
+ PLUGIN_DST="${QWENPAW_WORKING_DIR%/}/plugins/${PLUGIN_ID}"
26
+
27
+ # QwenPaw installs requirements.txt with its own interpreter (the CLI's
28
+ # shebang). On a platform without a matching wheel pip installs nothing and
29
+ # still exits 0, and a released wheel may predate the SDK surface the plugin
30
+ # imports, so prove the SDK imports there. QWENPAW_PYTHON overrides the
31
+ # interpreter; a CLI without a Python shebang (frozen build) is left unverified.
32
+ check_sdk() {
33
+ local python="${QWENPAW_PYTHON:-}"
34
+ if [ -z "$python" ]; then
35
+ python="$(sed -n '1s/^#![[:space:]]*//p' "$QWENPAW_BIN")"
36
+ case "$python" in
37
+ /usr/bin/env\ *) python="$(command -v "${python#/usr/bin/env }" 2>/dev/null || true)" ;;
38
+ esac
39
+ fi
40
+ if [ -z "$python" ] || [ ! -x "$python" ] || ! "$python" --version 2>&1 | grep -q '^Python '; then
41
+ return 2
42
+ fi
43
+ "$python" - <<'PY'
44
+ import sys
45
+ try:
46
+ import anolisa_tokenless as sdk
47
+ except ImportError as error:
48
+ sys.exit(f"anolisa_tokenless is not importable by {sys.executable}: {error}")
49
+ if not hasattr(sdk, "RecoveryMethod"):
50
+ sys.exit(f"anolisa_tokenless {sdk.__version__} predates the SDK surface the plugin needs")
51
+ print(sdk.__version__)
52
+ PY
53
+ }
54
+
55
+ echo "[${COMPONENT}] Installing ${AGENT} plugin..."
56
+
57
+ if [ ! -f "$PLUGIN_SRC/plugin.json" ] || [ ! -f "$PLUGIN_SRC/plugin.py" ] || [ ! -f "$PLUGIN_SRC/requirements.txt" ]; then
58
+ echo "[${COMPONENT}] Missing plugin.json, plugin.py or requirements.txt in $PLUGIN_SRC" >&2
59
+ exit 1
60
+ fi
61
+
62
+ if [ -z "$QWENPAW_BIN" ]; then
63
+ QWENPAW_BIN="$(command -v qwenpaw 2>/dev/null || true)"
64
+ fi
65
+ if [ -z "$QWENPAW_BIN" ] || [ ! -x "$QWENPAW_BIN" ]; then
66
+ echo "[${COMPONENT}] qwenpaw CLI not found — skipping plugin installation."
67
+ echo "[${COMPONENT}] Install QwenPaw first (https://qwenpaw.agentscope.io/), then run this script again."
68
+ exit 0
69
+ fi
70
+
71
+ if [ "$DRY_RUN" = "1" ]; then
72
+ echo "DRY-RUN: QWENPAW_WORKING_DIR=${QWENPAW_WORKING_DIR%/} $QWENPAW_BIN plugin install $PLUGIN_SRC --force"
73
+ exit 0
74
+ fi
75
+
76
+ QWENPAW_WORKING_DIR="${QWENPAW_WORKING_DIR%/}" "$QWENPAW_BIN" plugin install "$PLUGIN_SRC" --force
77
+
78
+ # A failed install may leave the previous bundle in place, so the installed
79
+ # files must match the source bundle, not merely exist.
80
+ for file in plugin.json plugin.py requirements.txt; do
81
+ if ! cmp -s "$PLUGIN_SRC/$file" "$PLUGIN_DST/$file"; then
82
+ echo "[${COMPONENT}] qwenpaw plugin install did not install $PLUGIN_SRC/$file into $PLUGIN_DST — see the output above." >&2
83
+ exit 1
84
+ fi
85
+ done
86
+
87
+ sdk_version="$(check_sdk)" && sdk_status=0 || sdk_status=$?
88
+ case "$sdk_status" in
89
+ 0) echo "[${COMPONENT}] anolisa_tokenless ${sdk_version} is importable by QwenPaw's Python." ;;
90
+ 2) echo "[${COMPONENT}] Could not find QwenPaw's Python interpreter; anolisa_tokenless import left unverified." >&2 ;;
91
+ *)
92
+ echo "[${COMPONENT}] ${sdk_version}" >&2
93
+ echo "[${COMPONENT}] The plugin is copied but cannot run; install a matching anolisa_tokenless wheel into QwenPaw's Python environment (see $PLUGIN_SRC/requirements.txt for the supported platforms)." >&2
94
+ exit 1
95
+ ;;
96
+ esac
97
+
98
+ echo "[${COMPONENT}] ${AGENT} plugin installed to $PLUGIN_DST (from $PLUGIN_SRC)."
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+ # uninstall.sh — Remove the tokenless plugin from QwenPaw.
3
+ set -euo pipefail
4
+
5
+ AGENT="${ANOLISA_TARGET:-qwenpaw}"
6
+ COMPONENT="${ANOLISA_COMPONENT:-tokenless}"
7
+ # Resolve the working directory like QwenPaw: QWENPAW_WORKING_DIR, else
8
+ # COPAW_WORKING_DIR, else a legacy ~/.copaw, else ~/.qwenpaw.
9
+ PLUGIN_ID="tokenless"
10
+ QWENPAW_WORKING_DIR="${QWENPAW_WORKING_DIR:-${COPAW_WORKING_DIR:-}}"
11
+ if [ -z "$QWENPAW_WORKING_DIR" ]; then
12
+ if [ -d "$HOME/.copaw" ]; then QWENPAW_WORKING_DIR="$HOME/.copaw"; other="$HOME/.qwenpaw"; else QWENPAW_WORKING_DIR="$HOME/.qwenpaw"; other="$HOME/.copaw"; fi
13
+ # ~/.copaw may have appeared or vanished since the plugin was installed:
14
+ # prefer the default location that actually holds it.
15
+ if [ ! -d "$QWENPAW_WORKING_DIR/plugins/$PLUGIN_ID" ] && [ -d "$other/plugins/$PLUGIN_ID" ]; then
16
+ QWENPAW_WORKING_DIR="$other"
17
+ fi
18
+ fi
19
+ QWENPAW_BIN="${QWENPAW_BIN:-}"
20
+ DRY_RUN="${ANOLISA_DRY_RUN:-0}"
21
+ export PATH="${QWENPAW_HOME:-$HOME/.qwenpaw}/bin:$HOME/.local/bin:/usr/local/bin:$PATH"
22
+
23
+ PLUGIN_DST="${QWENPAW_WORKING_DIR%/}/plugins/${PLUGIN_ID}"
24
+
25
+ echo "[${COMPONENT}] Uninstalling ${AGENT} plugin..."
26
+
27
+ if [ -z "$QWENPAW_BIN" ]; then
28
+ QWENPAW_BIN="$(command -v qwenpaw 2>/dev/null || true)"
29
+ fi
30
+
31
+ if [ "$DRY_RUN" = "1" ]; then
32
+ if [ -n "$QWENPAW_BIN" ] && [ -x "$QWENPAW_BIN" ]; then
33
+ echo "DRY-RUN: QWENPAW_WORKING_DIR=${QWENPAW_WORKING_DIR%/} $QWENPAW_BIN plugin uninstall ${PLUGIN_ID}"
34
+ else
35
+ echo "DRY-RUN: qwenpaw CLI not found; skip CLI uninstall"
36
+ fi
37
+ echo "DRY-RUN: rm -rf $PLUGIN_DST"
38
+ exit 0
39
+ fi
40
+
41
+ if [ ! -d "$PLUGIN_DST" ]; then
42
+ echo "[${COMPONENT}] no ${PLUGIN_ID} plugin is installed in $PLUGIN_DST; set QWENPAW_WORKING_DIR if QwenPaw uses another working directory."
43
+ exit 0
44
+ fi
45
+
46
+ # The CLI hot-unloads a running QwenPaw, prompts for confirmation, removes
47
+ # the plugin directory, and exits 0 even when it fails: the directory still
48
+ # being there means the plugin may still be loaded in a running QwenPaw.
49
+ if [ -n "$QWENPAW_BIN" ] && [ -x "$QWENPAW_BIN" ]; then
50
+ printf 'y\n' | QWENPAW_WORKING_DIR="${QWENPAW_WORKING_DIR%/}" "$QWENPAW_BIN" plugin uninstall "$PLUGIN_ID" || true
51
+ if [ -f "$PLUGIN_DST/plugin.json" ]; then
52
+ echo "[${COMPONENT}] qwenpaw plugin uninstall did not remove $PLUGIN_DST; a running QwenPaw may still have the plugin loaded. Restart QwenPaw and run this script again." >&2
53
+ exit 1
54
+ fi
55
+ else
56
+ echo "[${COMPONENT}] qwenpaw CLI not found; removing $PLUGIN_DST without hot-unloading. Restart QwenPaw if it is running." >&2
57
+ fi
58
+
59
+ rm -rf "$PLUGIN_DST"
60
+
61
+ echo "[${COMPONENT}] ${AGENT} plugin uninstalled."
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "anolisa-tokenless",
3
3
  "type": "module",
4
- "version": "0.7.14",
4
+ "version": "0.8.0",
5
5
  "description": "Token-Less — LLM token optimization toolkit (schema/response compression, command rewriting, tool readiness)",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
@@ -44,10 +44,10 @@
44
44
  "arm64"
45
45
  ],
46
46
  "optionalDependencies": {
47
- "@anolisa/tokenless-linux-x64": "0.7.14",
48
- "@anolisa/tokenless-linux-arm64": "0.7.14",
49
- "@anolisa/tokenless-darwin-x64": "0.7.14",
50
- "@anolisa/tokenless-darwin-arm64": "0.7.14"
47
+ "@anolisa/tokenless-linux-x64": "0.8.0",
48
+ "@anolisa/tokenless-linux-arm64": "0.8.0",
49
+ "@anolisa/tokenless-darwin-x64": "0.8.0",
50
+ "@anolisa/tokenless-darwin-arm64": "0.8.0"
51
51
  },
52
52
  "publishConfig": {
53
53
  "registry": "https://registry.npmjs.org/",
@@ -1,174 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Tokenless standalone TOON encoding hook.
3
-
4
- Reads a PostToolUse JSON from stdin, encodes the tool response
5
- to TOON format via ``tokenless compress-toon``, and writes a
6
- HookOutput JSON to stdout.
7
-
8
- This is a standalone TOON-only hook for users who want pure TOON
9
- encoding without response compression. The combined pipeline
10
- (response compression + TOON) is in compress_response_hook.py.
11
-
12
- Hook point: **PostToolUse**
13
-
14
- The agent ID is read from the TOKENLESS_AGENT_ID environment variable
15
- (set by the install action script).
16
- """
17
-
18
- import json
19
- import os
20
- import subprocess
21
- import sys
22
-
23
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
24
-
25
- from hook_utils import (
26
- _TOKENLESS_FALLBACK,
27
- _TOKENLESS_LOCAL_LIB,
28
- _TOKENLESS_LOCAL_SHARE,
29
- CONTENT_RETRIEVAL_TOOLS,
30
- is_skill_file,
31
- resolve_agent_id,
32
- resolve_binary,
33
- skip,
34
- try_parse_json,
35
- unwrap_string_json,
36
- warn,
37
- )
38
-
39
- # -- constants ---------------------------------------------------------------
40
-
41
- _AGENT_ID = resolve_agent_id()
42
-
43
- # Minimum payload size for TOON encoding. TOON on small JSON saves only a
44
- # few characters (observed ~0.3% below ~500 chars) while the per-event
45
- # encode cost stays the same, so smaller responses pass through untouched.
46
- # This early check avoids the subprocess spawn; the compress-toon CLI
47
- # enforces the same threshold by default (tokenless-runtime MIN_TOON_CHARS),
48
- # so keep the two values in sync.
49
- _MIN_TOON_CHARS = 500
50
-
51
-
52
- # -- main --------------------------------------------------------------------
53
-
54
-
55
- def main() -> None:
56
- # 1. Resolve binaries
57
- tokenless_bin = resolve_binary(
58
- "tokenless",
59
- _TOKENLESS_FALLBACK,
60
- _TOKENLESS_LOCAL_SHARE,
61
- _TOKENLESS_LOCAL_LIB,
62
- )
63
- if not tokenless_bin:
64
- warn("tokenless is not installed. TOON compression hook disabled.")
65
- skip()
66
-
67
- # 2. Read stdin JSON
68
- try:
69
- input_data = json.load(sys.stdin)
70
- except (json.JSONDecodeError, EOFError, ValueError):
71
- warn("failed to read PostToolUse payload. Passing through unchanged.")
72
- skip()
73
-
74
- # 3. Skip content-retrieval tools (preserve integrity)
75
- tool_name = input_data.get("tool_name", "unknown")
76
- if tool_name in CONTENT_RETRIEVAL_TOOLS:
77
- skip()
78
-
79
- # 4. Extract tool_response
80
- tool_response_raw = input_data.get("tool_response", "")
81
- if not tool_response_raw or tool_response_raw == "{}":
82
- skip()
83
-
84
- # 5. Skip skill files (YAML frontmatter)
85
- if isinstance(tool_response_raw, str) and is_skill_file(tool_response_raw):
86
- skip()
87
-
88
- # 6. Normalize: unwrap string-wrapped JSON
89
- if isinstance(tool_response_raw, str):
90
- tool_response = unwrap_string_json(tool_response_raw)
91
- if tool_response is None:
92
- skip() # Plain text, not JSON
93
- elif isinstance(tool_response_raw, (dict, list)):
94
- # ensure_ascii=False: the threshold below counts Unicode
95
- # characters (code points), not \uXXXX escape sequences, so
96
- # structured payloads are measured the same way as JSON string
97
- # inputs and the OpenClaw adapter.
98
- tool_response = json.dumps(
99
- tool_response_raw, separators=(",", ":"), ensure_ascii=False
100
- )
101
- else:
102
- skip()
103
-
104
- if not tool_response:
105
- skip()
106
-
107
- # 7. Skip payloads below the TOON minimum threshold (character count,
108
- # not byte length): TOON savings on small JSON are near-zero
109
- if len(tool_response) < _MIN_TOON_CHARS:
110
- skip()
111
-
112
- # 8. Validate it's JSON
113
- parsed = try_parse_json(tool_response)
114
- if parsed is None:
115
- skip()
116
-
117
- # 9. Extract caller context
118
- session_id = input_data.get("session_id", "")
119
- tool_use_id = input_data.get("tool_use_id") or input_data.get(
120
- "toolCallId", ""
121
- )
122
-
123
- # 10. Encode to TOON via tokenless compress-toon
124
- cmd = [tokenless_bin, "compress-toon", "--agent-id", _AGENT_ID]
125
- if session_id:
126
- cmd.extend(["--session-id", session_id])
127
- if tool_use_id:
128
- cmd.extend(["--tool-use-id", tool_use_id])
129
-
130
- try:
131
- proc = subprocess.run(
132
- cmd,
133
- input=tool_response,
134
- capture_output=True,
135
- text=True,
136
- timeout=10,
137
- )
138
- except Exception as e:
139
- warn(f"TOON encoding failed: {e}. Passing through unchanged.")
140
- skip()
141
-
142
- if proc.returncode != 0:
143
- detail = (proc.stderr or "").strip()[:200]
144
- warn(
145
- f"TOON encoding exited with code {proc.returncode}: {detail}"
146
- if detail
147
- else f"TOON encoding exited with code {proc.returncode}. Passing through unchanged."
148
- )
149
- skip()
150
-
151
- toon_output = proc.stdout.strip()
152
- if not toon_output:
153
- warn("TOON encoding returned empty output. Passing through unchanged.")
154
- skip()
155
-
156
- # 11. Size guard — skip if TOON output is not smaller
157
- before_chars = len(tool_response)
158
- after_chars = len(toon_output)
159
- if after_chars >= before_chars:
160
- skip()
161
-
162
- # 12. Build response
163
- output = {
164
- "suppressOutput": True,
165
- "hookSpecificOutput": {
166
- "hookEventName": "PostToolUse",
167
- "additionalContext": toon_output,
168
- },
169
- }
170
- print(json.dumps(output, ensure_ascii=False))
171
-
172
-
173
- if __name__ == "__main__":
174
- main()