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 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.
@@ -53,7 +53,7 @@ import {
53
53
  defaultHermesHome,
54
54
  hermesReceiptPaths,
55
55
  removeHermesWiring
56
- } from "../chunk-Z3FMJ567.js";
56
+ } from "../chunk-KLDUATG5.js";
57
57
  import "../chunk-QCGNGEGE.js";
58
58
  import {
59
59
  planClineRemoval,
@@ -808,7 +808,7 @@ var decidePreToolUse = async (input) => {
808
808
  };
809
809
  var repairCodexHoldsQuietly = async () => {
810
810
  try {
811
- const { repairCodexHolds } = await import("../reapply-5SZSMZJJ.js");
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-SCKZVTFV.js";
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-Z3FMJ567.js";
13
+ import "../chunk-KLDUATG5.js";
14
14
  import {
15
15
  writeHuman
16
16
  } from "../chunk-QCGNGEGE.js";
@@ -74,7 +74,7 @@ import {
74
74
  routesHermesQuestions,
75
75
  runsAnOlderPlugin,
76
76
  shippedHermesPlugin
77
- } from "../chunk-Z3FMJ567.js";
77
+ } from "../chunk-KLDUATG5.js";
78
78
  import "../chunk-QCGNGEGE.js";
79
79
  import {
80
80
  FX_PROFILE_REFUSALS,
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  reapplyClaudeForVersion
4
- } from "../chunk-SCKZVTFV.js";
4
+ } from "../chunk-A7GTEZGH.js";
5
5
  import "../chunk-54O76CMR.js";
6
- import "../chunk-Z3FMJ567.js";
6
+ import "../chunk-KLDUATG5.js";
7
7
  import "../chunk-SSJDAX4E.js";
8
8
  import "../chunk-N2FDORN6.js";
9
9
  import "../chunk-D5G2HSQ6.js";
@@ -117,7 +117,7 @@ import {
117
117
  routesHermesQuestions,
118
118
  runsAnOlderPlugin,
119
119
  shippedHermesPlugin
120
- } from "../chunk-Z3FMJ567.js";
120
+ } from "../chunk-KLDUATG5.js";
121
121
  import {
122
122
  setHumanStream
123
123
  } from "../chunk-QCGNGEGE.js";
@@ -28,7 +28,7 @@ import {
28
28
  import {
29
29
  ensureCodexTrustAfterReapply,
30
30
  reapplyAllAgents
31
- } from "../chunk-SCKZVTFV.js";
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-Z3FMJ567.js";
45
+ import "../chunk-KLDUATG5.js";
46
46
  import {
47
47
  FX_RELOAD_AFTER_BRIDGE,
48
48
  fxHttpEntryKeptNote,
@@ -7,7 +7,7 @@ import {
7
7
  installHermesPlugin,
8
8
  refreshHermesPluginFiles,
9
9
  shippedHermesPlugin
10
- } from "./chunk-Z3FMJ567.js";
10
+ } from "./chunk-KLDUATG5.js";
11
11
  import {
12
12
  fxIgnoresInstructionsFile,
13
13
  fxInstructionsBody,
@@ -34,7 +34,9 @@ from . import api, approval, host, identity, installation, schemas, tools
34
34
 
35
35
  logger = logging.getLogger(__name__)
36
36
 
37
- MAX_ERROR_NOTIFICATIONS = 3
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, "notified": 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 "[redaction unavailable]"
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 _tool_failure(result, status, error_message):
317
+ def _tool_failed(result, status):
316
318
  if status is not None:
317
- return (error_message or "tool returned an error") if status == "error" else None
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 None
322
- if isinstance(parsed, dict) and parsed.get("error"):
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
- failure = _tool_failure(result, status, error_message)
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, notified=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 _on_session_end(session_id=None, completed=False, interrupted=False, **kwargs):
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("session_end", session_id or identity.session_id())
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, notified=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.0\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" },
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-SCKZVTFV.js";
15
+ } from "./chunk-A7GTEZGH.js";
16
16
  import "./chunk-54O76CMR.js";
17
- import "./chunk-Z3FMJ567.js";
17
+ import "./chunk-KLDUATG5.js";
18
18
  import "./chunk-SSJDAX4E.js";
19
19
  import "./chunk-N2FDORN6.js";
20
20
  import "./chunk-D5G2HSQ6.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pushary",
3
- "version": "1.9.69",
3
+ "version": "1.9.70",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",