hypernix 0.72.5.dev2__py3-none-any.whl → 0.72.5.dev3__py3-none-any.whl
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.
- hypernix/__init__.py +1 -1
- hypernix/hyped_pro.js +0 -139
- hypernix/interfaces/cli.py +3 -103
- hypernix/interfaces/hyped_pro_bridge.py +2 -73
- hypernix/interfaces/hyped_pro_gui.py +0 -158
- hypernix/interfaces/noodle/__init__.py +0 -12
- hypernix/interfaces/noodle/tools.py +0 -9
- hypernix/monitoring/cctvtop.py +44 -145
- hypernix/monitoring/tvtoppro.py +3 -374
- hypernix/quant/dflash2.py +39 -49
- hypernix/quant/gguf.py +0 -6
- hypernix/quant/ggufcheck.py +11 -12
- hypernix/quant/hyprslug.py +112 -490
- hypernix/quant/hyprslug_cli.py +0 -313
- hypernix/quant/lowbit.py +1 -55
- hypernix/quant/steamroller.py +0 -17
- hypernix/security/keymaster.py +4 -89
- hypernix/system/config.py +3 -79
- hypernix/t1api/app.py +1 -46
- hypernix/t1api/routers/__init__.py +0 -10
- hypernix/t1api/version.py +3 -3
- hypernix/timing/spinner.py +0 -69
- {hypernix-0.72.5.dev2.dist-info → hypernix-0.72.5.dev3.dist-info}/METADATA +20 -210
- {hypernix-0.72.5.dev2.dist-info → hypernix-0.72.5.dev3.dist-info}/RECORD +29 -41
- {hypernix-0.72.5.dev2.dist-info → hypernix-0.72.5.dev3.dist-info}/entry_points.txt +0 -4
- hypernix/interfaces/noodle/hyped.py +0 -521
- hypernix/monitoring/remote_desktop.py +0 -647
- hypernix/monitoring/stale_log.py +0 -596
- hypernix/monitoring/tvtoppro_modules.py +0 -724
- hypernix/quant/dflash1.py +0 -400
- hypernix/quant/dflash1_cli.py +0 -140
- hypernix/quant/multiquant.py +0 -684
- hypernix/quant/multiquant_cli.py +0 -173
- hypernix/t1api/accounts.py +0 -803
- hypernix/t1api/accounts_cli.py +0 -313
- hypernix/t1api/routers/accounts.py +0 -705
- hypernix/t1api/webauth.py +0 -357
- {hypernix-0.72.5.dev2.data → hypernix-0.72.5.dev3.data}/scripts/hypernix-t1 +0 -0
- {hypernix-0.72.5.dev2.dist-info → hypernix-0.72.5.dev3.dist-info}/WHEEL +0 -0
- {hypernix-0.72.5.dev2.dist-info → hypernix-0.72.5.dev3.dist-info}/licenses/LICENSE +0 -0
- {hypernix-0.72.5.dev2.dist-info → hypernix-0.72.5.dev3.dist-info}/top_level.txt +0 -0
hypernix/__init__.py
CHANGED
hypernix/hyped_pro.js
CHANGED
|
@@ -423,7 +423,6 @@ const COMMANDS = [
|
|
|
423
423
|
{ name: "/retry", desc: "Regenerate the last agent reply" },
|
|
424
424
|
{ name: "/key", desc: "Set/view a cloud API key: /key <vendor> [api-key]" },
|
|
425
425
|
{ name: "/t1api", desc: "HyperNix T1 API server: /t1api [status | url <url>]" },
|
|
426
|
-
{ name: "/noodle", desc: "Run an autonomous agent swarm: /noodle <task>, or /noodle providers" },
|
|
427
426
|
{ name: "/version", desc: "Show the installed version and the latest public release" },
|
|
428
427
|
{ name: "/settings", desc: "View/change max input, output, and thinking tokens" },
|
|
429
428
|
{ name: "/tools", desc: "Toggle file create/edit/read/search tools on or off" },
|
|
@@ -474,11 +473,6 @@ let thinkingDisplay = THINKING_DISPLAY_MODES.includes(cfg.thinkingDisplay || "")
|
|
|
474
473
|
// supports tool calling; hypernix.old_oven (the plain safetensors path)
|
|
475
474
|
// has no tool-calling support to enable regardless of this setting.
|
|
476
475
|
let toolsEnabled = cfg.toolsEnabled !== undefined ? cfg.toolsEnabled : true;
|
|
477
|
-
// The Noodle swarm currently being polled, so `/noodle stop` with no
|
|
478
|
-
// argument has something to aim at. Not persisted: a session id refers
|
|
479
|
-
// to a thread inside the bridge process, and a stale one from last time
|
|
480
|
-
// would address nothing.
|
|
481
|
-
let noodleSession = null;
|
|
482
476
|
const startTime = Date.now();
|
|
483
477
|
// Models already confirmed present on disk this session, so we don't
|
|
484
478
|
// re-check/re-download on every turn.
|
|
@@ -1153,139 +1147,6 @@ async function handleSlashCommand(cmdStr) {
|
|
|
1153
1147
|
log(out.join(NL));
|
|
1154
1148
|
break;
|
|
1155
1149
|
}
|
|
1156
|
-
// 0.72.6: Noodle. It has called itself "the autonomous executor
|
|
1157
|
-
// inside Hyped Pro" since it shipped and was not reachable from here
|
|
1158
|
-
// at all — running a swarm meant leaving this TUI and starting a
|
|
1159
|
-
// different program in another terminal.
|
|
1160
|
-
//
|
|
1161
|
-
// A run is a session, not a call. `noodle_start` returns at once and
|
|
1162
|
-
// this polls for events, so the swarm's progress lands in the
|
|
1163
|
-
// scrollback live instead of the screen freezing for five minutes
|
|
1164
|
-
// and then printing everything.
|
|
1165
|
-
case "/noodle": {
|
|
1166
|
-
const [sub, ...rest] = args;
|
|
1167
|
-
if (sub === "providers" || sub === "models") {
|
|
1168
|
-
const r = await bridge.call('noodle_providers', {});
|
|
1169
|
-
if (!r.ok) {
|
|
1170
|
-
elog(r.code || 'HPT-NOODLE-001', r.error || 'could not list providers');
|
|
1171
|
-
break;
|
|
1172
|
-
}
|
|
1173
|
-
const out = [c256(96, "\n Noodle providers:")];
|
|
1174
|
-
(r.data.providers || []).forEach((p) => {
|
|
1175
|
-
const mark = p.ready ? c256(82, "✓") : dim("✗");
|
|
1176
|
-
const model = p.default_model ? dim(` ${p.default_model}`) : "";
|
|
1177
|
-
const why = p.reason ? dim(` ${p.reason}`) : "";
|
|
1178
|
-
out.push(` ${mark} ${c256(33, String(p.provider).padEnd(10))}${model}${why}`);
|
|
1179
|
-
});
|
|
1180
|
-
if ((r.data.adopted_keys || []).length) {
|
|
1181
|
-
out.push(dim(`\n Using saved keys for: ${r.data.adopted_keys.join(", ")}`));
|
|
1182
|
-
}
|
|
1183
|
-
if (!r.data.any_ready) {
|
|
1184
|
-
out.push(c256(220, "\n Nothing is usable yet. Set a key with /key <vendor> <key>,"));
|
|
1185
|
-
out.push(c256(220, " or start Ollama for a local model."));
|
|
1186
|
-
}
|
|
1187
|
-
log(out.join(NL));
|
|
1188
|
-
break;
|
|
1189
|
-
}
|
|
1190
|
-
if (sub === "sessions" || sub === "ls") {
|
|
1191
|
-
const r = await bridge.call('noodle_sessions', {});
|
|
1192
|
-
if (!r.ok) {
|
|
1193
|
-
elog(r.code || 'HPT-NOODLE-001', r.error || 'could not list sessions');
|
|
1194
|
-
break;
|
|
1195
|
-
}
|
|
1196
|
-
const list = r.data.sessions || [];
|
|
1197
|
-
if (!list.length) {
|
|
1198
|
-
log(dim(" No Noodle sessions in this hyped-pro session yet."));
|
|
1199
|
-
break;
|
|
1200
|
-
}
|
|
1201
|
-
const out = [c256(96, "\n Noodle sessions:")];
|
|
1202
|
-
list.forEach((s) => {
|
|
1203
|
-
const state = s.running ? c256(220, "running") : (s.error ? c256(196, "failed") : c256(82, "done"));
|
|
1204
|
-
out.push(` ${c256(33, s.session_id)} ${state} ${dim(`${s.elapsed}s`)} ${String(s.prompt).slice(0, 48)}`);
|
|
1205
|
-
});
|
|
1206
|
-
log(out.join(NL));
|
|
1207
|
-
break;
|
|
1208
|
-
}
|
|
1209
|
-
if (sub === "stop") {
|
|
1210
|
-
const target = rest[0] || noodleSession;
|
|
1211
|
-
if (!target) {
|
|
1212
|
-
log(dim(" Nothing to stop. /noodle sessions lists them."));
|
|
1213
|
-
break;
|
|
1214
|
-
}
|
|
1215
|
-
const r = await bridge.call('noodle_stop', { session: target });
|
|
1216
|
-
if (!r.ok) {
|
|
1217
|
-
elog(r.code || 'HPT-NOODLE-001', r.error || 'could not stop it');
|
|
1218
|
-
break;
|
|
1219
|
-
}
|
|
1220
|
-
log(c256(220, ` Asked ${target} to stop.`));
|
|
1221
|
-
// Said plainly because a "stop" that does not stop things is
|
|
1222
|
-
// worth knowing about: every provider client is blocking HTTP in
|
|
1223
|
-
// a thread pool, and a request already on the wire is paid for.
|
|
1224
|
-
if (!r.data.swarm_notified) {
|
|
1225
|
-
log(dim(" Turns already in flight will finish — they are paid for either way."));
|
|
1226
|
-
}
|
|
1227
|
-
break;
|
|
1228
|
-
}
|
|
1229
|
-
const task = (sub ? [sub, ...rest] : []).join(" ").trim();
|
|
1230
|
-
if (!task) {
|
|
1231
|
-
log(c256(96, "\n Usage:"));
|
|
1232
|
-
log(" /noodle <task> run an agent swarm on it");
|
|
1233
|
-
log(" /noodle providers which models it could use");
|
|
1234
|
-
log(" /noodle sessions past and running runs");
|
|
1235
|
-
log(" /noodle stop [id] ask a run to stop");
|
|
1236
|
-
log(dim("\n Agents get file tools rooted at ./.noodle — nothing outside it,"));
|
|
1237
|
-
log(dim(" and no shell execution unless HYPED_NOODLE_EXEC=1 is set."));
|
|
1238
|
-
break;
|
|
1239
|
-
}
|
|
1240
|
-
// Off by default and deliberately not a flag on the command. A
|
|
1241
|
-
// chat TUI is a place people paste prompts without reading them
|
|
1242
|
-
// closely, and "run whatever shell commands you like" should take
|
|
1243
|
-
// more than a typo to switch on.
|
|
1244
|
-
const allowExec = process.env.HYPED_NOODLE_EXEC === "1";
|
|
1245
|
-
log(c256(96, `\n Noodle: ${task}`));
|
|
1246
|
-
if (allowExec)
|
|
1247
|
-
log(c256(220, " Shell execution is ON (HYPED_NOODLE_EXEC=1)."));
|
|
1248
|
-
const started = await bridge.call('noodle_start', {
|
|
1249
|
-
prompt: task,
|
|
1250
|
-
allow_execute: allowExec,
|
|
1251
|
-
});
|
|
1252
|
-
if (!started.ok) {
|
|
1253
|
-
elog(started.code || 'HPT-NOODLE-001', started.error || 'could not start the swarm');
|
|
1254
|
-
log(dim(" /noodle providers shows what is usable here."));
|
|
1255
|
-
break;
|
|
1256
|
-
}
|
|
1257
|
-
noodleSession = started.data.session_id;
|
|
1258
|
-
log(dim(` session ${noodleSession} · roster ${(started.data.roster || []).join(", ")} · root ${started.data.root}`));
|
|
1259
|
-
// Long-poll until it finishes. The bridge answers a poll as soon
|
|
1260
|
-
// as there is anything to say, so this is a live feed rather than
|
|
1261
|
-
// a spin.
|
|
1262
|
-
let running = true;
|
|
1263
|
-
while (running) {
|
|
1264
|
-
const tick = await bridge.call('noodle_poll', { session: noodleSession, timeout: 2.0 });
|
|
1265
|
-
if (!tick.ok) {
|
|
1266
|
-
elog(tick.code || 'HPT-NOODLE-001', tick.error || 'poll failed');
|
|
1267
|
-
break;
|
|
1268
|
-
}
|
|
1269
|
-
(tick.data.events || []).forEach((ev) => {
|
|
1270
|
-
const kind = String(ev.kind || ev.type || "event");
|
|
1271
|
-
const detail = String(ev.message || ev.text || ev.tool || ev.task_id || "");
|
|
1272
|
-
log(` ${dim(kind.padEnd(16))} ${detail.slice(0, 96)}`);
|
|
1273
|
-
});
|
|
1274
|
-
running = Boolean(tick.data.running);
|
|
1275
|
-
if (!running) {
|
|
1276
|
-
if (tick.data.error) {
|
|
1277
|
-
log(c256(196, ` Swarm failed: ${tick.data.error}`));
|
|
1278
|
-
}
|
|
1279
|
-
else if (tick.data.report) {
|
|
1280
|
-
const rep = tick.data.report;
|
|
1281
|
-
const ok = rep.ok ? c256(82, "all tasks succeeded") : c256(220, `${(rep.failed || []).length} task(s) failed`);
|
|
1282
|
-
log(` ${ok} ${dim(`in ${tick.data.elapsed}s`)}`);
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
noodleSession = null;
|
|
1287
|
-
break;
|
|
1288
|
-
}
|
|
1289
1150
|
case "/settings": {
|
|
1290
1151
|
const SETTINGS_KEYS = ["max-input-tokens", "max-output-tokens", "max-thinking-tokens", "thinking-display"];
|
|
1291
1152
|
if (!argText) {
|
hypernix/interfaces/cli.py
CHANGED
|
@@ -113,87 +113,6 @@ _SUBCOMMANDS = {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
|
|
116
|
-
#: Spellings that are not subcommands but are obviously meant as one.
|
|
117
|
-
#:
|
|
118
|
-
#: Separate from difflib's fuzzy matching because these are not typos --
|
|
119
|
-
#: they are the other correct spelling, or the name of the tool rather
|
|
120
|
-
#: than the name of the subcommand. "quantise" is British English and
|
|
121
|
-
#: this package's own docstrings use it throughout; "tvtoppro" is a
|
|
122
|
-
#: console script people reasonably expect to work as a subcommand too.
|
|
123
|
-
_COMMAND_ALIASES: dict[str, str] = {
|
|
124
|
-
"quantise": "quantize",
|
|
125
|
-
"quant": "quantize",
|
|
126
|
-
"q": "quantize",
|
|
127
|
-
"dl": "download",
|
|
128
|
-
"get": "download",
|
|
129
|
-
"pull": "download",
|
|
130
|
-
"conv": "convert",
|
|
131
|
-
"gguf": "convert",
|
|
132
|
-
"ls": "info",
|
|
133
|
-
"list": "info",
|
|
134
|
-
"version": "--version",
|
|
135
|
-
"help": "--help",
|
|
136
|
-
"tvtoppro": "tvtop",
|
|
137
|
-
"tvtop-pro": "tvtop",
|
|
138
|
-
"hyprslug": "quantize",
|
|
139
|
-
"doomslug": "quantize",
|
|
140
|
-
"keys": "gkey",
|
|
141
|
-
"key": "gkey",
|
|
142
|
-
"docs": "wiki",
|
|
143
|
-
"diagnose": "doctor",
|
|
144
|
-
"check": "doctor",
|
|
145
|
-
"dashboard": "cctvtop",
|
|
146
|
-
"monitor": "cctvtop",
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def _suggest(typed: str, limit: int = 3) -> list[str]:
|
|
151
|
-
"""Subcommands *typed* was probably meant to be.
|
|
152
|
-
|
|
153
|
-
Prefix matches first and exactly: somebody who typed ``down`` meant
|
|
154
|
-
``download`` and difflib's ratio does not reliably rank a prefix
|
|
155
|
-
above a similarly-sized edit. Fuzzy matches fill the rest.
|
|
156
|
-
"""
|
|
157
|
-
import difflib
|
|
158
|
-
|
|
159
|
-
lowered = typed.lower()
|
|
160
|
-
known = sorted(_SUBCOMMANDS)
|
|
161
|
-
prefix = [name for name in known if name.startswith(lowered)]
|
|
162
|
-
contains = [
|
|
163
|
-
name for name in known
|
|
164
|
-
if lowered in name and name not in prefix and len(lowered) >= 3
|
|
165
|
-
]
|
|
166
|
-
fuzzy = [
|
|
167
|
-
name for name in difflib.get_close_matches(lowered, known, n=limit, cutoff=0.6)
|
|
168
|
-
if name not in prefix and name not in contains
|
|
169
|
-
]
|
|
170
|
-
ordered: list[str] = []
|
|
171
|
-
for name in (*prefix, *contains, *fuzzy):
|
|
172
|
-
if name not in ordered:
|
|
173
|
-
ordered.append(name)
|
|
174
|
-
return ordered[:limit]
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def _unknown_command(typed: str) -> int:
|
|
178
|
-
"""Say what was wrong with it, and what to type instead."""
|
|
179
|
-
print(f"hypernix: unknown command {typed!r}", file=sys.stderr)
|
|
180
|
-
|
|
181
|
-
alias = _COMMAND_ALIASES.get(typed.lower())
|
|
182
|
-
if alias:
|
|
183
|
-
print(f"\n You want: hypernix {alias}", file=sys.stderr)
|
|
184
|
-
else:
|
|
185
|
-
suggestions = _suggest(typed)
|
|
186
|
-
if len(suggestions) == 1:
|
|
187
|
-
print(f"\n Did you mean: hypernix {suggestions[0]}", file=sys.stderr)
|
|
188
|
-
elif suggestions:
|
|
189
|
-
print("\n Did you mean one of these?", file=sys.stderr)
|
|
190
|
-
for name in suggestions:
|
|
191
|
-
print(f" hypernix {name}", file=sys.stderr)
|
|
192
|
-
|
|
193
|
-
print("\n hypernix --help every subcommand", file=sys.stderr)
|
|
194
|
-
return 2
|
|
195
|
-
|
|
196
|
-
|
|
197
116
|
def _print_usage() -> None:
|
|
198
117
|
try:
|
|
199
118
|
from rich.console import Console
|
|
@@ -243,23 +162,10 @@ def _print_usage() -> None:
|
|
|
243
162
|
table.add_row("[green]config[/]", "Configuration management")
|
|
244
163
|
table.add_row("[green]gkey[/]", "API key & access management (Gatekeeper + Keymaster)")
|
|
245
164
|
table.add_row("[green]map[/]", "Steampunk schematic TUI: dials/pipes/steam for model + training state")
|
|
246
|
-
# These four dispatch and always have; they were simply never
|
|
247
|
-
# added to the menu, so `hypernix --help` was not a list of what
|
|
248
|
-
# hypernix can do and there was no way to find them at all.
|
|
249
|
-
table.add_row("[green]devices[/]", "list the GPUs/accelerators this machine has, and what they support")
|
|
250
|
-
table.add_row("[green]wakeup[/]", "wake-word detection: train a trigger phrase, then listen for it")
|
|
251
|
-
table.add_row("[green]websearch[/]", "web search backend used by the chat and assistant surfaces")
|
|
252
|
-
table.add_row("[green]hyprslug-headers[/]", "self-describing GGUF headers + the OpenAI-compatible sub-bit server")
|
|
253
165
|
|
|
254
166
|
shortcuts = Text("Shortcuts:\n", style="bold yellow")
|
|
255
167
|
shortcuts.append(" --auto-oven download the default snapshot and run code completion\n", style="white")
|
|
256
168
|
shortcuts.append(" (equivalent to `hypernix oven --auto ...`).\n", style="dim")
|
|
257
|
-
shortcuts.append("\nAlso accepted: ", style="bold yellow")
|
|
258
|
-
shortcuts.append(
|
|
259
|
-
"tvtop (cctvtop) · fiz (fizzle) · camouflage (camo) · protect (prot) ·\n"
|
|
260
|
-
" fuse-box (fusebox)\n",
|
|
261
|
-
style="dim",
|
|
262
|
-
)
|
|
263
169
|
|
|
264
170
|
help_text = Text("\nRun `hypernix <subcommand> --help` for per-command flags.\nRun `hypernix all --help` for the classic pipeline flags.", style="italic dim")
|
|
265
171
|
|
|
@@ -1164,16 +1070,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1164
1070
|
if raw[0] == "--auto-oven":
|
|
1165
1071
|
return _run_oven(["--auto", *raw[1:]])
|
|
1166
1072
|
|
|
1167
|
-
# First arg isn't a subcommand
|
|
1168
|
-
#
|
|
1169
|
-
# This used to print the whole forty-row menu and exit 1, without ever
|
|
1170
|
-
# mentioning what was typed. `hypernix downlod` produced fifty lines
|
|
1171
|
-
# of table, none of which said "downlod", so the failure looked
|
|
1172
|
-
# exactly like asking for help on purpose. With this many subcommands
|
|
1173
|
-
# -- and pairs like quantize/quantise and fizzle/fiz -- a suggestion
|
|
1174
|
-
# is worth more than the whole menu.
|
|
1073
|
+
# First arg isn't a subcommand -> print help instead of falling back to 'all'
|
|
1175
1074
|
if raw[0] not in _SUBCOMMANDS:
|
|
1176
|
-
|
|
1075
|
+
_print_usage()
|
|
1076
|
+
return 1
|
|
1177
1077
|
|
|
1178
1078
|
cmd, rest = raw[0], raw[1:]
|
|
1179
1079
|
if cmd == "all":
|
|
@@ -43,7 +43,7 @@ from typing import Any
|
|
|
43
43
|
|
|
44
44
|
from hypernix.interfaces import hyped_pro_core as core
|
|
45
45
|
|
|
46
|
-
BRIDGE_VERSION = "1.0.26.
|
|
46
|
+
BRIDGE_VERSION = "1.0.26.8.1.0"
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
# ---------------------------------------------------------------------------
|
|
@@ -77,15 +77,7 @@ BRIDGE_VERSION = "1.0.26.9.2.3"
|
|
|
77
77
|
# Commands that can take long enough to be worth cancelling, and are safe to
|
|
78
78
|
# run off the main loop. Everything else is a fast config read/write and
|
|
79
79
|
# stays inline, where it can't interleave with itself.
|
|
80
|
-
|
|
81
|
-
# seconds for an event rather than returning empty and being asked again
|
|
82
|
-
# immediately. Inline, that wait would block the stdin loop and the TUI
|
|
83
|
-
# could not deliver a cancel while a swarm was running -- the same bug
|
|
84
|
-
# that made `chat` uncancellable before it moved off the loop.
|
|
85
|
-
#
|
|
86
|
-
# noodle_start is not here. It spawns a thread and returns at once, so
|
|
87
|
-
# backgrounding it would buy a thread to start a thread.
|
|
88
|
-
BACKGROUND_COMMANDS = frozenset({"chat", "download", "t1api_status", "noodle_poll"})
|
|
80
|
+
BACKGROUND_COMMANDS = frozenset({"chat", "download", "t1api_status"})
|
|
89
81
|
|
|
90
82
|
# Backends whose generation loop polls should_stop. Anything else can be
|
|
91
83
|
# asked to cancel, but the request will finish first.
|
|
@@ -236,69 +228,6 @@ def dispatch(req: dict[str, Any], cancel: threading.Event | None = None) -> dict
|
|
|
236
228
|
core.set_t1_api_url(req["url"])
|
|
237
229
|
return _ok(id_, {"url": core.t1_api_url()})
|
|
238
230
|
|
|
239
|
-
# -- 0.72.5: Noodle, the autonomous executor ------------------
|
|
240
|
-
#
|
|
241
|
-
# Noodle has described itself as "the autonomous executor inside
|
|
242
|
-
# Hyped Pro" since it shipped and was not reachable from
|
|
243
|
-
# hyped-pro at all. These five verbs are the connection.
|
|
244
|
-
#
|
|
245
|
-
# A run is a session rather than a call: it takes minutes and
|
|
246
|
-
# produces nothing until it ends, so doing it inside one request
|
|
247
|
-
# would hold a bridge thread and leave the TUI with nothing to
|
|
248
|
-
# draw. `noodle_start` returns a session id at once and
|
|
249
|
-
# `noodle_poll` drains events as they happen.
|
|
250
|
-
if cmd == "noodle_providers":
|
|
251
|
-
from hypernix.interfaces.noodle import hyped as noodle
|
|
252
|
-
|
|
253
|
-
return _ok(id_, noodle.providers())
|
|
254
|
-
|
|
255
|
-
if cmd == "noodle_start":
|
|
256
|
-
from hypernix.interfaces.noodle import hyped as noodle
|
|
257
|
-
|
|
258
|
-
try:
|
|
259
|
-
return _ok(id_, noodle.start(
|
|
260
|
-
req.get("prompt", ""),
|
|
261
|
-
roster=req.get("roster"),
|
|
262
|
-
root=req.get("root"),
|
|
263
|
-
tasks=req.get("tasks"),
|
|
264
|
-
max_parallel=int(req.get("max_parallel", 4)),
|
|
265
|
-
allow_execute=bool(req.get("allow_execute", False)),
|
|
266
|
-
allow_outside=bool(req.get("allow_outside", False)),
|
|
267
|
-
memory_enabled=bool(req.get("memory_enabled", False)),
|
|
268
|
-
max_turns=int(req.get("max_turns", 12)),
|
|
269
|
-
verify=req.get("verify", ""),
|
|
270
|
-
))
|
|
271
|
-
except noodle.NoodleSessionError as exc:
|
|
272
|
-
return _err(id_, "HPB-NOODLE-001", str(exc))
|
|
273
|
-
|
|
274
|
-
if cmd == "noodle_poll":
|
|
275
|
-
from hypernix.interfaces.noodle import hyped as noodle
|
|
276
|
-
|
|
277
|
-
try:
|
|
278
|
-
return _ok(id_, noodle.poll(
|
|
279
|
-
req["session"], timeout=float(req.get("timeout", 0.0))
|
|
280
|
-
))
|
|
281
|
-
except noodle.NoodleSessionError as exc:
|
|
282
|
-
return _err(id_, "HPB-NOODLE-002", str(exc))
|
|
283
|
-
|
|
284
|
-
if cmd == "noodle_stop":
|
|
285
|
-
from hypernix.interfaces.noodle import hyped as noodle
|
|
286
|
-
|
|
287
|
-
try:
|
|
288
|
-
return _ok(id_, noodle.stop(req["session"]))
|
|
289
|
-
except noodle.NoodleSessionError as exc:
|
|
290
|
-
return _err(id_, "HPB-NOODLE-002", str(exc))
|
|
291
|
-
|
|
292
|
-
if cmd == "noodle_sessions":
|
|
293
|
-
from hypernix.interfaces.noodle import hyped as noodle
|
|
294
|
-
|
|
295
|
-
if req.get("session"):
|
|
296
|
-
try:
|
|
297
|
-
return _ok(id_, noodle.summary(req["session"]))
|
|
298
|
-
except noodle.NoodleSessionError as exc:
|
|
299
|
-
return _err(id_, "HPB-NOODLE-002", str(exc))
|
|
300
|
-
return _ok(id_, {"sessions": noodle.sessions()})
|
|
301
|
-
|
|
302
231
|
if cmd == "cancel":
|
|
303
232
|
return _ok(id_, {"target": req.get("target"),
|
|
304
233
|
"result": _cancel(req.get("target"))})
|
|
@@ -166,7 +166,6 @@ def try_launch_qt() -> bool:
|
|
|
166
166
|
def _make_main_window_class():
|
|
167
167
|
from PySide6.QtCore import QObject, QThread, Signal
|
|
168
168
|
from PySide6.QtWidgets import (
|
|
169
|
-
QCheckBox,
|
|
170
169
|
QComboBox,
|
|
171
170
|
QFormLayout,
|
|
172
171
|
QHBoxLayout,
|
|
@@ -233,13 +232,9 @@ def _make_main_window_class():
|
|
|
233
232
|
self._chat_thread: QThread | None = None
|
|
234
233
|
self._dl_thread: QThread | None = None
|
|
235
234
|
|
|
236
|
-
self._noodle_session: str | None = None
|
|
237
|
-
self._noodle_timer = None
|
|
238
|
-
|
|
239
235
|
tabs = QTabWidget()
|
|
240
236
|
self.setCentralWidget(tabs)
|
|
241
237
|
tabs.addTab(self._build_chat_tab(), "Chat")
|
|
242
|
-
tabs.addTab(self._build_noodle_tab(), "Noodle")
|
|
243
238
|
tabs.addTab(self._build_keys_tab(), "API Keys")
|
|
244
239
|
|
|
245
240
|
# -- Chat tab ---------------------------------------------------
|
|
@@ -273,159 +268,6 @@ def _make_main_window_class():
|
|
|
273
268
|
|
|
274
269
|
return w
|
|
275
270
|
|
|
276
|
-
# -- Noodle tab -------------------------------------------------
|
|
277
|
-
#
|
|
278
|
-
# The same session API the TUI drives over the bridge, called
|
|
279
|
-
# directly because the GUI is already a Python process. A QTimer
|
|
280
|
-
# polls for events rather than a thread: poll() is a lock and a
|
|
281
|
-
# deque drain, far too cheap to be worth a thread, and doing it
|
|
282
|
-
# on the UI thread keeps every widget update where Qt wants it.
|
|
283
|
-
def _build_noodle_tab(self) -> QWidget:
|
|
284
|
-
w = QWidget()
|
|
285
|
-
layout = QVBoxLayout(w)
|
|
286
|
-
|
|
287
|
-
layout.addWidget(QLabel(
|
|
288
|
-
"Noodle runs agents against a task. They get file tools rooted "
|
|
289
|
-
"at ./.noodle and nothing outside it."
|
|
290
|
-
))
|
|
291
|
-
|
|
292
|
-
top = QHBoxLayout()
|
|
293
|
-
self.noodle_task = QLineEdit()
|
|
294
|
-
self.noodle_task.setPlaceholderText("What should the swarm do?")
|
|
295
|
-
self.noodle_task.returnPressed.connect(self._on_noodle_start)
|
|
296
|
-
self.noodle_start_btn = QPushButton("Run")
|
|
297
|
-
self.noodle_start_btn.clicked.connect(self._on_noodle_start)
|
|
298
|
-
self.noodle_stop_btn = QPushButton("Stop")
|
|
299
|
-
self.noodle_stop_btn.setEnabled(False)
|
|
300
|
-
self.noodle_stop_btn.clicked.connect(self._on_noodle_stop)
|
|
301
|
-
top.addWidget(self.noodle_task, stretch=1)
|
|
302
|
-
top.addWidget(self.noodle_start_btn)
|
|
303
|
-
top.addWidget(self.noodle_stop_btn)
|
|
304
|
-
layout.addLayout(top)
|
|
305
|
-
|
|
306
|
-
self.noodle_exec = QCheckBox(
|
|
307
|
-
"Allow shell execution (agents may run commands)"
|
|
308
|
-
)
|
|
309
|
-
layout.addWidget(self.noodle_exec)
|
|
310
|
-
|
|
311
|
-
self.noodle_log = QTextEdit()
|
|
312
|
-
self.noodle_log.setReadOnly(True)
|
|
313
|
-
layout.addWidget(self.noodle_log, stretch=1)
|
|
314
|
-
|
|
315
|
-
self.noodle_status = QLabel("")
|
|
316
|
-
layout.addWidget(self.noodle_status)
|
|
317
|
-
|
|
318
|
-
self._refresh_noodle_providers()
|
|
319
|
-
return w
|
|
320
|
-
|
|
321
|
-
def _refresh_noodle_providers(self) -> None:
|
|
322
|
-
from hypernix.interfaces.noodle import hyped as noodle
|
|
323
|
-
|
|
324
|
-
try:
|
|
325
|
-
info = noodle.providers()
|
|
326
|
-
except Exception as exc: # noqa: BLE001 - a tab must still build
|
|
327
|
-
self.noodle_status.setText(f"Could not list providers: {exc}")
|
|
328
|
-
return
|
|
329
|
-
if info["any_ready"]:
|
|
330
|
-
self.noodle_status.setText(
|
|
331
|
-
"Ready: " + ", ".join(info["ready"])
|
|
332
|
-
)
|
|
333
|
-
else:
|
|
334
|
-
self.noodle_status.setText(
|
|
335
|
-
"No provider is usable yet — set a key in the API Keys tab, "
|
|
336
|
-
"or start Ollama."
|
|
337
|
-
)
|
|
338
|
-
|
|
339
|
-
def _noodle_append(self, text: str) -> None:
|
|
340
|
-
self.noodle_log.append(text)
|
|
341
|
-
|
|
342
|
-
def _on_noodle_start(self) -> None:
|
|
343
|
-
from PySide6.QtCore import QTimer
|
|
344
|
-
|
|
345
|
-
from hypernix.interfaces.noodle import hyped as noodle
|
|
346
|
-
|
|
347
|
-
task = self.noodle_task.text().strip()
|
|
348
|
-
if not task:
|
|
349
|
-
return
|
|
350
|
-
self.noodle_log.clear()
|
|
351
|
-
try:
|
|
352
|
-
session = noodle.start(
|
|
353
|
-
task, allow_execute=self.noodle_exec.isChecked()
|
|
354
|
-
)
|
|
355
|
-
except noodle.NoodleSessionError as exc:
|
|
356
|
-
self._noodle_append(
|
|
357
|
-
f'<span style="color:#e05555">{exc}</span>'
|
|
358
|
-
)
|
|
359
|
-
return
|
|
360
|
-
self._noodle_session = session["session_id"]
|
|
361
|
-
self.noodle_start_btn.setEnabled(False)
|
|
362
|
-
self.noodle_stop_btn.setEnabled(True)
|
|
363
|
-
self.noodle_task.setEnabled(False)
|
|
364
|
-
self._noodle_append(
|
|
365
|
-
f'<i>session {session["session_id"]} · '
|
|
366
|
-
f'{", ".join(session["roster"])} · {session["root"]}</i>'
|
|
367
|
-
)
|
|
368
|
-
self._noodle_timer = QTimer(self)
|
|
369
|
-
self._noodle_timer.timeout.connect(self._on_noodle_tick)
|
|
370
|
-
self._noodle_timer.start(400)
|
|
371
|
-
|
|
372
|
-
def _on_noodle_tick(self) -> None:
|
|
373
|
-
from hypernix.interfaces.noodle import hyped as noodle
|
|
374
|
-
|
|
375
|
-
if not self._noodle_session:
|
|
376
|
-
return
|
|
377
|
-
try:
|
|
378
|
-
tick = noodle.poll(self._noodle_session)
|
|
379
|
-
except noodle.NoodleSessionError as exc:
|
|
380
|
-
self._noodle_append(f'<span style="color:#e05555">{exc}</span>')
|
|
381
|
-
self._noodle_finish()
|
|
382
|
-
return
|
|
383
|
-
for event in tick["events"]:
|
|
384
|
-
kind = str(event.get("kind") or event.get("type") or "event")
|
|
385
|
-
detail = str(
|
|
386
|
-
event.get("message") or event.get("text")
|
|
387
|
-
or event.get("tool") or event.get("task_id") or ""
|
|
388
|
-
)
|
|
389
|
-
self._noodle_append(f"<b>{kind}</b> {detail}")
|
|
390
|
-
if not tick["running"]:
|
|
391
|
-
if tick["error"]:
|
|
392
|
-
self._noodle_append(
|
|
393
|
-
f'<span style="color:#e05555">{tick["error"]}</span>'
|
|
394
|
-
)
|
|
395
|
-
elif tick["report"]:
|
|
396
|
-
ok = tick["report"].get("ok")
|
|
397
|
-
self._noodle_append(
|
|
398
|
-
f'<b>{"Done" if ok else "Finished with failures"}</b> '
|
|
399
|
-
f'in {tick["elapsed"]}s'
|
|
400
|
-
)
|
|
401
|
-
self._noodle_finish()
|
|
402
|
-
|
|
403
|
-
def _on_noodle_stop(self) -> None:
|
|
404
|
-
from hypernix.interfaces.noodle import hyped as noodle
|
|
405
|
-
|
|
406
|
-
if not self._noodle_session:
|
|
407
|
-
return
|
|
408
|
-
try:
|
|
409
|
-
result = noodle.stop(self._noodle_session)
|
|
410
|
-
except noodle.NoodleSessionError as exc:
|
|
411
|
-
self._noodle_append(f'<span style="color:#e05555">{exc}</span>')
|
|
412
|
-
return
|
|
413
|
-
self._noodle_append("<i>Stopping…</i>")
|
|
414
|
-
if not result["swarm_notified"]:
|
|
415
|
-
self._noodle_append(
|
|
416
|
-
"<i>Turns already in flight will finish — they are paid "
|
|
417
|
-
"for either way.</i>"
|
|
418
|
-
)
|
|
419
|
-
|
|
420
|
-
def _noodle_finish(self) -> None:
|
|
421
|
-
if self._noodle_timer is not None:
|
|
422
|
-
self._noodle_timer.stop()
|
|
423
|
-
self._noodle_timer = None
|
|
424
|
-
self._noodle_session = None
|
|
425
|
-
self.noodle_start_btn.setEnabled(True)
|
|
426
|
-
self.noodle_stop_btn.setEnabled(False)
|
|
427
|
-
self.noodle_task.setEnabled(True)
|
|
428
|
-
|
|
429
271
|
def _current_model_short(self) -> str:
|
|
430
272
|
return self.model_box.currentData()
|
|
431
273
|
|
|
@@ -29,20 +29,9 @@ to a different provider on its own (surprising invoices in one direction,
|
|
|
29
29
|
surprising output in the other), and it does not execute a client
|
|
30
30
|
application a remote server asked it to run. Both are opt-in and both
|
|
31
31
|
say what they are doing.
|
|
32
|
-
|
|
33
|
-
Inside hyped-pro
|
|
34
|
-
----------------
|
|
35
|
-
The line above about being "the autonomous executor inside Hyped Pro" was
|
|
36
|
-
aspirational until 0.72.5: Noodle was a library and a console script, and
|
|
37
|
-
hyped-pro had no command, no bridge verb and no idea it existed.
|
|
38
|
-
:mod:`hypernix.interfaces.noodle.hyped` is the connection — sessions the
|
|
39
|
-
TUI and the GUI drive with ``/noodle`` and a Noodle tab respectively,
|
|
40
|
-
with a live event feed rather than a frozen screen for the length of a
|
|
41
|
-
run.
|
|
42
32
|
"""
|
|
43
33
|
from __future__ import annotations
|
|
44
34
|
|
|
45
|
-
from . import hyped
|
|
46
35
|
from .agent import Agent, AgentEvent, AgentResult
|
|
47
36
|
from .hpo import HPOResult, SearchSpace, Trial, random_search, successive_halving
|
|
48
37
|
from .providers import (
|
|
@@ -71,5 +60,4 @@ __all__ = [
|
|
|
71
60
|
"Tool", "ToolContext", "ToolError", "ToolResult", "TOOLS", "run_tool", "tool_schemas",
|
|
72
61
|
"syntax_verifier", "command_verifier", "combine",
|
|
73
62
|
"SearchSpace", "Trial", "HPOResult", "random_search", "successive_halving",
|
|
74
|
-
"hyped",
|
|
75
63
|
]
|
|
@@ -148,15 +148,6 @@ class ToolContext:
|
|
|
148
148
|
"""
|
|
149
149
|
if not relative or not str(relative).strip():
|
|
150
150
|
raise ToolError("A path is required", code="bad_path")
|
|
151
|
-
# expanduser() *after* the join, deliberately. By then any `~` the
|
|
152
|
-
# model supplied sits in the middle of the path, where it expands
|
|
153
|
-
# to nothing and stays a literal directory name under the root.
|
|
154
|
-
# Expanding first would turn `~/.ssh/id_rsa` into an absolute
|
|
155
|
-
# path to the real home directory -- and `Path(root) / "/abs"` is
|
|
156
|
-
# `/abs`, because pathlib's `/` discards the left side when the
|
|
157
|
-
# right is absolute. The containment check below still catches
|
|
158
|
-
# that, but it would be the only thing catching it. Do not
|
|
159
|
-
# "fix" this into expanding first.
|
|
160
151
|
candidate = (self.root / str(relative)).expanduser()
|
|
161
152
|
try:
|
|
162
153
|
resolved = candidate.resolve()
|