pushary 1.9.69 → 1.9.70
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/CHANGELOG.md +6 -0
- package/dist/bin/pushary-clean.js +1 -1
- package/dist/bin/pushary-codex-hook.js +1 -1
- package/dist/bin/pushary-daemon.js +2 -2
- package/dist/bin/pushary-doctor.js +1 -1
- package/dist/bin/pushary-session-start-hook.js +2 -2
- package/dist/bin/pushary-setup.js +1 -1
- package/dist/bin/pushary-upgrade.js +2 -2
- package/dist/{chunk-SCKZVTFV.js → chunk-A7GTEZGH.js} +1 -1
- package/dist/{chunk-Z3FMJ567.js → chunk-KLDUATG5.js} +35 -45
- package/dist/{reapply-5SZSMZJJ.js → reapply-TRR4XB6N.js} +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.70
|
|
4
|
+
|
|
5
|
+
- Hermes: the plugin (0.8.1) no longer sends "Tool error in Hermes" to your phone each time a tool call fails. Hermes counts any command that exits non-zero as a failed tool call, so a `grep` that finds nothing or a test run that fails on purpose became a high-urgency push, up to three per Hermes session, and Hermes recovers from those on its own.
|
|
6
|
+
- When Hermes marks a turn as failed, the push for that turn now reads "Hermes run failed" with the reason Hermes gives, instead of "Hermes finished", and the session keeps the reason. It is the same push that ends every turn and follows your updates setting, so a scheduled job that keeps failing sends no more pushes than before. A provider error that stops Hermes before it finishes the turn, such as a rejected API key, still sends nothing. Hermes redacts the reason before it leaves your computer.
|
|
7
|
+
- The daily update and `pushary upgrade` replace a plugin folder that setup installed. Run `hermes gateway restart` to load the new copy. Pushary already drops the old copy's tool-error reports without a push, so they stop reaching your phone before you restart.
|
|
8
|
+
|
|
3
9
|
## 1.9.69
|
|
4
10
|
|
|
5
11
|
- fx: upgrade and the daily update also switch a `pushary` server written as `"type": "streamable-http"`, which fx reads as HTTP. An entry with no `type` is no longer taken for an HTTP server, because fx runs it as a local process.
|
|
@@ -808,7 +808,7 @@ var decidePreToolUse = async (input) => {
|
|
|
808
808
|
};
|
|
809
809
|
var repairCodexHoldsQuietly = async () => {
|
|
810
810
|
try {
|
|
811
|
-
const { repairCodexHolds } = await import("../reapply-
|
|
811
|
+
const { repairCodexHolds } = await import("../reapply-TRR4XB6N.js");
|
|
812
812
|
repairCodexHolds();
|
|
813
813
|
} catch {
|
|
814
814
|
return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
inspectConfiguredManagedProviders
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-A7GTEZGH.js";
|
|
5
5
|
import {
|
|
6
6
|
HEARTBEAT_INTERVAL_MS,
|
|
7
7
|
sendHeartbeat
|
|
@@ -10,7 +10,7 @@ import "../chunk-54O76CMR.js";
|
|
|
10
10
|
import {
|
|
11
11
|
readSignedOutDiagnosis
|
|
12
12
|
} from "../chunk-QE22XDUK.js";
|
|
13
|
-
import "../chunk-
|
|
13
|
+
import "../chunk-KLDUATG5.js";
|
|
14
14
|
import {
|
|
15
15
|
writeHuman
|
|
16
16
|
} from "../chunk-QCGNGEGE.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
reapplyClaudeForVersion
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-A7GTEZGH.js";
|
|
5
5
|
import "../chunk-54O76CMR.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-KLDUATG5.js";
|
|
7
7
|
import "../chunk-SSJDAX4E.js";
|
|
8
8
|
import "../chunk-N2FDORN6.js";
|
|
9
9
|
import "../chunk-D5G2HSQ6.js";
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import {
|
|
29
29
|
ensureCodexTrustAfterReapply,
|
|
30
30
|
reapplyAllAgents
|
|
31
|
-
} from "../chunk-
|
|
31
|
+
} from "../chunk-A7GTEZGH.js";
|
|
32
32
|
import {
|
|
33
33
|
HERMES_RESTART_COMMAND,
|
|
34
34
|
describeCodexTrustConfirmation
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
readMacAppAgentKey
|
|
43
43
|
} from "../chunk-PK6WI4HH.js";
|
|
44
44
|
import "../chunk-XLKO2XEG.js";
|
|
45
|
-
import "../chunk-
|
|
45
|
+
import "../chunk-KLDUATG5.js";
|
|
46
46
|
import {
|
|
47
47
|
FX_RELOAD_AFTER_BRIDGE,
|
|
48
48
|
fxHttpEntryKeptNote,
|
|
@@ -34,7 +34,9 @@ from . import api, approval, host, identity, installation, schemas, tools
|
|
|
34
34
|
|
|
35
35
|
logger = logging.getLogger(__name__)
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
FAILED_TURN_WITHOUT_REASON = "no reason given"
|
|
38
|
+
REDACTION_UNAVAILABLE = "[redaction unavailable]"
|
|
39
|
+
FAILED_TURN_REASON_MAX_LENGTH = 200
|
|
38
40
|
SYSTEM_PROMPT_SECTION_ID = "pushary.reach-the-human"
|
|
39
41
|
HERMES_QUESTION_TOOL = "clarify"
|
|
40
42
|
QUESTION_ROUTING_PROMPT = (
|
|
@@ -82,7 +84,7 @@ TOOL_REGISTRATIONS = (
|
|
|
82
84
|
"Ask one of your own end-users and block on a fail-closed answer (Partner plan)"),
|
|
83
85
|
)
|
|
84
86
|
|
|
85
|
-
_session_totals = {"tools": 0, "errors": 0
|
|
87
|
+
_session_totals = {"tools": 0, "errors": 0}
|
|
86
88
|
_last_turn = {"completed": False, "interrupted": False}
|
|
87
89
|
_plugin_activity = {"attempted": False}
|
|
88
90
|
_plugin_activity_lock = threading.Lock()
|
|
@@ -173,7 +175,7 @@ def _redact_outbound_text(text: str) -> str:
|
|
|
173
175
|
return redact_for_egress(redacted) if redact_for_egress else redacted
|
|
174
176
|
except Exception:
|
|
175
177
|
logger.warning("[pushary] host redaction unavailable; omitting outbound detail")
|
|
176
|
-
return
|
|
178
|
+
return REDACTION_UNAVAILABLE
|
|
177
179
|
|
|
178
180
|
|
|
179
181
|
def _summarize_args(args):
|
|
@@ -312,52 +314,25 @@ def _on_pre_tool_call(tool_name=None, args=None, session_id=None, **kwargs):
|
|
|
312
314
|
return _block_unapproved(tool_name)
|
|
313
315
|
|
|
314
316
|
|
|
315
|
-
def
|
|
317
|
+
def _tool_failed(result, status):
|
|
316
318
|
if status is not None:
|
|
317
|
-
return
|
|
319
|
+
return status == "error"
|
|
318
320
|
try:
|
|
319
321
|
parsed = json.loads(result) if isinstance(result, str) else result
|
|
320
322
|
except (json.JSONDecodeError, TypeError):
|
|
321
|
-
return
|
|
322
|
-
|
|
323
|
-
return str(parsed["error"])
|
|
324
|
-
return None
|
|
323
|
+
return False
|
|
324
|
+
return isinstance(parsed, dict) and bool(parsed.get("error"))
|
|
325
325
|
|
|
326
326
|
|
|
327
|
-
def _on_post_tool_call(tool_name, args, result, duration_ms=0, status=None,
|
|
328
|
-
error_message=None, **kwargs):
|
|
327
|
+
def _on_post_tool_call(tool_name, args, result, duration_ms=0, status=None, **kwargs):
|
|
329
328
|
_session_totals["tools"] += 1
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if failure is None:
|
|
333
|
-
return
|
|
334
|
-
|
|
335
|
-
_session_totals["errors"] += 1
|
|
336
|
-
if not api.is_configured():
|
|
337
|
-
return
|
|
338
|
-
if _session_totals["notified"] >= MAX_ERROR_NOTIFICATIONS:
|
|
339
|
-
return
|
|
340
|
-
_session_totals["notified"] += 1
|
|
341
|
-
|
|
342
|
-
try:
|
|
343
|
-
failure = _redact_outbound_text(str(failure))
|
|
344
|
-
api.send_notification(
|
|
345
|
-
title="Tool error in Hermes",
|
|
346
|
-
body=f"{tool_name} failed: {failure[:150]}",
|
|
347
|
-
agent_name=identity.agent_name(),
|
|
348
|
-
context={
|
|
349
|
-
"type": "error",
|
|
350
|
-
"errorMessage": failure[:500],
|
|
351
|
-
"summary": f"Tool '{tool_name}' returned an error after {duration_ms}ms",
|
|
352
|
-
},
|
|
353
|
-
)
|
|
354
|
-
except Exception:
|
|
355
|
-
pass
|
|
329
|
+
if _tool_failed(result, status):
|
|
330
|
+
_session_totals["errors"] += 1
|
|
356
331
|
|
|
357
332
|
|
|
358
333
|
def _on_session_start(session_id=None, **kwargs):
|
|
359
334
|
identity.remember_session(session_id)
|
|
360
|
-
_session_totals.update(tools=0, errors=0
|
|
335
|
+
_session_totals.update(tools=0, errors=0)
|
|
361
336
|
_last_turn.update(completed=False, interrupted=False)
|
|
362
337
|
_report_session_event("session_start", session_id, can_drain=True)
|
|
363
338
|
_report_plugin_active_once(session_id)
|
|
@@ -397,26 +372,40 @@ def _on_pre_llm_call(session_id=None, **kwargs):
|
|
|
397
372
|
}
|
|
398
373
|
|
|
399
374
|
|
|
400
|
-
def _report_session_event(event, session_id, can_drain=False):
|
|
375
|
+
def _report_session_event(event, session_id, can_drain=False, error=None):
|
|
401
376
|
if not api.is_configured() or not session_id:
|
|
402
377
|
return
|
|
403
378
|
try:
|
|
404
|
-
api.agent_event(event, session_id, can_drain=can_drain)
|
|
379
|
+
api.agent_event(event, session_id, can_drain=can_drain, error=error)
|
|
405
380
|
except Exception:
|
|
406
381
|
pass
|
|
407
382
|
|
|
408
383
|
|
|
409
|
-
def
|
|
384
|
+
def _turn_failure_reason(failed, interrupted, turn_exit_reason):
|
|
385
|
+
if failed is not True or interrupted:
|
|
386
|
+
return None
|
|
387
|
+
reason = _redact_outbound_text(str(turn_exit_reason or "").strip())
|
|
388
|
+
if not reason or reason == REDACTION_UNAVAILABLE:
|
|
389
|
+
return FAILED_TURN_WITHOUT_REASON
|
|
390
|
+
return reason[:FAILED_TURN_REASON_MAX_LENGTH]
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _on_session_end(session_id=None, completed=False, interrupted=False, failed=False,
|
|
394
|
+
turn_exit_reason=None, **kwargs):
|
|
410
395
|
identity.remember_session(session_id)
|
|
411
396
|
_last_turn.update(completed=bool(completed), interrupted=bool(interrupted))
|
|
412
|
-
_report_session_event(
|
|
397
|
+
_report_session_event(
|
|
398
|
+
"session_end",
|
|
399
|
+
session_id or identity.session_id(),
|
|
400
|
+
error=_turn_failure_reason(failed, interrupted, turn_exit_reason),
|
|
401
|
+
)
|
|
413
402
|
|
|
414
403
|
|
|
415
404
|
def _on_session_finalize(session_id=None, **kwargs):
|
|
416
405
|
_report_session_event("session_closed", session_id or identity.session_id())
|
|
417
406
|
tool_count = _session_totals["tools"]
|
|
418
407
|
error_count = _session_totals["errors"]
|
|
419
|
-
_session_totals.update(tools=0, errors=0
|
|
408
|
+
_session_totals.update(tools=0, errors=0)
|
|
420
409
|
|
|
421
410
|
if not os.environ.get("PUSHARY_AUTO_NOTIFY_SESSION_END"):
|
|
422
411
|
identity.forget_session()
|
|
@@ -635,7 +624,7 @@ def report_plugin_active(session_id):
|
|
|
635
624
|
return False
|
|
636
625
|
|
|
637
626
|
|
|
638
|
-
def agent_event(event, session_id, can_drain=False):
|
|
627
|
+
def agent_event(event, session_id, can_drain=False, error=None):
|
|
639
628
|
"""Report a Hermes session and pick up a queued notch instruction."""
|
|
640
629
|
try:
|
|
641
630
|
pending = _read_pending(session_id) if can_drain else None
|
|
@@ -647,6 +636,7 @@ def agent_event(event, session_id, can_drain=False):
|
|
|
647
636
|
"canDrainCommand": can_drain and pending is None,
|
|
648
637
|
"ackMode": can_drain and pending is None,
|
|
649
638
|
"skipCommandDrain": pending is not None,
|
|
639
|
+
**({"error": error} if error else {}),
|
|
650
640
|
})
|
|
651
641
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
652
642
|
raw = resp.read().decode("utf-8")
|
|
@@ -1073,7 +1063,7 @@ def record_plugin_load(approval_transport_registered, home=None, pid=None, now=N
|
|
|
1073
1063
|
logger.debug("[pushary] could not record the plugin load: %s", exc)
|
|
1074
1064
|
return None
|
|
1075
1065
|
` },
|
|
1076
|
-
{ path: "plugin.yaml", contents: "name: pushary\nversion: 0.8.
|
|
1066
|
+
{ path: "plugin.yaml", contents: "name: pushary\nversion: 0.8.1\ndescription: Push notifications and human-in-the-loop for Hermes Agent via Pushary\nauthor: RalphNex OU\nrequires_env:\n - name: PUSHARY_API_KEY\n description: Your Pushary API key\n url: https://pushary.com/sign-up?from=hermes\n secret: true\nprovides_tools:\n - pushary_remind\n - pushary_notify\n - pushary_ask\n - pushary_wait\n - pushary_cancel\n - pushary_propose_scope\n - pushary_enroll\n - pushary_ask_end_user\nprovides_hooks:\n - pre_llm_call\n - pre_tool_call\n - post_tool_call\n - on_session_start\n - on_session_end\n - on_session_finalize\nprovides_approval_transports:\n - pushary\n" },
|
|
1077
1067
|
{ path: "schemas.py", contents: `PUSHARY_REMIND = {
|
|
1078
1068
|
"name": "pushary_remind",
|
|
1079
1069
|
"description": "Schedule a one-time personal reminder on Pushary, even after Hermes exits. Use body plus in_minutes or at (ISO timestamp with explicit UTC offset, within 30 days). Never guess the user's timezone. Confirm the returned time. No arguments lists pending reminders; cancel_reminder_id cancels a scheduled one. Use Hermes cron for agent work; use this for reminding the operator.",
|
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
reapplyOpenCodePlugin,
|
|
13
13
|
reapplyVsCodePlugin,
|
|
14
14
|
repairCodexHolds
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-A7GTEZGH.js";
|
|
16
16
|
import "./chunk-54O76CMR.js";
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-KLDUATG5.js";
|
|
18
18
|
import "./chunk-SSJDAX4E.js";
|
|
19
19
|
import "./chunk-N2FDORN6.js";
|
|
20
20
|
import "./chunk-D5G2HSQ6.js";
|