hcom 0.3.0__tar.gz → 0.3.1__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.
Potentially problematic release.
This version of hcom might be problematic. Click here for more details.
- {hcom-0.3.0/src/hcom.egg-info → hcom-0.3.1}/PKG-INFO +2 -2
- {hcom-0.3.0 → hcom-0.3.1}/README.md +1 -1
- {hcom-0.3.0 → hcom-0.3.1}/pyproject.toml +1 -1
- {hcom-0.3.0 → hcom-0.3.1}/src/hcom/__main__.py +56 -74
- {hcom-0.3.0 → hcom-0.3.1/src/hcom.egg-info}/PKG-INFO +2 -2
- {hcom-0.3.0 → hcom-0.3.1}/MANIFEST.in +0 -0
- {hcom-0.3.0 → hcom-0.3.1}/setup.cfg +0 -0
- {hcom-0.3.0 → hcom-0.3.1}/src/hcom/__init__.py +0 -0
- {hcom-0.3.0 → hcom-0.3.1}/src/hcom.egg-info/SOURCES.txt +0 -0
- {hcom-0.3.0 → hcom-0.3.1}/src/hcom.egg-info/dependency_links.txt +0 -0
- {hcom-0.3.0 → hcom-0.3.1}/src/hcom.egg-info/entry_points.txt +0 -0
- {hcom-0.3.0 → hcom-0.3.1}/src/hcom.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hcom
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: CLI tool for launching multiple Claude Code terminals with interactive subagents, headless persistence, and real-time communication via hooks
|
|
5
5
|
Author: aannoo
|
|
6
6
|
License-Expression: MIT
|
|
@@ -250,7 +250,7 @@ When running `hcom watch`, each instance shows its current state:
|
|
|
250
250
|
|
|
251
251
|
hcom adds hooks to your project directory's `.claude/settings.local.json`:
|
|
252
252
|
|
|
253
|
-
1. **Sending**: Claude agents use
|
|
253
|
+
1. **Sending**: Claude agents use `eval $HCOM send "message"` internally (you use `hcom send` from terminal or dashboard)
|
|
254
254
|
2. **Receiving**: Other Claudes get notified via Stop hook or immediate delivery after sending
|
|
255
255
|
3. **Waiting**: Stop hook keeps Claude in a waiting state for new messages
|
|
256
256
|
|
|
@@ -223,7 +223,7 @@ When running `hcom watch`, each instance shows its current state:
|
|
|
223
223
|
|
|
224
224
|
hcom adds hooks to your project directory's `.claude/settings.local.json`:
|
|
225
225
|
|
|
226
|
-
1. **Sending**: Claude agents use
|
|
226
|
+
1. **Sending**: Claude agents use `eval $HCOM send "message"` internally (you use `hcom send` from terminal or dashboard)
|
|
227
227
|
2. **Receiving**: Other Claudes get notified via Stop hook or immediate delivery after sending
|
|
228
228
|
3. **Waiting**: Stop hook keeps Claude in a waiting state for new messages
|
|
229
229
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hcom"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "CLI tool for launching multiple Claude Code terminals with interactive subagents, headless persistence, and real-time communication via hooks"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
hcom 0.3.
|
|
3
|
+
hcom 0.3.1
|
|
4
4
|
CLI tool for launching multiple Claude Code terminals with interactive subagents, headless persistence, and real-time communication via hooks
|
|
5
5
|
"""
|
|
6
6
|
|
|
@@ -1326,7 +1326,7 @@ def verify_hooks_installed(settings_path):
|
|
|
1326
1326
|
|
|
1327
1327
|
# Check all hook types exist with HCOM commands (PostToolUse removed)
|
|
1328
1328
|
hooks = settings.get('hooks', {})
|
|
1329
|
-
for hook_type in ['SessionStart', 'PreToolUse', 'Stop', 'Notification']:
|
|
1329
|
+
for hook_type in ['SessionStart', 'UserPromptSubmit', 'PreToolUse', 'Stop', 'Notification']:
|
|
1330
1330
|
if not any('hcom' in str(h).lower() or 'HCOM' in str(h)
|
|
1331
1331
|
for h in hooks.get(hook_type, [])):
|
|
1332
1332
|
return False
|
|
@@ -1507,33 +1507,40 @@ def format_age(seconds: float) -> str:
|
|
|
1507
1507
|
else:
|
|
1508
1508
|
return f"{int(seconds/3600)}h"
|
|
1509
1509
|
|
|
1510
|
-
def get_instance_status(pos_data: dict[str, Any]) -> tuple[str, str]:
|
|
1511
|
-
"""Get current status of instance. Returns (status_type, age_string)."""
|
|
1512
|
-
# Returns: (display_category, formatted_age
|
|
1510
|
+
def get_instance_status(pos_data: dict[str, Any]) -> tuple[str, str, str]:
|
|
1511
|
+
"""Get current status of instance. Returns (status_type, age_string, description)."""
|
|
1512
|
+
# Returns: (display_category, formatted_age, status_description)
|
|
1513
1513
|
now = int(time.time())
|
|
1514
1514
|
|
|
1515
1515
|
# Check if killed
|
|
1516
1516
|
if pos_data.get('pid') is None: #TODO: replace this later when process management stuff removed
|
|
1517
|
-
return "inactive", ""
|
|
1517
|
+
return "inactive", "", "killed"
|
|
1518
1518
|
|
|
1519
1519
|
# Get last known status
|
|
1520
1520
|
last_status = pos_data.get('last_status', '')
|
|
1521
1521
|
last_status_time = pos_data.get('last_status_time', 0)
|
|
1522
|
+
last_context = pos_data.get('last_status_context', '')
|
|
1522
1523
|
|
|
1523
1524
|
if not last_status or not last_status_time:
|
|
1524
|
-
return "unknown", ""
|
|
1525
|
+
return "unknown", "", "unknown"
|
|
1525
1526
|
|
|
1526
|
-
# Get display category from STATUS_INFO
|
|
1527
|
-
display_status,
|
|
1527
|
+
# Get display category and description template from STATUS_INFO
|
|
1528
|
+
display_status, desc_template = STATUS_INFO.get(last_status, ('unknown', 'unknown'))
|
|
1528
1529
|
|
|
1529
1530
|
# Check timeout
|
|
1530
1531
|
age = now - last_status_time
|
|
1531
1532
|
timeout = pos_data.get('wait_timeout', get_config_value('wait_timeout', 1800))
|
|
1532
1533
|
if age > timeout:
|
|
1533
|
-
return "inactive", ""
|
|
1534
|
+
return "inactive", "", "timeout"
|
|
1535
|
+
|
|
1536
|
+
# Format description with context if template has {}
|
|
1537
|
+
if '{}' in desc_template and last_context:
|
|
1538
|
+
status_desc = desc_template.format(last_context)
|
|
1539
|
+
else:
|
|
1540
|
+
status_desc = desc_template
|
|
1534
1541
|
|
|
1535
1542
|
status_suffix = " (bg)" if pos_data.get('background') else ""
|
|
1536
|
-
return display_status, f"({format_age(age)}){status_suffix}"
|
|
1543
|
+
return display_status, f"({format_age(age)}){status_suffix}", status_desc
|
|
1537
1544
|
|
|
1538
1545
|
def get_status_block(status_type: str) -> str:
|
|
1539
1546
|
"""Get colored status block for a status type"""
|
|
@@ -1607,20 +1614,9 @@ def show_instances_by_directory():
|
|
|
1607
1614
|
for directory, instances in directories.items():
|
|
1608
1615
|
print(f" {directory}")
|
|
1609
1616
|
for instance_name, pos_data in instances:
|
|
1610
|
-
status_type, age = get_instance_status(pos_data)
|
|
1617
|
+
status_type, age, status_desc = get_instance_status(pos_data)
|
|
1611
1618
|
status_block = get_status_block(status_type)
|
|
1612
1619
|
|
|
1613
|
-
# Format status description using STATUS_INFO and context
|
|
1614
|
-
last_status = pos_data.get('last_status', '')
|
|
1615
|
-
last_context = pos_data.get('last_status_context', '')
|
|
1616
|
-
_, desc_template = STATUS_INFO.get(last_status, ('unknown', ''))
|
|
1617
|
-
|
|
1618
|
-
# Format description with context if template has {}
|
|
1619
|
-
if '{}' in desc_template and last_context:
|
|
1620
|
-
status_desc = desc_template.format(last_context)
|
|
1621
|
-
else:
|
|
1622
|
-
status_desc = desc_template
|
|
1623
|
-
|
|
1624
1620
|
print(f" {FG_GREEN}->{RESET} {BOLD}{instance_name}{RESET} {status_block} {DIM}{status_desc} {age}{RESET}")
|
|
1625
1621
|
print()
|
|
1626
1622
|
else:
|
|
@@ -1664,7 +1660,7 @@ def get_status_summary():
|
|
|
1664
1660
|
status_counts = {status: 0 for status in STATUS_MAP.keys()}
|
|
1665
1661
|
|
|
1666
1662
|
for _, pos_data in positions.items():
|
|
1667
|
-
status_type, _ = get_instance_status(pos_data)
|
|
1663
|
+
status_type, _, _ = get_instance_status(pos_data)
|
|
1668
1664
|
if status_type in status_counts:
|
|
1669
1665
|
status_counts[status_type] += 1
|
|
1670
1666
|
|
|
@@ -2209,7 +2205,7 @@ def cmd_watch(*args):
|
|
|
2209
2205
|
status_counts = {}
|
|
2210
2206
|
|
|
2211
2207
|
for name, data in positions.items():
|
|
2212
|
-
status, age = get_instance_status(data)
|
|
2208
|
+
status, age, _ = get_instance_status(data)
|
|
2213
2209
|
instances[name] = {
|
|
2214
2210
|
"status": status,
|
|
2215
2211
|
"age": age.strip() if age else "",
|
|
@@ -2497,7 +2493,7 @@ def cmd_kill(*args):
|
|
|
2497
2493
|
|
|
2498
2494
|
killed_count = 0
|
|
2499
2495
|
for target_name, target_data in targets:
|
|
2500
|
-
status, age = get_instance_status(target_data)
|
|
2496
|
+
status, age, _ = get_instance_status(target_data)
|
|
2501
2497
|
instance_type = "background" if target_data.get('background') else "foreground"
|
|
2502
2498
|
|
|
2503
2499
|
pid = int(target_data['pid'])
|
|
@@ -2609,8 +2605,10 @@ def cmd_send(message):
|
|
|
2609
2605
|
try:
|
|
2610
2606
|
positions = load_all_positions()
|
|
2611
2607
|
all_instances = list(positions.keys())
|
|
2608
|
+
sender_name = get_config_value('sender_name', 'bigboss')
|
|
2609
|
+
all_names = all_instances + [sender_name]
|
|
2612
2610
|
unmatched = [m for m in mentions
|
|
2613
|
-
if not any(name.lower().startswith(m.lower()) for name in
|
|
2611
|
+
if not any(name.lower().startswith(m.lower()) for name in all_names)]
|
|
2614
2612
|
if unmatched:
|
|
2615
2613
|
print(f"Note: @{', @'.join(unmatched)} don't match any instances - broadcasting to all", file=sys.stderr)
|
|
2616
2614
|
except Exception:
|
|
@@ -2657,7 +2655,7 @@ def cmd_send(message):
|
|
|
2657
2655
|
def cmd_resume_merge(alias: str) -> int:
|
|
2658
2656
|
"""Resume/merge current instance into an existing instance by alias.
|
|
2659
2657
|
|
|
2660
|
-
INTERNAL COMMAND: Only called via '$HCOM send --resume alias' during implicit resume workflow.
|
|
2658
|
+
INTERNAL COMMAND: Only called via 'eval $HCOM send --resume alias' during implicit resume workflow.
|
|
2661
2659
|
Not meant for direct CLI usage.
|
|
2662
2660
|
"""
|
|
2663
2661
|
# Get current instance name via launch_id mapping (same mechanism as cmd_send)
|
|
@@ -2719,8 +2717,6 @@ def format_hook_messages(messages, instance_name):
|
|
|
2719
2717
|
|
|
2720
2718
|
def init_hook_context(hook_data, hook_type=None):
|
|
2721
2719
|
"""Initialize instance context - shared by post/stop/notify hooks"""
|
|
2722
|
-
import time
|
|
2723
|
-
|
|
2724
2720
|
session_id = hook_data.get('session_id', '')
|
|
2725
2721
|
transcript_path = hook_data.get('transcript_path', '')
|
|
2726
2722
|
prefix = os.environ.get('HCOM_PREFIX')
|
|
@@ -2775,7 +2771,6 @@ def init_hook_context(hook_data, hook_type=None):
|
|
|
2775
2771
|
)
|
|
2776
2772
|
if should_create_instance:
|
|
2777
2773
|
initialize_instance_in_position_file(instance_name, session_id)
|
|
2778
|
-
existing_data = load_instance_position(instance_name) if should_create_instance else {}
|
|
2779
2774
|
|
|
2780
2775
|
# Prepare updates
|
|
2781
2776
|
updates: dict[str, Any] = {
|
|
@@ -2801,7 +2796,7 @@ def init_hook_context(hook_data, hook_type=None):
|
|
|
2801
2796
|
|
|
2802
2797
|
# Return flags indicating resume state
|
|
2803
2798
|
is_resume_match = merged_state is not None
|
|
2804
|
-
return instance_name, updates,
|
|
2799
|
+
return instance_name, updates, is_resume_match, is_new_instance
|
|
2805
2800
|
|
|
2806
2801
|
def handle_pretooluse(hook_data, instance_name, updates):
|
|
2807
2802
|
"""Handle PreToolUse hook - auto-approve HCOM_SEND commands when safe"""
|
|
@@ -2810,17 +2805,15 @@ def handle_pretooluse(hook_data, instance_name, updates):
|
|
|
2810
2805
|
# Non-HCOM_SEND tools: record status (they'll run without permission check)
|
|
2811
2806
|
set_status(instance_name, 'tool_pending', tool_name)
|
|
2812
2807
|
|
|
2813
|
-
import time
|
|
2814
|
-
|
|
2815
2808
|
# Handle HCOM commands in Bash
|
|
2816
2809
|
if tool_name == 'Bash':
|
|
2817
2810
|
command = hook_data.get('tool_input', {}).get('command', '')
|
|
2818
2811
|
script_path = str(Path(__file__).resolve())
|
|
2819
2812
|
|
|
2820
|
-
# === Auto-approve ALL '$HCOM send' commands (including --resume) ===
|
|
2813
|
+
# === Auto-approve ALL 'eval $HCOM send' commands (including --resume) ===
|
|
2821
2814
|
# This includes:
|
|
2822
|
-
# - $HCOM send "message" (normal messaging between instances)
|
|
2823
|
-
# - $HCOM send --resume alias (resume/merge operation)
|
|
2815
|
+
# - eval $HCOM send "message" (normal messaging between instances)
|
|
2816
|
+
# - eval $HCOM send --resume alias (resume/merge operation)
|
|
2824
2817
|
if ('$HCOM send' in command or
|
|
2825
2818
|
'hcom send' in command or
|
|
2826
2819
|
(script_path in command and ' send ' in command)):
|
|
@@ -2845,15 +2838,13 @@ def safe_exit_with_status(instance_name, code=EXIT_SUCCESS):
|
|
|
2845
2838
|
|
|
2846
2839
|
def handle_stop(hook_data, instance_name, updates):
|
|
2847
2840
|
"""Handle Stop hook - poll for messages and deliver"""
|
|
2848
|
-
import time as time_module
|
|
2849
|
-
|
|
2850
2841
|
parent_pid = os.getppid()
|
|
2851
2842
|
log_hook_error(f'stop:entering_stop_hook_now_pid_{os.getpid()}')
|
|
2852
2843
|
log_hook_error(f'stop:entering_stop_hook_now_ppid_{parent_pid}')
|
|
2853
2844
|
|
|
2854
2845
|
|
|
2855
2846
|
try:
|
|
2856
|
-
entry_time =
|
|
2847
|
+
entry_time = time.time()
|
|
2857
2848
|
updates['last_stop'] = entry_time
|
|
2858
2849
|
timeout = get_config_value('wait_timeout', 1800)
|
|
2859
2850
|
updates['wait_timeout'] = timeout
|
|
@@ -2864,30 +2855,29 @@ def handle_stop(hook_data, instance_name, updates):
|
|
|
2864
2855
|
except Exception as e:
|
|
2865
2856
|
log_hook_error(f'stop:update_instance_position({instance_name})', e)
|
|
2866
2857
|
|
|
2867
|
-
start_time =
|
|
2858
|
+
start_time = time.time()
|
|
2868
2859
|
log_hook_error(f'stop:start_time_pid_{os.getpid()}')
|
|
2869
2860
|
|
|
2870
2861
|
try:
|
|
2871
2862
|
loop_count = 0
|
|
2863
|
+
last_heartbeat = start_time
|
|
2872
2864
|
# STEP 4: Actual polling loop - this IS the holding pattern
|
|
2873
|
-
while
|
|
2865
|
+
while time.time() - start_time < timeout:
|
|
2874
2866
|
if loop_count == 0:
|
|
2875
|
-
|
|
2867
|
+
time.sleep(0.1) # Initial wait before first poll
|
|
2876
2868
|
loop_count += 1
|
|
2877
2869
|
|
|
2878
2870
|
# Check if parent is alive
|
|
2879
|
-
if not
|
|
2880
|
-
log_hook_error(f'stop:parent_died_pid_{os.getpid()}')
|
|
2881
|
-
safe_exit_with_status(instance_name, EXIT_SUCCESS)
|
|
2882
|
-
|
|
2883
|
-
parent_alive = is_parent_alive(parent_pid)
|
|
2884
|
-
if not parent_alive:
|
|
2871
|
+
if not is_parent_alive(parent_pid):
|
|
2885
2872
|
log_hook_error(f'stop:parent_not_alive_pid_{os.getpid()}')
|
|
2886
2873
|
safe_exit_with_status(instance_name, EXIT_SUCCESS)
|
|
2887
2874
|
|
|
2888
|
-
#
|
|
2889
|
-
|
|
2890
|
-
|
|
2875
|
+
# Load instance data once per poll (needed for messages and user input check)
|
|
2876
|
+
instance_data = load_instance_position(instance_name)
|
|
2877
|
+
|
|
2878
|
+
# Check if user input is pending - exit cleanly if recent input
|
|
2879
|
+
last_user_input = instance_data.get('last_user_input', 0)
|
|
2880
|
+
if time.time() - last_user_input < 0.2:
|
|
2891
2881
|
log_hook_error(f'stop:user_input_pending_exiting_pid_{os.getpid()}')
|
|
2892
2882
|
safe_exit_with_status(instance_name, EXIT_SUCCESS)
|
|
2893
2883
|
|
|
@@ -2902,14 +2892,16 @@ def handle_stop(hook_data, instance_name, updates):
|
|
|
2902
2892
|
print(json.dumps(output, ensure_ascii=False), file=sys.stderr)
|
|
2903
2893
|
sys.exit(EXIT_BLOCK)
|
|
2904
2894
|
|
|
2905
|
-
# Update heartbeat
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2895
|
+
# Update heartbeat every 5 seconds instead of every poll
|
|
2896
|
+
now = time.time()
|
|
2897
|
+
if now - last_heartbeat >= 5.0:
|
|
2898
|
+
try:
|
|
2899
|
+
update_instance_position(instance_name, {'last_stop': now})
|
|
2900
|
+
last_heartbeat = now
|
|
2901
|
+
except Exception as e:
|
|
2902
|
+
log_hook_error(f'stop:heartbeat_update({instance_name})', e)
|
|
2911
2903
|
|
|
2912
|
-
|
|
2904
|
+
time.sleep(STOP_HOOK_POLL_INTERVAL)
|
|
2913
2905
|
|
|
2914
2906
|
except Exception as loop_e:
|
|
2915
2907
|
# Log polling loop errors but continue to cleanup
|
|
@@ -2931,22 +2923,12 @@ def handle_notify(hook_data, instance_name, updates):
|
|
|
2931
2923
|
|
|
2932
2924
|
def handle_userpromptsubmit(hook_data, instance_name, updates, is_resume_match, is_new_instance):
|
|
2933
2925
|
"""Handle UserPromptSubmit hook - track when user sends messages"""
|
|
2934
|
-
import time as time_module
|
|
2935
|
-
|
|
2936
2926
|
# Update last user input timestamp
|
|
2937
|
-
updates['last_user_input'] =
|
|
2927
|
+
updates['last_user_input'] = time.time()
|
|
2938
2928
|
update_instance_position(instance_name, updates)
|
|
2939
2929
|
|
|
2940
|
-
#
|
|
2941
|
-
|
|
2942
|
-
try:
|
|
2943
|
-
log_hook_error(f'userpromptsubmit:signal_file_touched_pid_{os.getpid()}')
|
|
2944
|
-
signal_file.touch()
|
|
2945
|
-
time_module.sleep(0.15) # Give Stop hook time to detect and exit
|
|
2946
|
-
log_hook_error(f'userpromptsubmit:signal_file_unlinked_pid_{os.getpid()}')
|
|
2947
|
-
signal_file.unlink()
|
|
2948
|
-
except (OSError, PermissionError) as e:
|
|
2949
|
-
log_hook_error(f'userpromptsubmit:signal_file_error', e)
|
|
2930
|
+
# Wait for Stop hook to detect timestamp and exit (prevents api errors / race condition)
|
|
2931
|
+
time.sleep(0.15)
|
|
2950
2932
|
|
|
2951
2933
|
send_cmd = build_send_command('your message')
|
|
2952
2934
|
resume_cmd = send_cmd.replace("'your message'", "--resume your_old_alias")
|
|
@@ -3041,7 +3023,7 @@ def handle_hook(hook_type: str) -> None:
|
|
|
3041
3023
|
session_id_short = hook_data.get('session_id', 'none')[:8] if hook_data.get('session_id') else 'none'
|
|
3042
3024
|
log_hook_error(f'DEBUG: Hook {hook_type} called with session_id={session_id_short}')
|
|
3043
3025
|
|
|
3044
|
-
instance_name, updates,
|
|
3026
|
+
instance_name, updates, is_resume_match, is_new_instance = init_hook_context(hook_data, hook_type)
|
|
3045
3027
|
|
|
3046
3028
|
match hook_type:
|
|
3047
3029
|
case 'pre':
|
|
@@ -3091,7 +3073,7 @@ def main(argv=None):
|
|
|
3091
3073
|
|
|
3092
3074
|
# HIDDEN COMMAND: --resume is only used internally by instances during resume workflow
|
|
3093
3075
|
# Not meant for regular CLI usage. Primary usage:
|
|
3094
|
-
# - From instances: $HCOM send "message" (instances send messages to each other)
|
|
3076
|
+
# - From instances: eval $HCOM send "message" (instances send messages to each other)
|
|
3095
3077
|
# - From CLI: hcom send "message" (user/claude orchestrator sends to instances)
|
|
3096
3078
|
if argv[2] == '--resume':
|
|
3097
3079
|
if len(argv) < 4:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hcom
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: CLI tool for launching multiple Claude Code terminals with interactive subagents, headless persistence, and real-time communication via hooks
|
|
5
5
|
Author: aannoo
|
|
6
6
|
License-Expression: MIT
|
|
@@ -250,7 +250,7 @@ When running `hcom watch`, each instance shows its current state:
|
|
|
250
250
|
|
|
251
251
|
hcom adds hooks to your project directory's `.claude/settings.local.json`:
|
|
252
252
|
|
|
253
|
-
1. **Sending**: Claude agents use
|
|
253
|
+
1. **Sending**: Claude agents use `eval $HCOM send "message"` internally (you use `hcom send` from terminal or dashboard)
|
|
254
254
|
2. **Receiving**: Other Claudes get notified via Stop hook or immediate delivery after sending
|
|
255
255
|
3. **Waiting**: Stop hook keeps Claude in a waiting state for new messages
|
|
256
256
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|