@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,354 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Install systemd --user units: profile gateway and fused heartbeat timer
|
|
3
|
-
# (board-reconciliation sentinel pass + gated runtime checkpoint, one tick).
|
|
4
|
-
# shellcheck source=_lib.sh
|
|
5
|
-
source "$(dirname "$0")/_lib.sh"
|
|
6
|
-
load_role_env
|
|
7
|
-
|
|
8
|
-
if [[ "$ROLE" == "reporter" ]]; then
|
|
9
|
-
log "[70] reporter systemd — gateway/consumer intentionally not installed"
|
|
10
|
-
log " use the reporter runtime's explicit install command after credential and policy preflight"
|
|
11
|
-
yaml_upsert_block_value service_state gateway not-applicable
|
|
12
|
-
yaml_upsert_block_value service_state heartbeat not-applicable
|
|
13
|
-
mark_done 70-systemd
|
|
14
|
-
exit 0
|
|
15
|
-
fi
|
|
16
|
-
|
|
17
|
-
# Resolve expected paths without creating directories or querying the user
|
|
18
|
-
# manager. A legacy skip-created marker is complete only when all units exist.
|
|
19
|
-
RUNTIME="$ROLE_DIR/runtime"
|
|
20
|
-
FLEET_HOME="${HERMES_FLEET_HOME:-$HOME/.hermes}"
|
|
21
|
-
PROFILE_HOME="$FLEET_HOME/profiles/${PROFILE_NAME:-$AGENT_ID}"
|
|
22
|
-
REPO_ROOT="$(project_repo_path)" || REPO_ROOT="$ROLE_DIR"
|
|
23
|
-
|
|
24
|
-
SYS_DIR="$HOME/.config/systemd/user"
|
|
25
|
-
GW_UNIT="hermes-${AGENT_ID}-gateway.service"
|
|
26
|
-
HB_SVC="hermes-${AGENT_ID}-heartbeat.service"
|
|
27
|
-
HB_TIMER="hermes-${AGENT_ID}-heartbeat.timer"
|
|
28
|
-
|
|
29
|
-
# Local-only provisioning must not inspect or mutate the host user manager.
|
|
30
|
-
# Honor the explicit skip before creating unit directories or querying legacy
|
|
31
|
-
# unit state; cleanup remains fail-closed whenever systemd management is active.
|
|
32
|
-
if [[ "${SKIP_SYSTEMD:-0}" == "1" ]]; then
|
|
33
|
-
if already_done 70-systemd \
|
|
34
|
-
&& [[ -f "$SYS_DIR/$GW_UNIT" && -f "$SYS_DIR/$HB_SVC" && -f "$SYS_DIR/$HB_TIMER" ]]; then
|
|
35
|
-
log "[70] systemd — SKIPPED; existing complete unit set preserved"
|
|
36
|
-
else
|
|
37
|
-
clear_done 70-systemd
|
|
38
|
-
log "[70] systemd — DEFERRED (completion marker cleared)"
|
|
39
|
-
fi
|
|
40
|
-
exit 0
|
|
41
|
-
fi
|
|
42
|
-
|
|
43
|
-
# Legacy manifests defaulted reconciliation off and had no way to distinguish
|
|
44
|
-
# that default from an operator decision. The new explicit_opt_out sentinel is
|
|
45
|
-
# authoritative: migrate unmarked roles to the operational PM default, while a
|
|
46
|
-
# rendered/operator-recorded opt-out remains checkpoint-only on every rerun.
|
|
47
|
-
if [[ "$(yaml_get reconcile.explicit_opt_out)" == "true" ]]; then
|
|
48
|
-
yaml_upsert_block_value reconcile enabled false bool
|
|
49
|
-
log " PM reconciliation explicit opt-out preserved"
|
|
50
|
-
else
|
|
51
|
-
yaml_upsert_block_value reconcile enabled true bool
|
|
52
|
-
yaml_upsert_block_value reconcile explicit_opt_out false bool
|
|
53
|
-
log " PM reconciliation enabled (operational default)"
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
# Render every caller-controlled systemd scalar through the same data-only
|
|
57
|
-
# serializer used by fleet backfill. This validation happens before mkdir,
|
|
58
|
-
# systemctl, or unit writes, so CR/LF/NUL cannot create a second directive and
|
|
59
|
-
# spaces/quotes/backslashes/specifier bytes remain exact.
|
|
60
|
-
PYTHON_BIN="$(builtin type -P python3)" || die "python3 is unavailable for systemd unit serialization"
|
|
61
|
-
systemd_value() {
|
|
62
|
-
"$PYTHON_BIN" -I "$FLEET_ENV_PARSER" --systemd-value "$1" \
|
|
63
|
-
|| die "systemd value validation failed"
|
|
64
|
-
}
|
|
65
|
-
systemd_scalar() {
|
|
66
|
-
"$PYTHON_BIN" -I "$FLEET_ENV_PARSER" --systemd-scalar "$1" \
|
|
67
|
-
|| die "systemd scalar validation failed"
|
|
68
|
-
}
|
|
69
|
-
systemd_environment() {
|
|
70
|
-
"$PYTHON_BIN" -I "$FLEET_ENV_PARSER" --systemd-environment "$1" "$2" \
|
|
71
|
-
|| die "systemd environment validation failed"
|
|
72
|
-
}
|
|
73
|
-
systemd_exec_value() {
|
|
74
|
-
"$PYTHON_BIN" -I "$FLEET_ENV_PARSER" --systemd-exec-value "$1" \
|
|
75
|
-
|| die "systemd ExecStart value validation failed"
|
|
76
|
-
}
|
|
77
|
-
GW_DESCRIPTION="$(systemd_scalar "Hermes Gateway — $DISPLAY_NAME")"
|
|
78
|
-
HB_DESCRIPTION="$(systemd_scalar "Hermes Heartbeat (reconcile + checkpoint) — $DISPLAY_NAME")"
|
|
79
|
-
TIMER_DESCRIPTION="$(systemd_scalar "Heartbeat (reconcile + checkpoint) for $AGENT_ID")"
|
|
80
|
-
ENV_HERMES_HOME="$(systemd_environment HERMES_HOME "$PROFILE_HOME")"
|
|
81
|
-
ENV_HERMES_BIN="$(systemd_environment HERMES_BIN "$HERMES_BIN")"
|
|
82
|
-
ENV_CODEX_HOME="$(systemd_environment CODEX_HOME "$CODEX_HOME")"
|
|
83
|
-
ENV_TERMINAL_CWD="$(systemd_environment TERMINAL_CWD "$REPO_ROOT")"
|
|
84
|
-
WORKING_DIRECTORY="$(systemd_scalar "$REPO_ROOT")"
|
|
85
|
-
RUNTIME_ENV_FILE="$(systemd_scalar "-$RUNTIME/.env")"
|
|
86
|
-
GW_LOG_OUTPUT="$(systemd_scalar "append:$RUNTIME/logs/gateway.systemd.log")"
|
|
87
|
-
HB_LOG_OUTPUT="$(systemd_scalar "append:$RUNTIME/logs/heartbeat.log")"
|
|
88
|
-
GW_EXEC_START="$(systemd_exec_value "$ROLE_DIR/.scripts/credential-launch.sh")"
|
|
89
|
-
HB_EXEC_START="$GW_EXEC_START"
|
|
90
|
-
|
|
91
|
-
# A model credential may be supplied through systemd's encrypted credential
|
|
92
|
-
# store. Chat-channel values are never materialized here: Hermes resolves their
|
|
93
|
-
# profile-scoped op:// references natively at process startup.
|
|
94
|
-
CREDENTIAL_DIR="${HERMES_SYSTEMD_CREDENTIAL_DIR:-$HOME/.config/hermes-agent/credentials}"
|
|
95
|
-
MODEL_CREDENTIAL="$CREDENTIAL_DIR/${AGENT_ID}-model-api-key.cred"
|
|
96
|
-
GW_CREDENTIAL_LINES=""
|
|
97
|
-
HB_CREDENTIAL_LINES=""
|
|
98
|
-
if [[ -f "$MODEL_CREDENTIAL" ]]; then
|
|
99
|
-
[[ -n "$(yaml_get model.key_env)" ]] \
|
|
100
|
-
|| die "encrypted model credential exists but model.key_env is blank in role.yaml"
|
|
101
|
-
command -v systemd-creds >/dev/null 2>&1 \
|
|
102
|
-
|| die "encrypted model credential exists but systemd-creds is unavailable"
|
|
103
|
-
GW_CREDENTIAL_LINES="${GW_CREDENTIAL_LINES}${GW_CREDENTIAL_LINES:+$'\n'}LoadCredentialEncrypted=$(systemd_value "model_api_key:$MODEL_CREDENTIAL")"
|
|
104
|
-
HB_CREDENTIAL_LINES="LoadCredentialEncrypted=$(systemd_value "model_api_key:$MODEL_CREDENTIAL")"
|
|
105
|
-
fi
|
|
106
|
-
|
|
107
|
-
# A gateway is eligible only when at least one channel identity is verified AND
|
|
108
|
-
# both the named-profile mapping and the referenced 1Password value resolve.
|
|
109
|
-
# Role metadata alone is not sufficient: a stale done marker must never revive
|
|
110
|
-
# a credential-less crash loop.
|
|
111
|
-
gateway_ready=0
|
|
112
|
-
gateway_validation_unavailable=0
|
|
113
|
-
if [[ "$(yaml_get telegram.provisioning_status)" == "verified" ]] \
|
|
114
|
-
&& profile_onepassword_ref_exists "$PROFILE_HOME" TELEGRAM_BOT_TOKEN; then
|
|
115
|
-
telegram_reference_rc=0
|
|
116
|
-
profile_onepassword_ref_validate "$PROFILE_HOME" TELEGRAM_BOT_TOKEN \
|
|
117
|
-
|| telegram_reference_rc=$?
|
|
118
|
-
if [[ $telegram_reference_rc -eq 0 ]]; then
|
|
119
|
-
gateway_ready=1
|
|
120
|
-
elif [[ $telegram_reference_rc -eq 75 ]]; then
|
|
121
|
-
gateway_validation_unavailable=1
|
|
122
|
-
fi
|
|
123
|
-
fi
|
|
124
|
-
if [[ "$(yaml_get slack.provisioning_status)" == "verified" ]] \
|
|
125
|
-
&& profile_onepassword_ref_exists "$PROFILE_HOME" SLACK_BOT_TOKEN \
|
|
126
|
-
&& profile_onepassword_ref_exists "$PROFILE_HOME" SLACK_APP_TOKEN; then
|
|
127
|
-
slack_bot_reference_rc=0
|
|
128
|
-
slack_app_reference_rc=0
|
|
129
|
-
profile_onepassword_ref_validate "$PROFILE_HOME" SLACK_BOT_TOKEN \
|
|
130
|
-
|| slack_bot_reference_rc=$?
|
|
131
|
-
profile_onepassword_ref_validate "$PROFILE_HOME" SLACK_APP_TOKEN \
|
|
132
|
-
|| slack_app_reference_rc=$?
|
|
133
|
-
if [[ $slack_bot_reference_rc -eq 0 && $slack_app_reference_rc -eq 0 ]]; then
|
|
134
|
-
gateway_ready=1
|
|
135
|
-
elif [[ $slack_bot_reference_rc -eq 75 || $slack_app_reference_rc -eq 75 ]]; then
|
|
136
|
-
gateway_validation_unavailable=1
|
|
137
|
-
fi
|
|
138
|
-
fi
|
|
139
|
-
if [[ $gateway_ready -eq 0 && $gateway_validation_unavailable -eq 1 ]]; then
|
|
140
|
-
die "channel 1Password validation is temporarily unavailable; preserving existing gateway state for retry"
|
|
141
|
-
fi
|
|
142
|
-
|
|
143
|
-
# Singleton-runtime contract: units set HERMES_HOME to the agent's NAMED PROFILE
|
|
144
|
-
# dir, never the raw runtime path — Hermes derives profile identity and shared
|
|
145
|
-
# fleet auth from the unresolved HERMES_HOME. $RUNTIME stays correct for
|
|
146
|
-
# EnvironmentFile and logs: those are the repo-owned side the profile links to.
|
|
147
|
-
mkdir -p "$SYS_DIR" "$RUNTIME/logs"
|
|
148
|
-
|
|
149
|
-
# Upgrade remediation must run before honoring a legacy done marker. Older
|
|
150
|
-
# templates installed one per-profile Bloodbank consumer; leaving even one
|
|
151
|
-
# enabled races the fleet-shared durable gateway and can duplicate execution.
|
|
152
|
-
LEGACY_CONSUMER_UNIT="hermes-${AGENT_ID}-consumer.service"
|
|
153
|
-
LEGACY_CONSUMER_PATH="$SYS_DIR/$LEGACY_CONSUMER_UNIT"
|
|
154
|
-
legacy_consumer_present=0
|
|
155
|
-
[[ -e "$LEGACY_CONSUMER_PATH" || -L "$LEGACY_CONSUMER_PATH" ]] \
|
|
156
|
-
&& legacy_consumer_present=1
|
|
157
|
-
if command -v systemctl >/dev/null 2>&1; then
|
|
158
|
-
legacy_active_result="$(systemctl_user_unit_state is-active "$LEGACY_CONSUMER_UNIT")"
|
|
159
|
-
legacy_enabled_result="$(systemctl_user_unit_state is-enabled "$LEGACY_CONSUMER_UNIT")"
|
|
160
|
-
[[ "$legacy_active_result" != error\|* ]] \
|
|
161
|
-
|| die "cannot safely query legacy consumer activity; preserving unit: ${legacy_active_result#*|}"
|
|
162
|
-
[[ "$legacy_enabled_result" != error\|* ]] \
|
|
163
|
-
|| die "cannot safely query legacy consumer enablement; preserving unit: ${legacy_enabled_result#*|}"
|
|
164
|
-
legacy_active_state="${legacy_active_result#*|}"
|
|
165
|
-
legacy_enabled_state="${legacy_enabled_result#*|}"
|
|
166
|
-
[[ "$legacy_active_state" == "not-found" && "$legacy_enabled_state" == "not-found" ]] \
|
|
167
|
-
|| legacy_consumer_present=1
|
|
168
|
-
elif [[ $legacy_consumer_present -eq 1 ]]; then
|
|
169
|
-
die "systemctl is unavailable; cannot safely retire legacy consumer: $LEGACY_CONSUMER_UNIT"
|
|
170
|
-
fi
|
|
171
|
-
if [[ $legacy_consumer_present -eq 1 ]]; then
|
|
172
|
-
systemctl --user disable --now "$LEGACY_CONSUMER_UNIT" >/dev/null 2>&1 \
|
|
173
|
-
|| die "legacy consumer disable failed; preserving unit: $LEGACY_CONSUMER_UNIT"
|
|
174
|
-
legacy_active_result="$(systemctl_user_unit_state is-active "$LEGACY_CONSUMER_UNIT")"
|
|
175
|
-
legacy_enabled_result="$(systemctl_user_unit_state is-enabled "$LEGACY_CONSUMER_UNIT")"
|
|
176
|
-
[[ "$legacy_active_result" == "ok|inactive" ]] \
|
|
177
|
-
|| die "legacy consumer is not proven inactive; preserving unit: ${legacy_active_result#*|}"
|
|
178
|
-
[[ "$legacy_enabled_result" == "ok|disabled" ]] \
|
|
179
|
-
|| die "legacy consumer is not proven disabled; preserving unit: ${legacy_enabled_result#*|}"
|
|
180
|
-
rm -f -- "$LEGACY_CONSUMER_PATH"
|
|
181
|
-
if systemd_user_available; then
|
|
182
|
-
systemctl --user daemon-reload >/dev/null 2>&1 \
|
|
183
|
-
|| warn " systemd daemon-reload failed after legacy consumer retirement"
|
|
184
|
-
fi
|
|
185
|
-
log " retired legacy per-profile Bloodbank consumer: $LEGACY_CONSUMER_UNIT"
|
|
186
|
-
fi
|
|
187
|
-
|
|
188
|
-
if already_done 70-systemd; then
|
|
189
|
-
if [[ -f "$SYS_DIR/$GW_UNIT" && -f "$SYS_DIR/$HB_SVC" && -f "$SYS_DIR/$HB_TIMER" ]]; then
|
|
190
|
-
log "[70] systemd already installed — reconciling unit definitions"
|
|
191
|
-
else
|
|
192
|
-
clear_done 70-systemd
|
|
193
|
-
log " stale/incomplete systemd marker cleared — reconciling required units"
|
|
194
|
-
fi
|
|
195
|
-
fi
|
|
196
|
-
|
|
197
|
-
# The heartbeat runner (board-reconciliation sentinel pass + gated checkpoint)
|
|
198
|
-
# and the checkpoint helper both render into the role dir; just ensure they are
|
|
199
|
-
# executable. heartbeat.sh calls checkpoint.sh internally.
|
|
200
|
-
HEARTBEAT_BIN="$ROLE_DIR/.scripts/heartbeat.sh"
|
|
201
|
-
CREDENTIAL_LAUNCHER="$ROLE_DIR/.scripts/credential-launch.sh"
|
|
202
|
-
chmod +x "$HEARTBEAT_BIN" "$CREDENTIAL_LAUNCHER" "$ROLE_DIR/.scripts/checkpoint.sh" 2>/dev/null || true
|
|
203
|
-
|
|
204
|
-
[[ -d "$PROFILE_HOME" && ! -L "$PROFILE_HOME" ]] \
|
|
205
|
-
|| die "named profile is not a real directory; run: pj migrate hermes.runtime-singleton '$REPO_ROOT'"
|
|
206
|
-
|
|
207
|
-
# Gateway unit
|
|
208
|
-
#
|
|
209
|
-
# StartLimit* belongs in [Unit], not [Service] — systemd only still parses it
|
|
210
|
-
# under [Service] for backwards compatibility. Without it, Restart=on-failure
|
|
211
|
-
# retries forever and a gateway that can never start (bad token, bad config)
|
|
212
|
-
# sits in `activating` indefinitely instead of settling into `failed`, so
|
|
213
|
-
# `systemctl --user --failed` never lists it and neither the sentinel nor a
|
|
214
|
-
# human ever sees the crashloop. That is exactly how the fleet accumulated
|
|
215
|
-
# 10,427 invisible restarts. 5 tries in 5 minutes, then stop and report failed.
|
|
216
|
-
cat > "$SYS_DIR/$GW_UNIT" <<UNIT
|
|
217
|
-
[Unit]
|
|
218
|
-
Description=$GW_DESCRIPTION
|
|
219
|
-
After=network-online.target
|
|
220
|
-
Wants=network-online.target
|
|
221
|
-
StartLimitIntervalSec=300
|
|
222
|
-
StartLimitBurst=5
|
|
223
|
-
|
|
224
|
-
[Service]
|
|
225
|
-
Type=simple
|
|
226
|
-
$ENV_HERMES_HOME
|
|
227
|
-
$ENV_HERMES_BIN
|
|
228
|
-
$ENV_CODEX_HOME
|
|
229
|
-
$ENV_TERMINAL_CWD
|
|
230
|
-
EnvironmentFile=$RUNTIME_ENV_FILE
|
|
231
|
-
$GW_CREDENTIAL_LINES
|
|
232
|
-
ExecStart=$GW_EXEC_START gateway
|
|
233
|
-
Restart=on-failure
|
|
234
|
-
RestartSec=10
|
|
235
|
-
StandardOutput=$GW_LOG_OUTPUT
|
|
236
|
-
StandardError=$GW_LOG_OUTPUT
|
|
237
|
-
|
|
238
|
-
[Install]
|
|
239
|
-
WantedBy=default.target
|
|
240
|
-
UNIT
|
|
241
|
-
|
|
242
|
-
# Fused heartbeat: board-reconciliation sentinel pass + gated runtime checkpoint.
|
|
243
|
-
# Frequent ticks (1 min); heartbeat.sh's own cooldown/lock logic rate-limits the
|
|
244
|
-
# full Hermes pass, and the checkpoint is gated to ~hourly inside the runner.
|
|
245
|
-
# The per-agent EnvironmentFiles load ticket-provider creds for the sentinel pass.
|
|
246
|
-
cat > "$SYS_DIR/$HB_SVC" <<UNIT
|
|
247
|
-
[Unit]
|
|
248
|
-
Description=$HB_DESCRIPTION
|
|
249
|
-
After=network-online.target
|
|
250
|
-
Wants=network-online.target
|
|
251
|
-
|
|
252
|
-
[Service]
|
|
253
|
-
Type=oneshot
|
|
254
|
-
WorkingDirectory=$WORKING_DIRECTORY
|
|
255
|
-
$ENV_HERMES_HOME
|
|
256
|
-
$ENV_HERMES_BIN
|
|
257
|
-
$ENV_CODEX_HOME
|
|
258
|
-
$ENV_TERMINAL_CWD
|
|
259
|
-
EnvironmentFile=-%h/.config/hermes-agent/env
|
|
260
|
-
EnvironmentFile=-%h/.hermes/env
|
|
261
|
-
EnvironmentFile=-%h/.hermes/hermes-agent.env
|
|
262
|
-
EnvironmentFile=-%h/.hermes/${AGENT_ID}.env
|
|
263
|
-
EnvironmentFile=$RUNTIME_ENV_FILE
|
|
264
|
-
$HB_CREDENTIAL_LINES
|
|
265
|
-
ExecStart=$HB_EXEC_START heartbeat
|
|
266
|
-
TimeoutStartSec=45min
|
|
267
|
-
StandardOutput=$HB_LOG_OUTPUT
|
|
268
|
-
StandardError=$HB_LOG_OUTPUT
|
|
269
|
-
UNIT
|
|
270
|
-
cat > "$SYS_DIR/$HB_TIMER" <<UNIT
|
|
271
|
-
[Unit]
|
|
272
|
-
Description=$TIMER_DESCRIPTION
|
|
273
|
-
|
|
274
|
-
[Timer]
|
|
275
|
-
OnBootSec=1min
|
|
276
|
-
OnUnitInactiveSec=1min
|
|
277
|
-
Unit=$HB_SVC
|
|
278
|
-
Persistent=true
|
|
279
|
-
|
|
280
|
-
[Install]
|
|
281
|
-
WantedBy=timers.target
|
|
282
|
-
UNIT
|
|
283
|
-
|
|
284
|
-
if systemd_user_available; then
|
|
285
|
-
systemctl --user daemon-reload
|
|
286
|
-
if systemctl --user enable --now "$HB_TIMER" >/dev/null 2>&1; then
|
|
287
|
-
# The timer's first scheduled tick can be a minute away. Run the oneshot
|
|
288
|
-
# once now so deployment proves the heartbeat command itself completed;
|
|
289
|
-
# timer activity alone is not an operational postcondition.
|
|
290
|
-
if ! systemctl --user start "$HB_SVC" >/dev/null 2>&1; then
|
|
291
|
-
systemctl --user disable --now "$HB_TIMER" >/dev/null 2>&1 || true
|
|
292
|
-
yaml_upsert_block_value service_state heartbeat error
|
|
293
|
-
clear_done 70-systemd
|
|
294
|
-
die "required heartbeat oneshot failed its deployment probe: $HB_SVC"
|
|
295
|
-
fi
|
|
296
|
-
if hb_health="$(systemd_wait_for_stable_health \
|
|
297
|
-
systemd_timer_health_snapshot "$HB_TIMER" "$HB_SVC")"; then
|
|
298
|
-
yaml_upsert_block_value service_state heartbeat active
|
|
299
|
-
log " heartbeat enabled + active with healthy latest result: $HB_TIMER"
|
|
300
|
-
else
|
|
301
|
-
systemctl --user disable --now "$HB_TIMER" >/dev/null 2>&1 || true
|
|
302
|
-
yaml_upsert_block_value service_state heartbeat error
|
|
303
|
-
clear_done 70-systemd
|
|
304
|
-
die "heartbeat did not stabilize healthy: $hb_health"
|
|
305
|
-
fi
|
|
306
|
-
else
|
|
307
|
-
yaml_upsert_block_value service_state heartbeat error
|
|
308
|
-
clear_done 70-systemd
|
|
309
|
-
die "failed to enable/start required heartbeat timer: $HB_TIMER"
|
|
310
|
-
fi
|
|
311
|
-
|
|
312
|
-
if [[ $gateway_ready -eq 1 ]]; then
|
|
313
|
-
if systemctl --user enable --now "$GW_UNIT" >/dev/null 2>&1; then
|
|
314
|
-
if gw_health="$(systemd_wait_for_stable_health \
|
|
315
|
-
systemd_service_health_snapshot "$GW_UNIT" running)"; then
|
|
316
|
-
yaml_upsert_block_value service_state gateway active
|
|
317
|
-
log " credentialed gateway enabled + active and stabilized: $GW_UNIT"
|
|
318
|
-
else
|
|
319
|
-
systemctl --user disable --now "$GW_UNIT" >/dev/null 2>&1 || true
|
|
320
|
-
systemctl --user reset-failed "$GW_UNIT" >/dev/null 2>&1 || true
|
|
321
|
-
yaml_upsert_block_value service_state gateway error
|
|
322
|
-
clear_done 70-systemd
|
|
323
|
-
die "credentialed gateway did not stabilize healthy: $gw_health"
|
|
324
|
-
fi
|
|
325
|
-
else
|
|
326
|
-
yaml_upsert_block_value service_state gateway error
|
|
327
|
-
clear_done 70-systemd
|
|
328
|
-
die "failed to enable/start credentialed gateway: $GW_UNIT"
|
|
329
|
-
fi
|
|
330
|
-
else
|
|
331
|
-
systemctl --user disable --now "$GW_UNIT" >/dev/null 2>&1 \
|
|
332
|
-
|| die "could not enforce deferred gateway disablement: $GW_UNIT"
|
|
333
|
-
systemctl --user reset-failed "$GW_UNIT" >/dev/null 2>&1 || true
|
|
334
|
-
gw_deferred_health="$(systemd_gateway_deferred_snapshot "$GW_UNIT")"
|
|
335
|
-
if [[ "$gw_deferred_health" == "ok|deferred" ]]; then
|
|
336
|
-
yaml_upsert_block_value service_state gateway deferred
|
|
337
|
-
log " gateway deferred: disabled + inactive until a channel credential is verified"
|
|
338
|
-
else
|
|
339
|
-
yaml_upsert_block_value service_state gateway error
|
|
340
|
-
clear_done 70-systemd
|
|
341
|
-
die "gateway deferral was not proven disabled + inactive ($gw_deferred_health)"
|
|
342
|
-
fi
|
|
343
|
-
fi
|
|
344
|
-
else
|
|
345
|
-
warn " systemd --user not available; units installed at $SYS_DIR but not enabled"
|
|
346
|
-
yaml_upsert_block_value service_state heartbeat installed
|
|
347
|
-
if [[ $gateway_ready -eq 1 ]]; then
|
|
348
|
-
yaml_upsert_block_value service_state gateway installed
|
|
349
|
-
else
|
|
350
|
-
yaml_upsert_block_value service_state gateway deferred
|
|
351
|
-
fi
|
|
352
|
-
fi
|
|
353
|
-
|
|
354
|
-
mark_done 70-systemd
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Append this agent's entry to the global fleet registry.
|
|
3
|
-
|
|
4
|
-
if [[ "${SKIP_HOST_STATE:-0}" == "1" ]]; then
|
|
5
|
-
printf '%s\n' '[80] fleet registry — DEFERRED (SKIP_HOST_STATE=1)' >&2
|
|
6
|
-
exit 0
|
|
7
|
-
fi
|
|
8
|
-
|
|
9
|
-
# shellcheck source=_lib.sh
|
|
10
|
-
source "$(dirname "$0")/_lib.sh"
|
|
11
|
-
load_role_env
|
|
12
|
-
|
|
13
|
-
fleet_lock_acquire
|
|
14
|
-
trap 'fleet_lock_release' EXIT
|
|
15
|
-
|
|
16
|
-
[[ ! -L "$REGISTRY_FILE" ]] || die "refusing to update registry symlink: $REGISTRY_FILE"
|
|
17
|
-
mkdir -p "$(dirname "$REGISTRY_FILE")"
|
|
18
|
-
if [[ ! -f "$REGISTRY_FILE" ]]; then
|
|
19
|
-
cat > "$REGISTRY_FILE" <<'YAML'
|
|
20
|
-
# Hermes agent fleet registry.
|
|
21
|
-
# One entry per provisioned agent. Managed by hermes-agent-template/.scripts/80-registry.sh.
|
|
22
|
-
schema_version: 1
|
|
23
|
-
agents: {}
|
|
24
|
-
YAML
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
PROJECT_PATH="$(project_repo_path)" || PROJECT_PATH=""
|
|
28
|
-
PLANE_PROJECT_ID="$(cat "$ROLE_DIR/.scripts/.plane-project-id" 2>/dev/null || true)"
|
|
29
|
-
|
|
30
|
-
log "[80] appending to fleet registry: $REGISTRY_FILE"
|
|
31
|
-
|
|
32
|
-
python3 - "$REGISTRY_FILE" "$AGENT_ID" "$REPO" "$ROLE" "$DISPLAY_NAME" \
|
|
33
|
-
"$PROJECT_PATH" "$ROLE_DIR" "$PROFILE_NAME" \
|
|
34
|
-
"$(yaml_get telegram.provisioning_status)" "$BOT_HANDLE" "$(yaml_get telegram.bot_id)" \
|
|
35
|
-
"$(yaml_get slack.provisioning_status)" "$(yaml_get slack.team_id)" \
|
|
36
|
-
"$(yaml_get slack.team_name)" "$(yaml_get slack.bot_user_id)" \
|
|
37
|
-
"$(yaml_get slack.bot_id)" "$(yaml_get slack.bot_username)" \
|
|
38
|
-
"$(yaml_get bloodbank.enabled)" "$(yaml_get bloodbank.gateway_scope)" \
|
|
39
|
-
"$(yaml_get bloodbank.target_agent_id)" \
|
|
40
|
-
"$PLANE_WORKSPACE" "$PLANE_PROJECT_ID" "$(yaml_get plane.identifier)" \
|
|
41
|
-
"$RUNTIME_REPO" "$HERMES_BIN" "$HERMES_AGENT_REPO" "$HERMES_RUNTIME_GIT_URL" \
|
|
42
|
-
"$HERMES_RUNTIME_GIT_REF" "$HERMES_RUNTIME_GIT_SHA" "$FLEET_ENV" \
|
|
43
|
-
"hermes-${AGENT_ID}-gateway.service" "hermes-${AGENT_ID}-heartbeat.timer" \
|
|
44
|
-
"$(yaml_get service_state.gateway)" "$(yaml_get service_state.heartbeat)" <<'PYEOF'
|
|
45
|
-
import datetime
|
|
46
|
-
import copy
|
|
47
|
-
import errno
|
|
48
|
-
import os
|
|
49
|
-
import pathlib
|
|
50
|
-
import sys
|
|
51
|
-
import tempfile
|
|
52
|
-
try:
|
|
53
|
-
import yaml # type: ignore
|
|
54
|
-
except ImportError:
|
|
55
|
-
sys.exit("PyYAML required; pip install pyyaml")
|
|
56
|
-
(path, agent_id, repo, role, display, project, role_dir, profile,
|
|
57
|
-
telegram_status, bot, telegram_bot_id,
|
|
58
|
-
slack_status, slack_team_id, slack_team_name, slack_user_id, slack_bot_id,
|
|
59
|
-
slack_username, bloodbank_enabled, bloodbank_scope, bloodbank_target, plane_ws, plane_id,
|
|
60
|
-
plane_ident, runtime_repo, hermes_bin, hermes_repo, hermes_git_url,
|
|
61
|
-
hermes_git_ref, hermes_git_sha, fleet_env, gw, heartbeat,
|
|
62
|
-
gateway_state, heartbeat_state) = sys.argv[1:35]
|
|
63
|
-
p = pathlib.Path(path)
|
|
64
|
-
if p.is_symlink():
|
|
65
|
-
raise SystemExit(f"refusing to update registry symlink: {p}")
|
|
66
|
-
data = yaml.safe_load(p.read_text(encoding="utf-8")) or {"schema_version": 1, "agents": {}}
|
|
67
|
-
if not isinstance(data, dict):
|
|
68
|
-
raise SystemExit("fleet registry root must be a mapping")
|
|
69
|
-
agents = data.setdefault("agents", {})
|
|
70
|
-
if not isinstance(agents, dict):
|
|
71
|
-
raise SystemExit("fleet registry agents must be a mapping")
|
|
72
|
-
if bloodbank_enabled == "":
|
|
73
|
-
bloodbank_enabled_value = False
|
|
74
|
-
elif bloodbank_enabled == "true":
|
|
75
|
-
bloodbank_enabled_value = True
|
|
76
|
-
elif bloodbank_enabled == "false":
|
|
77
|
-
bloodbank_enabled_value = False
|
|
78
|
-
else:
|
|
79
|
-
raise SystemExit("bloodbank.enabled must be the strict YAML boolean true or false")
|
|
80
|
-
existing = agents.get(agent_id, {})
|
|
81
|
-
if not isinstance(existing, dict):
|
|
82
|
-
raise SystemExit(f"fleet registry entry for {agent_id} must be a mapping")
|
|
83
|
-
provisioned_at = existing.get("provisioned_at")
|
|
84
|
-
if not isinstance(provisioned_at, str) or not provisioned_at:
|
|
85
|
-
provisioned_at = datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
|
|
86
|
-
managed = {
|
|
87
|
-
"repo": repo, "role": role, "display_name": display,
|
|
88
|
-
"project_path": project, "role_dir": role_dir,
|
|
89
|
-
"profile_name": profile,
|
|
90
|
-
"telegram": {
|
|
91
|
-
"provisioning_status": telegram_status,
|
|
92
|
-
"bot_username": bot,
|
|
93
|
-
"bot_id": telegram_bot_id,
|
|
94
|
-
},
|
|
95
|
-
"slack": {
|
|
96
|
-
"provisioning_status": slack_status,
|
|
97
|
-
"team_id": slack_team_id,
|
|
98
|
-
"team_name": slack_team_name,
|
|
99
|
-
"bot_user_id": slack_user_id,
|
|
100
|
-
"bot_id": slack_bot_id,
|
|
101
|
-
"bot_username": slack_username,
|
|
102
|
-
},
|
|
103
|
-
"bloodbank": {
|
|
104
|
-
"enabled": bloodbank_enabled_value,
|
|
105
|
-
"gateway_scope": bloodbank_scope,
|
|
106
|
-
"target_agent_id": bloodbank_target,
|
|
107
|
-
},
|
|
108
|
-
"plane": {"workspace": plane_ws, "project_id": plane_id, "identifier": plane_ident},
|
|
109
|
-
"runtime_repo": runtime_repo,
|
|
110
|
-
"hermes": {
|
|
111
|
-
"bin": hermes_bin,
|
|
112
|
-
"repo": hermes_repo,
|
|
113
|
-
"git_url": hermes_git_url,
|
|
114
|
-
"git_ref": hermes_git_ref,
|
|
115
|
-
"git_sha": hermes_git_sha,
|
|
116
|
-
"fleet_env": fleet_env,
|
|
117
|
-
},
|
|
118
|
-
"systemd": {
|
|
119
|
-
"gateway_unit": gw,
|
|
120
|
-
"heartbeat_timer": heartbeat,
|
|
121
|
-
"gateway_state": gateway_state,
|
|
122
|
-
"heartbeat_state": heartbeat_state,
|
|
123
|
-
},
|
|
124
|
-
"provisioned_at": provisioned_at,
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
def merge_managed(current, update):
|
|
128
|
-
result = copy.deepcopy(current)
|
|
129
|
-
for key, value in update.items():
|
|
130
|
-
if isinstance(value, dict) and isinstance(result.get(key), dict):
|
|
131
|
-
result[key] = merge_managed(result[key], value)
|
|
132
|
-
else:
|
|
133
|
-
result[key] = copy.deepcopy(value)
|
|
134
|
-
return result
|
|
135
|
-
|
|
136
|
-
entry = merge_managed(existing, managed)
|
|
137
|
-
# This is retired managed schema, not extension metadata. Keeping it would
|
|
138
|
-
# falsely advertise a second per-agent Bloodbank execution path.
|
|
139
|
-
systemd = entry.get("systemd")
|
|
140
|
-
if isinstance(systemd, dict):
|
|
141
|
-
systemd.pop("consumer_unit", None)
|
|
142
|
-
agents[agent_id] = entry
|
|
143
|
-
rendered = yaml.safe_dump(data, sort_keys=False)
|
|
144
|
-
p.parent.mkdir(parents=True, exist_ok=True)
|
|
145
|
-
|
|
146
|
-
def fsync_parent(target):
|
|
147
|
-
unsupported = {errno.EINVAL, getattr(errno, "ENOTSUP", errno.EINVAL), errno.ENOSYS}
|
|
148
|
-
flags = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0)
|
|
149
|
-
try:
|
|
150
|
-
directory_fd = os.open(target.parent, flags)
|
|
151
|
-
except OSError as exc:
|
|
152
|
-
if exc.errno in unsupported:
|
|
153
|
-
return
|
|
154
|
-
raise
|
|
155
|
-
try:
|
|
156
|
-
try:
|
|
157
|
-
os.fsync(directory_fd)
|
|
158
|
-
except OSError as exc:
|
|
159
|
-
if exc.errno not in unsupported:
|
|
160
|
-
raise
|
|
161
|
-
finally:
|
|
162
|
-
os.close(directory_fd)
|
|
163
|
-
|
|
164
|
-
fd, temporary = tempfile.mkstemp(prefix=f".{p.name}.registry-", dir=p.parent)
|
|
165
|
-
try:
|
|
166
|
-
os.fchmod(fd, 0o600)
|
|
167
|
-
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
168
|
-
handle.write(rendered)
|
|
169
|
-
handle.flush()
|
|
170
|
-
os.fsync(handle.fileno())
|
|
171
|
-
os.replace(temporary, p)
|
|
172
|
-
os.chmod(p, 0o600)
|
|
173
|
-
fsync_parent(p)
|
|
174
|
-
except BaseException:
|
|
175
|
-
try:
|
|
176
|
-
os.unlink(temporary)
|
|
177
|
-
except FileNotFoundError:
|
|
178
|
-
pass
|
|
179
|
-
raise
|
|
180
|
-
PYEOF
|
|
181
|
-
|
|
182
|
-
fleet_lock_release
|
|
183
|
-
trap - EXIT
|
|
184
|
-
|
|
185
|
-
mark_done 80-registry
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Print a reconciliation summary derived from durable role/service state.
|
|
3
|
-
# shellcheck source=_lib.sh
|
|
4
|
-
source "$(dirname "$0")/_lib.sh"
|
|
5
|
-
load_role_env
|
|
6
|
-
|
|
7
|
-
GW_UNIT="hermes-${AGENT_ID}-gateway.service"
|
|
8
|
-
HB_TIMER="hermes-${AGENT_ID}-heartbeat.timer"
|
|
9
|
-
GW_STATE="$(yaml_get service_state.gateway)"
|
|
10
|
-
HB_STATE="$(yaml_get service_state.heartbeat)"
|
|
11
|
-
TELEGRAM_STATE="$(yaml_get telegram.provisioning_status)"
|
|
12
|
-
SLACK_STATE="$(yaml_get slack.provisioning_status)"
|
|
13
|
-
RECONCILE_STATE="$(yaml_get reconcile.enabled)"
|
|
14
|
-
RECONCILE_OPT_OUT="$(yaml_get reconcile.explicit_opt_out)"
|
|
15
|
-
BOARD_ID="$(yaml_get ticket_provider.board_id)"
|
|
16
|
-
|
|
17
|
-
# Reconcile durable claims with live unit truth at presentation time. A stale
|
|
18
|
-
# role.yaml must never turn an exited launcher or failed heartbeat into an
|
|
19
|
-
# operational summary.
|
|
20
|
-
if [[ "$HB_STATE" == "active" || "$GW_STATE" =~ ^(active|deferred)$ ]]; then
|
|
21
|
-
if systemd_user_available; then
|
|
22
|
-
if [[ "$HB_STATE" == "active" ]]; then
|
|
23
|
-
hb_live="$(systemd_timer_health_snapshot "$HB_TIMER" "hermes-${AGENT_ID}-heartbeat.service")"
|
|
24
|
-
[[ "$hb_live" == ok\|* ]] || HB_STATE="error (stale active claim)"
|
|
25
|
-
fi
|
|
26
|
-
if [[ "$GW_STATE" == "active" ]]; then
|
|
27
|
-
gw_live="$(systemd_service_health_snapshot "$GW_UNIT" running)"
|
|
28
|
-
[[ "$gw_live" == ok\|* ]] || GW_STATE="error (stale active claim)"
|
|
29
|
-
elif [[ "$GW_STATE" == "deferred" ]]; then
|
|
30
|
-
gw_live="$(systemd_gateway_deferred_snapshot "$GW_UNIT")"
|
|
31
|
-
[[ "$gw_live" == "ok|deferred" ]] || GW_STATE="error (stale deferred claim)"
|
|
32
|
-
fi
|
|
33
|
-
else
|
|
34
|
-
[[ "$HB_STATE" != "active" ]] || HB_STATE="unverified (user manager unavailable)"
|
|
35
|
-
[[ "$GW_STATE" != "active" && "$GW_STATE" != "deferred" ]] \
|
|
36
|
-
|| GW_STATE="unverified (user manager unavailable)"
|
|
37
|
-
fi
|
|
38
|
-
fi
|
|
39
|
-
|
|
40
|
-
case "$HB_STATE:$GW_STATE" in
|
|
41
|
-
active:active) MODE="OPERATIONAL" ;;
|
|
42
|
-
active:deferred) MODE="OPERATIONAL_WITH_GATEWAY_DEFERRED" ;;
|
|
43
|
-
installed:installed|installed:deferred) MODE="INSTALLED_NOT_ACTIVE" ;;
|
|
44
|
-
*) MODE="INCOMPLETE" ;;
|
|
45
|
-
esac
|
|
46
|
-
|
|
47
|
-
cat >&2 <<EOF
|
|
48
|
-
|
|
49
|
-
╭─ Hermes deployment: $AGENT_ID ───────────────────────────────────────╮
|
|
50
|
-
│
|
|
51
|
-
│ Mode: $MODE
|
|
52
|
-
│ Gateway: $GW_STATE ($GW_UNIT)
|
|
53
|
-
│ Heartbeat: $HB_STATE ($HB_TIMER)
|
|
54
|
-
│ Reconciliation: $RECONCILE_STATE (explicit opt-out: ${RECONCILE_OPT_OUT:-false})
|
|
55
|
-
│ Telegram: $TELEGRAM_STATE
|
|
56
|
-
│ Slack: $SLACK_STATE
|
|
57
|
-
│ Board: ${BOARD_ID:-deferred}
|
|
58
|
-
│ Runtime: $ROLE_DIR/runtime (pure-local, ignored)
|
|
59
|
-
│ Hermes bin: $HERMES_BIN
|
|
60
|
-
│ Fleet env: $FLEET_ENV
|
|
61
|
-
│
|
|
62
|
-
EOF
|
|
63
|
-
|
|
64
|
-
if [[ "$TELEGRAM_STATE" == "verified" ]]; then
|
|
65
|
-
printf '│ Talk: @%s (Telegram DM)\n' "$BOT_HANDLE" >&2
|
|
66
|
-
fi
|
|
67
|
-
if [[ "$SLACK_STATE" == "verified" ]]; then
|
|
68
|
-
printf '│ Slack bot: %s\n' "$(yaml_get slack.bot_username)" >&2
|
|
69
|
-
fi
|
|
70
|
-
printf '│ Shell: %s/hermes chat "status"\n' "$ROLE_DIR" >&2
|
|
71
|
-
|
|
72
|
-
if [[ "$GW_STATE" == "deferred" ]]; then
|
|
73
|
-
cat >&2 <<EOF
|
|
74
|
-
│
|
|
75
|
-
│ Gateway activation is deferred. Store and verify a dedicated Telegram or
|
|
76
|
-
│ Slack credential through the provisioner; do not start $GW_UNIT manually.
|
|
77
|
-
EOF
|
|
78
|
-
elif [[ "$GW_STATE" == "active" ]]; then
|
|
79
|
-
printf '│ Gateway log: journalctl --user -fu %s\n' "$GW_UNIT" >&2
|
|
80
|
-
fi
|
|
81
|
-
|
|
82
|
-
cat >&2 <<'EOF'
|
|
83
|
-
│
|
|
84
|
-
╰────────────────────────────────────────────────────────────────────────╯
|
|
85
|
-
|
|
86
|
-
EOF
|
|
87
|
-
mark_done 99-summary
|