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.
Files changed (41) hide show
  1. hypernix/__init__.py +1 -1
  2. hypernix/hyped_pro.js +0 -139
  3. hypernix/interfaces/cli.py +3 -103
  4. hypernix/interfaces/hyped_pro_bridge.py +2 -73
  5. hypernix/interfaces/hyped_pro_gui.py +0 -158
  6. hypernix/interfaces/noodle/__init__.py +0 -12
  7. hypernix/interfaces/noodle/tools.py +0 -9
  8. hypernix/monitoring/cctvtop.py +44 -145
  9. hypernix/monitoring/tvtoppro.py +3 -374
  10. hypernix/quant/dflash2.py +39 -49
  11. hypernix/quant/gguf.py +0 -6
  12. hypernix/quant/ggufcheck.py +11 -12
  13. hypernix/quant/hyprslug.py +112 -490
  14. hypernix/quant/hyprslug_cli.py +0 -313
  15. hypernix/quant/lowbit.py +1 -55
  16. hypernix/quant/steamroller.py +0 -17
  17. hypernix/security/keymaster.py +4 -89
  18. hypernix/system/config.py +3 -79
  19. hypernix/t1api/app.py +1 -46
  20. hypernix/t1api/routers/__init__.py +0 -10
  21. hypernix/t1api/version.py +3 -3
  22. hypernix/timing/spinner.py +0 -69
  23. {hypernix-0.72.5.dev2.dist-info → hypernix-0.72.5.dev3.dist-info}/METADATA +20 -210
  24. {hypernix-0.72.5.dev2.dist-info → hypernix-0.72.5.dev3.dist-info}/RECORD +29 -41
  25. {hypernix-0.72.5.dev2.dist-info → hypernix-0.72.5.dev3.dist-info}/entry_points.txt +0 -4
  26. hypernix/interfaces/noodle/hyped.py +0 -521
  27. hypernix/monitoring/remote_desktop.py +0 -647
  28. hypernix/monitoring/stale_log.py +0 -596
  29. hypernix/monitoring/tvtoppro_modules.py +0 -724
  30. hypernix/quant/dflash1.py +0 -400
  31. hypernix/quant/dflash1_cli.py +0 -140
  32. hypernix/quant/multiquant.py +0 -684
  33. hypernix/quant/multiquant_cli.py +0 -173
  34. hypernix/t1api/accounts.py +0 -803
  35. hypernix/t1api/accounts_cli.py +0 -313
  36. hypernix/t1api/routers/accounts.py +0 -705
  37. hypernix/t1api/webauth.py +0 -357
  38. {hypernix-0.72.5.dev2.data → hypernix-0.72.5.dev3.data}/scripts/hypernix-t1 +0 -0
  39. {hypernix-0.72.5.dev2.dist-info → hypernix-0.72.5.dev3.dist-info}/WHEEL +0 -0
  40. {hypernix-0.72.5.dev2.dist-info → hypernix-0.72.5.dev3.dist-info}/licenses/LICENSE +0 -0
  41. {hypernix-0.72.5.dev2.dist-info → hypernix-0.72.5.dev3.dist-info}/top_level.txt +0 -0
hypernix/__init__.py CHANGED
@@ -63,7 +63,7 @@ import importlib.util
63
63
  import sys
64
64
  from typing import TYPE_CHECKING, Any
65
65
 
66
- __version__ = "0.72.5.dev2"
66
+ __version__ = "0.72.5.dev3"
67
67
  DEFAULT_REPO_ID = "ray0rf1re/hyper-Nix.2"
68
68
  DEFAULT_MODEL = "qwen3.5-4b" # New default model
69
69
 
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) {
@@ -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
- return _unknown_command(raw[0])
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.9.2.3"
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
- # noodle_poll is here because it long-polls: it waits up to `timeout`
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()