wafer-cli 0.2.9__py3-none-any.whl → 0.2.11__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/cli.py +1177 -278
- wafer/corpus.py +158 -32
- wafer/evaluate.py +75 -6
- wafer/kernel_scope.py +132 -31
- wafer/nsys_analyze.py +903 -73
- wafer/nsys_profile.py +511 -0
- wafer/output.py +241 -0
- wafer/skills/wafer-guide/SKILL.md +13 -0
- wafer/ssh_keys.py +261 -0
- wafer/targets_ops.py +718 -0
- wafer/wevin_cli.py +127 -18
- wafer/workspaces.py +232 -184
- {wafer_cli-0.2.9.dist-info → wafer_cli-0.2.11.dist-info}/METADATA +1 -1
- {wafer_cli-0.2.9.dist-info → wafer_cli-0.2.11.dist-info}/RECORD +19 -15
- {wafer_cli-0.2.9.dist-info → wafer_cli-0.2.11.dist-info}/WHEEL +0 -0
- {wafer_cli-0.2.9.dist-info → wafer_cli-0.2.11.dist-info}/entry_points.txt +0 -0
- {wafer_cli-0.2.9.dist-info → wafer_cli-0.2.11.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."""
|
|
@@ -335,31 +351,116 @@ def main( # noqa: PLR0913, PLR0915
|
|
|
335
351
|
single_turn: bool | None = None, # None = use template default
|
|
336
352
|
model: str | None = None,
|
|
337
353
|
resume: str | None = None,
|
|
354
|
+
from_turn: int | None = None,
|
|
338
355
|
tools: list[str] | None = None,
|
|
356
|
+
allow_spawn: bool = False,
|
|
357
|
+
max_tool_fails: int | None = None,
|
|
358
|
+
max_turns: int | None = None,
|
|
339
359
|
template: str | None = None,
|
|
340
360
|
template_args: dict[str, str] | None = None,
|
|
341
361
|
corpus_path: str | None = None,
|
|
342
362
|
list_sessions: bool = False,
|
|
363
|
+
get_session: str | None = None,
|
|
343
364
|
json_output: bool = False,
|
|
344
|
-
# Legacy args (ignored)
|
|
345
|
-
problem: str | None = None,
|
|
346
|
-
reference: str | None = None,
|
|
347
|
-
**kwargs: object,
|
|
348
365
|
) -> None:
|
|
349
366
|
"""Run wevin agent in-process via rollouts."""
|
|
367
|
+
import trio
|
|
368
|
+
from dataclasses import asdict
|
|
369
|
+
|
|
350
370
|
from wafer_core.rollouts import FileSessionStore
|
|
351
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
|
+
|
|
352
439
|
# Handle --list-sessions: show recent sessions and exit
|
|
353
440
|
if list_sessions:
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
else:
|
|
359
|
-
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 = []
|
|
360
445
|
for s in sessions:
|
|
361
|
-
|
|
362
|
-
|
|
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}")
|
|
363
464
|
return
|
|
364
465
|
|
|
365
466
|
# Emit early event for JSON mode before heavy imports
|
|
@@ -367,8 +468,7 @@ def main( # noqa: PLR0913, PLR0915
|
|
|
367
468
|
if json_output:
|
|
368
469
|
print(json.dumps({"type": "initializing"}), flush=True)
|
|
369
470
|
|
|
370
|
-
import
|
|
371
|
-
from wafer_core.rollouts import FileSessionStore, Message, Trajectory
|
|
471
|
+
from wafer_core.rollouts import Message, Trajectory
|
|
372
472
|
from wafer_core.rollouts.frontends import NoneFrontend, RunnerConfig, run_interactive
|
|
373
473
|
|
|
374
474
|
_setup_logging()
|
|
@@ -444,7 +544,9 @@ def main( # noqa: PLR0913, PLR0915
|
|
|
444
544
|
)
|
|
445
545
|
else:
|
|
446
546
|
if json_output:
|
|
447
|
-
|
|
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)
|
|
448
550
|
else:
|
|
449
551
|
frontend = NoneFrontend(show_tool_calls=True, show_thinking=False)
|
|
450
552
|
config = RunnerConfig(
|
|
@@ -455,6 +557,13 @@ def main( # noqa: PLR0913, PLR0915
|
|
|
455
557
|
hide_session_info=True, # We print our own resume command
|
|
456
558
|
)
|
|
457
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)
|
|
458
567
|
# Print resume command with full wafer agent prefix
|
|
459
568
|
if states and states[-1].session_id:
|
|
460
569
|
print(f"\nResume with: wafer agent --resume {states[-1].session_id}")
|