feat: add psagentai agent with deployment and configuration

Initial project setup including the main agent script, configuration
files, profile JSON state, systemd service unit, startup script,
deployment documentation, and Python dependencies.

Includes .env, venv/, and __pycache__/ which should likely be
excluded via .gitignore to avoid committing secrets and artifacts.
This commit is contained in:
2026-10-25 17:05:13 -03:00
commit 6b3b6541a7
3792 changed files with 805563 additions and 0 deletions
@@ -0,0 +1,153 @@
"""Relationship-related objects."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, cast
from docx.opc.oxml import CT_Relationships
if TYPE_CHECKING:
from docx.opc.part import Part
class Relationships(Dict[str, "_Relationship"]):
"""Collection object for |_Relationship| instances, having list semantics."""
def __init__(self, baseURI: str):
super(Relationships, self).__init__()
self._baseURI = baseURI
self._target_parts_by_rId: dict[str, Any] = {}
def add_relationship(
self, reltype: str, target: Part | str, rId: str, is_external: bool = False
) -> "_Relationship":
"""Return a newly added |_Relationship| instance."""
rel = _Relationship(rId, reltype, target, self._baseURI, is_external)
self[rId] = rel
if not is_external:
self._target_parts_by_rId[rId] = target
return rel
def get_or_add(self, reltype: str, target_part: Part) -> _Relationship:
"""Return relationship of `reltype` to `target_part`, newly added if not already
present in collection."""
rel = self._get_matching(reltype, target_part)
if rel is None:
rId = self._next_rId
rel = self.add_relationship(reltype, target_part, rId)
return rel
def get_or_add_ext_rel(self, reltype: str, target_ref: str) -> str:
"""Return rId of external relationship of `reltype` to `target_ref`, newly added
if not already present in collection."""
rel = self._get_matching(reltype, target_ref, is_external=True)
if rel is None:
rId = self._next_rId
rel = self.add_relationship(reltype, target_ref, rId, is_external=True)
return rel.rId
def part_with_reltype(self, reltype: str) -> Part:
"""Return target part of rel with matching `reltype`, raising |KeyError| if not
found and |ValueError| if more than one matching relationship is found."""
rel = self._get_rel_of_type(reltype)
return rel.target_part
@property
def related_parts(self):
"""Dict mapping rIds to target parts for all the internal relationships in the
collection."""
return self._target_parts_by_rId
@property
def xml(self) -> str:
"""Serialize this relationship collection into XML suitable for storage as a
.rels file in an OPC package."""
rels_elm = CT_Relationships.new()
for rel in self.values():
rels_elm.add_rel(rel.rId, rel.reltype, rel.target_ref, rel.is_external)
return rels_elm.xml
def _get_matching(
self, reltype: str, target: Part | str, is_external: bool = False
) -> _Relationship | None:
"""Return relationship of matching `reltype`, `target`, and `is_external` from
collection, or None if not found."""
def matches(rel: _Relationship, reltype: str, target: Part | str, is_external: bool):
if rel.reltype != reltype:
return False
if rel.is_external != is_external:
return False
rel_target = rel.target_ref if rel.is_external else rel.target_part
return rel_target == target
for rel in self.values():
if matches(rel, reltype, target, is_external):
return rel
return None
def _get_rel_of_type(self, reltype: str):
"""Return single relationship of type `reltype` from the collection.
Raises |KeyError| if no matching relationship is found. Raises |ValueError| if
more than one matching relationship is found.
"""
matching = [rel for rel in self.values() if rel.reltype == reltype]
if len(matching) == 0:
tmpl = "no relationship of type '%s' in collection"
raise KeyError(tmpl % reltype)
if len(matching) > 1:
tmpl = "multiple relationships of type '%s' in collection"
raise ValueError(tmpl % reltype)
return matching[0]
@property
def _next_rId(self) -> str: # pyright: ignore[reportReturnType]
"""Next available rId in collection, starting from 'rId1' and making use of any
gaps in numbering, e.g. 'rId2' for rIds ['rId1', 'rId3']."""
for n in range(1, len(self) + 2):
rId_candidate = "rId%d" % n # like 'rId19'
if rId_candidate not in self:
return rId_candidate
class _Relationship:
"""Value object for relationship to part."""
def __init__(
self, rId: str, reltype: str, target: Part | str, baseURI: str, external: bool = False
):
super(_Relationship, self).__init__()
self._rId = rId
self._reltype = reltype
self._target = target
self._baseURI = baseURI
self._is_external = bool(external)
@property
def is_external(self) -> bool:
return self._is_external
@property
def reltype(self) -> str:
return self._reltype
@property
def rId(self) -> str:
return self._rId
@property
def target_part(self) -> Part:
if self._is_external:
raise ValueError(
"target_part property on _Relationship is undefined when target mode is External"
)
return cast("Part", self._target)
@property
def target_ref(self) -> str:
if self._is_external:
return cast(str, self._target)
else:
target = cast("Part", self._target)
return target.partname.relative_ref(self._baseURI)