feat: unified fleet CLI, Main Chat preservation policy, sidechat routing, and file transfers

- Added CHAT_POLICY.md and README.md banner enforcing sidechat-first and file-transfer-first rules.
- Added strict Main Chat block to super dm send and super dm wo with --allow-main-chat override.
- Implemented file transfer staging and metadata registry in super dm send-file and super dm files (with clean subcommand).
- Added full job lifecycle management (show, create, enable, disable, delete, run --follow) to super-cli.py and box-ctl.py.
- Audited all jobs in jobs/*.json and redirected automated dispatches away from Main Chat.
- Hardened chromebox-watchdog.sh with systemd user session environment exports and stale singleton cleanup.
- Added compose_check command choice to muse-chat-api.py.
This commit is contained in:
operator
2026-10-04 16:34:25 +00:00
parent 0b7c75739b
commit 7a35b684c4
12 changed files with 608 additions and 141 deletions
+48 -8
View File
@@ -62,6 +62,12 @@ try:
except ImportError:
HAS_REGISTRY = False
try:
import pipeline_engine
HAS_PIPELINE = True
except ImportError:
HAS_PIPELINE = False
VALID_AGENTS = ["muse", "pip", "646", "opm"]
DEFAULT_PORTS = {"muse": 9410, "pip": 9420, "646": 9430, "opm": 9440}
@@ -384,8 +390,8 @@ def harvest_agent_thread(cdp, agent, thread_info, watermarks, followups, dry_run
}
if not dry_run:
append_jsonl(JOB_LOG, job_record)
# Trigger chain_next if configured
trigger_chain_next(job_id, result_text)
# Trigger pipeline chaining or next job if configured
trigger_chain_next(job_id, result_text, success=not is_fail)
# Check and clear pending follow-ups
clear_matching_followups(followups, agent, thread_id, mid, text, dry_run)
@@ -393,9 +399,8 @@ def harvest_agent_thread(cdp, agent, thread_info, watermarks, followups, dry_run
return new_messages, new_wm, job_results
def trigger_chain_next(job_id, result_text):
"""If the completed job has a chain_next property, dispatch it with context."""
# Job ID format: <name>-<timestamp>-<uuid>
def trigger_chain_next(job_id, result_text, success=True):
"""If the completed job has on_success, on_failure, or chain_next, dispatch downstream."""
parts = job_id.split("-")
if len(parts) < 3:
return
@@ -404,20 +409,55 @@ def trigger_chain_next(job_id, result_text):
if not job_file.exists():
return
# Update pipeline ledger if this job belongs to an active pipeline
pipeline_run_id = None
step_n = 1
if HAS_PIPELINE:
run_entry, step_entry = pipeline_engine.record_step_result(job_id, success, result_text)
if run_entry:
pipeline_run_id = run_entry.get("run_id")
step_n = step_entry.get("step_n", 1) + 1
try:
with open(job_file, "r", encoding="utf-8") as f:
cfg = json.load(f)
chain_next = cfg.get("chain_next")
if chain_next and (JOBS_DIR / f"{chain_next}.json").exists():
next_job = None
if success:
next_job = cfg.get("on_success") or cfg.get("chain_next")
else:
next_job = cfg.get("on_failure")
if next_job and (JOBS_DIR / f"{next_job}.json").exists():
# Inter-step settle delay to prevent browser race conditions
step_delay = int(cfg.get("step_delay", 5))
if step_delay > 0:
time.sleep(step_delay)
env = os.environ.copy()
env["CHAIN_PREV_JOB_ID"] = job_id
env["CHAIN_PREV_RESULT"] = result_text[:1000]
if pipeline_run_id:
env["CHAIN_PIPELINE_RUN_ID"] = pipeline_run_id
env["CHAIN_STEP_N"] = str(step_n)
cmd = [sys.executable, str(DISPATCH_PY), next_job]
if pipeline_run_id:
cmd.extend(["--pipeline-run", pipeline_run_id, "--step-n", str(step_n)])
subprocess.Popen(
[sys.executable, str(DISPATCH_PY), chain_next],
cmd,
env=env,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
else:
# End of chain for this pipeline run
if pipeline_run_id and HAS_PIPELINE:
if success:
pipeline_engine.complete_pipeline(pipeline_run_id)
else:
pipeline_engine.fail_pipeline(pipeline_run_id, "step_failed_without_fallback")
except Exception:
pass