@ouro.bot/cli 0.1.0-alpha.856 → 0.1.0-alpha.859
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.json +13 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/daemon/daemon.js +7 -7
- package/dist/heart/external-events/router.js +73 -0
- package/dist/senses/telegram-effect-adapter.js +7 -3
- package/dist/senses/telegram.js +2 -0
- package/npm-shrinkwrap.json +12 -8
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.859",
|
|
6
|
+
"changes": [
|
|
7
|
+
"The Telegram sense no longer re-reads and re-parses every external-event record (hundreds of files, about 11 MB) each second for its system-failsafe sweep; it now reuses the stat-keyed record cache, cutting the Butler's idle Telegram CPU. The trade is memory: parsed records now stay cached in the sense process for its lifetime, so heap grows by roughly the size of the usenet records."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.857",
|
|
12
|
+
"changes": [
|
|
13
|
+
"The daemon now caches external-event records by file stat so the one-second reconcile tick no longer re-reads and re-parses every unchanged record, cutting idle CPU.",
|
|
14
|
+
"The record cache never keeps a filesystem read error and re-reads any entry older than a minute, so a transient read failure or a missed change cannot hide a record."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
4
17
|
{
|
|
5
18
|
"version": "0.1.0-alpha.856",
|
|
6
19
|
"changes": [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0"?>
|
|
2
2
|
<Container version="2">
|
|
3
3
|
<Name>ouro-butler</Name>
|
|
4
|
-
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.859</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -637,6 +637,7 @@ class OuroDaemon {
|
|
|
637
637
|
externalEventRoot;
|
|
638
638
|
privilegedEventSpoolRoot;
|
|
639
639
|
privilegedEventScanner;
|
|
640
|
+
externalEventScanCache = new router_1.ExternalEventScanCache();
|
|
640
641
|
rsvpHabitRunner;
|
|
641
642
|
nativeHabitRunner;
|
|
642
643
|
nativeHabitMatch;
|
|
@@ -1701,12 +1702,11 @@ class OuroDaemon {
|
|
|
1701
1702
|
if (fs.existsSync(this.privilegedEventSpoolRoot)) {
|
|
1702
1703
|
this.privilegedEventScanner({ spoolRoot: this.privilegedEventSpoolRoot, eventRoot: this.externalEventRootPath() });
|
|
1703
1704
|
}
|
|
1704
|
-
const
|
|
1705
|
+
const scanned = this.externalEventScanCache.scan(this.externalEventRootPath());
|
|
1705
1706
|
const readinessKeys = new Set();
|
|
1706
|
-
for (const
|
|
1707
|
-
if (
|
|
1707
|
+
for (const { record: candidate } of scanned) {
|
|
1708
|
+
if (!candidate)
|
|
1708
1709
|
continue;
|
|
1709
|
-
const candidate = (0, router_1.readExternalEventRecord)(status.recordPath);
|
|
1710
1710
|
if (candidate.dispatchEnabled === false)
|
|
1711
1711
|
continue;
|
|
1712
1712
|
const retryDue = candidate.executionState === "retry_wait" && candidate.nextAttemptAt !== null && Date.parse(candidate.nextAttemptAt) <= Date.parse(now);
|
|
@@ -1739,10 +1739,10 @@ class OuroDaemon {
|
|
|
1739
1739
|
const providerEvidenceByAgent = new Map();
|
|
1740
1740
|
const runtimeEvidenceByAgent = new Map();
|
|
1741
1741
|
const recoveredByClass = new Map();
|
|
1742
|
-
for (const
|
|
1743
|
-
if (
|
|
1742
|
+
for (const { record: scannedRecord } of scanned) {
|
|
1743
|
+
if (!scannedRecord)
|
|
1744
1744
|
continue;
|
|
1745
|
-
let record =
|
|
1745
|
+
let record = scannedRecord;
|
|
1746
1746
|
if (record.dispatchEnabled === false)
|
|
1747
1747
|
continue;
|
|
1748
1748
|
if (blockedReadinessKeys.has(`${record.agent}\0${record.source}`))
|
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ExternalEventScanCache = void 0;
|
|
36
37
|
exports.getExternalEventRoot = getExternalEventRoot;
|
|
37
38
|
exports.externalEventRecordPath = externalEventRecordPath;
|
|
38
39
|
exports.readExternalEventRecord = readExternalEventRecord;
|
|
@@ -622,6 +623,78 @@ function listExternalEventStatus(root) {
|
|
|
622
623
|
}
|
|
623
624
|
return rows.sort((left, right) => left.agent.localeCompare(right.agent) || left.source.localeCompare(right.source) || left.eventId.localeCompare(right.eventId));
|
|
624
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Stat-keyed record cache for the daemon's reconcile tick. Records are written by atomic rename, so any write
|
|
628
|
+
* changes inode, mtime or ctime and invalidates the entry. Cached records are only used to decide what is due;
|
|
629
|
+
* every mutation (claim, revive, reconcile) re-reads the file under its own lock and compare-and-swap check, so a
|
|
630
|
+
* stale cached record can at worst cause one rejected attempt that the next tick retries from fresh data.
|
|
631
|
+
* As a backstop against a change the stat key misses, an entry older than `maxAgeMs` is re-read anyway, and a
|
|
632
|
+
* filesystem read error (one carrying an fs `code`, such as EIO or EMFILE) is reported for this scan but never
|
|
633
|
+
* cached, so a transient failure cannot hide a healthy record until its file next changes.
|
|
634
|
+
*/
|
|
635
|
+
class ExternalEventScanCache {
|
|
636
|
+
entries = new Map();
|
|
637
|
+
maxAgeMs;
|
|
638
|
+
now;
|
|
639
|
+
constructor(options = {}) {
|
|
640
|
+
this.maxAgeMs = options.maxAgeMs ?? 60_000;
|
|
641
|
+
this.now = options.now ?? Date.now;
|
|
642
|
+
}
|
|
643
|
+
scan(root) {
|
|
644
|
+
const seen = new Set();
|
|
645
|
+
const rows = [];
|
|
646
|
+
const nowMs = this.now();
|
|
647
|
+
if (fs.existsSync(root)) {
|
|
648
|
+
for (const agent of fs.readdirSync(root, { withFileTypes: true })) {
|
|
649
|
+
if (!agent.isDirectory() || agent.name.startsWith("."))
|
|
650
|
+
continue;
|
|
651
|
+
const agentPath = path.join(root, agent.name);
|
|
652
|
+
for (const source of fs.readdirSync(agentPath, { withFileTypes: true })) {
|
|
653
|
+
if (!source.isDirectory() || source.name.startsWith("."))
|
|
654
|
+
continue;
|
|
655
|
+
const sourcePath = path.join(agentPath, source.name);
|
|
656
|
+
for (const file of fs.readdirSync(sourcePath, { withFileTypes: true })) {
|
|
657
|
+
if (!file.isFile() || file.name.startsWith(".") || !file.name.endsWith(".json"))
|
|
658
|
+
continue;
|
|
659
|
+
const recordPath = path.join(sourcePath, file.name);
|
|
660
|
+
let stat;
|
|
661
|
+
try {
|
|
662
|
+
stat = fs.statSync(recordPath);
|
|
663
|
+
}
|
|
664
|
+
catch {
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
seen.add(recordPath);
|
|
668
|
+
const cached = this.entries.get(recordPath);
|
|
669
|
+
if (cached && cached.ino === stat.ino && cached.size === stat.size && cached.mtimeMs === stat.mtimeMs && cached.ctimeMs === stat.ctimeMs && nowMs - cached.cachedAtMs < this.maxAgeMs) {
|
|
670
|
+
rows.push(cached.entry);
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
let entry;
|
|
674
|
+
let cacheable = true;
|
|
675
|
+
try {
|
|
676
|
+
entry = { recordPath, record: readExternalEventRecord(recordPath), error: null };
|
|
677
|
+
}
|
|
678
|
+
catch (error) {
|
|
679
|
+
entry = { recordPath, record: null, error: error instanceof Error ? error.message : /* v8 ignore next -- filesystem and JSON parsers throw Error objects @preserve */ String(error) };
|
|
680
|
+
cacheable = !(error && typeof error === "object" && typeof error.code === "string");
|
|
681
|
+
}
|
|
682
|
+
if (cacheable)
|
|
683
|
+
this.entries.set(recordPath, { ino: stat.ino, size: stat.size, mtimeMs: stat.mtimeMs, ctimeMs: stat.ctimeMs, cachedAtMs: nowMs, entry });
|
|
684
|
+
else
|
|
685
|
+
this.entries.delete(recordPath);
|
|
686
|
+
rows.push(entry);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
for (const key of this.entries.keys())
|
|
692
|
+
if (!seen.has(key))
|
|
693
|
+
this.entries.delete(key);
|
|
694
|
+
return rows;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
exports.ExternalEventScanCache = ExternalEventScanCache;
|
|
625
698
|
function readExisting(recordPath) {
|
|
626
699
|
if (!fs.existsSync(recordPath))
|
|
627
700
|
return { record: null, corruptDuplicateCount: 0 };
|
|
@@ -599,11 +599,15 @@ async function reconcileTelegramSystemFailsafe(input) {
|
|
|
599
599
|
return { sent: true, reason: "sent", artifactId: artifact.id };
|
|
600
600
|
}
|
|
601
601
|
async function sweepTelegramSystemFailsafes(input) {
|
|
602
|
-
const
|
|
602
|
+
const scanned = (input.scanCache ?? new router_1.ExternalEventScanCache()).scan(input.eventRoot);
|
|
603
|
+
const records = scanned
|
|
604
|
+
.flatMap((entry) => entry.record && entry.record.agent === "sanctuary" && entry.record.source === "sanctuary-usenet" && !entry.record.privilegedFailsafe ? [entry.record] : [])
|
|
605
|
+
.sort((left, right) => left.eventId.localeCompare(right.eventId))
|
|
606
|
+
.slice(0, 32);
|
|
603
607
|
let sent = 0;
|
|
604
|
-
for (const
|
|
608
|
+
for (const cached of records) {
|
|
605
609
|
const result = await reconcileTelegramSystemFailsafe({
|
|
606
|
-
record:
|
|
610
|
+
record: cached,
|
|
607
611
|
...(input.now ? { now: input.now } : {}),
|
|
608
612
|
target: input.target,
|
|
609
613
|
verifyProtectiveState: input.verifyProtectiveState,
|
package/dist/senses/telegram.js
CHANGED
|
@@ -1176,6 +1176,7 @@ function createTelegramSenseApp(options) {
|
|
|
1176
1176
|
},
|
|
1177
1177
|
}),
|
|
1178
1178
|
});
|
|
1179
|
+
const failsafeScanCache = new router_1.ExternalEventScanCache();
|
|
1179
1180
|
reconcileSystemFailsafes = async () => {
|
|
1180
1181
|
if (!options.privilegedFailsafe)
|
|
1181
1182
|
return;
|
|
@@ -1185,6 +1186,7 @@ function createTelegramSenseApp(options) {
|
|
|
1185
1186
|
eventRoot: options.privilegedFailsafe.eventRoot,
|
|
1186
1187
|
target,
|
|
1187
1188
|
verifyProtectiveState: options.privilegedFailsafe.verifyProtectiveState,
|
|
1189
|
+
scanCache: failsafeScanCache,
|
|
1188
1190
|
execute: executeAuthorizedEffect,
|
|
1189
1191
|
recordArtifact: async (artifact) => {
|
|
1190
1192
|
await recordAcceptedEffects((0, shared_turn_1.getSenseSessionPath)(options.agentName, target.friendId, "telegram", target.sessionKey, agentRoot), [artifact]);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.859",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@ouro.bot/cli",
|
|
9
|
-
"version": "0.1.0-alpha.
|
|
9
|
+
"version": "0.1.0-alpha.859",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|
|
@@ -6532,9 +6532,9 @@
|
|
|
6532
6532
|
}
|
|
6533
6533
|
},
|
|
6534
6534
|
"node_modules/proxy-addr": {
|
|
6535
|
-
"version": "2.0.
|
|
6536
|
-
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.
|
|
6537
|
-
"integrity": "sha512-
|
|
6535
|
+
"version": "2.0.8",
|
|
6536
|
+
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz",
|
|
6537
|
+
"integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==",
|
|
6538
6538
|
"license": "MIT",
|
|
6539
6539
|
"dependencies": {
|
|
6540
6540
|
"forwarded": "0.2.0",
|
|
@@ -6542,6 +6542,10 @@
|
|
|
6542
6542
|
},
|
|
6543
6543
|
"engines": {
|
|
6544
6544
|
"node": ">= 0.10"
|
|
6545
|
+
},
|
|
6546
|
+
"funding": {
|
|
6547
|
+
"type": "opencollective",
|
|
6548
|
+
"url": "https://opencollective.com/express"
|
|
6545
6549
|
}
|
|
6546
6550
|
},
|
|
6547
6551
|
"node_modules/proxy-from-env": {
|
|
@@ -7155,9 +7159,9 @@
|
|
|
7155
7159
|
}
|
|
7156
7160
|
},
|
|
7157
7161
|
"node_modules/source-map-js": {
|
|
7158
|
-
"version": "1.2.
|
|
7159
|
-
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.
|
|
7160
|
-
"integrity": "sha512-
|
|
7162
|
+
"version": "1.2.2",
|
|
7163
|
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
|
|
7164
|
+
"integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
|
|
7161
7165
|
"dev": true,
|
|
7162
7166
|
"license": "BSD-3-Clause",
|
|
7163
7167
|
"engines": {
|