@delorenj/pjangler 1.4.6 → 1.5.3
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/README.md +41 -596
- package/dist/assets/project-notebook-skill/SHA256SUMS +5 -5
- package/dist/assets/project-notebook-skill/export-manifest.json +5 -5
- package/dist/assets/project-notebook-skill/hooks/session-end.sh +20 -1
- package/dist/assets/project-notebook-skill/hooks/session-start.sh +20 -1
- package/dist/assets/project-notebook-skill/references/configuration.md +2 -0
- package/dist/assets/project-notebook-skill/scripts/project-hooks.py +33 -3
- package/dist/assets/project-notebook-skill/tests/test_project_hooks.py +37 -0
- package/dist/index.js +9385 -18379
- package/dist/mcp-server.js +9432 -17852
- package/dist/notebook/journal-service-main.js +5682 -0
- package/dist/project-registry-service.js +748 -0
- package/dist/prompt.js +2 -1
- package/package.json +15 -10
- package/scripts/install-project-registry.mjs +34 -0
- package/scripts/migrate-project-registry.mjs +117 -0
- package/src/project/registryClient.ts +62 -0
- package/templates/commonproject/copier.yml +16 -19
- package/templates/commonproject/template/.agents/hooks/README.md +10 -4
- package/templates/commonproject/template/.agents/hooks/hermes/hindsight-hook.sh +12 -2
- package/templates/commonproject/template/.agents/hooks/hooks.master.json +10 -10
- package/templates/commonproject/template/.agents/hooks/lib/hook-guard.sh +13 -0
- package/templates/commonproject/template/.agents/hooks/sync.py +66 -17
- package/templates/commonproject/template/.agents/skills.json +0 -1
- package/templates/commonproject/template/.github/workflows/code-review.yml.jinja +7 -1
- package/templates/commonproject/template/.project.json.jinja +1 -1
- package/templates/commonproject/template/mise.toml.jinja +11 -21
- package/contracts/fleet-contract.yaml +0 -584
- package/templates/commonproject/template/.mise/scripts/provision-packs.py +0 -587
- package/templates/commonproject/template/.mise/scripts/sync-skills.py +0 -2183
- package/templates/hermes-agent/copier.yml +0 -186
- package/templates/hermes-agent/template/.gitignore.jinja +0 -11
- package/templates/hermes-agent/template/.runtime-scaffold/.gitattributes +0 -10
- package/templates/hermes-agent/template/.runtime-scaffold/.gitignore.jinja +0 -44
- package/templates/hermes-agent/template/.runtime-scaffold/README.md +0 -56
- package/templates/hermes-agent/template/.runtime-scaffold/decisions/.gitkeep +0 -2
- package/templates/hermes-agent/template/.runtime-scaffold/logs/.gitkeep +0 -1
- package/templates/hermes-agent/template/.runtime-scaffold/memories/MEMORY.md +0 -50
- package/templates/hermes-agent/template/.runtime-scaffold/memories/USER.md +0 -51
- package/templates/hermes-agent/template/.scripts/00-banner.sh +0 -21
- package/templates/hermes-agent/template/.scripts/01-config.sh +0 -60
- package/templates/hermes-agent/template/.scripts/05-fleet-env.sh +0 -42
- package/templates/hermes-agent/template/.scripts/10-hermes-profile.sh +0 -222
- package/templates/hermes-agent/template/.scripts/20-runtime-repo.sh +0 -199
- package/templates/hermes-agent/template/.scripts/30-telegram.sh +0 -363
- package/templates/hermes-agent/template/.scripts/31-slack.sh +0 -457
- package/templates/hermes-agent/template/.scripts/40-plane.sh +0 -60
- package/templates/hermes-agent/template/.scripts/42-ticket-provider.sh +0 -373
- package/templates/hermes-agent/template/.scripts/60-bloodbank.sh +0 -18
- package/templates/hermes-agent/template/.scripts/70-systemd.sh +0 -354
- package/templates/hermes-agent/template/.scripts/80-registry.sh +0 -185
- package/templates/hermes-agent/template/.scripts/99-summary.sh +0 -87
- package/templates/hermes-agent/template/.scripts/_lib.sh +0 -1138
- package/templates/hermes-agent/template/.scripts/channel-transaction.py +0 -2340
- package/templates/hermes-agent/template/.scripts/checkpoint.sh +0 -82
- package/templates/hermes-agent/template/.scripts/config.example.toml +0 -70
- package/templates/hermes-agent/template/.scripts/credential-launch.sh +0 -85
- package/templates/hermes-agent/template/.scripts/heartbeat.sh +0 -283
- package/templates/hermes-agent/template/.scripts/lib/fleet-env.sh +0 -202
- package/templates/hermes-agent/template/.scripts/lib/parse-fleet-env.py +0 -761
- package/templates/hermes-agent/template/.scripts/lib/profile-config-lock.py +0 -182
- package/templates/hermes-agent/template/.scripts/lib/profile-config-seed.py +0 -108
- package/templates/hermes-agent/template/.scripts/lib/ticket-provider.sh +0 -199
- package/templates/hermes-agent/template/.scripts/lib/voice-config.py +0 -546
- package/templates/hermes-agent/template/.scripts/lifecycle.sh +0 -126
- package/templates/hermes-agent/template/.scripts/momo-wip-lock.py +0 -137
- package/templates/hermes-agent/template/.scripts/providers/linear.sh +0 -329
- package/templates/hermes-agent/template/.scripts/providers/plane.sh +0 -671
- package/templates/hermes-agent/template/.scripts/providers/trello.sh +0 -241
- package/templates/hermes-agent/template/.scripts/reporter-watchdog.py +0 -242
- package/templates/hermes-agent/template/.scripts/secret-scan.py +0 -134
- package/templates/hermes-agent/template/.scripts/sentinel/bin/emit-event.py +0 -106
- package/templates/hermes-agent/template/.scripts/sentinel/bin/issue-autonomous-review.sh +0 -339
- package/templates/hermes-agent/template/.scripts/sentinel/bin/issue-close-gate.sh +0 -179
- package/templates/hermes-agent/template/.scripts/sentinel/docs/autonomous-delegated-review.md +0 -162
- package/templates/hermes-agent/template/.scripts/sentinel/docs/bloodbank-events.md +0 -42
- package/templates/hermes-agent/template/.scripts/sentinel/docs/continuous-ticket-orchestration.md +0 -77
- package/templates/hermes-agent/template/.scripts/sentinel.prompt.md.jinja +0 -116
- package/templates/hermes-agent/template/.scripts/store-onepassword-secret.py +0 -260
- package/templates/hermes-agent/template/SOUL.md.jinja +0 -197
- package/templates/hermes-agent/template/hermes.jinja +0 -109
- package/templates/hermes-agent/template/momo.jinja +0 -177
- package/templates/hermes-agent/template/role.yaml.jinja +0 -112
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Capture a BotFather token and wire it into the runtime profile.
|
|
3
|
-
#
|
|
4
|
-
# Telegram credentials are invocation-only inputs. Capture them before _lib.sh
|
|
5
|
-
# sources fleet.env so a token parked in shared fleet state can never provision
|
|
6
|
-
# or be silently reused by a profile. The non-secret allow-list may be shared.
|
|
7
|
-
INVOCATION_TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN-}"
|
|
8
|
-
INVOCATION_TELEGRAM_ALLOWED_USERS="${TELEGRAM_ALLOWED_USERS-}"
|
|
9
|
-
# Invocation credentials may have arrived as exported variables. Remove them
|
|
10
|
-
# before even resolving/sourcing _lib.sh: that path invokes utilities and loads
|
|
11
|
-
# fleet state, and no child involved in setup should inherit a raw token.
|
|
12
|
-
unset TELEGRAM_BOT_TOKEN TELEGRAM_ALLOWED_USERS
|
|
13
|
-
|
|
14
|
-
# Every channel write lands in the host-global profile root that 10-hermes-profile.sh
|
|
15
|
-
# creates. When that step is deferred the root does not exist yet, so there is no
|
|
16
|
-
# honest Telegram state to record -- not even the disabled state, which is itself a
|
|
17
|
-
# write into the profile. Defer with it. This guard must precede _lib.sh because that
|
|
18
|
-
# library creates the role log and reads fleet configuration.
|
|
19
|
-
if [[ "${SKIP_HOST_STATE:-0}" == "1" ]]; then
|
|
20
|
-
printf '%s\n' '[30] telegram — DEFERRED (SKIP_HOST_STATE=1)' >&2
|
|
21
|
-
exit 0
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
# shellcheck source=_lib.sh
|
|
25
|
-
source "$(dirname "$0")/_lib.sh"
|
|
26
|
-
load_role_env
|
|
27
|
-
|
|
28
|
-
TELEGRAM_BOT_TOKEN="$INVOCATION_TELEGRAM_BOT_TOKEN"
|
|
29
|
-
if [[ -n "$INVOCATION_TELEGRAM_ALLOWED_USERS" ]]; then
|
|
30
|
-
TELEGRAM_ALLOWED_USERS="$INVOCATION_TELEGRAM_ALLOWED_USERS"
|
|
31
|
-
else
|
|
32
|
-
TELEGRAM_ALLOWED_USERS="${TELEGRAM_ALLOWED_USERS-}"
|
|
33
|
-
fi
|
|
34
|
-
unset INVOCATION_TELEGRAM_BOT_TOKEN INVOCATION_TELEGRAM_ALLOWED_USERS
|
|
35
|
-
|
|
36
|
-
PROFILE_HOME="$HOME/.hermes/profiles/$PROFILE_NAME"
|
|
37
|
-
profile_root_require_real "$PROFILE_HOME"
|
|
38
|
-
RUNTIME="$ROLE_DIR/runtime"
|
|
39
|
-
ENVF="$RUNTIME/.env"
|
|
40
|
-
mkdir -p "$RUNTIME"
|
|
41
|
-
[[ ! -L "$ENVF" ]] || die "refusing to write Telegram credentials through symlink: $ENVF"
|
|
42
|
-
|
|
43
|
-
telegram_reconcile_existing() {
|
|
44
|
-
# Registry first, then the transaction helper's profile lock. The helper
|
|
45
|
-
# snapshots and validates refs plus role metadata only inside this window.
|
|
46
|
-
local rc=0
|
|
47
|
-
fleet_lock_acquire
|
|
48
|
-
trap 'fleet_lock_release' EXIT
|
|
49
|
-
channel_transaction_telegram_existing "$PROFILE_HOME" "$ENVF" || rc=$?
|
|
50
|
-
fleet_lock_release
|
|
51
|
-
trap - EXIT
|
|
52
|
-
return "$rc"
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
telegram_defer_if_unconfigured() {
|
|
56
|
-
# Prepare and, when already verified, reconcile under one registry-lock
|
|
57
|
-
# window. The helpers independently take the profile lock second.
|
|
58
|
-
local prepare_rc=0 rc=0
|
|
59
|
-
fleet_lock_acquire
|
|
60
|
-
trap 'fleet_lock_release' EXIT
|
|
61
|
-
channel_transaction_telegram_prepare_unconfigured \
|
|
62
|
-
"$PROFILE_HOME" "$ENVF" || prepare_rc=$?
|
|
63
|
-
case "$prepare_rc" in
|
|
64
|
-
0)
|
|
65
|
-
fleet_lock_release
|
|
66
|
-
trap - EXIT
|
|
67
|
-
return 2
|
|
68
|
-
;;
|
|
69
|
-
3)
|
|
70
|
-
channel_transaction_telegram_existing "$PROFILE_HOME" "$ENVF" || rc=$?
|
|
71
|
-
;;
|
|
72
|
-
*)
|
|
73
|
-
die "Telegram deferred-state preparation transaction failed"
|
|
74
|
-
;;
|
|
75
|
-
esac
|
|
76
|
-
case "$rc" in
|
|
77
|
-
0)
|
|
78
|
-
fleet_lock_release
|
|
79
|
-
trap - EXIT
|
|
80
|
-
return 0
|
|
81
|
-
;;
|
|
82
|
-
75)
|
|
83
|
-
die "Telegram 1Password reference validation is temporarily unavailable; preserved existing verified wiring for retry"
|
|
84
|
-
;;
|
|
85
|
-
*)
|
|
86
|
-
die "Telegram verified wiring reconciliation transaction failed"
|
|
87
|
-
;;
|
|
88
|
-
esac
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
telegram_prepare_for_attempt() {
|
|
92
|
-
# A new profile becomes explicitly disabled before remote verification. A
|
|
93
|
-
# verified profile is a no-op so a failed rotation preserves active wiring.
|
|
94
|
-
local rc=0
|
|
95
|
-
fleet_lock_acquire
|
|
96
|
-
trap 'fleet_lock_release' EXIT
|
|
97
|
-
channel_transaction_telegram_prepare_unconfigured \
|
|
98
|
-
"$PROFILE_HOME" "$ENVF" || rc=$?
|
|
99
|
-
fleet_lock_release
|
|
100
|
-
trap - EXIT
|
|
101
|
-
case "$rc" in
|
|
102
|
-
0|3) return 0 ;;
|
|
103
|
-
*) die "Telegram deferred-state preparation transaction failed" ;;
|
|
104
|
-
esac
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
# A valid durable mapping survives a transient 1Password outage. If the caller
|
|
108
|
-
# supplied no rotation value, adopt/revalidate the complete current wiring
|
|
109
|
-
# under registry -> profile locks. No ref or identity snapshot escapes that
|
|
110
|
-
# window.
|
|
111
|
-
if [[ -z "${TELEGRAM_BOT_TOKEN:-}" ]]; then
|
|
112
|
-
telegram_reference_rc=0
|
|
113
|
-
telegram_reconcile_existing || telegram_reference_rc=$?
|
|
114
|
-
case "$telegram_reference_rc" in
|
|
115
|
-
0)
|
|
116
|
-
log "[30] telegram — existing verified 1Password wiring reconciled"
|
|
117
|
-
exit 0
|
|
118
|
-
;;
|
|
119
|
-
2) ;;
|
|
120
|
-
75)
|
|
121
|
-
die "Telegram 1Password reference validation is temporarily unavailable; preserved existing verified wiring for retry"
|
|
122
|
-
;;
|
|
123
|
-
*)
|
|
124
|
-
die "Telegram verified wiring reconciliation transaction failed"
|
|
125
|
-
;;
|
|
126
|
-
esac
|
|
127
|
-
fi
|
|
128
|
-
|
|
129
|
-
# Establish explicit disabled state for a first-time credential attempt. This
|
|
130
|
-
# recheck is serialized with rotations: existing verified wiring is preserved,
|
|
131
|
-
# while an unconfigured profile is disabled before any fallible remote check.
|
|
132
|
-
if [[ -n "${TELEGRAM_BOT_TOKEN:-}" ]]; then
|
|
133
|
-
telegram_prepare_for_attempt
|
|
134
|
-
fi
|
|
135
|
-
|
|
136
|
-
if [[ "${SKIP_TELEGRAM:-0}" == "1" ]]; then
|
|
137
|
-
telegram_defer_rc=0
|
|
138
|
-
telegram_defer_if_unconfigured || telegram_defer_rc=$?
|
|
139
|
-
if [[ $telegram_defer_rc -eq 2 ]]; then
|
|
140
|
-
log "[30] telegram — DEFERRED (SKIP_TELEGRAM=1; no verified 1Password reference)"
|
|
141
|
-
else
|
|
142
|
-
log "[30] telegram — existing verified 1Password wiring reconciled"
|
|
143
|
-
fi
|
|
144
|
-
exit 0
|
|
145
|
-
fi
|
|
146
|
-
|
|
147
|
-
if already_done 30-telegram; then
|
|
148
|
-
log "[30] existing completion marker preserved while Telegram is reconciled"
|
|
149
|
-
fi
|
|
150
|
-
|
|
151
|
-
cat >&2 <<EOF
|
|
152
|
-
|
|
153
|
-
╭─ BotFather steps for @$BOT_HANDLE ─────────────────────────────────────╮
|
|
154
|
-
│ 1. Open Telegram, message @BotFather │
|
|
155
|
-
│ 2. /newbot │
|
|
156
|
-
│ 3. Display name: $DISPLAY_NAME │
|
|
157
|
-
│ 4. Username: $BOT_HANDLE (must end in _bot) │
|
|
158
|
-
│ 5. Copy the HTTP API token from the reply. │
|
|
159
|
-
│ 6. /setjoingroups → Disable │
|
|
160
|
-
│ 7. /setprivacy → Disable │
|
|
161
|
-
╰────────────────────────────────────────────────────────────────────────╯
|
|
162
|
-
|
|
163
|
-
EOF
|
|
164
|
-
|
|
165
|
-
if [[ -z "${TELEGRAM_BOT_TOKEN:-}" && -t 0 ]]; then
|
|
166
|
-
read -r -s -p "Paste the bot token for @$BOT_HANDLE (or empty to skip): " TELEGRAM_BOT_TOKEN
|
|
167
|
-
echo >&2
|
|
168
|
-
fi
|
|
169
|
-
|
|
170
|
-
if [[ -z "${TELEGRAM_BOT_TOKEN:-}" ]]; then
|
|
171
|
-
telegram_defer_rc=0
|
|
172
|
-
telegram_defer_if_unconfigured || telegram_defer_rc=$?
|
|
173
|
-
if [[ $telegram_defer_rc -eq 2 ]]; then
|
|
174
|
-
warn " no token provided; Telegram step deferred"
|
|
175
|
-
warn " re-run later with a profile-dedicated TELEGRAM_BOT_TOKEN invocation"
|
|
176
|
-
else
|
|
177
|
-
log "[30] telegram — existing verified 1Password wiring reconciled"
|
|
178
|
-
fi
|
|
179
|
-
exit 0
|
|
180
|
-
fi
|
|
181
|
-
|
|
182
|
-
# Sanity check
|
|
183
|
-
if [[ ! "$TELEGRAM_BOT_TOKEN" =~ ^[0-9]+:[A-Za-z0-9_-]+$ ]]; then
|
|
184
|
-
die "token doesn't look like a Telegram bot token"
|
|
185
|
-
fi
|
|
186
|
-
log " verifying token..."
|
|
187
|
-
# Keep the credential out of process argv and environment. curl reads its
|
|
188
|
-
# complete request description from an anonymous stdin pipe.
|
|
189
|
-
info="$({
|
|
190
|
-
printf 'url = "https://api.telegram.org/bot%s/getMe"\n' "$TELEGRAM_BOT_TOKEN"
|
|
191
|
-
printf '%s\n' 'fail' 'silent' 'show-error'
|
|
192
|
-
} | curl --config -)" \
|
|
193
|
-
|| die "Telegram getMe request failed"
|
|
194
|
-
identity=$(printf '%s' "$info" | python3 -c '
|
|
195
|
-
import json, sys
|
|
196
|
-
try:
|
|
197
|
-
data = json.load(sys.stdin)
|
|
198
|
-
except Exception:
|
|
199
|
-
raise SystemExit("Telegram getMe returned an invalid response")
|
|
200
|
-
result = data.get("result") or {}
|
|
201
|
-
if not data.get("ok") or not result.get("id") or not result.get("username"):
|
|
202
|
-
raise SystemExit("Telegram rejected the bot token or omitted identity fields")
|
|
203
|
-
print("{}\t{}".format(
|
|
204
|
-
result.get("id"), str(result.get("username")).replace(chr(9), " ").replace(chr(10), " ")
|
|
205
|
-
))
|
|
206
|
-
') || die "Telegram bot identity verification failed"
|
|
207
|
-
IFS=$'\t' read -r bot_id bot_username <<< "$identity"
|
|
208
|
-
log " verified: @$bot_username (id=$bot_id)"
|
|
209
|
-
|
|
210
|
-
# Resolve operator's allowed user id before taking the fleet claim lock.
|
|
211
|
-
if [[ -z "${TELEGRAM_ALLOWED_USERS:-}" && -f "$HOME/.hermes/.env" ]]; then
|
|
212
|
-
TELEGRAM_ALLOWED_USERS=$(grep -E '^[[:space:]]*#?[[:space:]]*TELEGRAM_ALLOWED_USERS=' "$HOME/.hermes/.env" \
|
|
213
|
-
| tail -1 | sed -E 's/^[[:space:]]*#?[[:space:]]*TELEGRAM_ALLOWED_USERS=//; s/^"//; s/"$//')
|
|
214
|
-
fi
|
|
215
|
-
if [[ -z "${TELEGRAM_ALLOWED_USERS:-}" && -t 0 ]]; then
|
|
216
|
-
read -r -p "Your Telegram user id (allow-list for this bot): " TELEGRAM_ALLOWED_USERS
|
|
217
|
-
fi
|
|
218
|
-
if [[ -z "${TELEGRAM_ALLOWED_USERS:-}" ]]; then
|
|
219
|
-
warn " Telegram is wired but denies all inbound users until TELEGRAM_ALLOWED_USERS is set"
|
|
220
|
-
fi
|
|
221
|
-
|
|
222
|
-
# Reject token reuse, token rotation onto an identity owned by another agent,
|
|
223
|
-
# and credentials parked in shared fleet/root env files. The scan, durable
|
|
224
|
-
# identity claim, and profile credential write share one fleet-wide flock.
|
|
225
|
-
fleet_lock_acquire
|
|
226
|
-
trap 'fleet_lock_release' EXIT
|
|
227
|
-
python3 /dev/fd/3 "$REGISTRY_FILE" "$FLEET_ENV" "$ENVF" "$AGENT_ID" "$bot_id" \
|
|
228
|
-
"$ROLE_DIR" "$PROFILE_NAME" "$bot_username" \
|
|
229
|
-
3<<'PYEOF' <<<"$TELEGRAM_BOT_TOKEN"
|
|
230
|
-
import os
|
|
231
|
-
import pathlib
|
|
232
|
-
import re
|
|
233
|
-
import sys
|
|
234
|
-
try:
|
|
235
|
-
import yaml # type: ignore
|
|
236
|
-
except ImportError:
|
|
237
|
-
raise SystemExit("PyYAML is required for Telegram fleet claims")
|
|
238
|
-
|
|
239
|
-
(
|
|
240
|
-
registry_path,
|
|
241
|
-
fleet_path,
|
|
242
|
-
target_path,
|
|
243
|
-
agent_id,
|
|
244
|
-
bot_id,
|
|
245
|
-
role_dir,
|
|
246
|
-
profile_name,
|
|
247
|
-
bot_username,
|
|
248
|
-
) = sys.argv[1:]
|
|
249
|
-
token = sys.stdin.read()
|
|
250
|
-
if token.endswith("\n"):
|
|
251
|
-
token = token[:-1]
|
|
252
|
-
if not token:
|
|
253
|
-
raise SystemExit("Telegram ownership scan received no credential")
|
|
254
|
-
for metadata_path in (pathlib.Path("/proc/self/cmdline"), pathlib.Path("/proc/self/environ")):
|
|
255
|
-
if metadata_path.is_file() and token.encode() in metadata_path.read_bytes():
|
|
256
|
-
raise SystemExit("Telegram ownership scan detected credential exposure in process metadata")
|
|
257
|
-
target = pathlib.Path(target_path).resolve(strict=False)
|
|
258
|
-
|
|
259
|
-
def env_token(path):
|
|
260
|
-
try:
|
|
261
|
-
text = path.read_text(encoding="utf-8")
|
|
262
|
-
except (OSError, UnicodeError):
|
|
263
|
-
return None
|
|
264
|
-
match = re.search(
|
|
265
|
-
r"(?m)^\s*(?:export\s+)?TELEGRAM_BOT_TOKEN\s*=\s*(.*)$", text
|
|
266
|
-
)
|
|
267
|
-
if not match:
|
|
268
|
-
return None
|
|
269
|
-
value = match.group(1).strip()
|
|
270
|
-
if len(value) >= 2 and value[0] == value[-1] and value[0] in "\"'":
|
|
271
|
-
value = value[1:-1]
|
|
272
|
-
return value
|
|
273
|
-
|
|
274
|
-
owners = [("shared fleet environment", pathlib.Path(fleet_path))]
|
|
275
|
-
registry = pathlib.Path(registry_path)
|
|
276
|
-
if registry.is_symlink():
|
|
277
|
-
raise SystemExit(f"refusing to update registry symlink: {registry}")
|
|
278
|
-
data = {"schema_version": 1, "agents": {}}
|
|
279
|
-
if registry.is_file():
|
|
280
|
-
try:
|
|
281
|
-
data = yaml.safe_load(registry.read_text(encoding="utf-8")) or data
|
|
282
|
-
except Exception as exc:
|
|
283
|
-
raise SystemExit(f"cannot safely inspect Telegram ownership registry: {type(exc).__name__}")
|
|
284
|
-
if not isinstance(data, dict) or not isinstance(data.get("agents", {}), dict):
|
|
285
|
-
raise SystemExit("cannot safely inspect Telegram ownership registry: invalid agents mapping")
|
|
286
|
-
agents = data.setdefault("agents", {})
|
|
287
|
-
for other_id, entry in agents.items():
|
|
288
|
-
if other_id == agent_id or not isinstance(entry, dict):
|
|
289
|
-
continue
|
|
290
|
-
telegram = entry.get("telegram") or {}
|
|
291
|
-
if isinstance(telegram, dict) and str(telegram.get("bot_id") or "") == bot_id:
|
|
292
|
-
raise SystemExit(f"Telegram bot identity is already assigned to agent {other_id}")
|
|
293
|
-
other_role_dir = entry.get("role_dir")
|
|
294
|
-
if other_role_dir:
|
|
295
|
-
owners.append((f"agent {other_id}", pathlib.Path(str(other_role_dir)) / "runtime" / ".env"))
|
|
296
|
-
|
|
297
|
-
home_value = os.environ.get("HOME", "")
|
|
298
|
-
if home_value:
|
|
299
|
-
home = pathlib.Path(home_value)
|
|
300
|
-
owners.append(("shared Hermes root", home / ".hermes" / ".env"))
|
|
301
|
-
profiles = home / ".hermes" / "profiles"
|
|
302
|
-
if profiles.is_dir():
|
|
303
|
-
for profile in profiles.iterdir():
|
|
304
|
-
owners.append((f"profile {profile.name}", profile / ".env"))
|
|
305
|
-
|
|
306
|
-
seen = set()
|
|
307
|
-
for owner, path in owners:
|
|
308
|
-
resolved = path.resolve(strict=False)
|
|
309
|
-
if resolved == target or resolved in seen:
|
|
310
|
-
continue
|
|
311
|
-
seen.add(resolved)
|
|
312
|
-
if env_token(path) == token:
|
|
313
|
-
raise SystemExit(f"Telegram bot token is already assigned to {owner}")
|
|
314
|
-
|
|
315
|
-
# This phase is intentionally read-only. The later channel transaction repeats
|
|
316
|
-
# identity-conflict validation under the same lock before committing registry
|
|
317
|
-
# metadata, so a failed vault stage cannot leave a false ownership claim.
|
|
318
|
-
PYEOF
|
|
319
|
-
|
|
320
|
-
# Stage the credential in a new immutable 1Password item. The helper verifies
|
|
321
|
-
# the staged field and returns the immutable item id plus reference; no local
|
|
322
|
-
# ownership/config state has changed yet.
|
|
323
|
-
ONEPASSWORD_ITEM_PREFIX="${HERMES_ONEPASSWORD_ITEM_PREFIX:-$(config_get fleet.onepassword_item_prefix 'hermes-agent')}"
|
|
324
|
-
telegram_stage="$(stage_onepassword_secret \
|
|
325
|
-
"${ONEPASSWORD_ITEM_PREFIX}-${AGENT_ID}-telegram-bot-token" \
|
|
326
|
-
telegram_bot_token "$TELEGRAM_BOT_TOKEN")" \
|
|
327
|
-
|| die "Telegram credential could not be staged and verified in 1Password"
|
|
328
|
-
if [[ "$telegram_stage" != *$'\n'* ]]; then
|
|
329
|
-
die "Telegram credential staging returned an invalid result"
|
|
330
|
-
fi
|
|
331
|
-
telegram_staged_item_id="${telegram_stage%%$'\n'*}"
|
|
332
|
-
telegram_reference="${telegram_stage#*$'\n'}"
|
|
333
|
-
[[ "$telegram_reference" != *$'\n'* ]] \
|
|
334
|
-
|| die "Telegram credential staging returned too many references"
|
|
335
|
-
unset TELEGRAM_BOT_TOKEN
|
|
336
|
-
|
|
337
|
-
# From here until commit, any failure archives the staged item by immutable id
|
|
338
|
-
# and releases the fleet lock. The transaction itself keeps the platform
|
|
339
|
-
# disabled while committing refs, role metadata, runtime policy, and registry;
|
|
340
|
-
# activation and the done marker are its final writes.
|
|
341
|
-
telegram_transaction_exit() {
|
|
342
|
-
local rc=$?
|
|
343
|
-
trap - EXIT
|
|
344
|
-
if [[ -n "${telegram_staged_item_id:-}" ]]; then
|
|
345
|
-
delete_staged_onepassword_item "$telegram_staged_item_id" >/dev/null 2>&1 \
|
|
346
|
-
|| warn " staged Telegram item cleanup failed; archive manually by immutable item id"
|
|
347
|
-
fi
|
|
348
|
-
fleet_lock_release
|
|
349
|
-
exit "$rc"
|
|
350
|
-
}
|
|
351
|
-
trap telegram_transaction_exit EXIT
|
|
352
|
-
|
|
353
|
-
channel_transaction_telegram \
|
|
354
|
-
"$PROFILE_HOME" "$ENVF" "$telegram_reference" \
|
|
355
|
-
"$bot_username" "$bot_id" "${TELEGRAM_ALLOWED_USERS:-}" \
|
|
356
|
-
|| die "Telegram local credential transaction failed"
|
|
357
|
-
|
|
358
|
-
telegram_staged_item_id=""
|
|
359
|
-
|
|
360
|
-
fleet_lock_release
|
|
361
|
-
trap - EXIT
|
|
362
|
-
|
|
363
|
-
log " wired @$bot_username (id=$bot_id) through 1Password reference"
|