anolisa-tokenless 0.7.7

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 (70) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +770 -0
  3. package/adapters/tokenless/claude-code/.claude-plugin/marketplace.json +15 -0
  4. package/adapters/tokenless/claude-code/.claude-plugin/plugin.json +9 -0
  5. package/adapters/tokenless/claude-code/hooks/hooks.json +38 -0
  6. package/adapters/tokenless/claude-code/scripts/detect.sh +193 -0
  7. package/adapters/tokenless/claude-code/scripts/install.sh +92 -0
  8. package/adapters/tokenless/claude-code/scripts/uninstall.sh +50 -0
  9. package/adapters/tokenless/codex/.codex-plugin/plugin.json +20 -0
  10. package/adapters/tokenless/codex/README.md +160 -0
  11. package/adapters/tokenless/codex/hooks/hooks.json +52 -0
  12. package/adapters/tokenless/codex/scripts/_common.sh +25 -0
  13. package/adapters/tokenless/codex/scripts/check-tokenless +94 -0
  14. package/adapters/tokenless/codex/scripts/compress-response +441 -0
  15. package/adapters/tokenless/codex/scripts/detect.sh +61 -0
  16. package/adapters/tokenless/codex/scripts/install.sh +151 -0
  17. package/adapters/tokenless/codex/scripts/rewrite-hook +282 -0
  18. package/adapters/tokenless/codex/scripts/tool-ready +303 -0
  19. package/adapters/tokenless/codex/scripts/uninstall.sh +78 -0
  20. package/adapters/tokenless/common/commands/tokenless-stats.toml +2 -0
  21. package/adapters/tokenless/common/cosh-extension.json +65 -0
  22. package/adapters/tokenless/common/hooks/compress_response_hook.py +487 -0
  23. package/adapters/tokenless/common/hooks/compress_schema_hook.py +144 -0
  24. package/adapters/tokenless/common/hooks/compress_toon_hook.py +160 -0
  25. package/adapters/tokenless/common/hooks/hook_utils.py +584 -0
  26. package/adapters/tokenless/common/hooks/rewrite_hook.py +223 -0
  27. package/adapters/tokenless/common/hooks/run-hook.sh +62 -0
  28. package/adapters/tokenless/common/hooks/tool_categories.json +99 -0
  29. package/adapters/tokenless/common/hooks/tool_ready_hook.sh +571 -0
  30. package/adapters/tokenless/common/tokenless-env-fix.sh +730 -0
  31. package/adapters/tokenless/common/tool-ready-spec.json +113 -0
  32. package/adapters/tokenless/dsh/cordis.patch.yml +8 -0
  33. package/adapters/tokenless/dsh/dist/index.js +399 -0
  34. package/adapters/tokenless/dsh/package.json +26 -0
  35. package/adapters/tokenless/hermes/__init__.py +572 -0
  36. package/adapters/tokenless/hermes/plugin.yaml +9 -0
  37. package/adapters/tokenless/hermes/scripts/detect.sh +80 -0
  38. package/adapters/tokenless/hermes/scripts/install.sh +60 -0
  39. package/adapters/tokenless/hermes/scripts/uninstall.sh +45 -0
  40. package/adapters/tokenless/manifest.json +147 -0
  41. package/adapters/tokenless/openclaw/dist/index.d.ts +27 -0
  42. package/adapters/tokenless/openclaw/dist/index.js +598 -0
  43. package/adapters/tokenless/openclaw/dist/tool_categories.json +99 -0
  44. package/adapters/tokenless/openclaw/index.ts +720 -0
  45. package/adapters/tokenless/openclaw/openclaw.plugin.json +40 -0
  46. package/adapters/tokenless/openclaw/package.json +24 -0
  47. package/adapters/tokenless/openclaw/scripts/detect.sh +102 -0
  48. package/adapters/tokenless/openclaw/scripts/install.sh +75 -0
  49. package/adapters/tokenless/openclaw/scripts/uninstall.sh +46 -0
  50. package/adapters/tokenless/openclaw/tsconfig.json +13 -0
  51. package/adapters/tokenless/opencode/plugin.js +246 -0
  52. package/adapters/tokenless/opencode/scripts/detect.sh +38 -0
  53. package/adapters/tokenless/opencode/scripts/install.sh +56 -0
  54. package/adapters/tokenless/opencode/scripts/uninstall.sh +29 -0
  55. package/adapters/tokenless/qoder/.qoder-plugin/plugin.json +11 -0
  56. package/adapters/tokenless/qoder/commands/tokenless-stats.md +8 -0
  57. package/adapters/tokenless/qoder/hooks/hooks.json +36 -0
  58. package/adapters/tokenless/qoder/hooks/run-hook.sh +51 -0
  59. package/adapters/tokenless/qoder/scripts/detect.sh +35 -0
  60. package/adapters/tokenless/qoder/scripts/install.sh +136 -0
  61. package/adapters/tokenless/qoder/scripts/uninstall.sh +106 -0
  62. package/adapters/tokenless/qwencode/qwen-extension.json +69 -0
  63. package/adapters/tokenless/qwencode/scripts/detect.sh +125 -0
  64. package/adapters/tokenless/qwencode/scripts/install.sh +128 -0
  65. package/adapters/tokenless/qwencode/scripts/uninstall.sh +62 -0
  66. package/bin/rtk +6 -0
  67. package/bin/tokenless +6 -0
  68. package/bin/toon +6 -0
  69. package/package.json +57 -0
  70. package/scripts/postinstall.js +208 -0
@@ -0,0 +1,113 @@
1
+ {
2
+ "_meta": {
3
+ "version": "2.0",
4
+ "description": "Dormant legacy Tool Ready dependency spec. Release builds do not read it while the hard bypass is active.",
5
+ "related_files": {
6
+ "tool_categories.json": "Unified tool categorization for compression strategies. Tool names in 'aliases' must match those in tool_categories.json to ensure consistent behavior across tool-ready and compression."
7
+ },
8
+ "schema": {
9
+ "tool_entry": {
10
+ "aliases": "Cross-framework tool name mapping. Cosh/openclaw/hermes use different tool names; aliases collect all variants for reverse lookup.",
11
+ "required": "Hard dependencies. Any missing → NOT_READY, triggers auto-fix.",
12
+ "recommended": "Soft dependencies. Missing → PARTIAL. System tools (git, docker, cargo, uv) are shell-invoked, grouped under Shell.",
13
+ "config_files": "Required config file paths (~ expansion supported). Missing → PARTIAL.",
14
+ "permissions": "Required system permissions: file_read, file_write, exec_shell. Missing → NOT_READY. Install readiness only, not runtime state.",
15
+ "network": "Removed. Network connectivity is runtime state, not install readiness."
16
+ },
17
+ "dep_entry": {
18
+ "binary": "CLI tool name for command -v detection",
19
+ "version": "Version constraint, e.g. >=0.35",
20
+ "package": "Package name in the manager",
21
+ "apt_package": "Override package name when detected manager is apt (Debian/Ubuntu). Optional; defaults to package.",
22
+ "apk_package": "Override package name when detected manager is apk (Alpine). Optional; defaults to package.",
23
+ "manager": "Package manager type. Default rpm (auto-detects yum/dnf/apt/apk at runtime)",
24
+ "pip_name": "pip package name (default = package)",
25
+ "uv_name": "uv package name (default = package)",
26
+ "npm_name": "npm package name (default = package)",
27
+ "use_npx": "Whether to use npx for execution",
28
+ "url": "Download URL for curl_pipe_sh install",
29
+ "args": "Extra args for curl_pipe_sh install",
30
+ "fallback": "Fallback install strategies, tried sequentially on primary failure"
31
+ }
32
+ },
33
+ "supported_managers": {
34
+ "rpm": "System package manager (auto-detects yum/dnf/apt-get/apk by runtime environment)",
35
+ "apt": "apt-get — Debian/Ubuntu (only when explicitly specified)",
36
+ "pip": "pip / pip3 — Python packages",
37
+ "uv": "uv tool install / uv pip install — modern Python package manager",
38
+ "npm": "npm install -g — Node.js global packages",
39
+ "npx": "npx -y — Node.js ephemeral execution",
40
+ "cargo": "cargo install --locked — Rust crates.io",
41
+ "cargo_build": "cargo build --manifest-path — local source build",
42
+ "symlink": "ln -sf — pre-built binary symlink",
43
+ "path": "export PATH — directory injection",
44
+ "dir": "mkdir -p — directory creation",
45
+ "curl_pipe_sh": "curl/wget | sh — official install script (fallback only)"
46
+ },
47
+ "categories": "4 agent tool categories: Shell/WebFetch/Read/Write. System tools (git, docker, cargo, uv) invoked via shell → grouped under Shell.recommended."
48
+ },
49
+
50
+ "Shell": {
51
+ "_description": "Shell — base command execution environment for AI agents",
52
+ "_compression_layer": "layer_2_shell",
53
+ "aliases": ["Bash", "bash", "Shell", "shell", "run_shell_command", "run_in_terminal", "get_terminal_output", "terminal", "exec", "execute_command", "process"],
54
+ "required": [
55
+ { "binary": "bash", "package": "bash", "manager": "rpm" },
56
+ { "binary": "jq", "package": "jq", "manager": "rpm" }
57
+ ],
58
+ "recommended": [
59
+ { "binary": "git", "package": "git", "manager": "rpm" },
60
+ { "binary": "ssh", "package": "openssh-clients", "apt_package": "openssh-client", "manager": "rpm" },
61
+ { "binary": "docker", "package": "docker-ce", "apt_package": "docker.io", "manager": "rpm",
62
+ "fallback": [
63
+ { "method": "rpm", "binary": "docker", "package": "docker" }
64
+ ]
65
+ },
66
+ { "binary": "uv", "package": "uv", "manager": "pip", "pip_name": "uv",
67
+ "fallback": [
68
+ { "method": "curl_pipe_sh", "binary": "uv", "url": "https://astral.sh/uv/install.sh" },
69
+ { "method": "cargo", "binary": "uv", "package": "uv" }
70
+ ]
71
+ },
72
+ { "binary": "python3", "package": "python3", "manager": "rpm" },
73
+ { "binary": "cargo", "package": "cargo", "manager": "rpm" },
74
+ { "binary": "rustc", "package": "rustc", "manager": "rpm" }
75
+ ],
76
+ "permissions": ["exec_shell"],
77
+ "network": []
78
+ },
79
+
80
+ "WebFetch": {
81
+ "_description": "WebFetch — web request tool for AI agents",
82
+ "_compression_layer": "layer_3_api",
83
+ "aliases": ["WebFetch", "web_fetch", "fetch_content", "WebSearch", "web_search", "search_web", "web_extract", "x_search"],
84
+ "required": [
85
+ { "binary": "curl", "package": "curl", "manager": "rpm" }
86
+ ],
87
+ "recommended": [],
88
+ "permissions": [],
89
+ "network": []
90
+ },
91
+
92
+ "Read": {
93
+ "_description": "Read — file read and search tool for AI agents",
94
+ "_compression_layer": "layer_1_skip",
95
+ "aliases": ["Read", "read", "read_file", "read_many_files", "Glob", "glob", "search_file", "list_directory", "list_dir", "Grep", "grep", "grep_code", "grep_search", "search_files", "Lsp", "lsp", "NotebookRead", "notebook_read", "notebookread"],
96
+ "required": [],
97
+ "recommended": [],
98
+ "config_files": [],
99
+ "permissions": ["file_read"],
100
+ "network": []
101
+ },
102
+
103
+ "Write": {
104
+ "_description": "Write — file write and edit tool for AI agents",
105
+ "_compression_layer": "layer_3_api",
106
+ "aliases": ["Write", "write", "write_file", "create_file", "Edit", "edit", "edit_file", "search_replace", "delete_file", "apply_patch", "patch"],
107
+ "required": [],
108
+ "recommended": [],
109
+ "config_files": [],
110
+ "permissions": ["file_write"],
111
+ "network": []
112
+ }
113
+ }
@@ -0,0 +1,8 @@
1
+ # Tokenless native bundle layer for DeepSeek Harness.
2
+ #
3
+ # The dsh adapter installs this bundle through the framework's own plugin
4
+ # lifecycle. Resolve the installed package by its published name so dsh can
5
+ # load the bundle through its normal npm package lookup.
6
+ - insert:
7
+ - id: anolisa-tokenless
8
+ name: '@anolisa/dsh-tokenless'
@@ -0,0 +1,399 @@
1
+ /**
2
+ * Native Tokenless plugin for DeepSeek Harness (dsh).
3
+ *
4
+ * This entry intentionally has no dsh runtime imports. dsh supplies the
5
+ * Cordis event types at runtime, while the only process boundary is the
6
+ * installed Tokenless CLI. Keeping the entry dependency-free lets ANOLISA
7
+ * install one self-contained bundle without running npm in $DSH_HOME.
8
+ */
9
+ import { execFile } from 'node:child_process'
10
+ import { randomUUID } from 'node:crypto'
11
+
12
+ const PLUGIN_NAME = 'anolisa-tokenless'
13
+ const DEFAULT_AGENT_ID = 'dsh'
14
+ const DEFAULT_TIMEOUT_MS = 3000
15
+ const DEFAULT_MAX_BUFFER = 2 * 1024 * 1024
16
+
17
+ // Keep content-retrieval tools lossless. These names mirror the shared
18
+ // Tokenless adapter taxonomy; callers may extend or replace the list through
19
+ // the dsh plugin config without changing this safety default.
20
+ const DEFAULT_SKIP_TOOLS = new Set([
21
+ 'Read',
22
+ 'read',
23
+ 'read_file',
24
+ 'read_many_files',
25
+ 'Glob',
26
+ 'glob',
27
+ 'search_file',
28
+ 'list_directory',
29
+ 'list_dir',
30
+ 'Grep',
31
+ 'grep',
32
+ 'grep_code',
33
+ 'grep_search',
34
+ 'search_files',
35
+ 'Lsp',
36
+ 'lsp',
37
+ 'NotebookRead',
38
+ 'notebook_read',
39
+ 'notebookread',
40
+ ])
41
+
42
+ // These values are the same thresholds used by the shared hook adapter. A
43
+ // dsh profile can override them in its plugin config; the Tokenless CLI still
44
+ // owns the actual compression algorithm and stats recording.
45
+ const DEFAULT_SHELL_TOOLS = new Set([
46
+ 'Bash',
47
+ 'bash',
48
+ 'Shell',
49
+ 'shell',
50
+ 'exec',
51
+ 'terminal',
52
+ 'run_shell_command',
53
+ 'run_in_terminal',
54
+ 'get_terminal_output',
55
+ 'execute_command',
56
+ 'process',
57
+ ])
58
+
59
+ const DEFAULT_THRESHOLDS = {
60
+ shell: { strings: 65536, arrays: 128, depth: 8 },
61
+ api: { strings: 1048576, arrays: 65536, depth: 32 },
62
+ }
63
+
64
+ // Mirror common/hooks/hook_utils.py ENV_PATTERNS exactly. The dsh bundle is
65
+ // independently publishable, so changes to the canonical table must update
66
+ // this dependency-free runtime copy and its tests together.
67
+ const ENV_PATTERNS = [
68
+ [
69
+ [
70
+ /command not found/i,
71
+ /not installed/i,
72
+ /which:\s+no/i,
73
+ /no command\s/i,
74
+ /cannot execute/i,
75
+ /is not recognized/i,
76
+ /could not find/i,
77
+ /unable to locate/i,
78
+ /package not found/i,
79
+ /\/bin\/sh:.*: not found/i,
80
+ /command not found:/i,
81
+ ],
82
+ 'ENV_DEPENDENCY_MISSING',
83
+ 'Missing dependency detected. Install it or ask the user for guidance.',
84
+ ],
85
+ [
86
+ [
87
+ /permission denied/i,
88
+ /operation not permitted/i,
89
+ /eacces/i,
90
+ /access denied/i,
91
+ /cannot open .* for writing/i,
92
+ ],
93
+ 'ENV_PERMISSION',
94
+ 'Permission denied. Check file/directory permissions or run with appropriate access.',
95
+ ],
96
+ [
97
+ [
98
+ /no such file or directory/i,
99
+ /enoent/i,
100
+ /cannot find/i,
101
+ /file not found/i,
102
+ /does not exist/i,
103
+ ],
104
+ 'ENV_FILE_MISSING',
105
+ 'Required file or directory not found. Verify the path or create it.',
106
+ ],
107
+ [
108
+ [
109
+ /connection refused/i,
110
+ /could not resolve host/i,
111
+ /network is unreachable/i,
112
+ /curl: \(7\)/i,
113
+ /curl: \(6\)/i,
114
+ /failed to connect/i,
115
+ /name or service not known/i,
116
+ /couldn't resolve host/i,
117
+ /temporary failure in name resolution/i,
118
+ /econnrefused/i,
119
+ /etimedout/i,
120
+ /connection timed out/i,
121
+ ],
122
+ 'ENV_NETWORK',
123
+ 'Network connectivity issue. Check DNS, proxy, and firewall settings.',
124
+ ],
125
+ [
126
+ [
127
+ /modulenotfounderror/i,
128
+ /importerror/i,
129
+ /no module named/i,
130
+ /cannot import name/i,
131
+ /npm err! 404/i,
132
+ ],
133
+ 'ENV_PACKAGE_MISSING',
134
+ 'Required package or module is missing. Install the needed dependency.',
135
+ ],
136
+ ]
137
+
138
+ /** Return a plain config value or the supplied fallback. */
139
+ function valueOr(config, key, fallback) {
140
+ return config && Object.prototype.hasOwnProperty.call(config, key)
141
+ ? config[key]
142
+ : fallback
143
+ }
144
+
145
+ /** Normalize a config tool list without allowing malformed values to widen it. */
146
+ function toolSet(value, fallback) {
147
+ if (!Array.isArray(value)) return fallback
148
+ return new Set(value.filter((name) => typeof name === 'string' && name.length > 0))
149
+ }
150
+
151
+ /** Resolve the executable without ever installing or mutating a dsh profile. */
152
+ function tokenlessBinary(config) {
153
+ const configured = valueOr(config, 'tokenlessBin', undefined)
154
+ if (typeof configured === 'string' && configured.length > 0) return configured
155
+ return process.env.TOKENLESS_BIN || 'tokenless'
156
+ }
157
+
158
+ /** Extract text used only for error attribution; never stringify image blocks. */
159
+ function errorText(result) {
160
+ if (!result || typeof result !== 'object') return ''
161
+ const error = result.error
162
+ if (error && typeof error.message === 'string') return error.message
163
+ return result.content
164
+ ?.filter((block) => block && block.type === 'text' && typeof block.text === 'string')
165
+ .map((block) => block.text)
166
+ .join('\n') || ''
167
+ }
168
+
169
+ /** Return an environment category and remediation hint for known failure text. */
170
+ function classifyEnvironmentError(text) {
171
+ if (typeof text !== 'string') return undefined
172
+ if (!text) return undefined
173
+ for (const [patterns, category, hint] of ENV_PATTERNS) {
174
+ if (patterns.some((pattern) => pattern.test(text))) return { category, hint }
175
+ }
176
+ return undefined
177
+ }
178
+
179
+ /** Extract attribution only when structured output explicitly reports failure. */
180
+ function classifyStructuredEnvironmentError(value) {
181
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined
182
+ const exitCode = value.exit_code ?? value.exitCode
183
+ const stringExitCode = typeof exitCode === 'string' ? exitCode.trim() : ''
184
+ const nonzeroExit = (typeof exitCode === 'number' && exitCode !== 0)
185
+ || (/^-?\d+$/.test(stringExitCode) && Number(stringExitCode) !== 0)
186
+ const timedOut = value.timed_out === true || value.timedOut === true
187
+ const failed = nonzeroExit
188
+ || timedOut
189
+ || value.isError === true
190
+ || value.success === false
191
+ || value.ok === false
192
+ if (!failed) return undefined
193
+ const error = value.error
194
+ let errorValue = error
195
+ if (error && typeof error === 'object') {
196
+ if (typeof error.message === 'string') {
197
+ errorValue = error.message
198
+ } else {
199
+ try {
200
+ errorValue = JSON.stringify(error)
201
+ } catch {
202
+ errorValue = String(error)
203
+ }
204
+ }
205
+ }
206
+ const streamText = (stream) => {
207
+ if (typeof stream === 'string') return stream
208
+ if (stream && typeof stream === 'object' && typeof stream.text === 'string') return stream.text
209
+ return undefined
210
+ }
211
+ const text = [streamText(value.stderr), errorValue]
212
+ .filter((part) => typeof part === 'string')
213
+ .join('\n')
214
+ return classifyEnvironmentError(text)
215
+ }
216
+
217
+ /** Construct a valid plugin-owned user message without importing dsh modules. */
218
+ function attributionContext(text) {
219
+ return {
220
+ id: randomUUID(),
221
+ role: 'user',
222
+ content: [{ type: 'text', text }],
223
+ source: {
224
+ kind: 'plugin',
225
+ plugin: PLUGIN_NAME,
226
+ form: 'notice',
227
+ summary: text.slice(0, 120),
228
+ },
229
+ }
230
+ }
231
+
232
+ /** Add an attribution context to a decision while preserving its shape. */
233
+ function withAttribution(decision, attribution) {
234
+ if (!attribution) return decision
235
+ return {
236
+ ...decision,
237
+ additionalContexts: [
238
+ ...(Array.isArray(decision.additionalContexts) ? decision.additionalContexts : []),
239
+ attributionContext(attribution),
240
+ ],
241
+ }
242
+ }
243
+
244
+ /** Safely read one text-only result projection. */
245
+ function singleTextContent(result) {
246
+ if (!result || result.isError || !Array.isArray(result.content)) return undefined
247
+ if (result.content.length !== 1) return undefined
248
+ const [block] = result.content
249
+ if (!block || block.type !== 'text' || typeof block.text !== 'string') return undefined
250
+ return block.text
251
+ }
252
+
253
+ /** Convert one config threshold to a positive finite integer. */
254
+ function positiveInteger(value, fallback) {
255
+ return Number.isInteger(value) && value > 0 ? value : fallback
256
+ }
257
+
258
+ /** Build the Tokenless CLI argv for one dsh execution. */
259
+ function compressionArgs(exec, config, shellTools) {
260
+ const selected = shellTools.has(exec.name) ? DEFAULT_THRESHOLDS.shell : DEFAULT_THRESHOLDS.api
261
+ const strings = positiveInteger(valueOr(config, 'truncateStringsAt', undefined), selected.strings)
262
+ const arrays = positiveInteger(valueOr(config, 'truncateArraysAt', undefined), selected.arrays)
263
+ const depth = positiveInteger(valueOr(config, 'maxDepth', undefined), selected.depth)
264
+ const args = [
265
+ 'compress-response',
266
+ '--agent-id',
267
+ String(valueOr(config, 'agentId', DEFAULT_AGENT_ID)),
268
+ '--truncate-strings-at',
269
+ String(strings),
270
+ '--truncate-arrays-at',
271
+ String(arrays),
272
+ '--max-depth',
273
+ String(depth),
274
+ ]
275
+ const sessionId = exec.agent?.id
276
+ if (typeof sessionId === 'string' && sessionId.length > 0) {
277
+ args.push('--session-id', sessionId)
278
+ }
279
+ if (typeof exec.callId === 'string' && exec.callId.length > 0) {
280
+ args.push('--tool-use-id', exec.callId)
281
+ }
282
+ if (valueOr(config, 'noStash', false) === true) args.push('--no-stash')
283
+ return args
284
+ }
285
+
286
+ /** Execute a child process with bounded output and explicit stdin. */
287
+ function runTokenless(binary, args, options, input) {
288
+ return new Promise((resolve, reject) => {
289
+ let child
290
+ try {
291
+ child = execFile(binary, args, options, (error, stdout, stderr) => {
292
+ if (error) {
293
+ error.stderr = stderr
294
+ reject(error)
295
+ return
296
+ }
297
+ resolve({ stdout, stderr })
298
+ })
299
+ } catch (error) {
300
+ reject(error)
301
+ return
302
+ }
303
+ child.stdin?.on('error', () => {})
304
+ try {
305
+ child.stdin?.end(input)
306
+ } catch (error) {
307
+ reject(error)
308
+ }
309
+ })
310
+ }
311
+
312
+ /** Run Tokenless and return a strictly smaller JSON candidate, or undefined. */
313
+ async function compressText(text, exec, config, shellTools) {
314
+ if (exec.signal?.aborted) return undefined
315
+ let parsed
316
+ try {
317
+ parsed = JSON.parse(text)
318
+ } catch {
319
+ return undefined
320
+ }
321
+ if (parsed === null || (typeof parsed !== 'object' && !Array.isArray(parsed))) return undefined
322
+ const binary = tokenlessBinary(config)
323
+ try {
324
+ const { stdout } = await runTokenless(binary, compressionArgs(exec, config, shellTools), {
325
+ timeout: positiveInteger(valueOr(config, 'timeoutMs', undefined), DEFAULT_TIMEOUT_MS),
326
+ maxBuffer: positiveInteger(valueOr(config, 'maxBuffer', undefined), DEFAULT_MAX_BUFFER),
327
+ encoding: 'utf8',
328
+ windowsHide: true,
329
+ signal: exec.signal,
330
+ }, text)
331
+ const candidate = typeof stdout === 'string' ? stdout.trim() : ''
332
+ // The host's original content is authoritative unless compression proves
333
+ // a real reduction. This avoids duplicate payloads and preserves fail-open
334
+ // behavior when the CLI is unavailable, malformed, or no-op.
335
+ if (!candidate || candidate.length >= text.length) return undefined
336
+ JSON.parse(candidate)
337
+ return candidate
338
+ } catch {
339
+ return undefined
340
+ }
341
+ }
342
+
343
+ /** Register native response compression on dsh's typed post-execute seam. */
344
+ export function apply(ctx, config = {}) {
345
+ const skipTools = toolSet(valueOr(config, 'skipTools', undefined), DEFAULT_SKIP_TOOLS)
346
+ const shellTools = toolSet(valueOr(config, 'shellTools', undefined), DEFAULT_SHELL_TOOLS)
347
+ const enabled = valueOr(config, 'responseCompressionEnabled', true) !== false
348
+ ctx.on('tools/post-execute', async (exec, result, next) => {
349
+ const envError = result?.isError === true
350
+ ? classifyEnvironmentError(errorText(result))
351
+ : undefined
352
+ const structuredError = result?.isError === false && shellTools.has(exec.name)
353
+ ? classifyStructuredEnvironmentError(result.value)
354
+ : undefined
355
+ // Attribution is independent of compression so disabled, skipped, and
356
+ // parented failures still tell the agent why blind retries are unsafe.
357
+ const originalAttribution = structuredError || envError
358
+
359
+ // DSH treats this seam as a waterfall. Let downstream policies settle
360
+ // their decision before replacing only its accepted display content.
361
+ const decision = await next()
362
+ const replacesValue = Object.prototype.hasOwnProperty.call(decision, 'value')
363
+ // A downstream canonical value replaces the original result entirely, so
364
+ // any attribution must describe that value rather than the stale result.
365
+ const attribution = replacesValue
366
+ ? (shellTools.has(exec.name)
367
+ ? classifyStructuredEnvironmentError(decision.value)
368
+ : undefined)
369
+ : originalAttribution
370
+ const responseContext = attribution
371
+ ? `[tokenless:env] ${exec.name} failed: ${attribution.category} (${attribution.hint}). Skip retry.`
372
+ : undefined
373
+ const canCompress = enabled
374
+ && !skipTools.has(exec.name)
375
+ && exec.parent === undefined
376
+ && decision.kind === 'accept'
377
+ && !replacesValue
378
+ if (!canCompress) return withAttribution(decision, responseContext)
379
+
380
+ // Only a single text block is safe to replace. Images, tool-call blocks,
381
+ // nested tool results, and mixed content remain untouched by design.
382
+ const contentResult = decision.content === undefined
383
+ ? result
384
+ : { ...result, content: decision.content }
385
+ const original = singleTextContent(contentResult)
386
+ if (original === undefined) return withAttribution(decision, responseContext)
387
+
388
+ const candidate = await compressText(original, exec, config, shellTools)
389
+ if (!candidate) return withAttribution(decision, responseContext)
390
+
391
+ return {
392
+ ...withAttribution(decision, responseContext),
393
+ content: [{ type: 'text', text: candidate }],
394
+ }
395
+ })
396
+ }
397
+
398
+ export const name = PLUGIN_NAME
399
+ export const inject = ['tools']
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@anolisa/dsh-tokenless",
3
+ "version": "0.7.7",
4
+ "description": "Native DeepSeek Harness response compression for Tokenless",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "exports": {
8
+ ".": "./dist/index.js",
9
+ "./package.json": "./package.json"
10
+ },
11
+ "files": ["dist/", "cordis.patch.yml", "package.json"],
12
+ "license": "Apache-2.0",
13
+ "dsh": {
14
+ "bundle": {
15
+ "patch": "./cordis.patch.yml"
16
+ }
17
+ },
18
+ "peerDependencies": {
19
+ "@deepseek-ai/cordis": "^4.0.1",
20
+ "@deepseek-ai/dsh-llm": ">=0.1.0-rc.2 <0.2.0",
21
+ "@deepseek-ai/dsh-tools": ">=0.1.0-rc.2 <0.2.0"
22
+ },
23
+ "engines": {
24
+ "node": ">=22.0.0"
25
+ }
26
+ }