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.
- package/README.md +206 -79
- package/adapters/tokenless/claude-code/.claude-plugin/plugin.json +1 -1
- package/adapters/tokenless/claude-code/hooks/run-hook.sh +62 -0
- package/adapters/tokenless/codex/.codex-plugin/plugin.json +1 -1
- package/adapters/tokenless/common/cosh-extension.json +4 -4
- package/adapters/tokenless/common/hooks/compress_response_hook.py +180 -96
- package/adapters/tokenless/common/hooks/compress_schema_hook.py +19 -31
- package/adapters/tokenless/common/hooks/hook_utils.py +240 -70
- package/adapters/tokenless/common/hooks/rewrite_hook.py +53 -169
- package/adapters/tokenless/dsh/dist/index.js +353 -264
- package/adapters/tokenless/dsh/package.json +2 -2
- package/adapters/tokenless/hermes/__init__.py +191 -357
- package/adapters/tokenless/hermes/plugin.yaml +2 -2
- package/adapters/tokenless/manifest.json +17 -1
- package/adapters/tokenless/openclaw/dist/index.d.ts +4 -16
- package/adapters/tokenless/openclaw/dist/index.js +291 -507
- package/adapters/tokenless/openclaw/index.ts +408 -628
- package/adapters/tokenless/openclaw/openclaw.plugin.json +4 -20
- package/adapters/tokenless/openclaw/package.json +6 -4
- package/adapters/tokenless/qoder/.qoder-plugin/plugin.json +1 -1
- package/adapters/tokenless/qwencode/hooks/run-hook.sh +62 -0
- package/adapters/tokenless/qwencode/qwen-extension.json +4 -4
- package/adapters/tokenless/qwenpaw/plugin.json +17 -0
- package/adapters/tokenless/qwenpaw/plugin.py +390 -0
- package/adapters/tokenless/qwenpaw/requirements.txt +6 -0
- package/adapters/tokenless/qwenpaw/scripts/detect.sh +131 -0
- package/adapters/tokenless/qwenpaw/scripts/install.sh +98 -0
- package/adapters/tokenless/qwenpaw/scripts/uninstall.sh +61 -0
- package/package.json +5 -5
- 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.
|
|
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.
|
|
48
|
-
"@anolisa/tokenless-linux-arm64": "0.
|
|
49
|
-
"@anolisa/tokenless-darwin-x64": "0.
|
|
50
|
-
"@anolisa/tokenless-darwin-arm64": "0.
|
|
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()
|