pushary 1.9.126 → 1.9.127
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 +4 -0
- package/dist/bin/pushary-daemon.js +68 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.127
|
|
4
|
+
|
|
5
|
+
- The background daemon reads the record of a settled phone message to Codex or Hermes once, instead of every few seconds, and deletes its own records once they are more than 7 days old. Before, every record was reread on each scan and kept forever, so the work grew with every message.
|
|
6
|
+
|
|
3
7
|
## 1.9.126
|
|
4
8
|
|
|
5
9
|
- With two or more sessions taking turns, a tool call no longer fetches your policy again just because another session fetched it last, and each session can ride out an outage on its own copy. Before, nearly every call refetched, and an outage often left a session with no copy. When a new session starts, copies older than 12 hours are deleted.
|
|
@@ -179,7 +179,8 @@ import {
|
|
|
179
179
|
openSync,
|
|
180
180
|
writeSync,
|
|
181
181
|
fsyncSync,
|
|
182
|
-
closeSync
|
|
182
|
+
closeSync,
|
|
183
|
+
unlinkSync
|
|
183
184
|
} from "fs";
|
|
184
185
|
import { dirname, join, resolve } from "path";
|
|
185
186
|
var privateNode = (path, kind) => {
|
|
@@ -311,13 +312,21 @@ var controlRequest = (path, request, timeout) => new Promise((resolve3, reject)
|
|
|
311
312
|
s.destroy();
|
|
312
313
|
});
|
|
313
314
|
});
|
|
315
|
+
var JOURNAL_FILE = /^[0-9a-f]{64}\.json$/;
|
|
316
|
+
var outlivedTheServersCommand = (age, nowMs, monotonicNowMs) => nowMs - age.submittedAtMs >= COMMAND_RECOVERY.receiptRetentionMs && monotonicNowMs - age.firstSeenMonotonicMs >= COMMAND_RECOVERY.retrySendWindowMs;
|
|
317
|
+
var isMissingFile = (error) => error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
314
318
|
var NativeHostClient = class {
|
|
315
319
|
constructor(options) {
|
|
316
320
|
this.options = options;
|
|
317
321
|
this.timeout = options.timeoutMs ?? NATIVE_INPUT.rpcTimeoutMs;
|
|
322
|
+
this.monotonicNow = options.monotonicNow ?? (() => performance.now());
|
|
318
323
|
}
|
|
319
324
|
timeout;
|
|
320
325
|
lastDiscoveryFailure = null;
|
|
326
|
+
journalsOfOthers = /* @__PURE__ */ new Set();
|
|
327
|
+
settledJournals = /* @__PURE__ */ new Map();
|
|
328
|
+
firstSeenMonotonicMs = /* @__PURE__ */ new Map();
|
|
329
|
+
monotonicNow;
|
|
321
330
|
async connect() {
|
|
322
331
|
if ((this.options.platform ?? process.platform) !== "darwin")
|
|
323
332
|
throw new Error("native host platform unqualified");
|
|
@@ -557,21 +566,74 @@ var NativeHostClient = class {
|
|
|
557
566
|
...hermesCursor ? { hermesCursor } : {}
|
|
558
567
|
};
|
|
559
568
|
} catch (e) {
|
|
560
|
-
if (e
|
|
569
|
+
if (isMissingFile(e)) return null;
|
|
561
570
|
throw e;
|
|
562
571
|
}
|
|
563
572
|
}
|
|
573
|
+
ownsJournal(raw) {
|
|
574
|
+
return raw.agent === this.options.agent && raw.settlementScope === this.options.settlementScope;
|
|
575
|
+
}
|
|
576
|
+
mayRemoveOwnJournals() {
|
|
577
|
+
return this.options.settlementScope !== void 0;
|
|
578
|
+
}
|
|
579
|
+
removeJournal(file) {
|
|
580
|
+
this.settledJournals.delete(file);
|
|
581
|
+
this.firstSeenMonotonicMs.delete(file);
|
|
582
|
+
try {
|
|
583
|
+
unlinkSync(join(this.options.journalDir, file));
|
|
584
|
+
} catch (error) {
|
|
585
|
+
if (!isMissingFile(error)) this.options.report?.(`could not remove a native-input journal: ${error instanceof Error ? error.message : String(error)}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
observeJournalAge(file, submittedAtMs, monotonicNowMs) {
|
|
589
|
+
const firstSeen = this.firstSeenMonotonicMs.get(file) ?? monotonicNowMs;
|
|
590
|
+
this.firstSeenMonotonicMs.set(file, firstSeen);
|
|
591
|
+
return { submittedAtMs, firstSeenMonotonicMs: firstSeen };
|
|
592
|
+
}
|
|
593
|
+
removeSettledJournalsPastRetention(nowMs, monotonicNowMs) {
|
|
594
|
+
if (!this.mayRemoveOwnJournals()) return;
|
|
595
|
+
for (const [file, age] of [...this.settledJournals]) {
|
|
596
|
+
if (outlivedTheServersCommand(age, nowMs, monotonicNowMs)) this.removeJournal(file);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
forgetJournalsNoLongerListed(listed) {
|
|
600
|
+
for (const file of [...this.journalsOfOthers]) if (!listed.has(file)) this.journalsOfOthers.delete(file);
|
|
601
|
+
for (const file of [...this.settledJournals.keys()]) if (!listed.has(file)) this.settledJournals.delete(file);
|
|
602
|
+
for (const file of [...this.firstSeenMonotonicMs.keys()]) if (!listed.has(file)) this.firstSeenMonotonicMs.delete(file);
|
|
603
|
+
}
|
|
604
|
+
needsReading(file) {
|
|
605
|
+
return JOURNAL_FILE.test(file) && !this.journalsOfOthers.has(file) && !this.settledJournals.has(file);
|
|
606
|
+
}
|
|
564
607
|
pendingSettlements() {
|
|
608
|
+
const nowMs = Date.now();
|
|
609
|
+
const monotonicNowMs = this.monotonicNow();
|
|
610
|
+
this.removeSettledJournalsPastRetention(nowMs, monotonicNowMs);
|
|
565
611
|
try {
|
|
566
|
-
|
|
612
|
+
const listed = readdirSync(this.options.journalDir);
|
|
613
|
+
this.forgetJournalsNoLongerListed(new Set(listed));
|
|
614
|
+
return listed.filter((file) => this.needsReading(file)).flatMap((file) => {
|
|
567
615
|
try {
|
|
568
616
|
const raw = JSON.parse(
|
|
569
617
|
readFileSync(join(this.options.journalDir, file), "utf8")
|
|
570
618
|
);
|
|
571
619
|
if (!nativeRecord(raw) || !nativeIdentifier(raw.sessionId) || !nativeIdentifier(raw.commandId))
|
|
572
620
|
return [];
|
|
621
|
+
if (!this.ownsJournal(raw)) {
|
|
622
|
+
this.journalsOfOthers.add(file);
|
|
623
|
+
return [];
|
|
624
|
+
}
|
|
573
625
|
const intent = this.readIntent(raw.sessionId, raw.commandId);
|
|
574
|
-
|
|
626
|
+
if (!intent) return [];
|
|
627
|
+
const age = this.observeJournalAge(file, intent.submittedAtMs, monotonicNowMs);
|
|
628
|
+
if (this.mayRemoveOwnJournals() && outlivedTheServersCommand(age, nowMs, monotonicNowMs)) {
|
|
629
|
+
this.removeJournal(file);
|
|
630
|
+
return [];
|
|
631
|
+
}
|
|
632
|
+
if (intent.settled) {
|
|
633
|
+
this.settledJournals.set(file, age);
|
|
634
|
+
return [];
|
|
635
|
+
}
|
|
636
|
+
return intent.accepted ? [{ sessionId: intent.sessionId, commandId: intent.commandId }] : [];
|
|
575
637
|
} catch {
|
|
576
638
|
return [];
|
|
577
639
|
}
|
|
@@ -971,7 +1033,7 @@ import {
|
|
|
971
1033
|
readSync,
|
|
972
1034
|
statSync,
|
|
973
1035
|
writeFileSync,
|
|
974
|
-
unlinkSync
|
|
1036
|
+
unlinkSync as unlinkSync2
|
|
975
1037
|
} from "fs";
|
|
976
1038
|
import { dirname as dirname2, isAbsolute, join as join3, resolve as resolve2 } from "path";
|
|
977
1039
|
import { Socket } from "net";
|
|
@@ -1185,7 +1247,7 @@ var ClaudeInboxClient = class {
|
|
|
1185
1247
|
return { file, attempt };
|
|
1186
1248
|
});
|
|
1187
1249
|
for (const name of files.slice(CLAUDE_WAKE.retainedAttempts))
|
|
1188
|
-
|
|
1250
|
+
unlinkSync2(join3(directory, name));
|
|
1189
1251
|
return attempts;
|
|
1190
1252
|
}
|
|
1191
1253
|
async wake(sessionId, signal) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pushary",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.127",
|
|
4
4
|
"description": "Human-in-the-loop (HITL) for AI agents: ask a person for approvals or answers by push notification. One command connects Claude Code, Codex, Cursor, Gemini CLI and more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pushary",
|