@delorenj/pjangler 1.2.21 → 1.2.25
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.
- package/dist/index.js +1852 -368
- package/dist/mcp-server.js +1782 -296
- package/package.json +14 -3
- package/templates/commonproject/copier.yml +3 -0
- package/templates/commonproject/template/.mise/scripts/provision-bmad-skills.py +480 -0
- package/templates/commonproject/template/.mise/scripts/sync-skills.py +449 -0
- package/templates/commonproject/template/mise.toml.jinja +9 -2
- package/templates/commonproject/tests/test_provision_bmad_skills.py +203 -0
- package/templates/commonproject/tests/test_sync_skills_topology.py +120 -0
- package/templates/hermes-agent/README.md +70 -29
- package/templates/hermes-agent/copier.yml +9 -6
- package/templates/hermes-agent/docs/architecture.md +75 -51
- package/templates/hermes-agent/docs/fleet-control-plane/architecture.md +11 -6
- package/templates/hermes-agent/docs/fleet-control-plane/epics-and-stories.md +2 -2
- package/templates/hermes-agent/docs/fleet-control-plane/prd.md +2 -3
- package/templates/hermes-agent/docs/operations.md +179 -81
- package/templates/hermes-agent/docs/runbooks/runtime-checkpoint-repair.md +31 -171
- package/templates/hermes-agent/docs/sentinel/README.md +10 -10
- package/templates/hermes-agent/docs/sentinel/architecture.md +6 -8
- package/templates/hermes-agent/docs/sentinel/development.md +12 -15
- package/templates/hermes-agent/docs/sentinel.md +1 -1
- package/templates/hermes-agent/install-local.sh +41 -4
- package/templates/hermes-agent/runtime-scaffold/README.md +4 -2
- package/templates/hermes-agent/scripts/degit-runtime.sh +192 -0
- package/templates/hermes-agent/scripts/fleet-prune-debris.sh +128 -0
- package/templates/hermes-agent/scripts/fleet-sync.sh +164 -7
- package/templates/hermes-agent/scripts/hermes-runtime-templatize.py +321 -0
- package/templates/hermes-agent/scripts/migrate-profile-scoped-chat-secrets.py +136 -0
- package/templates/hermes-agent/scripts/momo-unify-agent.py +224 -0
- package/templates/hermes-agent/scripts/repair-runtime-checkpoint.sh +7 -119
- package/templates/hermes-agent/template/.gitignore.jinja +5 -2
- package/templates/hermes-agent/template/.runtime-scaffold/README.md +4 -2
- package/templates/hermes-agent/template/.scripts/00-banner.sh +1 -1
- package/templates/hermes-agent/template/.scripts/01-config.sh +3 -0
- package/templates/hermes-agent/template/.scripts/10-hermes-profile.sh +12 -1
- package/templates/hermes-agent/template/.scripts/20-runtime-repo.sh +55 -129
- package/templates/hermes-agent/template/.scripts/30-telegram.sh +276 -22
- package/templates/hermes-agent/template/.scripts/31-slack.sh +362 -0
- package/templates/hermes-agent/template/.scripts/60-bloodbank.sh +8 -66
- package/templates/hermes-agent/template/.scripts/70-systemd.sh +44 -30
- package/templates/hermes-agent/template/.scripts/80-registry.sh +93 -13
- package/templates/hermes-agent/template/.scripts/99-summary.sh +1 -4
- package/templates/hermes-agent/template/.scripts/_lib.sh +66 -1
- package/templates/hermes-agent/template/.scripts/checkpoint.sh +3 -2
- package/templates/hermes-agent/template/.scripts/config.example.toml +10 -10
- package/templates/hermes-agent/template/.scripts/heartbeat.sh +1 -1
- package/templates/hermes-agent/template/SOUL.md.jinja +2 -2
- package/templates/hermes-agent/template/hermes.jinja +6 -3
- package/templates/hermes-agent/template/role.yaml.jinja +24 -13
- package/templates/hermes-agent/tests/test_bloodbank_consumer_contract.py +429 -130
- package/templates/hermes-agent/tests/test_fleet_upgrade_contract.py +176 -0
- package/templates/hermes-agent/tests/test_launcher_chat_secret_isolation.py +85 -0
- package/templates/hermes-agent/tests/test_local_runtime_provisioning.py +130 -0
- package/templates/hermes-agent/tests/test_operator_docs_local_runtime_contract.py +132 -0
- package/templates/hermes-agent/tests/test_profile_env_detach_contract.py +14 -0
- package/templates/hermes-agent/tests/test_profile_scoped_chat_secrets.py +89 -0
- package/templates/hermes-agent/tests/test_repository_privacy_contract.py +32 -0
- package/templates/hermes-agent/tests/test_slack_provisioning.py +344 -0
- package/templates/hermes-agent/tests/test_telegram_provisioning.py +475 -0
- package/templates/hermes-agent/.codegraph/daemon.pid +0 -6
- package/templates/hermes-agent/.omo/run-continuation/ses_0e5b28303ffeXxL53hZjKggXDW.json +0 -10
- package/templates/hermes-agent/runtime-scaffold/bloodbank-consumer.py +0 -181
- package/templates/hermes-agent/template/.runtime-scaffold/bloodbank-consumer.py +0 -181
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Bloodbank consumer for hermes agent {{agent_id}}.
|
|
4
|
-
|
|
5
|
-
Subscribes to:
|
|
6
|
-
bloodbank.evt.v1.repo.>
|
|
7
|
-
bloodbank.cmd.v1.agent.>
|
|
8
|
-
|
|
9
|
-
The Bloodbank subject identifies the fixed domain/entity/action route. Repo and
|
|
10
|
-
agent identifiers stay in envelope data, so this consumer filters repo events
|
|
11
|
-
by ``data.repo`` and agent commands by ``data.target_agent_id``.
|
|
12
|
-
|
|
13
|
-
For each event/command received, writes it to the agent's notification queue at
|
|
14
|
-
$HERMES_HOME/bloodbank-inbox/<timestamp>.json so the gateway can ingest it on
|
|
15
|
-
the next tick. Reconnects on broker churn; idempotent across restarts.
|
|
16
|
-
|
|
17
|
-
Generated by hermes-agent-template/runtime-scaffold/bloodbank-consumer.py and
|
|
18
|
-
rendered with role-specific values at provision time. Re-render by re-running
|
|
19
|
-
agents/hermes/{{role}}/.scripts/20-runtime-repo.sh — do NOT edit by hand.
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
import asyncio
|
|
23
|
-
import json
|
|
24
|
-
import os
|
|
25
|
-
import pathlib
|
|
26
|
-
import signal
|
|
27
|
-
import sys
|
|
28
|
-
import time
|
|
29
|
-
import uuid
|
|
30
|
-
from datetime import datetime, timezone
|
|
31
|
-
|
|
32
|
-
try:
|
|
33
|
-
import nats
|
|
34
|
-
except ImportError:
|
|
35
|
-
sys.stderr.write("nats-py not installed. install with: pip install nats-py\n")
|
|
36
|
-
sys.exit(2)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
HERMES_HOME = pathlib.Path(os.environ.get("HERMES_HOME", str(pathlib.Path.home() / ".hermes")))
|
|
40
|
-
INBOX_DIR = HERMES_HOME / "bloodbank-inbox"
|
|
41
|
-
INBOX_DIR.mkdir(parents=True, exist_ok=True)
|
|
42
|
-
|
|
43
|
-
NATS_HOST = os.environ.get("BLOODBANK_NATS_HOST", "127.0.0.1")
|
|
44
|
-
NATS_PORT = int(os.environ.get("BLOODBANK_NATS_PORT", "4222"))
|
|
45
|
-
NATS_URL = f"nats://{NATS_HOST}:{NATS_PORT}"
|
|
46
|
-
|
|
47
|
-
AGENT_ID = "{{agent_id}}"
|
|
48
|
-
REPO = "{{repo}}"
|
|
49
|
-
ROLE = "{{role}}"
|
|
50
|
-
PRODUCER = f"hermes-agent:{AGENT_ID}"
|
|
51
|
-
SOURCE = f"hermes://agent/{AGENT_ID}"
|
|
52
|
-
|
|
53
|
-
SUBJECTS = [
|
|
54
|
-
"bloodbank.evt.v1.repo.>",
|
|
55
|
-
"bloodbank.cmd.v1.agent.>",
|
|
56
|
-
]
|
|
57
|
-
|
|
58
|
-
KIND_MARKERS = {"event": "evt", "command": "cmd", "reply": "rpy"}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _now():
|
|
62
|
-
return datetime.now(timezone.utc).isoformat()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def _subject_and_domain(ce_type, kind):
|
|
66
|
-
parts = ce_type.split(".")
|
|
67
|
-
if len(parts) != 5 or parts[0] != "bloodbank" or parts[1] != "v1":
|
|
68
|
-
raise ValueError(f"type {ce_type!r} must match bloodbank.v1.<domain>.<entity>.<action>")
|
|
69
|
-
if kind not in KIND_MARKERS:
|
|
70
|
-
raise ValueError(f"unknown envelope kind {kind!r}")
|
|
71
|
-
_vendor, version, domain, entity, action = parts
|
|
72
|
-
return f"bloodbank.{KIND_MARKERS[kind]}.{version}.{domain}.{entity}.{action}", domain
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def build_envelope(ce_type, data, *, kind="event", correlationid=None, causationid=None):
|
|
76
|
-
"""Mirror of bloodbank/services/agent-hooks/core/envelope.build_envelope."""
|
|
77
|
-
subject, domain = _subject_and_domain(ce_type, kind)
|
|
78
|
-
env = {
|
|
79
|
-
"specversion": "1.0",
|
|
80
|
-
"type": ce_type,
|
|
81
|
-
"id": str(uuid.uuid4()),
|
|
82
|
-
"source": SOURCE,
|
|
83
|
-
"subject": subject,
|
|
84
|
-
"time": _now(),
|
|
85
|
-
"datacontenttype": "application/json",
|
|
86
|
-
"kind": kind,
|
|
87
|
-
"producer": PRODUCER,
|
|
88
|
-
"service": "hermes-agent",
|
|
89
|
-
"domain": domain,
|
|
90
|
-
"actor": {"type": "agent", "agent_id": AGENT_ID, "cli": "hermes"},
|
|
91
|
-
"data": data,
|
|
92
|
-
"correlationid": correlationid or str(uuid.uuid4()),
|
|
93
|
-
"causationid": causationid or str(uuid.uuid4()),
|
|
94
|
-
}
|
|
95
|
-
if kind == "event":
|
|
96
|
-
env["ordering_key"] = f"agent:{AGENT_ID}"
|
|
97
|
-
return env
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def _is_for_consumer(subject, payload):
|
|
101
|
-
"""Route canonical subjects using identifiers carried in envelope data."""
|
|
102
|
-
if not isinstance(payload, dict):
|
|
103
|
-
return False
|
|
104
|
-
parts = subject.split(".")
|
|
105
|
-
if len(parts) != 6 or parts[0] != "bloodbank" or parts[2] != "v1":
|
|
106
|
-
return False
|
|
107
|
-
|
|
108
|
-
_vendor, kind, version, domain, entity, action = parts
|
|
109
|
-
if kind not in KIND_MARKERS.values():
|
|
110
|
-
return False
|
|
111
|
-
if KIND_MARKERS.get(payload.get("kind")) != kind:
|
|
112
|
-
return False
|
|
113
|
-
if payload.get("subject") != subject:
|
|
114
|
-
return False
|
|
115
|
-
if payload.get("type") != f"bloodbank.{version}.{domain}.{entity}.{action}":
|
|
116
|
-
return False
|
|
117
|
-
data = payload.get("data")
|
|
118
|
-
if not isinstance(data, dict):
|
|
119
|
-
return False
|
|
120
|
-
if kind == "evt" and domain == "repo":
|
|
121
|
-
return data.get("repo") == REPO
|
|
122
|
-
if kind == "cmd" and domain == "agent":
|
|
123
|
-
return data.get("target_agent_id") == AGENT_ID
|
|
124
|
-
return False
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
async def main():
|
|
128
|
-
nc = await nats.connect(NATS_URL, name=f"hermes-{AGENT_ID}")
|
|
129
|
-
stop = asyncio.Event()
|
|
130
|
-
|
|
131
|
-
async def on_msg(msg):
|
|
132
|
-
try:
|
|
133
|
-
payload = json.loads(msg.data.decode())
|
|
134
|
-
except Exception:
|
|
135
|
-
sys.stderr.write(f"[bloodbank-consumer:{AGENT_ID}] skipped unroutable payload on {msg.subject}\n")
|
|
136
|
-
return
|
|
137
|
-
if not _is_for_consumer(msg.subject, payload):
|
|
138
|
-
return
|
|
139
|
-
entry = {
|
|
140
|
-
"received_at": _now(),
|
|
141
|
-
"subject": msg.subject,
|
|
142
|
-
"reply_to": msg.reply,
|
|
143
|
-
"payload": payload,
|
|
144
|
-
}
|
|
145
|
-
fname = f"{int(time.time()*1000):013d}-{uuid.uuid4().hex[:8]}.json"
|
|
146
|
-
tmp = INBOX_DIR / (fname + ".tmp")
|
|
147
|
-
tmp.write_text(json.dumps(entry, indent=2))
|
|
148
|
-
tmp.rename(INBOX_DIR / fname)
|
|
149
|
-
sys.stderr.write(f"[bloodbank-consumer:{AGENT_ID}] {msg.subject} -> {fname}\n")
|
|
150
|
-
|
|
151
|
-
for subj in SUBJECTS:
|
|
152
|
-
await nc.subscribe(subj, cb=on_msg)
|
|
153
|
-
sys.stderr.write(f"[bloodbank-consumer:{AGENT_ID}] subscribed: {subj}\n")
|
|
154
|
-
|
|
155
|
-
# Announce presence
|
|
156
|
-
online = build_envelope(
|
|
157
|
-
"bloodbank.v1.agent.online.changed",
|
|
158
|
-
{"agent_id": AGENT_ID, "repo": REPO, "role": ROLE, "state": "online"},
|
|
159
|
-
)
|
|
160
|
-
await nc.publish(online["subject"], json.dumps(online).encode())
|
|
161
|
-
|
|
162
|
-
def _shutdown(*_):
|
|
163
|
-
stop.set()
|
|
164
|
-
signal.signal(signal.SIGTERM, _shutdown)
|
|
165
|
-
signal.signal(signal.SIGINT, _shutdown)
|
|
166
|
-
|
|
167
|
-
await stop.wait()
|
|
168
|
-
|
|
169
|
-
offline = build_envelope(
|
|
170
|
-
"bloodbank.v1.agent.online.changed",
|
|
171
|
-
{"agent_id": AGENT_ID, "repo": REPO, "role": ROLE, "state": "offline"},
|
|
172
|
-
)
|
|
173
|
-
await nc.publish(offline["subject"], json.dumps(offline).encode())
|
|
174
|
-
await nc.drain()
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if __name__ == "__main__":
|
|
178
|
-
try:
|
|
179
|
-
asyncio.run(main())
|
|
180
|
-
except KeyboardInterrupt:
|
|
181
|
-
pass
|