feat: add AI Workspace Menu Bar App design and enhance MCP server with resource definitions and read functionality
This commit is contained in:
@@ -220,6 +220,37 @@ def health_ok(config: dict[str, Any], timeout: float = 1.0) -> tuple[bool | None
|
||||
return None, f"unknown health type: {kind}"
|
||||
|
||||
|
||||
def service_status(profile: str, ref: ServiceRef) -> dict[str, Any]:
|
||||
enabled = ref.config.get("enabled", True)
|
||||
kind = ref.config.get("kind", "process")
|
||||
command = ref.config.get("command") or []
|
||||
pid = read_pid(profile, ref.name) if enabled and kind != "app-launcher" else None
|
||||
running = is_running(pid)
|
||||
ok, detail = health_ok(ref.config) if enabled else (None, "health skipped")
|
||||
if not enabled:
|
||||
label = "disabled"
|
||||
elif kind == "app-launcher":
|
||||
label = "launcher"
|
||||
elif running and ok is not False:
|
||||
label = "running"
|
||||
elif running:
|
||||
label = "unhealthy"
|
||||
elif ok is True:
|
||||
label = "externally running"
|
||||
else:
|
||||
label = "stopped"
|
||||
return {
|
||||
"name": ref.name,
|
||||
"enabled": enabled,
|
||||
"kind": kind,
|
||||
"status": label,
|
||||
"pid": pid,
|
||||
"command": command,
|
||||
"health": {"ok": ok, "detail": detail},
|
||||
"state": read_state(profile, ref.name),
|
||||
}
|
||||
|
||||
|
||||
def wait_for_health(config: dict[str, Any], seconds: float = 8.0) -> tuple[bool | None, str]:
|
||||
deadline = time.time() + seconds
|
||||
last: tuple[bool | None, str] = (None, "no health check")
|
||||
@@ -319,29 +350,17 @@ def stop_service(profile: str, ref: ServiceRef) -> None:
|
||||
|
||||
|
||||
def status_service(profile: str, ref: ServiceRef) -> None:
|
||||
enabled = ref.config.get("enabled", True)
|
||||
kind = ref.config.get("kind", "process")
|
||||
if not enabled:
|
||||
status = service_status(profile, ref)
|
||||
if not status["enabled"]:
|
||||
print(f"{ref.name}: disabled")
|
||||
return
|
||||
if kind == "app-launcher":
|
||||
state = read_state(profile, ref.name)
|
||||
if status["kind"] == "app-launcher":
|
||||
state = status["state"]
|
||||
launched = state.get("launched_at")
|
||||
suffix = f"last launched {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(launched))}" if launched else "not launched by manager"
|
||||
print(f"{ref.name}: launcher ({suffix})")
|
||||
return
|
||||
pid = read_pid(profile, ref.name)
|
||||
running = is_running(pid)
|
||||
ok, detail = health_ok(ref.config)
|
||||
if running and ok is not False:
|
||||
label = "running"
|
||||
elif running:
|
||||
label = "unhealthy"
|
||||
elif ok is True:
|
||||
label = "externally running"
|
||||
else:
|
||||
label = "stopped"
|
||||
print(f"{ref.name}: {label} pid={pid or '-'} ({detail})")
|
||||
print(f"{ref.name}: {status['status']} pid={status['pid'] or '-'} ({status['health']['detail']})")
|
||||
|
||||
|
||||
def tail_log(profile: str, service: str, lines: int) -> None:
|
||||
@@ -354,42 +373,64 @@ def tail_log(profile: str, service: str, lines: int) -> None:
|
||||
print(line)
|
||||
|
||||
|
||||
def run_doctor(profile: str, manifest: dict[str, Any]) -> None:
|
||||
def doctor_report(profile: str, manifest: dict[str, Any]) -> dict[str, Any]:
|
||||
errors = validate_manifest(manifest)
|
||||
service_reports = []
|
||||
for ref in service_items(manifest, include_disabled=True):
|
||||
command = ref.config.get("command") or []
|
||||
first = command[0] if command else ""
|
||||
doctor = ref.config.get("doctor") or {}
|
||||
checks = []
|
||||
for command_name in doctor.get("required_commands") or []:
|
||||
checks.append({"type": "required_command", "name": command_name, "ok": command_exists(command_name)})
|
||||
for command_name in doctor.get("optional_commands") or []:
|
||||
checks.append({"type": "optional_command", "name": command_name, "ok": command_exists(command_name)})
|
||||
for raw_path in doctor.get("required_paths") or []:
|
||||
checks.append({"type": "required_path", "name": str(raw_path), "ok": resolve_workspace_path(str(raw_path)).exists()})
|
||||
for raw_path in doctor.get("optional_paths") or []:
|
||||
checks.append({"type": "optional_path", "name": str(raw_path), "ok": resolve_workspace_path(str(raw_path)).exists()})
|
||||
status = service_status(profile, ref)
|
||||
status["command_ok"] = command_exists(first) if first else False
|
||||
status["checks"] = checks
|
||||
service_reports.append(status)
|
||||
return {
|
||||
"profile": profile,
|
||||
"workspace": str(ROOT),
|
||||
"manifest": str(manifest_path(profile)),
|
||||
"runtime": str(RUNTIME_DIR),
|
||||
"manifest_ok": not errors,
|
||||
"manifest_errors": errors,
|
||||
"services": service_reports,
|
||||
}
|
||||
|
||||
|
||||
def run_doctor(profile: str, manifest: dict[str, Any], json_output: bool = False) -> None:
|
||||
report = doctor_report(profile, manifest)
|
||||
if json_output:
|
||||
print(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True))
|
||||
return
|
||||
print(f"AI Workspace doctor profile={profile}")
|
||||
print(f"workspace: {ROOT}")
|
||||
print(f"manifest: {manifest_path(profile)}")
|
||||
ensure_runtime()
|
||||
print(f"runtime: {RUNTIME_DIR}")
|
||||
errors = validate_manifest(manifest)
|
||||
errors = report["manifest_errors"]
|
||||
if errors:
|
||||
print("manifest: invalid")
|
||||
for error in errors:
|
||||
print(f" ! {error}")
|
||||
else:
|
||||
print("manifest: ok")
|
||||
for ref in service_items(manifest, include_disabled=True):
|
||||
enabled = ref.config.get("enabled", True)
|
||||
command = ref.config.get("command") or []
|
||||
first = command[0] if command else ""
|
||||
command_ok = command_exists(first) if first else False
|
||||
enabled_text = "enabled" if enabled else "disabled"
|
||||
if not enabled:
|
||||
print(f"- {ref.name}: {enabled_text}; command={'ok' if command_ok else 'missing'}; health skipped")
|
||||
for service in report["services"]:
|
||||
enabled_text = "enabled" if service["enabled"] else "disabled"
|
||||
if not service["enabled"]:
|
||||
print(f"- {service['name']}: {enabled_text}; command={'ok' if service['command_ok'] else 'missing'}; health skipped")
|
||||
continue
|
||||
ok, detail = health_ok(ref.config)
|
||||
health_text = detail if ok is not None else "no health check"
|
||||
print(f"- {ref.name}: {enabled_text}; command={'ok' if command_ok else 'missing'}; {health_text}")
|
||||
doctor = ref.config.get("doctor") or {}
|
||||
for command_name in doctor.get("required_commands") or []:
|
||||
print(f" required command {command_name}: {'ok' if command_exists(command_name) else 'missing'}")
|
||||
for command_name in doctor.get("optional_commands") or []:
|
||||
print(f" optional command {command_name}: {'ok' if command_exists(command_name) else 'missing'}")
|
||||
for raw_path in doctor.get("required_paths") or []:
|
||||
path = resolve_workspace_path(str(raw_path))
|
||||
print(f" required path {raw_path}: {'ok' if path.exists() else 'missing'}")
|
||||
for raw_path in doctor.get("optional_paths") or []:
|
||||
path = resolve_workspace_path(str(raw_path))
|
||||
print(f" optional path {raw_path}: {'ok' if path.exists() else 'missing'}")
|
||||
health_text = service["health"]["detail"] if service["health"]["ok"] is not None else "no health check"
|
||||
print(f"- {service['name']}: {enabled_text}; command={'ok' if service['command_ok'] else 'missing'}; {health_text}")
|
||||
for check in service["checks"]:
|
||||
label = check["type"].replace("_", " ")
|
||||
print(f" {label} {check['name']}: {'ok' if check['ok'] else 'missing'}")
|
||||
|
||||
|
||||
def shutil_which(command: str) -> str | None:
|
||||
@@ -408,13 +449,14 @@ def main() -> None:
|
||||
parser.add_argument("--profile", default=os.getenv("AIW_PROJECT_PROFILE", "fidelity"))
|
||||
parser.add_argument("--group", default="", help="Start/stop/status services in a group, e.g. communication or inbox.")
|
||||
parser.add_argument("--lines", type=int, default=80, help="Number of log lines for logs action.")
|
||||
parser.add_argument("--json", action="store_true", help="Emit machine-readable JSON for supported actions such as doctor.")
|
||||
args = parser.parse_args()
|
||||
|
||||
ensure_runtime()
|
||||
manifest = load_manifest(args.profile)
|
||||
|
||||
if args.action == "doctor":
|
||||
run_doctor(args.profile, manifest)
|
||||
run_doctor(args.profile, manifest, json_output=args.json)
|
||||
return
|
||||
|
||||
errors = validate_manifest(manifest)
|
||||
|
||||
Reference in New Issue
Block a user