wafer-cli 0.2.8__py3-none-any.whl → 0.2.10__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.
- wafer/GUIDE.md +18 -7
- wafer/api_client.py +4 -0
- wafer/auth.py +85 -0
- wafer/cli.py +2339 -404
- wafer/corpus.py +158 -32
- wafer/evaluate.py +1232 -201
- wafer/gpu_run.py +5 -1
- wafer/kernel_scope.py +554 -0
- wafer/nsys_analyze.py +903 -73
- wafer/nsys_profile.py +511 -0
- wafer/output.py +241 -0
- wafer/problems.py +357 -0
- wafer/skills/wafer-guide/SKILL.md +13 -0
- wafer/ssh_keys.py +261 -0
- wafer/target_lock.py +270 -0
- wafer/targets.py +490 -0
- wafer/targets_ops.py +718 -0
- wafer/wevin_cli.py +129 -18
- wafer/workspaces.py +282 -182
- {wafer_cli-0.2.8.dist-info → wafer_cli-0.2.10.dist-info}/METADATA +1 -1
- wafer_cli-0.2.10.dist-info/RECORD +40 -0
- wafer_cli-0.2.8.dist-info/RECORD +0 -33
- {wafer_cli-0.2.8.dist-info → wafer_cli-0.2.10.dist-info}/WHEEL +0 -0
- {wafer_cli-0.2.8.dist-info → wafer_cli-0.2.10.dist-info}/entry_points.txt +0 -0
- {wafer_cli-0.2.8.dist-info → wafer_cli-0.2.10.dist-info}/top_level.txt +0 -0
wafer/wevin_cli.py
CHANGED
|
@@ -25,6 +25,7 @@ class StreamingChunkFrontend:
|
|
|
25
25
|
|
|
26
26
|
Designed for programmatic consumption by extensions/UIs.
|
|
27
27
|
Emits events in the format expected by wevin-extension handleWevinEvent:
|
|
28
|
+
- {type: 'session_start', session_id: '...', model: '...'}
|
|
28
29
|
- {type: 'text_delta', delta: '...'}
|
|
29
30
|
- {type: 'tool_call_start', tool_name: '...'}
|
|
30
31
|
- {type: 'tool_call_end', tool_name: '...', args: {...}}
|
|
@@ -33,16 +34,31 @@ class StreamingChunkFrontend:
|
|
|
33
34
|
- {type: 'error', error: '...'}
|
|
34
35
|
"""
|
|
35
36
|
|
|
36
|
-
def __init__(self) -> None:
|
|
37
|
+
def __init__(self, session_id: str | None = None, model: str | None = None) -> None:
|
|
37
38
|
self._current_tool_call: dict | None = None
|
|
39
|
+
self._session_id = session_id
|
|
40
|
+
self._model = model
|
|
38
41
|
|
|
39
42
|
def _emit(self, obj: dict) -> None:
|
|
40
43
|
"""Emit a single NDJSON line."""
|
|
41
44
|
print(json.dumps(obj, ensure_ascii=False), flush=True)
|
|
42
45
|
|
|
43
46
|
async def start(self) -> None:
|
|
44
|
-
"""Initialize frontend."""
|
|
45
|
-
|
|
47
|
+
"""Initialize frontend and emit session_start if session_id is known."""
|
|
48
|
+
if self._session_id:
|
|
49
|
+
self._emit({
|
|
50
|
+
"type": "session_start",
|
|
51
|
+
"session_id": self._session_id,
|
|
52
|
+
"model": self._model,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
def emit_session_start(self, session_id: str, model: str | None = None) -> None:
|
|
56
|
+
"""Emit session_start event (for new sessions created during run)."""
|
|
57
|
+
self._emit({
|
|
58
|
+
"type": "session_start",
|
|
59
|
+
"session_id": session_id,
|
|
60
|
+
"model": model or self._model,
|
|
61
|
+
})
|
|
46
62
|
|
|
47
63
|
async def stop(self) -> None:
|
|
48
64
|
"""Emit session_end event."""
|
|
@@ -253,6 +269,7 @@ def _build_environment(
|
|
|
253
269
|
) -> Environment:
|
|
254
270
|
"""Build a CodingEnvironment from template config."""
|
|
255
271
|
from wafer_core.environments.coding import CodingEnvironment
|
|
272
|
+
from wafer_core.rollouts.templates import DANGEROUS_BASH_COMMANDS
|
|
256
273
|
|
|
257
274
|
working_dir = Path(corpus_path) if corpus_path else Path.cwd()
|
|
258
275
|
resolved_tools = tools_override or tpl.tools
|
|
@@ -260,6 +277,7 @@ def _build_environment(
|
|
|
260
277
|
working_dir=working_dir,
|
|
261
278
|
enabled_tools=resolved_tools,
|
|
262
279
|
bash_allowlist=tpl.bash_allowlist,
|
|
280
|
+
bash_denylist=DANGEROUS_BASH_COMMANDS,
|
|
263
281
|
) # type: ignore[assignment]
|
|
264
282
|
return env
|
|
265
283
|
|
|
@@ -333,31 +351,116 @@ def main( # noqa: PLR0913, PLR0915
|
|
|
333
351
|
single_turn: bool | None = None, # None = use template default
|
|
334
352
|
model: str | None = None,
|
|
335
353
|
resume: str | None = None,
|
|
354
|
+
from_turn: int | None = None,
|
|
336
355
|
tools: list[str] | None = None,
|
|
356
|
+
allow_spawn: bool = False,
|
|
357
|
+
max_tool_fails: int | None = None,
|
|
358
|
+
max_turns: int | None = None,
|
|
337
359
|
template: str | None = None,
|
|
338
360
|
template_args: dict[str, str] | None = None,
|
|
339
361
|
corpus_path: str | None = None,
|
|
340
362
|
list_sessions: bool = False,
|
|
363
|
+
get_session: str | None = None,
|
|
341
364
|
json_output: bool = False,
|
|
342
|
-
# Legacy args (ignored)
|
|
343
|
-
problem: str | None = None,
|
|
344
|
-
reference: str | None = None,
|
|
345
|
-
**kwargs: object,
|
|
346
365
|
) -> None:
|
|
347
366
|
"""Run wevin agent in-process via rollouts."""
|
|
367
|
+
import trio
|
|
368
|
+
from dataclasses import asdict
|
|
369
|
+
|
|
348
370
|
from wafer_core.rollouts import FileSessionStore
|
|
349
371
|
|
|
372
|
+
session_store = FileSessionStore()
|
|
373
|
+
|
|
374
|
+
# Handle --get-session: load session by ID and print
|
|
375
|
+
if get_session:
|
|
376
|
+
async def _get_session() -> None:
|
|
377
|
+
try:
|
|
378
|
+
session, err = await session_store.get(get_session)
|
|
379
|
+
if err or not session:
|
|
380
|
+
if json_output:
|
|
381
|
+
print(json.dumps({"error": err or f"Session {get_session} not found"}))
|
|
382
|
+
sys.exit(1)
|
|
383
|
+
else:
|
|
384
|
+
print(f"Error: {err or 'Session not found'}", file=sys.stderr)
|
|
385
|
+
sys.exit(1)
|
|
386
|
+
|
|
387
|
+
if json_output:
|
|
388
|
+
# Serialize messages to dicts
|
|
389
|
+
try:
|
|
390
|
+
messages_data = [asdict(msg) for msg in session.messages]
|
|
391
|
+
except Exception as e:
|
|
392
|
+
# If serialization fails, return error
|
|
393
|
+
error_msg = f"Failed to serialize messages: {e}"
|
|
394
|
+
print(json.dumps({"error": error_msg}))
|
|
395
|
+
sys.exit(1)
|
|
396
|
+
|
|
397
|
+
print(json.dumps({
|
|
398
|
+
"session_id": session.session_id,
|
|
399
|
+
"status": session.status.value,
|
|
400
|
+
"model": session.endpoint.model if session.endpoint else None,
|
|
401
|
+
"created_at": session.created_at,
|
|
402
|
+
"updated_at": session.updated_at,
|
|
403
|
+
"messages": messages_data,
|
|
404
|
+
"tags": session.tags,
|
|
405
|
+
}))
|
|
406
|
+
else:
|
|
407
|
+
print(f"Session: {session.session_id}")
|
|
408
|
+
print(f"Status: {session.status.value}")
|
|
409
|
+
print(f"Messages: {len(session.messages)}")
|
|
410
|
+
for i, msg in enumerate(session.messages):
|
|
411
|
+
# Fail fast if message can't be converted to string - corrupted data is a bug
|
|
412
|
+
content_preview = str(msg.content)[:100] if msg.content else ""
|
|
413
|
+
print(f" [{i}] {msg.role}: {content_preview}...")
|
|
414
|
+
except KeyboardInterrupt:
|
|
415
|
+
# User cancelled - exit cleanly
|
|
416
|
+
sys.exit(130) # Standard exit code for SIGINT
|
|
417
|
+
except Exception as e:
|
|
418
|
+
# Any other error - log and exit with error
|
|
419
|
+
error_msg = f"Failed to load session {get_session}: {e}"
|
|
420
|
+
if json_output:
|
|
421
|
+
print(json.dumps({"error": error_msg}))
|
|
422
|
+
else:
|
|
423
|
+
print(f"Error: {error_msg}", file=sys.stderr)
|
|
424
|
+
sys.exit(1)
|
|
425
|
+
|
|
426
|
+
try:
|
|
427
|
+
trio.run(_get_session)
|
|
428
|
+
except KeyboardInterrupt:
|
|
429
|
+
sys.exit(130)
|
|
430
|
+
except Exception as e:
|
|
431
|
+
error_msg = f"Failed to run session loader: {e}"
|
|
432
|
+
if json_output:
|
|
433
|
+
print(json.dumps({"error": error_msg}))
|
|
434
|
+
else:
|
|
435
|
+
print(f"Error: {error_msg}", file=sys.stderr)
|
|
436
|
+
sys.exit(1)
|
|
437
|
+
return
|
|
438
|
+
|
|
350
439
|
# Handle --list-sessions: show recent sessions and exit
|
|
351
440
|
if list_sessions:
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
else:
|
|
357
|
-
print("Recent sessions:")
|
|
441
|
+
sessions = session_store.list_sync(limit=50)
|
|
442
|
+
if json_output:
|
|
443
|
+
# Return metadata only - messages loaded on-demand via --get-session
|
|
444
|
+
sessions_data = []
|
|
358
445
|
for s in sessions:
|
|
359
|
-
|
|
360
|
-
|
|
446
|
+
sessions_data.append({
|
|
447
|
+
"session_id": s.session_id,
|
|
448
|
+
"status": s.status.value,
|
|
449
|
+
"model": s.endpoint.model if s.endpoint else None,
|
|
450
|
+
"created_at": s.created_at if hasattr(s, "created_at") else None,
|
|
451
|
+
"updated_at": s.updated_at if hasattr(s, "updated_at") else None,
|
|
452
|
+
"message_count": len(s.messages),
|
|
453
|
+
"preview": _get_session_preview(s),
|
|
454
|
+
})
|
|
455
|
+
print(json.dumps({"sessions": sessions_data}))
|
|
456
|
+
else:
|
|
457
|
+
if not sessions:
|
|
458
|
+
print("No sessions found.")
|
|
459
|
+
else:
|
|
460
|
+
print("Recent sessions:")
|
|
461
|
+
for s in sessions:
|
|
462
|
+
preview = _get_session_preview(s)
|
|
463
|
+
print(f" {s.session_id} {preview}")
|
|
361
464
|
return
|
|
362
465
|
|
|
363
466
|
# Emit early event for JSON mode before heavy imports
|
|
@@ -365,8 +468,7 @@ def main( # noqa: PLR0913, PLR0915
|
|
|
365
468
|
if json_output:
|
|
366
469
|
print(json.dumps({"type": "initializing"}), flush=True)
|
|
367
470
|
|
|
368
|
-
import
|
|
369
|
-
from wafer_core.rollouts import FileSessionStore, Message, Trajectory
|
|
471
|
+
from wafer_core.rollouts import Message, Trajectory
|
|
370
472
|
from wafer_core.rollouts.frontends import NoneFrontend, RunnerConfig, run_interactive
|
|
371
473
|
|
|
372
474
|
_setup_logging()
|
|
@@ -442,7 +544,9 @@ def main( # noqa: PLR0913, PLR0915
|
|
|
442
544
|
)
|
|
443
545
|
else:
|
|
444
546
|
if json_output:
|
|
445
|
-
|
|
547
|
+
# Emit session_start if we have a session_id (from --resume)
|
|
548
|
+
model_name = endpoint.model if hasattr(endpoint, 'model') else None
|
|
549
|
+
frontend = StreamingChunkFrontend(session_id=session_id, model=model_name)
|
|
446
550
|
else:
|
|
447
551
|
frontend = NoneFrontend(show_tool_calls=True, show_thinking=False)
|
|
448
552
|
config = RunnerConfig(
|
|
@@ -453,6 +557,13 @@ def main( # noqa: PLR0913, PLR0915
|
|
|
453
557
|
hide_session_info=True, # We print our own resume command
|
|
454
558
|
)
|
|
455
559
|
states = await run_interactive(trajectory, endpoint, frontend, environment, config)
|
|
560
|
+
# Emit session_start for new sessions (if session_id was None and we got one)
|
|
561
|
+
# Check first state to emit as early as possible
|
|
562
|
+
if json_output and isinstance(frontend, StreamingChunkFrontend):
|
|
563
|
+
first_session_id = states[0].session_id if states and states[0].session_id else None
|
|
564
|
+
if first_session_id and not session_id: # New session created
|
|
565
|
+
model_name = endpoint.model if hasattr(endpoint, 'model') else None
|
|
566
|
+
frontend.emit_session_start(first_session_id, model_name)
|
|
456
567
|
# Print resume command with full wafer agent prefix
|
|
457
568
|
if states and states[-1].session_id:
|
|
458
569
|
print(f"\nResume with: wafer agent --resume {states[-1].session_id}")
|