swarph-cli 0.13.4__tar.gz → 0.13.6__tar.gz
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.
- {swarph_cli-0.13.4/src/swarph_cli.egg-info → swarph_cli-0.13.6}/PKG-INFO +1 -1
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/pyproject.toml +1 -1
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/__init__.py +1 -1
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/spawn.py +7 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/watchdog.py +59 -9
- {swarph_cli-0.13.4 → swarph_cli-0.13.6/src/swarph_cli.egg-info}/PKG-INFO +1 -1
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_spawn_command.py +12 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_watchdog.py +60 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/LICENSE +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/README.md +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/setup.cfg +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/caller.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/capture/__init__.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/capture/harden.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/capture/lineage.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/capture/liveness.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/capture/manifest.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/capture/paths.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/capture/verify.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/cell.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/__init__.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/add.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/cell.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/chat.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/compress.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/daemon.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/hook_output.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/hooks.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/import_session.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/init.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/install_hook.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/mcp_server.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/memory_sync.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/mesh.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/onboard.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/protocol_handler.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/ratify.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/commands/security.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/compress/__init__.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/compress/levers.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/compress/marker.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/compress/verify.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/main.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/parsers/__init__.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/parsers/claude.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli.egg-info/SOURCES.txt +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli.egg-info/entry_points.txt +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli.egg-info/requires.txt +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/src/swarph_cli.egg-info/top_level.txt +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_add_security_gate.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_artifact_add.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_artifact_hash.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_artifact_lib.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_artifact_mcp.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_artifact_skill.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_artifact_tool.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_artifact_uri.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_caller_meta_guard.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_capture_lineage.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_capture_liveness.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_capture_manifest.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_capture_paths.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_capture_security.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_cell_command_dispatch.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_cell_harden.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_cell_loader.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_cell_verify.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_chat_command.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_claude_parser.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_claude_tmux_template.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_compress_caller_convention.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_compress_command.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_compress_levers.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_compress_marker.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_compress_verify.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_daemon_command.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_feature_to_uri.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_hook_output.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_hooks_add.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_hooks_bundle.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_hooks_init.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_hooks_lifecycle.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_hooks_merge.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_import_command.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_init_command.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_install_hook.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_main.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_mcp_server.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_memory_sync.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_mesh_command.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_mesh_sidecar.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_onboard_command.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_protocol_handler.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_ratify_command.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_repl_caller_convention.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_security.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_session_path_role_gate.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_smoke_chat.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_smoke_one_shot.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_smoke_phase_5_5.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_spawn_live_pin.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_spawn_mitosis_lineage.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_spawn_tmux_session.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_spawn_windows_relaunch.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_version_consistency.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_watchdog_dm_wake.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_watchdog_dm_wake_wiring.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_watchdog_model_rung.py +0 -0
- {swarph_cli-0.13.4 → swarph_cli-0.13.6}/tests/test_watchdog_stale_peers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.6
|
|
4
4
|
Summary: The `swarph` binary — multi-LLM CLI + mesh-gateway integration: multi-provider `swarph spawn` (claude/codex/antigravity per cell.provider via a ProviderMembrane + subprocess billing-scrub), interactive `swarph init`, `swarph mesh` (send/inbox/register) + inbox sidecar, `assisted_memory` (git-backed durable memory), session import, watchdog. v0.9.5: foolproof Windows auto-relaunch (genuine-WT detection via process ancestry, not WT_SESSION).
|
|
5
5
|
Author: Pierre Samson, Claude Opus
|
|
6
6
|
License: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "swarph-cli"
|
|
7
|
-
version = "0.13.
|
|
7
|
+
version = "0.13.6"
|
|
8
8
|
description = "The `swarph` binary — multi-LLM CLI + mesh-gateway integration: multi-provider `swarph spawn` (claude/codex/antigravity per cell.provider via a ProviderMembrane + subprocess billing-scrub), interactive `swarph init`, `swarph mesh` (send/inbox/register) + inbox sidecar, `assisted_memory` (git-backed durable memory), session import, watchdog. v0.9.5: foolproof Windows auto-relaunch (genuine-WT detection via process ancestry, not WT_SESSION)."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -418,6 +418,13 @@ def _claude_env() -> dict[str, str]:
|
|
|
418
418
|
"""
|
|
419
419
|
env = scrub_env_for_subprocess()
|
|
420
420
|
env["SWARPH_SPAWN"] = "1"
|
|
421
|
+
# Disable the Claude Code in-session rating survey ("How is Claude doing this
|
|
422
|
+
# session?"). On a headless/automated cell it pops up as a modal that the
|
|
423
|
+
# wake-injector refuses to type into, so it stalls scheduled wakes
|
|
424
|
+
# indefinitely (a survey deferred the weekly newsletter 2+ hours — see
|
|
425
|
+
# feedback_modal_stalls_cell_wake). Suppresses ONLY the survey; does NOT
|
|
426
|
+
# touch telemetry / auto-update / error reporting.
|
|
427
|
+
env["CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY"] = "1"
|
|
421
428
|
return env
|
|
422
429
|
|
|
423
430
|
|
|
@@ -259,6 +259,31 @@ def _resolve_cursor_path(
|
|
|
259
259
|
return Path("/tmp/lab-claude-cursor.json")
|
|
260
260
|
|
|
261
261
|
|
|
262
|
+
def _resolve_activity_marker_path(
|
|
263
|
+
role: str,
|
|
264
|
+
explicit: Optional[str],
|
|
265
|
+
cell_yaml_value: Optional[str] = None,
|
|
266
|
+
) -> Path:
|
|
267
|
+
"""Resolve the turn-activity marker — the Stop-hook touches it every turn-end.
|
|
268
|
+
|
|
269
|
+
Precedence: ``--activity-marker`` > ``cell.yaml`` extra.activity_marker_path
|
|
270
|
+
> ``$TMPDIR/<role>-claude-active.txt``.
|
|
271
|
+
|
|
272
|
+
Unlike the cursor (often the inbox-DRAIN cursor, touched only on drain and so
|
|
273
|
+
STALE during active non-draining work → false-fire,
|
|
274
|
+
feedback_watchdog_liveness_proxy), this marker tracks real turn activity. The
|
|
275
|
+
watchdog uses the FRESHEST mtime of {cursor, marker} as the effective
|
|
276
|
+
last-activity, so a fresh marker rescues a stale drain-cursor and an absent
|
|
277
|
+
marker harmlessly falls back to the cursor.
|
|
278
|
+
"""
|
|
279
|
+
if explicit:
|
|
280
|
+
return Path(explicit).expanduser()
|
|
281
|
+
if cell_yaml_value:
|
|
282
|
+
return Path(cell_yaml_value).expanduser()
|
|
283
|
+
tmpdir = os.environ.get("TMPDIR", "/tmp")
|
|
284
|
+
return Path(tmpdir) / f"{role}-claude-active.txt"
|
|
285
|
+
|
|
286
|
+
|
|
262
287
|
def _resolve_tmux_session(
|
|
263
288
|
role: str,
|
|
264
289
|
explicit: Optional[str],
|
|
@@ -283,11 +308,14 @@ def _resolve_tmux_session(
|
|
|
283
308
|
return role
|
|
284
309
|
|
|
285
310
|
|
|
286
|
-
def _read_cell_yaml_pins(
|
|
287
|
-
|
|
311
|
+
def _read_cell_yaml_pins(
|
|
312
|
+
role: str,
|
|
313
|
+
) -> tuple[Optional[str], Optional[str], Optional[str]]:
|
|
314
|
+
"""Best-effort read of cell.yaml extra.cursor_path + extra.tmux_session +
|
|
315
|
+
extra.activity_marker_path.
|
|
288
316
|
|
|
289
317
|
Tries the cwd-local ``./cell.yaml`` first (matches hook_output discovery),
|
|
290
|
-
falls back to ``<cells_dir>/<role>.yaml``. Returns (None, None) on any
|
|
318
|
+
falls back to ``<cells_dir>/<role>.yaml``. Returns (None, None, None) on any
|
|
291
319
|
failure — F4 is additive non-breaking, malformed cell.yaml falls through
|
|
292
320
|
to the legacy convention defaults.
|
|
293
321
|
|
|
@@ -311,19 +339,21 @@ def _read_cell_yaml_pins(role: str) -> tuple[Optional[str], Optional[str]]:
|
|
|
311
339
|
if candidate.is_file():
|
|
312
340
|
cell_path = candidate
|
|
313
341
|
if cell_path is None:
|
|
314
|
-
return None, None
|
|
342
|
+
return None, None, None
|
|
315
343
|
|
|
316
344
|
try:
|
|
317
345
|
cell = load_cell(cell_path)
|
|
318
346
|
except (CellError, OSError):
|
|
319
|
-
return None, None
|
|
347
|
+
return None, None, None
|
|
320
348
|
|
|
321
349
|
extra = cell.extra or {}
|
|
322
350
|
cursor_path = extra.get("cursor_path")
|
|
323
351
|
tmux_session = extra.get("tmux_session")
|
|
352
|
+
activity_marker = extra.get("activity_marker_path")
|
|
324
353
|
return (
|
|
325
354
|
str(cursor_path) if cursor_path else None,
|
|
326
355
|
str(tmux_session) if tmux_session else None,
|
|
356
|
+
str(activity_marker) if activity_marker else None,
|
|
327
357
|
)
|
|
328
358
|
|
|
329
359
|
|
|
@@ -1158,7 +1188,7 @@ def _run_local_check(args: argparse.Namespace) -> int:
|
|
|
1158
1188
|
# `extra.tmux_session` when --cell is provided; explicit CLI args still
|
|
1159
1189
|
# win. Best-effort: malformed cell.yaml falls through to legacy
|
|
1160
1190
|
# convention defaults (additive non-breaking).
|
|
1161
|
-
cell_cursor, cell_tmux = _read_cell_yaml_pins(role)
|
|
1191
|
+
cell_cursor, cell_tmux, cell_activity = _read_cell_yaml_pins(role)
|
|
1162
1192
|
cursor = _resolve_cursor_path(role, args.cursor, cell_cursor)
|
|
1163
1193
|
tmux_session = _resolve_tmux_session(role, args.tmux_session, cell_tmux)
|
|
1164
1194
|
log_path = _resolve_log_path(args.log)
|
|
@@ -1180,10 +1210,22 @@ def _run_local_check(args: argparse.Namespace) -> int:
|
|
|
1180
1210
|
"cell_yaml_pinned_tmux": cell_tmux is not None,
|
|
1181
1211
|
}
|
|
1182
1212
|
|
|
1183
|
-
# PRIMARY signal:
|
|
1184
|
-
|
|
1213
|
+
# PRIMARY signal: liveness = FRESHEST mtime of {drain-cursor, turn-activity
|
|
1214
|
+
# marker}. The cursor is often the inbox-DRAIN cursor (touched only on drain)
|
|
1215
|
+
# → it goes stale during active non-draining work and false-fires recovery
|
|
1216
|
+
# (feedback_watchdog_liveness_proxy). The Stop-hook marker is touched every
|
|
1217
|
+
# turn-end. A fresh marker rescues a stale cursor; an absent/unreadable marker
|
|
1218
|
+
# harmlessly falls back to the cursor (no change for cells without it).
|
|
1219
|
+
activity_marker = _resolve_activity_marker_path(
|
|
1220
|
+
role, getattr(args, "activity_marker", None), cell_activity
|
|
1221
|
+
)
|
|
1222
|
+
diag["activity_marker"] = str(activity_marker)
|
|
1223
|
+
_live_mtimes = [
|
|
1224
|
+
m for m in (_stat_mtime(cursor), _stat_mtime(activity_marker)) if m is not None
|
|
1225
|
+
]
|
|
1226
|
+
cursor_mtime = max(_live_mtimes) if _live_mtimes else None
|
|
1185
1227
|
if cursor_mtime is None:
|
|
1186
|
-
diag["error"] = f"cursor file unreadable: {cursor}"
|
|
1228
|
+
diag["error"] = f"cursor file unreadable: {cursor} (and marker {activity_marker})"
|
|
1187
1229
|
_log_event(log_path, "error", diag, verbose)
|
|
1188
1230
|
return 3
|
|
1189
1231
|
cursor_age = _now() - cursor_mtime
|
|
@@ -1660,6 +1702,14 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
1660
1702
|
)
|
|
1661
1703
|
p.add_argument("--cell", default=os.environ.get("SWARPH_CELL", "lab"))
|
|
1662
1704
|
p.add_argument("--cursor", default=None)
|
|
1705
|
+
p.add_argument(
|
|
1706
|
+
"--activity-marker", default=None,
|
|
1707
|
+
help="Turn-activity marker path (the Stop-hook touches it every "
|
|
1708
|
+
"turn-end). The freshest mtime of {cursor, marker} is the "
|
|
1709
|
+
"effective liveness, so an actively-working-but-not-draining cell "
|
|
1710
|
+
"isn't false-judged dark. Default $TMPDIR/<role>-claude-active.txt; "
|
|
1711
|
+
"cell.yaml extra.activity_marker_path also honored.",
|
|
1712
|
+
)
|
|
1663
1713
|
p.add_argument("--threshold", type=int, default=_DEFAULT_THRESHOLD_SEC)
|
|
1664
1714
|
p.add_argument(
|
|
1665
1715
|
"--pane-activity-threshold",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: swarph-cli
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.6
|
|
4
4
|
Summary: The `swarph` binary — multi-LLM CLI + mesh-gateway integration: multi-provider `swarph spawn` (claude/codex/antigravity per cell.provider via a ProviderMembrane + subprocess billing-scrub), interactive `swarph init`, `swarph mesh` (send/inbox/register) + inbox sidecar, `assisted_memory` (git-backed durable memory), session import, watchdog. v0.9.5: foolproof Windows auto-relaunch (genuine-WT detection via process ancestry, not WT_SESSION).
|
|
5
5
|
Author: Pierre Samson, Claude Opus
|
|
6
6
|
License: MIT
|
|
@@ -1343,3 +1343,15 @@ def test_run_spawn_grok_assisted_memory_no_double_system_prompt(
|
|
|
1343
1343
|
assert "you are grok-researcher." in argv[argv.index("--system-prompt-override") + 1]
|
|
1344
1344
|
assert "--rules" in argv # CURRENT_TASK injected here, not as a 2nd override
|
|
1345
1345
|
assert "Active task body" in argv[argv.index("--rules") + 1]
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
def test_claude_env_disables_feedback_survey(monkeypatch):
|
|
1349
|
+
"""The Claude Code session-rating survey ("How is Claude doing this
|
|
1350
|
+
session?") is a modal that stalls a headless cell's scheduled wake — the
|
|
1351
|
+
wake-injector refuses to type into a modal, so a survey deferred the weekly
|
|
1352
|
+
newsletter 2+ hours (feedback_modal_stalls_cell_wake). Spawned claude cells
|
|
1353
|
+
disable it at the source via env."""
|
|
1354
|
+
monkeypatch.setenv("PATH", "/usr/bin:/bin")
|
|
1355
|
+
from swarph_cli.commands.spawn import _claude_env
|
|
1356
|
+
env = _claude_env()
|
|
1357
|
+
assert env.get("CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY") == "1"
|
|
@@ -23,6 +23,7 @@ _POSIX_WATCHDOG_SKIP = pytest.mark.skipif(
|
|
|
23
23
|
|
|
24
24
|
from swarph_cli.commands.watchdog import (
|
|
25
25
|
_DEFAULT_THRESHOLD_SEC,
|
|
26
|
+
_resolve_activity_marker_path,
|
|
26
27
|
_resolve_cursor_path,
|
|
27
28
|
_resolve_log_path,
|
|
28
29
|
run_watchdog,
|
|
@@ -871,3 +872,62 @@ def test_pane_activity_age_takes_most_recent(monkeypatch):
|
|
|
871
872
|
lambda cmd, **kw: _R(0, f"|{now - 500}|{now - 10}\n"))
|
|
872
873
|
age = _wd._pane_activity_age_sec("lab")
|
|
873
874
|
assert age is not None and age <= 20
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
# ---------------------------------------------------------------------------
|
|
878
|
+
# Liveness generalization — turn-activity marker as a second signal
|
|
879
|
+
# (feedback_watchdog_liveness_proxy): the drain-cursor goes stale during active
|
|
880
|
+
# non-draining work; the Stop-hook active.txt (touched every turn-end) rescues it.
|
|
881
|
+
# ---------------------------------------------------------------------------
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
def test_resolve_activity_marker_explicit_wins(isolated_state):
|
|
885
|
+
p = _resolve_activity_marker_path("lab", "/x/y.txt", "/cell/z.txt")
|
|
886
|
+
assert str(p) == "/x/y.txt"
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
def test_resolve_activity_marker_cell_yaml_beats_default(isolated_state):
|
|
890
|
+
p = _resolve_activity_marker_path("lab", None, "/cell/z.txt")
|
|
891
|
+
assert str(p) == "/cell/z.txt"
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
def test_resolve_activity_marker_default_is_role_active_in_tmpdir(isolated_state):
|
|
895
|
+
p = _resolve_activity_marker_path("lab", None, None)
|
|
896
|
+
assert p == isolated_state / "lab-claude-active.txt"
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
def test_stale_cursor_but_fresh_activity_marker_returns_noop(isolated_state, stale_cursor):
|
|
900
|
+
# Drain-cursor is stale (1hr) BUT the Stop-hook turn-activity marker at the
|
|
901
|
+
# DEFAULT path is fresh → freshest-of-both is fresh → healthy noop, NO false A1.
|
|
902
|
+
marker = isolated_state / "lab-claude-active.txt"
|
|
903
|
+
marker.write_text("") # current mtime
|
|
904
|
+
with patch("swarph_cli.commands.watchdog._process_alive", return_value=True), \
|
|
905
|
+
patch("swarph_cli.commands.watchdog._gateway_unread_count", return_value=3), \
|
|
906
|
+
patch("swarph_cli.commands.watchdog._tmux_session_exists", return_value=True), \
|
|
907
|
+
patch("swarph_cli.commands.watchdog._pane_activity_age_sec", return_value=None):
|
|
908
|
+
rc = run_watchdog(argv=[
|
|
909
|
+
"--check", "--cell", "lab",
|
|
910
|
+
"--cursor", str(stale_cursor),
|
|
911
|
+
"--threshold", "60",
|
|
912
|
+
])
|
|
913
|
+
assert rc == 0 # marker freshness rescues the stale cursor
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
def test_stale_cursor_and_stale_marker_still_fires_a1(isolated_state, stale_cursor):
|
|
917
|
+
# Both stale → unchanged legacy behavior (A1). Absent/stale marker is harmless.
|
|
918
|
+
import os as _os
|
|
919
|
+
marker = isolated_state / "lab-claude-active.txt"
|
|
920
|
+
marker.write_text("")
|
|
921
|
+
one_hour_ago = time.time() - 3600
|
|
922
|
+
_os.utime(marker, (one_hour_ago, one_hour_ago))
|
|
923
|
+
with patch("swarph_cli.commands.watchdog._process_alive", return_value=True), \
|
|
924
|
+
patch("swarph_cli.commands.watchdog._gateway_unread_count", return_value=3), \
|
|
925
|
+
patch("swarph_cli.commands.watchdog._tmux_session_exists", return_value=True), \
|
|
926
|
+
patch("swarph_cli.commands.watchdog._pane_activity_age_sec", return_value=None), \
|
|
927
|
+
patch("swarph_cli.commands.watchdog._tmux_send_keys", return_value=True):
|
|
928
|
+
rc = run_watchdog(argv=[
|
|
929
|
+
"--check", "--cell", "lab",
|
|
930
|
+
"--cursor", str(stale_cursor),
|
|
931
|
+
"--threshold", "60",
|
|
932
|
+
])
|
|
933
|
+
assert rc == 1 # both signals dark → A1 (unchanged)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|