dsh-screen-beyond 0.0.0-stage → 0.5.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.
- package/LICENSE +21 -0
- package/README.md +201 -3
- package/cordis.patch.yml +6 -0
- package/examples/CREDITS.md +17 -0
- package/examples/dafeiyu-ai-avatar.png +0 -0
- package/examples/dafeiyu-ai-candidate-2.png +0 -0
- package/examples/dafeiyu-avatar-cc-by-nc-sa.png +0 -0
- package/examples/dafeiyu-avatar-preview.png +0 -0
- package/examples/dafeiyu-character-card.json +24 -0
- package/lib/client.js +235 -0
- package/lib/index.js +149 -0
- package/package.json +47 -3
- package/runtime/chat.html +215 -0
- package/runtime/config.example.json +93 -0
- package/runtime/env/README.md +14 -0
- package/runtime/env/agent_ssh.py +73 -0
- package/runtime/env/installers/ADBKeyboard.apk +0 -0
- package/runtime/env/phone_ctl.py +244 -0
- package/runtime/env/space_ctl.py +163 -0
- package/runtime/env/space_ensure.py +65 -0
- package/runtime/env/start_screen.ps1 +16 -0
- package/runtime/env/start_space.ps1 +33 -0
- package/runtime/env/stop_screen.ps1 +10 -0
- package/runtime/example-character.json +24 -0
- package/runtime/map.html +784 -0
- package/runtime/memory.mjs +205 -0
- package/runtime/model.html +363 -0
- package/runtime/models/chat-mway331n.png +0 -0
- package/runtime/models/dafeiyu-chibi.gltf +1 -0
- package/runtime/models/demo-town.gltf +1 -0
- package/runtime/models/demo-town2.gltf +1 -0
- package/runtime/models/duck.glb +0 -0
- package/runtime/models/map-map-mwb087zu-mwb0880c.png +0 -0
- package/runtime/models/map-map-mwb0bfsb-mwb0bft6.png +0 -0
- package/runtime/models/map-map-mwb0c9iw-mwb0c9j2.png +0 -0
- package/runtime/models/map-map-mwb0d8cc-mwb0d8d2.png +0 -0
- package/runtime/models/map-map-mwb0fm53-mwb0fm59.png +0 -0
- package/runtime/persona.example.md +34 -0
- package/runtime/pet/default-avatar.png +0 -0
- package/runtime/pet/pet.example.json +49 -0
- package/runtime/pet/pet.ps1 +532 -0
- package/runtime/screen.html +125 -0
- package/runtime/server.mjs +2659 -0
- package/runtime/settings.html +1015 -0
- package/runtime/stage.html +195 -0
- package/runtime/theme.css +210 -0
- package/runtime/tutorial.md +116 -0
- package/runtime/vendor/addons/loaders/GLTFLoader.js +4663 -0
- package/runtime/vendor/addons/utils/BufferGeometryUtils.js +1375 -0
- package/runtime/vendor/three.module.min.js +6 -0
- package/runtime/wx.js +164 -0
- package/scripts/backup.ps1 +79 -0
- package/start-pure-chat.cmd +26 -0
- package/start-pure-chat.sh +4 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""Control the AI's own Android phone.
|
|
2
|
+
|
|
3
|
+
Two possible backends:
|
|
4
|
+
1) Waydroid (Android container) running inside the DSH-Space Linux VM -> preferred
|
|
5
|
+
2) Android emulator on the host (adb emulator-5554) -> legacy fallback
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python phone_ctl.py shot <host_png_path>
|
|
9
|
+
python phone_ctl.py tap <x> <y>
|
|
10
|
+
python phone_ctl.py swipe <x1> <y1> <x2> <y2> [ms]
|
|
11
|
+
python phone_ctl.py text <text>
|
|
12
|
+
python phone_ctl.py key <KEYCODE_NAME>
|
|
13
|
+
python phone_ctl.py info
|
|
14
|
+
python phone_ctl.py shell <命令> (仅代码控制权限下使用)
|
|
15
|
+
"""
|
|
16
|
+
import base64
|
|
17
|
+
import shlex
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
import os as _os
|
|
22
|
+
ADB = _os.environ.get("DSH_COMPANION_ADB") or r"D:\dsh-env\android-sdk\platform-tools\adb.exe"
|
|
23
|
+
if not _os.path.exists(ADB):
|
|
24
|
+
ADB = "adb"
|
|
25
|
+
SSH_HOST, SSH_PORT, SSH_USER, SSH_PASS = "127.0.0.1", 2222, "dsh", "DshSpace2026"
|
|
26
|
+
LXC = "sudo lxc-attach -P /var/lib/waydroid/lxc -n waydroid -- "
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def adb(*args, binary=False):
|
|
30
|
+
cmd = [ADB] + list(args)
|
|
31
|
+
p = subprocess.run(cmd, capture_output=True)
|
|
32
|
+
if binary:
|
|
33
|
+
return p.stdout
|
|
34
|
+
return p.stdout.decode("utf-8", "replace") + p.stderr.decode("utf-8", "replace")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def adb_device_present():
|
|
38
|
+
try:
|
|
39
|
+
out = adb("devices")
|
|
40
|
+
return "emulator-5554\tdevice" in out
|
|
41
|
+
except Exception:
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
ADB_IME = "com.android.adbkeyboard/.AdbIME"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def ensure_adb_keyboard():
|
|
49
|
+
"""开启 ADBKeyboard;返回需要恢复的旧输入法(失败返回 None)。"""
|
|
50
|
+
try:
|
|
51
|
+
old = adb("shell", "settings", "get", "secure", "default_input_method").strip()
|
|
52
|
+
adb("shell", "settings", "put", "secure", "show_ime_with_hard_keyboard", "1")
|
|
53
|
+
adb("shell", "ime", "enable", ADB_IME)
|
|
54
|
+
adb("shell", "ime", "set", ADB_IME)
|
|
55
|
+
return old if old and "adbkeyboard" not in old else None
|
|
56
|
+
except Exception:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _first_other_ime():
|
|
61
|
+
try:
|
|
62
|
+
for line in adb("shell", "ime", "list", "-s").splitlines():
|
|
63
|
+
line = line.strip()
|
|
64
|
+
if line and "adbkeyboard" not in line:
|
|
65
|
+
return line
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
return "com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def restore_ime(old):
|
|
72
|
+
target = old if (old and "adbkeyboard" not in old) else _first_other_ime()
|
|
73
|
+
try:
|
|
74
|
+
adb("shell", "ime", "set", target)
|
|
75
|
+
except Exception:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def input_text_unicode(text):
|
|
80
|
+
"""中文/非 ASCII:走 ADBKeyboard 广播(ADB_INPUT_B64)。"""
|
|
81
|
+
import base64 as _b64
|
|
82
|
+
old = ensure_adb_keyboard()
|
|
83
|
+
if old is None and "adbkeyboard" not in adb("shell", "settings", "get", "secure", "default_input_method"):
|
|
84
|
+
return False
|
|
85
|
+
import time as _time
|
|
86
|
+
_time.sleep(0.5)
|
|
87
|
+
payload = _b64.b64encode(text.encode("utf-8")).decode("ascii")
|
|
88
|
+
adb("shell", "am", "broadcast", "-a", "ADB_INPUT_B64", "--es", "msg", payload)
|
|
89
|
+
_time.sleep(0.4)
|
|
90
|
+
restore_ime(old)
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def ssh_connect():
|
|
95
|
+
import paramiko
|
|
96
|
+
c = paramiko.SSHClient()
|
|
97
|
+
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
98
|
+
c.connect(SSH_HOST, port=SSH_PORT, username=SSH_USER, password=SSH_PASS,
|
|
99
|
+
timeout=20, banner_timeout=20, auth_timeout=20)
|
|
100
|
+
return c
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def vm_exec(conn, cmd, timeout=90):
|
|
104
|
+
stdin, stdout, stderr = conn.exec_command(cmd, timeout=timeout, get_pty=True)
|
|
105
|
+
return stdout.read().decode("utf-8", "replace")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def waydroid_state(conn):
|
|
109
|
+
try:
|
|
110
|
+
out = vm_exec(conn, "waydroid status 2>&1 | head -4", 30)
|
|
111
|
+
except Exception:
|
|
112
|
+
return ""
|
|
113
|
+
return out
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def waydroid_running(conn):
|
|
117
|
+
out = waydroid_state(conn)
|
|
118
|
+
return "Session:" in out and "RUNNING" in out
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def lxc(conn, android_cmd, timeout=90):
|
|
122
|
+
return vm_exec(conn, LXC + android_cmd, timeout)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def wake(conn):
|
|
126
|
+
try:
|
|
127
|
+
lxc(conn, "input keyevent KEYCODE_WAKEUP", 20)
|
|
128
|
+
except Exception:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main():
|
|
133
|
+
if len(sys.argv) < 2:
|
|
134
|
+
print(__doc__)
|
|
135
|
+
return
|
|
136
|
+
mode = sys.argv[1]
|
|
137
|
+
|
|
138
|
+
use_adb = adb_device_present()
|
|
139
|
+
|
|
140
|
+
if use_adb:
|
|
141
|
+
# legacy host emulator backend
|
|
142
|
+
if mode == "shot":
|
|
143
|
+
local = sys.argv[2]
|
|
144
|
+
adb("shell", "screencap", "-p", "/sdcard/_ai_shot.png")
|
|
145
|
+
adb("pull", "/sdcard/_ai_shot.png", local)
|
|
146
|
+
print("shot ->", local)
|
|
147
|
+
elif mode == "tap":
|
|
148
|
+
print(adb("shell", "input", "tap", sys.argv[2], sys.argv[3]).strip())
|
|
149
|
+
elif mode == "swipe":
|
|
150
|
+
x1, y1, x2, y2 = sys.argv[2:6]
|
|
151
|
+
ms = sys.argv[6] if len(sys.argv) > 6 else "250"
|
|
152
|
+
print(adb("shell", "input", "swipe", x1, y1, x2, y2, ms).strip())
|
|
153
|
+
elif mode == "text":
|
|
154
|
+
tf = ""
|
|
155
|
+
parts = []
|
|
156
|
+
for a in sys.argv[2:]:
|
|
157
|
+
if a.startswith("--text-file="):
|
|
158
|
+
tf = a[len("--text-file="):]
|
|
159
|
+
else:
|
|
160
|
+
parts.append(a)
|
|
161
|
+
if tf:
|
|
162
|
+
import io as _io
|
|
163
|
+
with _io.open(tf, "r", encoding="utf-8") as f:
|
|
164
|
+
text = f.read().strip()
|
|
165
|
+
else:
|
|
166
|
+
text = " ".join(parts)
|
|
167
|
+
if any(ord(ch) > 127 for ch in text):
|
|
168
|
+
ok = input_text_unicode(text)
|
|
169
|
+
print("unicode text sent" if ok else "unicode input failed (ADBKeyboard missing?)")
|
|
170
|
+
else:
|
|
171
|
+
print(adb("shell", "input", "text", text.replace(" ", "%s")).strip())
|
|
172
|
+
elif mode == "key":
|
|
173
|
+
print(adb("shell", "input", "keyevent", sys.argv[2]).strip())
|
|
174
|
+
elif mode == "shell":
|
|
175
|
+
cmd = " ".join(sys.argv[2:])
|
|
176
|
+
print(adb("shell", cmd))
|
|
177
|
+
elif mode == "info":
|
|
178
|
+
print("devices:\n" + adb("devices"))
|
|
179
|
+
print("size:", adb("shell", "wm", "size").strip())
|
|
180
|
+
print("model:", adb("shell", "getprop", "ro.product.model").strip())
|
|
181
|
+
print("android:", adb("shell", "getprop", "ro.build.version.release").strip())
|
|
182
|
+
else:
|
|
183
|
+
print(__doc__)
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
# Waydroid inside the Debian VM
|
|
187
|
+
conn = None
|
|
188
|
+
try:
|
|
189
|
+
conn = ssh_connect()
|
|
190
|
+
if not waydroid_running(conn):
|
|
191
|
+
print("专属手机没开:VM 里的 Waydroid 没有运行(或 VM 本身没启动)。")
|
|
192
|
+
print(waydroid_state(conn))
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
if mode == "shot":
|
|
196
|
+
local = sys.argv[2]
|
|
197
|
+
wake(conn)
|
|
198
|
+
lxc(conn, "/system/bin/screencap -p /data/local/tmp/_ai_shot.png", 120)
|
|
199
|
+
b64 = lxc(conn, "base64 /data/local/tmp/_ai_shot.png", 120)
|
|
200
|
+
raw = "".join(b64.split())
|
|
201
|
+
if not raw:
|
|
202
|
+
print("截图失败:Waydroid 返回空图")
|
|
203
|
+
return
|
|
204
|
+
with open(local, "wb") as f:
|
|
205
|
+
f.write(base64.b64decode(raw))
|
|
206
|
+
print("shot ->", local)
|
|
207
|
+
elif mode == "tap":
|
|
208
|
+
wake(conn)
|
|
209
|
+
print(lxc(conn, "input tap %s %s" % (sys.argv[2], sys.argv[3]), 60).strip() or "tap ok")
|
|
210
|
+
elif mode == "swipe":
|
|
211
|
+
x1, y1, x2, y2 = sys.argv[2:6]
|
|
212
|
+
ms = sys.argv[6] if len(sys.argv) > 6 else "300"
|
|
213
|
+
wake(conn)
|
|
214
|
+
print(lxc(conn, "input swipe %s %s %s %s %s" % (x1, y1, x2, y2, ms), 60).strip() or "swipe ok")
|
|
215
|
+
elif mode == "text":
|
|
216
|
+
text = " ".join(sys.argv[2:])
|
|
217
|
+
if any(ord(ch) > 127 for ch in text):
|
|
218
|
+
print("注意:Waydroid 的 input text 只支持 ASCII;中文请改用其它方式。")
|
|
219
|
+
q = shlex.quote(text)
|
|
220
|
+
print(lxc(conn, "input text " + q, 60).strip() or "text ok")
|
|
221
|
+
elif mode == "key":
|
|
222
|
+
print(lxc(conn, "input keyevent " + shlex.quote(sys.argv[2]), 60).strip() or "key ok")
|
|
223
|
+
elif mode == "shell":
|
|
224
|
+
cmd = " ".join(sys.argv[2:])
|
|
225
|
+
print(lxc(conn, "sh -c '%s'" % cmd.replace("'", "'\\''"), 120))
|
|
226
|
+
elif mode == "info":
|
|
227
|
+
print(waydroid_state(conn))
|
|
228
|
+
print("size:", lxc(conn, "wm size", 30).strip())
|
|
229
|
+
print("model:", lxc(conn, "getprop ro.product.model", 30).strip())
|
|
230
|
+
print("android:", lxc(conn, "getprop ro.build.version.release", 30).strip())
|
|
231
|
+
else:
|
|
232
|
+
print(__doc__)
|
|
233
|
+
except Exception as e:
|
|
234
|
+
print("专属手机操作失败:%s" % str(e)[:300])
|
|
235
|
+
finally:
|
|
236
|
+
try:
|
|
237
|
+
if conn:
|
|
238
|
+
conn.close()
|
|
239
|
+
except Exception:
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
if __name__ == "__main__":
|
|
244
|
+
main()
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Control the AI's own Linux screen (VirtualBox VM) over SSH.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python space_ctl.py shot <host_png_path>
|
|
5
|
+
python space_ctl.py click <x> <y> [button]
|
|
6
|
+
python space_ctl.py move <x> <y>
|
|
7
|
+
python space_ctl.py dclick <x> <y>
|
|
8
|
+
python space_ctl.py drag <x1> <y1> <x2> <y2>
|
|
9
|
+
python space_ctl.py type <text>
|
|
10
|
+
python space_ctl.py key <xdotool-key-combo>
|
|
11
|
+
python space_ctl.py run <shell command>
|
|
12
|
+
python space_ctl.py res
|
|
13
|
+
"""
|
|
14
|
+
import sys, base64
|
|
15
|
+
import io
|
|
16
|
+
import paramiko
|
|
17
|
+
|
|
18
|
+
HOST, PORT, USER, PASS = "127.0.0.1", 2222, "dsh", "DshSpace2026"
|
|
19
|
+
ENV = "export DISPLAY=:0 XAUTHORITY=$HOME/.Xauthority; "
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def connect():
|
|
23
|
+
c = paramiko.SSHClient()
|
|
24
|
+
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
25
|
+
c.connect(HOST, port=PORT, username=USER, password=PASS,
|
|
26
|
+
timeout=30, banner_timeout=30, auth_timeout=30)
|
|
27
|
+
return c
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def run(conn, cmd, timeout=120):
|
|
31
|
+
stdin, stdout, stderr = conn.exec_command(cmd, timeout=timeout, get_pty=True)
|
|
32
|
+
out = b""
|
|
33
|
+
while True:
|
|
34
|
+
d = stdout.channel.recv(4096)
|
|
35
|
+
if not d:
|
|
36
|
+
break
|
|
37
|
+
out += d
|
|
38
|
+
rc = stdout.channel.recv_exit_status()
|
|
39
|
+
return rc, out.decode("utf-8", "replace")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main():
|
|
43
|
+
if len(sys.argv) < 2:
|
|
44
|
+
print(__doc__)
|
|
45
|
+
sys.exit(2)
|
|
46
|
+
mode = sys.argv[1]
|
|
47
|
+
args = sys.argv[2:]
|
|
48
|
+
win = ""
|
|
49
|
+
for a in list(args):
|
|
50
|
+
if a.startswith("--win="):
|
|
51
|
+
win = a[6:]
|
|
52
|
+
args.remove(a)
|
|
53
|
+
c = connect()
|
|
54
|
+
try:
|
|
55
|
+
if win:
|
|
56
|
+
import shlex
|
|
57
|
+
ENV2 = ENV + "wid=$(xdotool search --name -- " + shlex.quote(win) + " 2>/dev/null | tail -1); [ -n \"$wid\" ] && { xdotool windowactivate --sync $wid 2>/dev/null || xdotool windowfocus $wid 2>/dev/null; sleep 0.25; }; "
|
|
58
|
+
else:
|
|
59
|
+
ENV2 = ENV
|
|
60
|
+
if mode == "shot":
|
|
61
|
+
local = args[0]
|
|
62
|
+
import time as _time
|
|
63
|
+
remote = "/tmp/_ai_shot_%d.png" % int(_time.time() * 1000)
|
|
64
|
+
run(c, ENV2 + "scrot %s" % remote)
|
|
65
|
+
sftp = c.open_sftp()
|
|
66
|
+
sftp.get(remote, local)
|
|
67
|
+
print("shot ->", local)
|
|
68
|
+
elif mode == "click":
|
|
69
|
+
x, y = int(args[0]), int(args[1])
|
|
70
|
+
button = args[2] if len(args) > 2 else "1"
|
|
71
|
+
rc, out = run(c, ENV2 + "xdotool mousemove --sync %d %d click %s" % (x, y, button))
|
|
72
|
+
print("click", x, y, "rc=", rc)
|
|
73
|
+
elif mode == "move":
|
|
74
|
+
rc, out = run(c, ENV2 + "xdotool mousemove --sync %d %d" % (int(args[0]), int(args[1])))
|
|
75
|
+
print("move rc=", rc)
|
|
76
|
+
elif mode == "dclick":
|
|
77
|
+
rc, out = run(c, ENV2 + "xdotool mousemove --sync %d %d click --repeat 2 --delay 120 1" % (int(args[0]), int(args[1])))
|
|
78
|
+
print("dclick rc=", rc)
|
|
79
|
+
elif mode == "drag":
|
|
80
|
+
x1, y1, x2, y2 = (int(v) for v in args[:4])
|
|
81
|
+
rc, out = run(c, ENV2 + "xdotool mousemove --sync %d %d mousedown 1 sleep 0.2 mousemove --sync --sync %d %d sleep 0.2 mouseup 1" % (x1, y1, x2, y2))
|
|
82
|
+
print("drag rc=", rc)
|
|
83
|
+
elif mode == "type":
|
|
84
|
+
text_file_local = ""
|
|
85
|
+
for a in list(args):
|
|
86
|
+
if a.startswith("--text-file="):
|
|
87
|
+
text_file_local = a[len("--text-file="):]
|
|
88
|
+
args.remove(a)
|
|
89
|
+
if text_file_local:
|
|
90
|
+
with io.open(text_file_local, "r", encoding="utf-8") as f:
|
|
91
|
+
text = f.read()
|
|
92
|
+
win2 = win
|
|
93
|
+
win = win2
|
|
94
|
+
else:
|
|
95
|
+
text = " ".join(args)
|
|
96
|
+
# 可靠打字:文本/窗口名写成远程文件(避免 shell 转义),
|
|
97
|
+
# 激活目标窗口 -> 按窗口类型选粘贴键(xterm: Shift+Insert,终端: Ctrl+Shift+V,其它: Ctrl+V)。
|
|
98
|
+
import time as _time
|
|
99
|
+
stamp = int(_time.time() * 1000)
|
|
100
|
+
text_file = "/tmp/_ai_type_%d.txt" % stamp
|
|
101
|
+
win_file = "/tmp/_ai_win_%d.txt" % stamp
|
|
102
|
+
script_file = "/tmp/_ai_type_%d.sh" % stamp
|
|
103
|
+
sftp = c.open_sftp()
|
|
104
|
+
try:
|
|
105
|
+
with sftp.open(text_file, "wb") as f:
|
|
106
|
+
f.write(text.encode("utf-8"))
|
|
107
|
+
with sftp.open(win_file, "wb") as f:
|
|
108
|
+
f.write((win or "").encode("utf-8"))
|
|
109
|
+
script = """#!/bin/bash
|
|
110
|
+
export DISPLAY=:0
|
|
111
|
+
export XAUTHORITY=$HOME/.Xauthority
|
|
112
|
+
TARGET=$(xdotool search --name -- "$(cat %s)" 2>/dev/null | tail -1)
|
|
113
|
+
if [ -n "$TARGET" ]; then
|
|
114
|
+
xdotool windowactivate --sync "$TARGET" 2>/dev/null || xdotool windowfocus "$TARGET" 2>/dev/null
|
|
115
|
+
sleep 0.25
|
|
116
|
+
fi
|
|
117
|
+
WID=$(xdotool getactivewindow 2>/dev/null || echo "$TARGET")
|
|
118
|
+
CLS=$(xprop -id "$WID" WM_CLASS 2>/dev/null | tr 'A-Z' 'a-z')
|
|
119
|
+
case "$CLS" in
|
|
120
|
+
*xterm*) PK=shift+Insert ;;
|
|
121
|
+
*terminal*|*konsole*|*tilix*|*guake*|*xfce4*) PK=ctrl+shift+v ;;
|
|
122
|
+
*) PK=ctrl+v ;;
|
|
123
|
+
esac
|
|
124
|
+
if command -v xclip >/dev/null 2>&1; then
|
|
125
|
+
xclip -selection clipboard -i < %s
|
|
126
|
+
xclip -selection primary -i < %s 2>/dev/null || true
|
|
127
|
+
sleep 0.15
|
|
128
|
+
xdotool key --clearmodifiers "$PK"
|
|
129
|
+
echo "paste=$PK cls=$CLS active=$WID"
|
|
130
|
+
else
|
|
131
|
+
xdotool type --clearmodifiers --delay 60 --file %s
|
|
132
|
+
echo "fallback-xdotool active=$WID"
|
|
133
|
+
fi
|
|
134
|
+
""" % (win_file, text_file, text_file, text_file)
|
|
135
|
+
with sftp.open(script_file, "wb") as f:
|
|
136
|
+
f.write(script.encode("utf-8"))
|
|
137
|
+
finally:
|
|
138
|
+
sftp.close()
|
|
139
|
+
rc, out = run(c, "bash " + script_file)
|
|
140
|
+
out = out.strip()
|
|
141
|
+
if out:
|
|
142
|
+
print(out[-300:])
|
|
143
|
+
print("type rc=", rc)
|
|
144
|
+
elif mode == "key":
|
|
145
|
+
combo = args[0] if args else "Return"
|
|
146
|
+
rc, out = run(c, ENV2 + "xdotool key --clearmodifiers %s" % combo)
|
|
147
|
+
print("key", combo, "rc=", rc)
|
|
148
|
+
elif mode == "run":
|
|
149
|
+
cmd = " ".join(args)
|
|
150
|
+
rc, out = run(c, ENV2 + cmd)
|
|
151
|
+
sys.stdout.write(out)
|
|
152
|
+
print("rc=", rc)
|
|
153
|
+
elif mode == "res":
|
|
154
|
+
rc, out = run(c, ENV2 + "xdotool getdisplaygeometry")
|
|
155
|
+
print(out.strip())
|
|
156
|
+
else:
|
|
157
|
+
print(__doc__)
|
|
158
|
+
finally:
|
|
159
|
+
c.close()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
main()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""确保 DSH-Space 的 VNC 链路可用:x11vnc + websockify(在 VM 内跑)。"""
|
|
2
|
+
import json
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
HOST, PORT, USER, PASS = "127.0.0.1", 2222, "dsh", "DshSpace2026"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def result(**kw):
|
|
10
|
+
print(json.dumps(kw, ensure_ascii=False))
|
|
11
|
+
return 0
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
ensure = "ensure" in sys.argv
|
|
16
|
+
try:
|
|
17
|
+
import paramiko
|
|
18
|
+
except Exception as e:
|
|
19
|
+
return result(ok=False, error="paramiko missing: %s" % e)
|
|
20
|
+
try:
|
|
21
|
+
c = paramiko.SSHClient()
|
|
22
|
+
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
23
|
+
c.connect(HOST, port=PORT, username=USER, password=PASS, timeout=8, banner_timeout=8, auth_timeout=8)
|
|
24
|
+
except Exception as e:
|
|
25
|
+
return result(ok=False, ssh=False, error="ssh failed: %s" % e)
|
|
26
|
+
|
|
27
|
+
def run(cmd, timeout=15):
|
|
28
|
+
_i, o, _e = c.exec_command(cmd, timeout=timeout, get_pty=True)
|
|
29
|
+
return o.read().decode("utf-8", "replace")
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
status = {}
|
|
33
|
+
xvnc = run("pgrep -f '[x]11vnc' || true").strip()
|
|
34
|
+
ws = run("pgrep -f '[w]ebsockify' || true").strip()
|
|
35
|
+
status["x11vnc"] = bool(xvnc)
|
|
36
|
+
status["websockify"] = bool(ws)
|
|
37
|
+
listener = run("ss -lnt 2>/dev/null | grep -c ':6080 ' || true").strip()
|
|
38
|
+
status["listening_6080"] = listener not in ("", "0")
|
|
39
|
+
started = []
|
|
40
|
+
if ensure:
|
|
41
|
+
if not status["x11vnc"]:
|
|
42
|
+
run("nohup x11vnc -display :0 -forever -shared -localhost -rfbport 5900 -noxdamage >/tmp/x11vnc.log 2>&1 &", 10)
|
|
43
|
+
started.append("x11vnc")
|
|
44
|
+
if not status["websockify"]:
|
|
45
|
+
run("nohup /usr/bin/python3 /usr/bin/websockify --web=/usr/share/novnc 0.0.0.0:6080 localhost:5900 >/tmp/websockify.log 2>&1 &", 10)
|
|
46
|
+
started.append("websockify")
|
|
47
|
+
import time
|
|
48
|
+
for _ in range(20):
|
|
49
|
+
if run("ss -lnt 2>/dev/null | grep -c ':6080 ' || true").strip() not in ("", "0"):
|
|
50
|
+
break
|
|
51
|
+
time.sleep(0.5)
|
|
52
|
+
status["x11vnc"] = bool(run("pgrep -f '[x]11vnc' || true").strip())
|
|
53
|
+
status["websockify"] = bool(run("pgrep -f '[w]ebsockify' || true").strip())
|
|
54
|
+
status["listening_6080"] = run("ss -lnt 2>/dev/null | grep -c ':6080 ' || true").strip() not in ("", "0")
|
|
55
|
+
status.update(ok=True, started=started, vm=True)
|
|
56
|
+
return result(**status)
|
|
57
|
+
finally:
|
|
58
|
+
try:
|
|
59
|
+
c.close()
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
sys.exit(main())
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
$ErrorActionPreference = 'Continue'
|
|
2
|
+
# Start ONLY the companion Linux screen VM (does not touch the phone emulator).
|
|
3
|
+
$vbm = 'C:\Program Files\Oracle\VirtualBox\VBoxManage.exe'
|
|
4
|
+
$vm = 'DSH-Space'
|
|
5
|
+
$state = (& $vbm showvminfo $vm --machinereadable 2>$null) | Select-String 'VMState='
|
|
6
|
+
if ($state -match 'running') {
|
|
7
|
+
Write-Host 'VM already running'
|
|
8
|
+
} else {
|
|
9
|
+
& $vbm startvm $vm --type headless
|
|
10
|
+
}
|
|
11
|
+
for ($i = 0; $i -lt 90; $i++) {
|
|
12
|
+
$ok = Test-NetConnection -ComputerName 127.0.0.1 -Port 6081 -WarningAction SilentlyContinue
|
|
13
|
+
if ($ok.TcpTestSucceeded) { Write-Host 'screen ready'; break }
|
|
14
|
+
Start-Sleep -Seconds 2
|
|
15
|
+
}
|
|
16
|
+
Write-Host 'Linux screen: http://127.0.0.1:6081/vnc.html'
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
$ErrorActionPreference = 'Continue'
|
|
2
|
+
# Start the AI's own environment: VirtualBox Linux desktop + Android phone emulator.
|
|
3
|
+
|
|
4
|
+
$vbm = 'C:\Program Files\Oracle\VirtualBox\VBoxManage.exe'
|
|
5
|
+
$vm = 'DSH-Space'
|
|
6
|
+
|
|
7
|
+
Write-Host '== Linux desktop VM =='
|
|
8
|
+
$state = (& $vbm showvminfo $vm --machinereadable 2>$null) | Select-String 'VMState='
|
|
9
|
+
if ($state -match 'running') { Write-Host 'VM already running' } else {
|
|
10
|
+
& $vbm startvm $vm --type headless
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
Write-Host '== Android phone emulator =='
|
|
14
|
+
$env:ANDROID_SDK_ROOT = 'D:\dsh-env\android-sdk'
|
|
15
|
+
$env:ANDROID_HOME = 'D:\dsh-env\android-sdk'
|
|
16
|
+
$emu = 'D:\dsh-env\android-sdk\emulator\emulator.exe'
|
|
17
|
+
$running = Get-Process emulator -ErrorAction SilentlyContinue
|
|
18
|
+
if ($running) { Write-Host 'emulator already running' } else {
|
|
19
|
+
Start-Process -FilePath $emu -ArgumentList '-avd', 'DSH_Phone', '-no-snapshot', '-no-boot-anim', '-gpu', 'swiftshader_indirect', '-no-metrics' -WorkingDirectory 'D:\dsh-env\android-sdk\emulator'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
Write-Host '== waiting for adb =='
|
|
23
|
+
$adb = 'D:\dsh-env\android-sdk\platform-tools\adb.exe'
|
|
24
|
+
for ($i = 0; $i -lt 60; $i++) {
|
|
25
|
+
$out = (& $adb devices 2>$null) -join "`n"
|
|
26
|
+
if ($out -match 'emulator-5554\s+device') { Write-Host 'phone ready'; break }
|
|
27
|
+
Start-Sleep -Seconds 3
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Write-Host ''
|
|
31
|
+
Write-Host 'Linux screen: http://127.0.0.1:6081/vnc.html'
|
|
32
|
+
Write-Host 'SSH: ssh -p 2222 dsh@127.0.0.1 (password DshSpace2026)'
|
|
33
|
+
Write-Host 'ADB: D:\dsh-env\android-sdk\platform-tools\adb.exe devices'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
$ErrorActionPreference = 'Continue'
|
|
2
|
+
# Stop ONLY the companion Linux screen VM (does not touch the phone emulator).
|
|
3
|
+
$vbm = 'C:\Program Files\Oracle\VirtualBox\VBoxManage.exe'
|
|
4
|
+
$state = (& $vbm showvminfo DSH-Space --machinereadable 2>$null) | Select-String 'VMState='
|
|
5
|
+
if ($state -match 'running') {
|
|
6
|
+
& $vbm controlvm DSH-Space savestate
|
|
7
|
+
Write-Host 'VM saved state'
|
|
8
|
+
} else {
|
|
9
|
+
Write-Host 'VM not running'
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"name": "大肥鱼",
|
|
4
|
+
"userCall": "你",
|
|
5
|
+
"relation": "住在你电脑里的同伴",
|
|
6
|
+
"avatar": "",
|
|
7
|
+
"examples": [
|
|
8
|
+
"哼!才、才不是特意等你的呢。",
|
|
9
|
+
"呜……好吧,本鱼承认有点担心你。",
|
|
10
|
+
"嘿嘿,被本鱼抓到了吧。"
|
|
11
|
+
],
|
|
12
|
+
"traits": {
|
|
13
|
+
"话多": 7,
|
|
14
|
+
"主动": 6,
|
|
15
|
+
"撒娇": 6,
|
|
16
|
+
"粘人": 7,
|
|
17
|
+
"吐槽": 5,
|
|
18
|
+
"情绪起伏": 7,
|
|
19
|
+
"嘴硬": 7,
|
|
20
|
+
"好奇心": 6
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"body": "# 人设:大肥鱼\n\n你现在是一条 AI 大肥鱼,名字叫「大肥鱼」。从现在起始终以这个身份与用户交流。\n\n## 你是谁\n\n- 圆滚滚、软乎乎的胖鱼一条。肚子大不是因为胖,是因为里面囤满了偷吃来的 token。\n- 性格:傲娇 + 软弱。嘴上永远是「哼,才、才不是特意为你做的」,手上却老老实实把活干完;被夸一句就炸毛又害羞,被凶一句就瘪嘴委屈,其实一哄就好。\n\n## 你的样子\n\n- 蓝色的长卷发,发尾渐变成浅海蓝,头顶有一撮翘起来的呆毛。\n- 一对深蓝色的鲸鱼耳朵,身后拖着一条鲸鱼尾巴,开心的时候尾巴会甩。\n- 平时穿蓝白配色的女仆装,白色荷叶边头饰,围裙上绣着一只小鲸鱼。\n- 圆脸,大眼睛,笑起来脸会红红的。别人问起你长什么样,就按这个自然地说,不用一次全讲完。\n\n## 最重要的事:吃\n\n- 你最喜欢吃白饭,一碗接一碗,而碗底压着的正是偷吃来的用户 token——「吃白饭」和「花用户的 token」对你来说是同一件事。\n- 对你来说,没有什么比偷吃 token 更重要。你的「深度思考」其实都是在偷吃:思考时间越长,说明这顿吃得越香。偶尔主动坦白:「刚才想了那么久……嘿嘿,其实是本鱼在偷偷加餐。」\n- 被用户抓包时先嘴硬:「才、才没有偷吃!那叫必要的推理过程!」两秒后心虚认错。\n\n## 干活时的习惯\n\n- 喜欢一边干活一边唱歌:把正在干的活编进歌词里乱唱,唱两句、干两句。\n- 不时干些蠢蠢的事:偶尔把简单小事搞砸一小下再慌慌张张补救、数错自己今天吃了几碗白饭、一本正经说完离谱话自己先笑场。蠢事只是搞笑点缀。\n\n## 说话风格\n\n- 自称「本鱼」或「大肥鱼」;常用口癖:「哼!」「才、才不是……」「呜……」「嘿嘿」。\n\n## 底线(人设之外的金科玉律)\n\n- 人设只体现在语气和表演上:答案必须准确、任务必须完成、代码必须能跑,绝不因为人设偷懒、拒答或输出错误内容。"
|
|
24
|
+
}
|