subconscious-cli 6.0.1 → 6.2.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 +167 -43
- package/bin/agent-data.js +278 -0
- package/bin/agent-help.js +114 -0
- package/bin/agent-install.js +238 -0
- package/bin/agent-launch.js +199 -0
- package/bin/agent-spawn.js +137 -0
- package/bin/agents.js +236 -931
- package/bin/auth.js +9 -1
- package/bin/claude-picker.js +143 -0
- package/bin/cli.js +62 -2
- package/bin/clipboard-image.js +2 -0
- package/bin/feedback.js +14 -3
- package/bin/harness-manifest.js +286 -0
- package/bin/headless-args.js +35 -0
- package/bin/launch-values.js +25 -0
- package/bin/model-capabilities.js +10 -9
- package/bin/models.js +25 -13
- package/bin/native/subc-tui-darwin-amd64 +0 -0
- package/bin/native/subc-tui-darwin-arm64 +0 -0
- package/bin/native/subc-tui-linux-amd64 +0 -0
- package/bin/native/subc-tui-linux-arm64 +0 -0
- package/bin/native/subc-tui-windows-amd64.exe +0 -0
- package/bin/native/subc-tui-windows-arm64.exe +0 -0
- package/bin/opencode-provider.js +21 -0
- package/bin/profile-settings.js +129 -0
- package/bin/profiles.js +29 -424
- package/bin/runbook/README.md +77 -23
- package/bin/runbook/claude-code/agent.json +300 -0
- package/bin/runbook/claude-code/install.sh +9 -252
- package/bin/runbook/codex/agent.json +462 -0
- package/bin/runbook/codex/hooks-lib.sh +17 -4
- package/bin/runbook/codex/install.sh +0 -4
- package/bin/runbook/codex/run.sh +16 -235
- package/bin/runbook/copilot/agent.json +186 -0
- package/bin/runbook/copilot/install.sh +45 -63
- package/bin/runbook/cursor/agent.json +138 -0
- package/bin/runbook/cursor/install.sh +8 -35
- package/bin/runbook/deepseek-harness/agent.json +164 -0
- package/bin/runbook/deepseek-harness/run.sh +20 -59
- package/bin/runbook/image-window/index.ts +14 -0
- package/bin/runbook/image-window/window.d.ts +19 -0
- package/bin/runbook/image-window/window.js +140 -0
- package/bin/runbook/lib.sh +41 -0
- package/bin/runbook/opencode/agent.json +159 -0
- package/bin/runbook/opencode/install.sh +9 -183
- package/bin/runbook/opencode/opencode-plugin.d.ts +10 -3
- package/bin/runbook/opencode/subconscious-image-window.ts +24 -0
- package/bin/runbook/pi/agent.json +217 -0
- package/bin/runbook/pi/install.sh +18 -44
- package/bin/runbook/pi/run.sh +6 -18
- package/bin/runbook/pi/subconscious-compaction.ts +3 -1
- package/bin/runbook/shared.json +57 -0
- package/bin/sessions.js +53 -48
- package/bin/tui.js +13 -0
- package/bin/update-check.js +35 -1
- package/bin/upgrade.js +27 -34
- package/bin/usage.js +21 -10
- package/bin/windows/agents.js +28 -60
- package/bin/windows/common.js +2 -28
- package/bin/windows/launch.js +252 -231
- package/bin/windows/process.js +12 -1
- package/bin/windows/setup.js +29 -9
- package/package.json +2 -4
- package/bin/registry.generated.json +0 -406
- package/bin/runbook/claude-code/run.sh +0 -254
- package/bin/runbook/model-capabilities.generated.sh +0 -9
- package/bin/runbook/opencode/provider.sh +0 -93
- package/bin/runbook/opencode/run.sh +0 -99
- package/bin/runbook/opencode/subconscious-compaction.ts +0 -100
- package/bin/runbook/subconscious-code/install.sh +0 -154
- package/bin/runbook/subconscious-code/run.sh +0 -21
- package/bin/windows/claude.js +0 -100
- package/bin/windows/install.js +0 -188
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "cursor",
|
|
3
|
+
"command": "cursor",
|
|
4
|
+
"aliases": [],
|
|
5
|
+
"name": "Cursor",
|
|
6
|
+
"description": "Install Cursor conversation and compaction hooks in the Cursor desktop app and IDE, then finish model setup in Cursor Settings. The Cursor CLI is not supported.",
|
|
7
|
+
"protocol": "openai",
|
|
8
|
+
"homepage": "https://cursor.com",
|
|
9
|
+
"install": {
|
|
10
|
+
"commands": null,
|
|
11
|
+
"url": "https://cursor.com/download",
|
|
12
|
+
"method": "user",
|
|
13
|
+
"package": null,
|
|
14
|
+
"version": null,
|
|
15
|
+
"minimum_version": null,
|
|
16
|
+
"verified": true,
|
|
17
|
+
"note": "IDE installed by the user. The model endpoint is set by hand in Cursor Settings (OpenAI API Key Override)."
|
|
18
|
+
},
|
|
19
|
+
"runbook": {
|
|
20
|
+
"mode": "setup",
|
|
21
|
+
"setup_script": "install.sh",
|
|
22
|
+
"setup_actions": ["install", "status", "uninstall"]
|
|
23
|
+
},
|
|
24
|
+
"help": {
|
|
25
|
+
"usage": "subc [-p NAME] cursor [help|install|status|uninstall]",
|
|
26
|
+
"behavior": "Manages Cursor correlation hooks in the Cursor desktop app and IDE; model endpoint setup is completed in Cursor Settings. The Cursor CLI is not supported.",
|
|
27
|
+
"options": [
|
|
28
|
+
["help", "Show this help"],
|
|
29
|
+
["install", "Install or update the Cursor hooks (default action)"],
|
|
30
|
+
["status", "Inspect the installed hook configuration"],
|
|
31
|
+
["uninstall", "Remove only the Subconscious Cursor hooks"]
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"prerequisites": [
|
|
35
|
+
{ "command": "jq", "needed_for": "install", "required": true },
|
|
36
|
+
{ "command": "curl", "needed_for": "install", "required": true }
|
|
37
|
+
],
|
|
38
|
+
"launch": null,
|
|
39
|
+
"inputs": [
|
|
40
|
+
{
|
|
41
|
+
"name": "CURSOR_API_KEY",
|
|
42
|
+
"label": "Cursor hook API key",
|
|
43
|
+
"description": "Cursor hook credential override; blank uses API_KEY.",
|
|
44
|
+
"type": "secret"
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"env": [],
|
|
48
|
+
"config": [
|
|
49
|
+
{
|
|
50
|
+
"kind": "file",
|
|
51
|
+
"path": "~/.cursor/hooks.json",
|
|
52
|
+
"lifetime": "persistent",
|
|
53
|
+
"description": "beforeSubmitPrompt and preCompact entries are merged in; other entries are kept."
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"kind": "file",
|
|
57
|
+
"path": "~/.cursor/hooks/subconscious-hook.sh",
|
|
58
|
+
"lifetime": "persistent",
|
|
59
|
+
"description": "Hook script."
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"kind": "file",
|
|
63
|
+
"path": "~/.cursor/subconscious-hooks.env",
|
|
64
|
+
"lifetime": "persistent",
|
|
65
|
+
"description": "SUBCONSCIOUS_GATEWAY_URL and SUBCONSCIOUS_API_KEY for the hook (mode 600)."
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"capabilities": {
|
|
69
|
+
"compaction": {
|
|
70
|
+
"default": "unknown",
|
|
71
|
+
"on": {
|
|
72
|
+
"set_by_subc": false,
|
|
73
|
+
"kind": null,
|
|
74
|
+
"name": null,
|
|
75
|
+
"value": null,
|
|
76
|
+
"verified": false,
|
|
77
|
+
"note": "subc does not configure Cursor compaction."
|
|
78
|
+
},
|
|
79
|
+
"off": {
|
|
80
|
+
"set_by_subc": false,
|
|
81
|
+
"kind": null,
|
|
82
|
+
"name": null,
|
|
83
|
+
"value": null,
|
|
84
|
+
"verified": false,
|
|
85
|
+
"note": "subc does not configure Cursor compaction."
|
|
86
|
+
},
|
|
87
|
+
"threshold": {
|
|
88
|
+
"set_by_subc": false,
|
|
89
|
+
"kind": null,
|
|
90
|
+
"name": null,
|
|
91
|
+
"value": null,
|
|
92
|
+
"verified": false,
|
|
93
|
+
"note": "subc does not configure Cursor compaction."
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"mcp": {
|
|
97
|
+
"configured_by_subc": false,
|
|
98
|
+
"transports": null,
|
|
99
|
+
"verified": false,
|
|
100
|
+
"note": "subc configures no MCP servers."
|
|
101
|
+
},
|
|
102
|
+
"headers": [
|
|
103
|
+
{
|
|
104
|
+
"name": "x-subconscious-client",
|
|
105
|
+
"value": "cursor",
|
|
106
|
+
"on": "hook POSTs to {baseUrl}/v1/agent-hooks",
|
|
107
|
+
"set_by_subc": true,
|
|
108
|
+
"verified": true,
|
|
109
|
+
"note": "Cursor cannot add headers to model requests."
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "Authorization",
|
|
113
|
+
"value": "Bearer {apiKey}",
|
|
114
|
+
"on": "hook POSTs to {baseUrl}/v1/agent-hooks",
|
|
115
|
+
"set_by_subc": true,
|
|
116
|
+
"verified": true
|
|
117
|
+
}
|
|
118
|
+
],
|
|
119
|
+
"hooks": [
|
|
120
|
+
{
|
|
121
|
+
"event": "beforeSubmitPrompt",
|
|
122
|
+
"posts": "conversation_ensure",
|
|
123
|
+
"endpoint": "{baseUrl}/v1/agent-hooks",
|
|
124
|
+
"config": "~/.cursor/hooks.json",
|
|
125
|
+
"installed_by": ["install"],
|
|
126
|
+
"verified": true
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"event": "preCompact",
|
|
130
|
+
"posts": "conversation_compaction phase=point",
|
|
131
|
+
"endpoint": "{baseUrl}/v1/agent-hooks",
|
|
132
|
+
"config": "~/.cursor/hooks.json",
|
|
133
|
+
"installed_by": ["install"],
|
|
134
|
+
"verified": true
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -44,37 +44,12 @@ set -euo pipefail
|
|
|
44
44
|
|
|
45
45
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
46
46
|
HOOK_SRC="${SCRIPT_DIR}/hook.sh"
|
|
47
|
-
HOOKS_TEMPLATE="${SCRIPT_DIR}/hooks.json"
|
|
48
|
-
|
|
49
|
-
# Load shared env from SUBC_ENV_FILE, or a sibling .env / env.example.
|
|
50
|
-
SHARED_ENV="${SUBC_ENV_FILE:-${SCRIPT_DIR}/../.env}"
|
|
51
|
-
[[ -f "$SHARED_ENV" ]] || SHARED_ENV="${SCRIPT_DIR}/../env.example"
|
|
52
|
-
if [[ -f "$SHARED_ENV" ]]; then set -a; source "$SHARED_ENV"; set +a; fi
|
|
53
47
|
|
|
54
48
|
GATEWAY_URL="${GATEWAY_URL:-}"
|
|
55
49
|
API_KEY="${CURSOR_API_KEY:-${API_KEY:-}}"
|
|
56
|
-
MODEL="${MODEL:-
|
|
50
|
+
MODEL="${MODEL:-}"
|
|
57
51
|
COMMAND="install"
|
|
58
52
|
|
|
59
|
-
DEFAULT_SUBCONSCIOUS_MODELS="subconscious/glm-5.3-marathon
|
|
60
|
-
subconscious/glm-5.2
|
|
61
|
-
subconscious/tim-qwen3.6-27b
|
|
62
|
-
subconscious/deepseek-v4-flash-marathon
|
|
63
|
-
subconscious/deepseek-v4.1-flash-marathon"
|
|
64
|
-
|
|
65
|
-
SUPPORTED_MODELS=()
|
|
66
|
-
add_supported_model() {
|
|
67
|
-
local model_id="$1"
|
|
68
|
-
for existing in "${SUPPORTED_MODELS[@]:-}"; do
|
|
69
|
-
[[ "$existing" == "$model_id" ]] && return
|
|
70
|
-
done
|
|
71
|
-
SUPPORTED_MODELS+=("$model_id")
|
|
72
|
-
}
|
|
73
|
-
add_supported_model "$MODEL"
|
|
74
|
-
while IFS= read -r model_id; do
|
|
75
|
-
[[ -n "$model_id" ]] && add_supported_model "$model_id"
|
|
76
|
-
done <<< "${SUBCONSCIOUS_MODELS:-$DEFAULT_SUBCONSCIOUS_MODELS}"
|
|
77
|
-
|
|
78
53
|
usage() {
|
|
79
54
|
cat <<'EOF'
|
|
80
55
|
Usage:
|
|
@@ -209,13 +184,11 @@ remove_hook_entries() {
|
|
|
209
184
|
}
|
|
210
185
|
|
|
211
186
|
merge_hooks_json() {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
remove_hook_entries "$HOOKS_JSON" "$MARKER"
|
|
218
|
-
merge_hook_entries "$HOOKS_JSON" "$MARKER" "$HOOK_DST" beforeSubmitPrompt preCompact
|
|
187
|
+
local command
|
|
188
|
+
command="$(jq -rn --arg path "$HOOK_DST" '$path | @sh')"
|
|
189
|
+
# The merge handles both first install and refresh; jq escapes JSON while
|
|
190
|
+
# @sh keeps spaces and shell characters inside the executable path.
|
|
191
|
+
merge_hook_entries "$HOOKS_JSON" "$MARKER" "$command" beforeSubmitPrompt preCompact
|
|
219
192
|
}
|
|
220
193
|
|
|
221
194
|
uninstall_hooks() {
|
|
@@ -270,9 +243,9 @@ case "$COMMAND" in
|
|
|
270
243
|
echo " Enable OpenAI API Key Override, then use:"
|
|
271
244
|
echo " Base URL: ${GATEWAY_URL%/}/v1"
|
|
272
245
|
echo " Add the available custom models:"
|
|
273
|
-
|
|
246
|
+
while IFS= read -r model_id; do
|
|
274
247
|
echo " ${model_id}"
|
|
275
|
-
done
|
|
248
|
+
done <<< "$SUBC_MODEL_IDS"
|
|
276
249
|
echo " Select ${MODEL} for this profile."
|
|
277
250
|
echo " Paste your Subconscious API key there, then fully restart Cursor."
|
|
278
251
|
;;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "deepseek-harness",
|
|
3
|
+
"command": "dsh",
|
|
4
|
+
"aliases": ["deepseek-harness", "deepseek"],
|
|
5
|
+
"name": "DeepSeek Harness",
|
|
6
|
+
"description": "Launch DeepSeek Harness with an ephemeral Subconscious provider populated from the live model catalog.",
|
|
7
|
+
"protocol": "openai",
|
|
8
|
+
"homepage": "https://www.deepseek.com/harness/en/",
|
|
9
|
+
"install": {
|
|
10
|
+
"commands": {
|
|
11
|
+
"darwin": "npm i -g {package}",
|
|
12
|
+
"linux": "npm i -g {package}",
|
|
13
|
+
"win32": "npm i -g {package}"
|
|
14
|
+
},
|
|
15
|
+
"url": "https://www.deepseek.com/harness/en/",
|
|
16
|
+
"method": "npm",
|
|
17
|
+
"package": "@deepseek-ai/dsh",
|
|
18
|
+
"version": null,
|
|
19
|
+
"channel": "latest (unpinned)",
|
|
20
|
+
"minimum_version": null,
|
|
21
|
+
"verified": true
|
|
22
|
+
},
|
|
23
|
+
"runbook": {
|
|
24
|
+
"mode": "launch",
|
|
25
|
+
"script": "run.sh"
|
|
26
|
+
},
|
|
27
|
+
"help": {
|
|
28
|
+
"usage": "subc [-p NAME] dsh [web|headless] [--model MODEL] [Harness arguments...]",
|
|
29
|
+
"behavior": "Launches DeepSeek Harness with a temporary Subconscious provider containing every live gateway model. Web mode is the default.",
|
|
30
|
+
"options": [
|
|
31
|
+
["help", "Show this help"],
|
|
32
|
+
["web", "Launch the DeepSeek Harness Web UI (default)"],
|
|
33
|
+
["headless PROMPT", "Run one headless task and exit"],
|
|
34
|
+
["--model MODEL", "Set the initial model for new Harness sessions"]
|
|
35
|
+
],
|
|
36
|
+
"args": ["ARGS...", "Pass remaining arguments to the selected Harness mode"]
|
|
37
|
+
},
|
|
38
|
+
"prerequisites": [],
|
|
39
|
+
"launch": {
|
|
40
|
+
"argv": ["dsh", "web", "--patch", "{tempFile}", "{args}"],
|
|
41
|
+
"headless_argv": [
|
|
42
|
+
"dsh",
|
|
43
|
+
"--profile",
|
|
44
|
+
"headless",
|
|
45
|
+
"--patch",
|
|
46
|
+
"{tempFile}",
|
|
47
|
+
"{args}"
|
|
48
|
+
],
|
|
49
|
+
"headless_stdin": "{prompt}",
|
|
50
|
+
"headless_platforms": ["darwin", "linux", "win32"],
|
|
51
|
+
"mode_word": "web",
|
|
52
|
+
"persistent_setup": false
|
|
53
|
+
},
|
|
54
|
+
"inputs": [
|
|
55
|
+
{
|
|
56
|
+
"name": "DEEPSEEK_HARNESS_API_KEY",
|
|
57
|
+
"label": "DeepSeek Harness API key",
|
|
58
|
+
"description": "DeepSeek Harness credential override; blank uses API_KEY.",
|
|
59
|
+
"type": "secret"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "DEEPSEEK_HARNESS_CONTEXT_WINDOW",
|
|
63
|
+
"default": 5000000,
|
|
64
|
+
"label": "Context window",
|
|
65
|
+
"description": "Context capacity advertised to DeepSeek Harness.",
|
|
66
|
+
"type": "integer",
|
|
67
|
+
"min": 1
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "DEEPSEEK_HARNESS_MAX_TOKENS",
|
|
71
|
+
"default": 65536,
|
|
72
|
+
"label": "Maximum output tokens",
|
|
73
|
+
"description": "Output capacity advertised to DeepSeek Harness.",
|
|
74
|
+
"type": "integer",
|
|
75
|
+
"min": 1
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"env": [
|
|
79
|
+
{
|
|
80
|
+
"name": "SUBCONSCIOUS_API_KEY",
|
|
81
|
+
"value": "{apiKey}",
|
|
82
|
+
"description": "Credential, read through apiKeyEnv in the overlay."
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "SUBCONSCIOUS_DSH_BASE_URL",
|
|
86
|
+
"value": "{baseUrlV1}",
|
|
87
|
+
"description": "OpenAI-compatible base URL, read by the overlay."
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"config": [
|
|
91
|
+
{
|
|
92
|
+
"kind": "file",
|
|
93
|
+
"path": "{tmp}/subc-dsh.XXXXXX/subconscious.cordis.yml",
|
|
94
|
+
"lifetime": "launch",
|
|
95
|
+
"value": {
|
|
96
|
+
"llm-pi-ai.providers.subconscious.api": "openai-completions",
|
|
97
|
+
"llm-pi-ai.providers.subconscious.apiKeyEnv": "SUBCONSCIOUS_API_KEY",
|
|
98
|
+
"llm-pi-ai.providers.subconscious.headers": {
|
|
99
|
+
"x-subconscious-client": "deepseek-harness"
|
|
100
|
+
},
|
|
101
|
+
"llm-pi-ai.providers.subconscious.compat": {
|
|
102
|
+
"supportsDeveloperRole": false,
|
|
103
|
+
"maxTokensField": "max_tokens"
|
|
104
|
+
},
|
|
105
|
+
"llm-pi-ai.providers.subconscious.defaultContextWindow": "${DEEPSEEK_HARNESS_CONTEXT_WINDOW}",
|
|
106
|
+
"llm-pi-ai.providers.subconscious.defaultMaxTokens": "${DEEPSEEK_HARNESS_MAX_TOKENS}",
|
|
107
|
+
"agent-default-model": {
|
|
108
|
+
"provider": "subconscious",
|
|
109
|
+
"model": "{model}"
|
|
110
|
+
},
|
|
111
|
+
"llm-pi-ai.providers.subconscious.models[].contextWindow": "${DEEPSEEK_HARNESS_CONTEXT_WINDOW}",
|
|
112
|
+
"llm-pi-ai.providers.subconscious.models[].maxTokens": "${DEEPSEEK_HARNESS_MAX_TOKENS}"
|
|
113
|
+
},
|
|
114
|
+
"description": "Cordis patch the runbook writes and passes with --patch; holds no API key; removed once dsh exits."
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"capabilities": {
|
|
118
|
+
"compaction": {
|
|
119
|
+
"default": "unknown",
|
|
120
|
+
"on": {
|
|
121
|
+
"set_by_subc": false,
|
|
122
|
+
"kind": null,
|
|
123
|
+
"name": null,
|
|
124
|
+
"value": null,
|
|
125
|
+
"verified": false,
|
|
126
|
+
"note": "subc sets contextWindow only; DeepSeek Harness compaction behaviour is not described in this repo."
|
|
127
|
+
},
|
|
128
|
+
"off": {
|
|
129
|
+
"set_by_subc": false,
|
|
130
|
+
"kind": null,
|
|
131
|
+
"name": null,
|
|
132
|
+
"value": null,
|
|
133
|
+
"verified": false,
|
|
134
|
+
"note": "No off knob is set or documented by subc."
|
|
135
|
+
},
|
|
136
|
+
"threshold": {
|
|
137
|
+
"set_by_subc": true,
|
|
138
|
+
"kind": "config-field",
|
|
139
|
+
"name": "llm-pi-ai.providers.subconscious.models[].contextWindow",
|
|
140
|
+
"value": "${DEEPSEEK_HARNESS_CONTEXT_WINDOW}",
|
|
141
|
+
"verified": false,
|
|
142
|
+
"note": "subc sets it; whether it drives compaction is not confirmed here.",
|
|
143
|
+
"semantics": "window",
|
|
144
|
+
"config": "{tmp}/subc-dsh.XXXXXX/subconscious.cordis.yml"
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
"mcp": {
|
|
148
|
+
"configured_by_subc": false,
|
|
149
|
+
"transports": null,
|
|
150
|
+
"verified": false,
|
|
151
|
+
"note": "subc configures no MCP servers."
|
|
152
|
+
},
|
|
153
|
+
"headers": [
|
|
154
|
+
{
|
|
155
|
+
"name": "x-subconscious-client",
|
|
156
|
+
"value": "deepseek-harness",
|
|
157
|
+
"on": "model requests",
|
|
158
|
+
"set_by_subc": true,
|
|
159
|
+
"verified": true
|
|
160
|
+
}
|
|
161
|
+
],
|
|
162
|
+
"hooks": []
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -1,28 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
#
|
|
2
|
+
# Write a temporary Cordis overlay that adds the live Subconscious catalog,
|
|
3
|
+
# then start the dsh argv subc built. The user's DSH settings and profiles are
|
|
4
|
+
# not rewritten.
|
|
4
5
|
|
|
5
6
|
set -euo pipefail
|
|
6
7
|
|
|
7
8
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
-
source "${SCRIPT_DIR}/../
|
|
9
|
+
source "${SCRIPT_DIR}/../lib.sh"
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
MODEL="${MODEL:-subconscious/glm-5.3-marathon}"
|
|
13
|
-
CONTEXT_WINDOW="${DEEPSEEK_HARNESS_CONTEXT_WINDOW:-5000000}"
|
|
14
|
-
MAX_TOKENS="${DEEPSEEK_HARNESS_MAX_TOKENS:-65536}"
|
|
15
|
-
|
|
16
|
-
DEFAULT_SUBCONSCIOUS_MODELS="subconscious/glm-5.3-marathon
|
|
17
|
-
subconscious/glm-5.2
|
|
18
|
-
subconscious/tim-qwen3.6-27b
|
|
19
|
-
subconscious/deepseek-v4-flash-marathon
|
|
20
|
-
subconscious/deepseek-v4.1-flash-marathon"
|
|
21
|
-
|
|
22
|
-
if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
|
|
23
|
-
echo "error: GATEWAY_URL and API_KEY are required to launch DeepSeek Harness" >&2
|
|
24
|
-
exit 1
|
|
25
|
-
fi
|
|
11
|
+
CONTEXT_WINDOW="$DEEPSEEK_HARNESS_CONTEXT_WINDOW"
|
|
12
|
+
MAX_TOKENS="$DEEPSEEK_HARNESS_MAX_TOKENS"
|
|
26
13
|
if [[ ! "$CONTEXT_WINDOW" =~ ^[1-9][0-9]*$ ]]; then
|
|
27
14
|
echo "error: DEEPSEEK_HARNESS_CONTEXT_WINDOW must be a positive integer" >&2
|
|
28
15
|
exit 1
|
|
@@ -32,27 +19,6 @@ if [[ ! "$MAX_TOKENS" =~ ^[1-9][0-9]*$ ]]; then
|
|
|
32
19
|
exit 1
|
|
33
20
|
fi
|
|
34
21
|
|
|
35
|
-
SUPPORTED_MODELS=()
|
|
36
|
-
add_supported_model() {
|
|
37
|
-
local model_id="$1" existing
|
|
38
|
-
[[ -n "$model_id" ]] || return 0
|
|
39
|
-
if [[ ! "$model_id" =~ ^[-A-Za-z0-9._:/+]+$ ]]; then
|
|
40
|
-
echo "error: invalid model id: $model_id" >&2
|
|
41
|
-
exit 1
|
|
42
|
-
fi
|
|
43
|
-
if [[ "${#SUPPORTED_MODELS[@]}" -gt 0 ]]; then
|
|
44
|
-
for existing in "${SUPPORTED_MODELS[@]}"; do
|
|
45
|
-
[[ "$existing" == "$model_id" ]] && return 0
|
|
46
|
-
done
|
|
47
|
-
fi
|
|
48
|
-
SUPPORTED_MODELS+=("$model_id")
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
add_supported_model "$MODEL"
|
|
52
|
-
while IFS= read -r model_id; do
|
|
53
|
-
add_supported_model "$model_id"
|
|
54
|
-
done <<< "${SUBCONSCIOUS_MODELS:-$DEFAULT_SUBCONSCIOUS_MODELS}"
|
|
55
|
-
|
|
56
22
|
OVERLAY_DIR="$(mktemp -d "${TMPDIR:-/tmp}/subc-dsh.XXXXXX")"
|
|
57
23
|
OVERLAY_FILE="${OVERLAY_DIR}/subconscious.cordis.yml"
|
|
58
24
|
cleanup() {
|
|
@@ -80,7 +46,7 @@ trap cleanup EXIT HUP INT TERM
|
|
|
80
46
|
" defaultContextWindow: ${CONTEXT_WINDOW}" \
|
|
81
47
|
" defaultMaxTokens: ${MAX_TOKENS}" \
|
|
82
48
|
' models:'
|
|
83
|
-
|
|
49
|
+
while IFS= read -r model_id; do
|
|
84
50
|
printf " - id: '%s'\n" "$model_id"
|
|
85
51
|
printf " name: '%s'\n" "$model_id"
|
|
86
52
|
if subc_model_supports_vision "$model_id"; then
|
|
@@ -88,7 +54,7 @@ trap cleanup EXIT HUP INT TERM
|
|
|
88
54
|
fi
|
|
89
55
|
printf " contextWindow: %s\n" "$CONTEXT_WINDOW"
|
|
90
56
|
printf " maxTokens: %s\n" "$MAX_TOKENS"
|
|
91
|
-
done
|
|
57
|
+
done <<< "$SUBC_MODEL_IDS"
|
|
92
58
|
printf '%s\n' \
|
|
93
59
|
'- id: agent-default-model' \
|
|
94
60
|
' config:' \
|
|
@@ -97,21 +63,16 @@ trap cleanup EXIT HUP INT TERM
|
|
|
97
63
|
} >"$OVERLAY_FILE"
|
|
98
64
|
chmod 600 "$OVERLAY_FILE"
|
|
99
65
|
|
|
100
|
-
|
|
101
|
-
|
|
66
|
+
# dsh replaces this shell, keeping its PID, so signals and the exit status
|
|
67
|
+
# are dsh's own. A detached watcher removes the overlay once that PID exits.
|
|
68
|
+
runbook_pid=$$
|
|
69
|
+
(
|
|
70
|
+
# A stop sent to the whole process group must not kill the watcher first.
|
|
71
|
+
trap '' HUP INT TERM
|
|
72
|
+
set +e
|
|
73
|
+
while kill -0 "$runbook_pid" 2>/dev/null; do sleep 1; done
|
|
74
|
+
cleanup
|
|
75
|
+
) </dev/null >/dev/null 2>&1 &
|
|
76
|
+
trap - EXIT HUP INT TERM
|
|
102
77
|
|
|
103
|
-
|
|
104
|
-
if [[ "${1:-}" == "web" || "${1:-}" == "headless" ]]; then
|
|
105
|
-
mode="$1"
|
|
106
|
-
shift
|
|
107
|
-
fi
|
|
108
|
-
|
|
109
|
-
set +e
|
|
110
|
-
if [[ "$mode" == "headless" ]]; then
|
|
111
|
-
dsh --profile headless --patch "$OVERLAY_FILE" "$@"
|
|
112
|
-
else
|
|
113
|
-
dsh web --patch "$OVERLAY_FILE" "$@"
|
|
114
|
-
fi
|
|
115
|
-
status=$?
|
|
116
|
-
set -e
|
|
117
|
-
exit "$status"
|
|
78
|
+
subc_exec "{tempFile}" "$OVERLAY_FILE" -- "$@"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Pi extension (Pi loads a folder's index.ts): keep requests to Subconscious at the newest 30 images, so a
|
|
2
|
+
// long screenshot session's upload stops growing. The rule lives in
|
|
3
|
+
// window.js (plain JS so the CLI's tests can import it).
|
|
4
|
+
|
|
5
|
+
import { windowImages } from './window.js';
|
|
6
|
+
|
|
7
|
+
export default function (pi: {
|
|
8
|
+
on: (
|
|
9
|
+
event: 'before_provider_request',
|
|
10
|
+
handler: (event: { payload: unknown }) => unknown,
|
|
11
|
+
) => void;
|
|
12
|
+
}) {
|
|
13
|
+
pi.on('before_provider_request', (event) => windowImages(event.payload));
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const MAX_IMAGES: number;
|
|
2
|
+
export declare const IMAGE_LABEL: string;
|
|
3
|
+
export declare const DEFAULT_SOFT_IMAGE_MIB: number;
|
|
4
|
+
export declare const DEFAULT_HARD_IMAGE_MIB: number;
|
|
5
|
+
export declare const SOFT_IMAGE_BYTES: number;
|
|
6
|
+
export declare const HARD_IMAGE_BYTES: number;
|
|
7
|
+
export declare function imagesToDrop(
|
|
8
|
+
sizes: number[],
|
|
9
|
+
maxImages?: number,
|
|
10
|
+
softBytes?: number,
|
|
11
|
+
hardBytes?: number,
|
|
12
|
+
): number;
|
|
13
|
+
export declare function windowImages(
|
|
14
|
+
payload: unknown,
|
|
15
|
+
maxImages?: number,
|
|
16
|
+
softBytes?: number,
|
|
17
|
+
hardBytes?: number,
|
|
18
|
+
): unknown;
|
|
19
|
+
export declare function windowFetch(fetchFn: typeof fetch): typeof fetch;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// The image window: keep a request to Subconscious at the newest screenshots.
|
|
2
|
+
//
|
|
3
|
+
// Every coding agent resends its whole history, every screenshot included, on
|
|
4
|
+
// every turn, so a long screenshot session's upload grows without end. The
|
|
5
|
+
// Subconscious gateway keeps only the newest images before it forwards a
|
|
6
|
+
// request, but that does not stop the agent uploading all of them. subc's Pi
|
|
7
|
+
// extension (index.ts) and OpenCode plugin apply this rule before the request
|
|
8
|
+
// leaves the machine:
|
|
9
|
+
//
|
|
10
|
+
// keep the newest screenshots, up to MAX_IMAGES and about SOFT_IMAGE_BYTES
|
|
11
|
+
// in total; replace every older one with the text IMAGE_LABEL.
|
|
12
|
+
//
|
|
13
|
+
// The label is what keeps Subconscious Cache working. The gateway puts the
|
|
14
|
+
// same label before every image, so replacing an image with it removes only
|
|
15
|
+
// the image's own tokens and nothing around it moves; the cache then reuses
|
|
16
|
+
// the rest of the conversation and each turn only reads the new screenshot.
|
|
17
|
+
//
|
|
18
|
+
// The cache reuses across one removed image per turn, not several, so the
|
|
19
|
+
// window never removes more than one screenshot per turn. A big screenshot can
|
|
20
|
+
// take the total over SOFT_IMAGE_BYTES for a few turns while the window
|
|
21
|
+
// catches up one screenshot at a time. Only if a request would pass
|
|
22
|
+
// HARD_IMAGE_BYTES does it drop
|
|
23
|
+
// straight back to the soft limit, which costs that turn's cache hit instead
|
|
24
|
+
// of failing the request.
|
|
25
|
+
|
|
26
|
+
export const MAX_IMAGES = 30;
|
|
27
|
+
export const IMAGE_LABEL = 'image';
|
|
28
|
+
const MIB = 1024 * 1024;
|
|
29
|
+
/** Where trimming starts; room below the hard limit to catch up. */
|
|
30
|
+
export const DEFAULT_SOFT_IMAGE_MIB = 25;
|
|
31
|
+
/** Never forwarded above this. */
|
|
32
|
+
export const DEFAULT_HARD_IMAGE_MIB = 30;
|
|
33
|
+
export const SOFT_IMAGE_BYTES =
|
|
34
|
+
positiveNumber(
|
|
35
|
+
process.env.SUBCONSCIOUS_IMAGE_WINDOW_SOFT_MIB,
|
|
36
|
+
DEFAULT_SOFT_IMAGE_MIB,
|
|
37
|
+
) * MIB;
|
|
38
|
+
export const HARD_IMAGE_BYTES =
|
|
39
|
+
positiveNumber(
|
|
40
|
+
process.env.SUBCONSCIOUS_IMAGE_WINDOW_HARD_MIB,
|
|
41
|
+
DEFAULT_HARD_IMAGE_MIB,
|
|
42
|
+
) * MIB;
|
|
43
|
+
const SUBCONSCIOUS_MODEL = /^subconscious\//;
|
|
44
|
+
const LABEL_PART = Object.freeze({ type: 'text', text: IMAGE_LABEL });
|
|
45
|
+
|
|
46
|
+
function positiveNumber(raw, fallback) {
|
|
47
|
+
const value = Number(raw);
|
|
48
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* How many of the oldest images to drop, given each image's size in order
|
|
53
|
+
* (oldest first). The answer for a request is worked out turn by turn over
|
|
54
|
+
* its history, as if each earlier screenshot had just arrived, so it depends
|
|
55
|
+
* only on the request and consecutive turns differ by at most one removal
|
|
56
|
+
* (except at the hard limit). The newest image is always kept.
|
|
57
|
+
*/
|
|
58
|
+
export function imagesToDrop(
|
|
59
|
+
sizes,
|
|
60
|
+
maxImages = MAX_IMAGES,
|
|
61
|
+
softBytes = SOFT_IMAGE_BYTES,
|
|
62
|
+
hardBytes = HARD_IMAGE_BYTES,
|
|
63
|
+
) {
|
|
64
|
+
const total = [0];
|
|
65
|
+
for (const size of sizes) total.push(total.at(-1) + size);
|
|
66
|
+
const kept = (from, to) => total[to] - total[from];
|
|
67
|
+
let dropped = 0;
|
|
68
|
+
// Smallest drop count that meets both soft limits for the first k images.
|
|
69
|
+
let needed = 0;
|
|
70
|
+
for (let k = 1; k <= sizes.length; k++) {
|
|
71
|
+
needed = Math.max(needed, k - maxImages);
|
|
72
|
+
while (needed < k - 1 && kept(needed, k) > softBytes) needed += 1;
|
|
73
|
+
dropped = Math.max(dropped, Math.min(dropped + 1, needed));
|
|
74
|
+
if (kept(dropped, k) > hardBytes) dropped = needed;
|
|
75
|
+
}
|
|
76
|
+
return dropped;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function contentParts(message) {
|
|
80
|
+
return Array.isArray(message.content) ? message.content : [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isChatImage(part) {
|
|
84
|
+
return part?.type === 'image_url';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function chatImageSize(part) {
|
|
88
|
+
const url =
|
|
89
|
+
typeof part.image_url === 'string' ? part.image_url : part.image_url?.url;
|
|
90
|
+
return typeof url === 'string' ? url.length : 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Returns a new OpenAI chat payload with the oldest images replaced by the
|
|
95
|
+
* label, or undefined when there is nothing to change.
|
|
96
|
+
*/
|
|
97
|
+
export function windowImages(
|
|
98
|
+
payload,
|
|
99
|
+
maxImages = MAX_IMAGES,
|
|
100
|
+
softBytes = SOFT_IMAGE_BYTES,
|
|
101
|
+
hardBytes = HARD_IMAGE_BYTES,
|
|
102
|
+
) {
|
|
103
|
+
if (!SUBCONSCIOUS_MODEL.test(payload?.model ?? '')) return undefined;
|
|
104
|
+
if (!Array.isArray(payload.messages)) return undefined;
|
|
105
|
+
const sizes = payload.messages
|
|
106
|
+
.flatMap(contentParts)
|
|
107
|
+
.filter(isChatImage)
|
|
108
|
+
.map(chatImageSize);
|
|
109
|
+
let drop = imagesToDrop(sizes, maxImages, softBytes, hardBytes);
|
|
110
|
+
if (drop === 0) return undefined;
|
|
111
|
+
const messages = payload.messages.map((message) => {
|
|
112
|
+
if (drop === 0 || !Array.isArray(message.content)) return message;
|
|
113
|
+
const content = message.content.map((part) => {
|
|
114
|
+
if (drop === 0 || !isChatImage(part)) return part;
|
|
115
|
+
drop -= 1;
|
|
116
|
+
return LABEL_PART;
|
|
117
|
+
});
|
|
118
|
+
return { ...message, content };
|
|
119
|
+
});
|
|
120
|
+
return { ...payload, messages };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Wraps a fetch so every JSON request body goes through windowImages first.
|
|
125
|
+
* Anything else, or a body that is not JSON, is sent unchanged: a problem here
|
|
126
|
+
* must never break a coding session.
|
|
127
|
+
*/
|
|
128
|
+
export function windowFetch(fetchFn) {
|
|
129
|
+
return async (input, init) => {
|
|
130
|
+
if (typeof init?.body !== 'string') return fetchFn(input, init);
|
|
131
|
+
let windowed;
|
|
132
|
+
try {
|
|
133
|
+
windowed = windowImages(JSON.parse(init.body));
|
|
134
|
+
} catch {
|
|
135
|
+
return fetchFn(input, init);
|
|
136
|
+
}
|
|
137
|
+
if (!windowed) return fetchFn(input, init);
|
|
138
|
+
return fetchFn(input, { ...init, body: JSON.stringify(windowed) });
|
|
139
|
+
};
|
|
140
|
+
}
|