mirror of
https://github.com/pocketpaw/pocketpaw.git
synced 2026-05-18 16:06:22 +00:00
* feat(fleet): installable bundle runtime + Sales Fleet template
A FleetTemplate is a YAML manifest naming a soul template + pocket +
connector list + scope tags. install_fleet() orchestrates the install
using existing primitives — SoulFactory, ConnectorRegistry, pocket
service — without introducing new runtime concepts. Sales Fleet ships
as the first bundled example.
What landed:
- ee/fleet/models.py
- FleetConnector — name + config + optional flag.
- FleetTemplate — name, display_name, version, soul_template ref,
pocket name + widgets, connector list, scope list, open metadata.
- FleetInstallStep + FleetInstallReport — per-step status
(succeeded/skipped/failed) so partial installs are observable
without re-running the whole pipeline.
- ee/fleet/installer.py
- load_fleet(path_or_name) reads YAML/JSON or resolves a bundled
name (sales-fleet → src/pocketpaw/fleet_templates/sales-fleet.yaml).
- install_fleet(fleet, *, soul_factory, connector_registry,
pocket_creator) — pure orchestrator. Each external dep is
injectable so tests substitute fakes; production callers pass
the real services.
- Each step is wrapped: per-step exceptions are caught + logged
+ marked as failed in the report so install never crashes the
runtime.
- Optional connectors get "skipped" when missing; required get
"failed" so admins see what to fix.
- src/pocketpaw/fleet_templates/sales-fleet.yaml
- Arrow soul + Pipeline pocket + HubSpot + Gong connectors,
scoped org:sales:*. Connectors marked optional so the demo
install works without external API keys.
Tests: 15 new in tests/cloud/test_fleet_installer.py covering:
- YAML + JSON manifest loading + bundled-by-name resolution +
missing-file error
- install_fleet creates soul + pocket + registers connectors
with mocked deps
- Skips pocket cleanly when creator unavailable
- Optional missing connector → skipped, required missing → failed
- Per-step exception is captured in the report
- Returns early on soul creation failure (no orphan pocket)
- Sales Fleet bundled, has Arrow soul + sales scope
- Sales Fleet connectors all optional (demo-friendly)
- Report.succeeded() + failed_steps() helpers
First PR of Move 7 PR-B. PR-C ships the Install Fleet UI.
* style(fleet): ruff auto-fix
* fix(fleet): point PyYAML import error at pocketpaw[soul]
PyYAML is pulled in transitively via pocketpaw[soul] -> soul-protocol[engine].
The error message was pointing at the transitive package, which sent operators
chasing the wrong install command. Point it at the pocketpaw extra that
actually owns the dependency.
---------
Co-authored-by: Prakash-1 <prakash-1@Mac.lan>
65 lines
2.2 KiB
Python
65 lines
2.2 KiB
Python
# ee/fleet/models.py — FleetTemplate manifest + install report types.
|
|
# Created: 2026-04-13 (Move 7 PR-B) — A fleet is a thin orchestration over
|
|
# primitives that already exist (soul template, pocket, connectors, scope).
|
|
# No new runtime concepts; the manifest just names them in one place so a
|
|
# non-technical operator can install the whole bundle in one step.
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class FleetConnector(BaseModel):
|
|
"""One connector to register when the fleet is installed."""
|
|
|
|
name: str
|
|
config: dict[str, Any] = Field(default_factory=dict)
|
|
optional: bool = False # Skip silently if the connector module is missing.
|
|
|
|
|
|
class FleetTemplate(BaseModel):
|
|
"""An installable bundle of soul + pocket + connectors + scopes."""
|
|
|
|
name: str
|
|
display_name: str = ""
|
|
description: str = ""
|
|
version: str = "0.1.0"
|
|
soul_template: str # Bundled soul template name (arrow / flash / cyborg / analyst)
|
|
soul_name: str = "" # Override; defaults to template's name
|
|
pocket_name: str # Pocket created at install time
|
|
pocket_description: str = ""
|
|
pocket_widgets: list[dict[str, Any]] = Field(default_factory=list)
|
|
connectors: list[FleetConnector] = Field(default_factory=list)
|
|
scopes: list[str] = Field(default_factory=list)
|
|
metadata: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
class FleetInstallStep(BaseModel):
|
|
"""One step in the install pipeline. Reports succeeded / skipped / failed
|
|
so the UI can show partial progress without re-running the whole install.
|
|
"""
|
|
|
|
name: str
|
|
status: Literal["succeeded", "skipped", "failed"]
|
|
detail: str = ""
|
|
duration_ms: int = 0
|
|
|
|
|
|
class FleetInstallReport(BaseModel):
|
|
"""Full report of an install run."""
|
|
|
|
fleet: str
|
|
installed_at: datetime = Field(default_factory=datetime.now)
|
|
steps: list[FleetInstallStep] = Field(default_factory=list)
|
|
soul_id: str | None = None
|
|
pocket_id: str | None = None
|
|
|
|
def succeeded(self) -> bool:
|
|
return all(step.status != "failed" for step in self.steps)
|
|
|
|
def failed_steps(self) -> list[FleetInstallStep]:
|
|
return [s for s in self.steps if s.status == "failed"]
|