@ouro.bot/cli 0.1.0-alpha.784 → 0.1.0-alpha.785

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 CHANGED
@@ -1,6 +1,12 @@
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.785",
6
+ "changes": [
7
+ "Harden Telegram effect recovery so invalid transport-control artifacts cannot crash the butler startup loop."
8
+ ]
9
+ },
4
10
  {
5
11
  "version": "0.1.0-alpha.784",
6
12
  "changes": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.784",
2
+ "runtimeVersion": "0.1.0-alpha.785",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-09-03T00:00:00.000Z"
5
5
  }
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0"?>
2
2
  <Container version="2">
3
3
  <Name>Mendelow Cloud Butler</Name>
4
- <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.784</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.785</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -241,6 +241,25 @@ class FileTelegramEffectJournal {
241
241
  const filePath = this.artifactPath(id);
242
242
  return fs.existsSync(filePath) ? this.read(id) : null;
243
243
  }
244
+ quarantineInvalid(id, reason) {
245
+ this.assertRootIdentity();
246
+ const filePath = this.artifactPath(id);
247
+ if (!fs.existsSync(filePath))
248
+ return;
249
+ const quarantineRoot = path.join(path.dirname(this.root), `${path.basename(this.root)}.quarantine`);
250
+ fs.mkdirSync(quarantineRoot, { recursive: true, mode: 0o700 });
251
+ fs.chmodSync(quarantineRoot, 0o700);
252
+ const quarantinePath = path.join(quarantineRoot, `${id}.${Date.now()}.${process.pid}.invalid.json`);
253
+ fs.renameSync(filePath, quarantinePath);
254
+ fs.chmodSync(quarantinePath, 0o600);
255
+ (0, runtime_1.emitNervesEvent)({
256
+ level: "error",
257
+ component: "senses",
258
+ event: "senses.telegram_effect_artifact_quarantined",
259
+ message: "quarantined invalid Telegram effect artifact",
260
+ meta: { artifactId: id, reason },
261
+ });
262
+ }
244
263
  write(artifact) {
245
264
  this.assertRootIdentity();
246
265
  const filePath = this.artifactPath(artifact.id);
@@ -268,6 +287,28 @@ class FileTelegramEffectJournal {
268
287
  .filter((entry) => entry.isFile() && /^[a-f0-9]{64}\.json$/u.test(entry.name))
269
288
  .map((entry) => this.read(entry.name.slice(0, -5)));
270
289
  }
290
+ listRecoverable() {
291
+ this.assertRootIdentity();
292
+ const entries = fs.readdirSync(this.descriptorRoot, { withFileTypes: true });
293
+ this.assertRootIdentity();
294
+ const artifacts = [];
295
+ for (const entry of entries.filter((candidate) => candidate.isFile() && /^[a-f0-9]{64}\.json$/u.test(candidate.name))) {
296
+ const id = entry.name.slice(0, -5);
297
+ try {
298
+ artifacts.push(this.read(id));
299
+ }
300
+ catch (error) {
301
+ (0, runtime_1.emitNervesEvent)({
302
+ level: "error",
303
+ component: "senses",
304
+ event: "senses.telegram_effect_recovery_artifact_skipped",
305
+ message: "skipped invalid Telegram effect artifact during recovery",
306
+ meta: { artifactId: id, error: error instanceof Error ? error.message : String(error) },
307
+ });
308
+ }
309
+ }
310
+ return artifacts;
311
+ }
271
312
  }
272
313
  exports.FileTelegramEffectJournal = FileTelegramEffectJournal;
273
314
  function prepareTelegramEffect(store, input) {
@@ -281,7 +322,15 @@ function prepareTelegramEffect(store, input) {
281
322
  if (target.kind === "admission_gate" && input.authorization.transport.chatId !== target.chatId)
282
323
  throw new Error("Telegram admission transport route changed");
283
324
  const id = artifactId(idempotencyKey);
284
- const existing = store.readIfExists(id);
325
+ let existing = null;
326
+ try {
327
+ existing = store.readIfExists(id);
328
+ }
329
+ catch (error) {
330
+ if (!isTransportOnlyControl({ authorClass: input.authorClass, effect: input.effect, target }))
331
+ throw error;
332
+ store.quarantineInvalid(id, error instanceof Error ? error.message : String(error));
333
+ }
285
334
  if (existing) {
286
335
  if (JSON.stringify(existing.target) !== JSON.stringify(target)
287
336
  || existing.authorClass !== input.authorClass
@@ -526,7 +575,7 @@ async function sweepTelegramSystemFailsafes(input) {
526
575
  }
527
576
  async function recoverTelegramEffectOutbox(input) {
528
577
  const limit = Math.min(Math.max(input.maxArtifacts ?? 20, 1), 100);
529
- const candidates = input.store.list()
578
+ const candidates = input.store.listRecoverable()
530
579
  .filter((artifact) => (!input.matches || input.matches(artifact))
531
580
  && !artifact.parts.some((part) => part.state === "attempting" || part.state === "indeterminate")
532
581
  && artifact.parts.some((part) => part.state === "prepared" && (part.attempts ?? 0) < 3))
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.784",
3
+ "version": "0.1.0-alpha.785",
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.784",
9
+ "version": "0.1.0-alpha.785",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.78.0",
12
12
  "@azure/identity": "^4.13.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.784",
3
+ "version": "0.1.0-alpha.785",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },