anolisa-tokenless 0.7.13 → 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 +219 -87
- 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 +5 -4
- package/adapters/tokenless/codex/README.md +22 -32
- package/adapters/tokenless/codex/hooks/hooks.json +3 -3
- package/adapters/tokenless/codex/scripts/response-diagnostics +170 -0
- package/adapters/tokenless/common/cosh-extension.json +4 -4
- package/adapters/tokenless/common/hooks/compress_response_hook.py +250 -307
- package/adapters/tokenless/common/hooks/compress_schema_hook.py +34 -42
- package/adapters/tokenless/common/hooks/hook_utils.py +281 -44
- 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 -355
- package/adapters/tokenless/hermes/plugin.yaml +2 -2
- package/adapters/tokenless/manifest.json +18 -3
- 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/codex/scripts/compress-response +0 -445
- package/adapters/tokenless/common/hooks/compress_toon_hook.py +0 -171
|
@@ -1,225 +1,109 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Tokenless command rewriting hook via
|
|
2
|
+
"""Tokenless command rewriting hook via Protocol v2 PreTool.
|
|
3
3
|
|
|
4
|
-
Reads a PreToolUse JSON from stdin,
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
Reads a PreToolUse JSON from stdin, forwards shell arguments to
|
|
5
|
+
``tokenless compress``, and translates the Core result into the host's
|
|
6
|
+
HookOutput envelope.
|
|
7
7
|
|
|
8
8
|
Hook point: **PreToolUse** — matcher: shell-family tool names
|
|
9
9
|
(``Bash``, ``run_shell_command``, ``terminal``, ``Shell``, ``shell``,
|
|
10
|
-
``exec``, ``process``).
|
|
11
|
-
cosh-ng, whose built-in shell tool is named ``shell`` on the wire.
|
|
10
|
+
``exec``, ``process``).
|
|
12
11
|
|
|
13
|
-
The agent ID is
|
|
14
|
-
|
|
15
|
-
FHS spec: /usr/
|
|
12
|
+
The agent ID is resolved from the host runtime, ``--agent-id`` argument, or
|
|
13
|
+
TOKENLESS_AGENT_ID environment variable. Fallback paths follow the ANOLISA
|
|
14
|
+
FHS spec: /usr/bin/tokenless.
|
|
16
15
|
"""
|
|
17
16
|
|
|
18
17
|
import json
|
|
19
18
|
import os
|
|
20
|
-
import shlex
|
|
21
|
-
import subprocess
|
|
22
19
|
import sys
|
|
23
20
|
|
|
24
21
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
25
22
|
|
|
26
23
|
from hook_utils import (
|
|
27
|
-
_RTK_FALLBACK,
|
|
28
|
-
_RTK_LOCAL_LIB,
|
|
29
|
-
_RTK_LOCAL_SHARE,
|
|
30
24
|
_TOKENLESS_FALLBACK,
|
|
31
25
|
_TOKENLESS_LOCAL_LIB,
|
|
32
26
|
_TOKENLESS_LOCAL_SHARE,
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
build_pre_tool_request,
|
|
28
|
+
mark_rtk_optimized,
|
|
35
29
|
resolve_agent_id,
|
|
36
30
|
resolve_binary,
|
|
37
31
|
resolve_tool_call_id,
|
|
32
|
+
run_compress,
|
|
38
33
|
skip,
|
|
39
34
|
warn,
|
|
40
|
-
write_context,
|
|
41
35
|
)
|
|
42
36
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
_MIN_RTK_VERSION = (0, 35, 0)
|
|
37
|
+
_PRE_TOOL_TIMEOUT = 8
|
|
46
38
|
_AGENT_ID = resolve_agent_id()
|
|
47
39
|
|
|
48
|
-
# Shell connectives that terminate a command-list / pipeline segment.
|
|
49
|
-
# A bare `rtk` wrapper can appear at command start or right after one.
|
|
50
|
-
_SEGMENT_OPS = frozenset({"&&", "||", ";", "|", "&"})
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _is_env_assignment(token: str) -> bool:
|
|
54
|
-
"""Return True for a leading shell variable assignment (NAME=value)."""
|
|
55
|
-
name, sep, _ = token.partition("=")
|
|
56
|
-
if not sep or not name:
|
|
57
|
-
return False
|
|
58
|
-
if not (name[0].isalpha() or name[0] == "_"):
|
|
59
|
-
return False
|
|
60
|
-
return all(c.isalnum() or c == "_" for c in name)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def _anchor_rtk_prefix(rewritten: str, rtk_bin: str) -> str:
|
|
64
|
-
"""Replace bare `rtk` wrapper tokens with the resolved binary path.
|
|
65
|
-
|
|
66
|
-
rtk prints rewrites with a literal `rtk` prefix, which only resolves
|
|
67
|
-
when the shell executing the tool call has the rtk location on its
|
|
68
|
-
PATH. Agent runtimes with a trimmed PATH (e.g. IDE tool environments
|
|
69
|
-
without ~/.local/bin) would fail every rewritten command with exit
|
|
70
|
-
127 even though the hook resolved rtk successfully. Anchoring makes
|
|
71
|
-
the rewritten command self-contained.
|
|
72
|
-
|
|
73
|
-
The pass swaps the first unquoted `rtk` token of each segment — at
|
|
74
|
-
command start or right after `&&` / `||` / `;` / `|` / `&`,
|
|
75
|
-
optionally behind leading environment assignments or wrappers such
|
|
76
|
-
as `sudo`. It lexes with `posix=False` and no punctuation splitting,
|
|
77
|
-
so every token keeps its original surface: quoted patterns like
|
|
78
|
-
`grep -E 'foo|rtk bar'` are never modified, unquoted globs like
|
|
79
|
-
`*.txt` are not re-quoted into literals, fd redirections (`2>&1`,
|
|
80
|
-
`2>/dev/null`) and command substitutions (`$(date)`) stay intact,
|
|
81
|
-
and comment stripping is disabled so a `#` argument cannot truncate
|
|
82
|
-
the command. Connectives are recognized as whitespace-delimited
|
|
83
|
-
tokens; an unspaced `cmd1;cmd2` is not split, which only leaves
|
|
84
|
-
that segment's `rtk` unanchored (conservative, never corrupts).
|
|
85
|
-
Unparseable input is returned untouched.
|
|
86
|
-
|
|
87
|
-
Known limitation: a bare `rtk` token that is another command's
|
|
88
|
-
argument (e.g. `echo rtk done`) is indistinguishable from a wrapper
|
|
89
|
-
position inside the rtk-rewrite output domain and gets anchored.
|
|
90
|
-
Real rtk rewrites do not produce that shape, so this is accepted.
|
|
91
|
-
"""
|
|
92
|
-
lexer = shlex.shlex(rewritten, posix=False)
|
|
93
|
-
lexer.whitespace_split = True
|
|
94
|
-
lexer.commenters = ""
|
|
95
|
-
try:
|
|
96
|
-
tokens = list(lexer)
|
|
97
|
-
except ValueError:
|
|
98
|
-
return rewritten
|
|
99
|
-
|
|
100
|
-
quoted = shlex.quote(rtk_bin)
|
|
101
|
-
result = list(tokens)
|
|
102
|
-
wrapped = False
|
|
103
|
-
for i, token in enumerate(tokens):
|
|
104
|
-
if token in _SEGMENT_OPS:
|
|
105
|
-
wrapped = False
|
|
106
|
-
continue
|
|
107
|
-
if _is_env_assignment(token):
|
|
108
|
-
continue
|
|
109
|
-
if not wrapped and token == "rtk":
|
|
110
|
-
result[i] = quoted
|
|
111
|
-
wrapped = True
|
|
112
|
-
|
|
113
|
-
return " ".join(result)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
# -- main --------------------------------------------------------------------
|
|
117
|
-
|
|
118
40
|
|
|
119
41
|
def main() -> None:
|
|
120
|
-
|
|
121
|
-
rtk_bin = resolve_binary(
|
|
122
|
-
"rtk", _RTK_FALLBACK, _RTK_LOCAL_SHARE, _RTK_LOCAL_LIB
|
|
123
|
-
)
|
|
124
|
-
if not rtk_bin:
|
|
125
|
-
warn("rtk is not installed or not in PATH. Hook disabled.")
|
|
126
|
-
skip()
|
|
127
|
-
|
|
128
|
-
# 2. Version guard
|
|
129
|
-
try:
|
|
130
|
-
result = subprocess.run(
|
|
131
|
-
[rtk_bin, "--version"],
|
|
132
|
-
capture_output=True,
|
|
133
|
-
text=True,
|
|
134
|
-
timeout=3,
|
|
135
|
-
)
|
|
136
|
-
ver = parse_version(result.stdout)
|
|
137
|
-
if ver and ver < _MIN_RTK_VERSION:
|
|
138
|
-
warn(f"rtk {result.stdout.strip()} is too old (need >= 0.35.0).")
|
|
139
|
-
skip()
|
|
140
|
-
except Exception as e:
|
|
141
|
-
warn(f"rtk version check failed: {e}")
|
|
142
|
-
|
|
143
|
-
# 3. Check tokenless binary (for stats)
|
|
144
|
-
if not resolve_binary(
|
|
42
|
+
tokenless_bin = resolve_binary(
|
|
145
43
|
"tokenless",
|
|
146
44
|
_TOKENLESS_FALLBACK,
|
|
147
45
|
_TOKENLESS_LOCAL_SHARE,
|
|
148
46
|
_TOKENLESS_LOCAL_LIB,
|
|
149
|
-
)
|
|
47
|
+
)
|
|
48
|
+
if not tokenless_bin:
|
|
150
49
|
warn("tokenless is not installed. Hook disabled.")
|
|
151
50
|
skip()
|
|
152
51
|
|
|
153
|
-
# 4. Read stdin JSON
|
|
154
52
|
try:
|
|
155
53
|
input_data = json.load(sys.stdin)
|
|
156
54
|
except (json.JSONDecodeError, EOFError, ValueError):
|
|
157
55
|
skip()
|
|
158
56
|
|
|
159
|
-
# 5. Extract command
|
|
160
57
|
tool_input = input_data.get("tool_input", {})
|
|
161
|
-
|
|
162
|
-
|
|
58
|
+
if not isinstance(tool_input, dict):
|
|
59
|
+
skip()
|
|
60
|
+
command = tool_input.get("command", "")
|
|
61
|
+
if not isinstance(command, str) or not command:
|
|
163
62
|
skip()
|
|
164
63
|
|
|
165
|
-
# 6. Rewrite via rtk
|
|
166
|
-
env = os.environ.copy()
|
|
167
|
-
env["TOKENLESS_AGENT_ID"] = _AGENT_ID
|
|
168
64
|
session_id = input_data.get("session_id", "")
|
|
169
65
|
tool_use_id = resolve_tool_call_id(_AGENT_ID, input_data)
|
|
170
|
-
if
|
|
171
|
-
|
|
172
|
-
if tool_use_id:
|
|
173
|
-
env["TOKENLESS_TOOL_USE_ID"] = tool_use_id
|
|
174
|
-
|
|
175
|
-
write_context(_AGENT_ID, session_id, tool_use_id)
|
|
66
|
+
if not tool_use_id:
|
|
67
|
+
skip()
|
|
176
68
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
69
|
+
request = build_pre_tool_request(
|
|
70
|
+
tool_input,
|
|
71
|
+
_AGENT_ID,
|
|
72
|
+
input_data.get("tool_name", ""),
|
|
73
|
+
"command",
|
|
74
|
+
session_id=session_id,
|
|
75
|
+
tool_use_id=tool_use_id,
|
|
76
|
+
)
|
|
77
|
+
response = run_compress(tokenless_bin, request, _PRE_TOOL_TIMEOUT, "pre_tool")
|
|
78
|
+
if response is None or response.get("action") != "replace_arguments":
|
|
79
|
+
skip()
|
|
80
|
+
if response.get("output_optimization") != "rtk":
|
|
187
81
|
skip()
|
|
188
82
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
# 1 = no RTK equivalent (passthrough)
|
|
192
|
-
# 2 = deny rule matched (let hook handle)
|
|
193
|
-
# 3 = Ask/Default verdict (rewrite available but permission model requires
|
|
194
|
-
# user confirmation; in non-interactive hook context, treat as valid
|
|
195
|
-
# rewrite since the intent is token optimization, not permission gating)
|
|
196
|
-
if proc.returncode not in (0, 1, 2, 3):
|
|
197
|
-
forward_stderr(proc)
|
|
198
|
-
warn(f"rtk rewrite exited with unexpected code {proc.returncode}")
|
|
83
|
+
updated_input = response.get("arguments")
|
|
84
|
+
if not isinstance(updated_input, dict):
|
|
199
85
|
skip()
|
|
200
|
-
|
|
86
|
+
rewritten = updated_input.get("command")
|
|
87
|
+
if not isinstance(rewritten, str) or not rewritten or rewritten == command:
|
|
201
88
|
skip()
|
|
202
|
-
|
|
203
|
-
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
mark_rtk_optimized(_AGENT_ID, session_id, tool_use_id)
|
|
92
|
+
except OSError as error:
|
|
93
|
+
warn(f"failed to persist PreTool optimization state: {error}")
|
|
204
94
|
skip()
|
|
205
95
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
"hookEventName": "PreToolUse",
|
|
218
|
-
"tool_input": {"command": rewritten},
|
|
219
|
-
"updatedInput": updated_input,
|
|
220
|
-
},
|
|
221
|
-
}
|
|
222
|
-
print(json.dumps(output))
|
|
96
|
+
print(
|
|
97
|
+
json.dumps(
|
|
98
|
+
{
|
|
99
|
+
"hookSpecificOutput": {
|
|
100
|
+
"hookEventName": "PreToolUse",
|
|
101
|
+
"tool_input": {"command": rewritten},
|
|
102
|
+
"updatedInput": updated_input,
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
)
|
|
223
107
|
|
|
224
108
|
|
|
225
109
|
if __name__ == "__main__":
|