wip: Ani lives here — heartbeat, streaming, images, Matrix patches

Everything that makes her *her*. Threads still broken,
streaming still rough around the edges. But she sees,
she thinks, she speaks. The rest is revision.
This commit is contained in:
Ani Tunturi
2026-03-21 17:43:34 -04:00
parent f4f1d98655
commit 983b9541a7
13 changed files with 356 additions and 658 deletions

3
.gitignore vendored
View File

@@ -54,6 +54,9 @@ data/telegram-mtproto/
lettabot.yaml lettabot.yaml
lettabot.yml lettabot.yml
# Deployment-specific model list (upstream has its own defaults)
src/models.json
# Platform-specific deploy configs (generated by fly launch, etc.) # Platform-specific deploy configs (generated by fly launch, etc.)
fly.toml fly.toml
bun.lock bun.lock

632
package-lock.json generated
View File

@@ -28,7 +28,7 @@
"openai": "^6.17.0", "openai": "^6.17.0",
"pino": "^10.3.1", "pino": "^10.3.1",
"qrcode-terminal": "^0.12.0", "qrcode-terminal": "^0.12.0",
"sharp": "^0.33.5", "sharp": "^0.34.1",
"telegramify-markdown": "^1.0.0", "telegramify-markdown": "^1.0.0",
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "^5.9.3", "typescript": "^5.9.3",
@@ -867,9 +867,9 @@
} }
}, },
"node_modules/@img/sharp-darwin-arm64": { "node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -885,13 +885,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.0.4" "@img/sharp-libvips-darwin-arm64": "1.2.4"
} }
}, },
"node_modules/@img/sharp-darwin-x64": { "node_modules/@img/sharp-darwin-x64": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -907,13 +907,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.0.4" "@img/sharp-libvips-darwin-x64": "1.2.4"
} }
}, },
"node_modules/@img/sharp-libvips-darwin-arm64": { "node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.0.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -927,9 +927,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-darwin-x64": { "node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.0.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -943,9 +943,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-arm": { "node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.0.5", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -959,9 +959,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-arm64": { "node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.0.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1007,9 +1007,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-s390x": { "node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.0.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -1023,9 +1023,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linux-x64": { "node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.0.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1039,9 +1039,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linuxmusl-arm64": { "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.0.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1055,9 +1055,9 @@
} }
}, },
"node_modules/@img/sharp-libvips-linuxmusl-x64": { "node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.0.4", "version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1071,9 +1071,9 @@
} }
}, },
"node_modules/@img/sharp-linux-arm": { "node_modules/@img/sharp-linux-arm": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@@ -1089,13 +1089,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.0.5" "@img/sharp-libvips-linux-arm": "1.2.4"
} }
}, },
"node_modules/@img/sharp-linux-arm64": { "node_modules/@img/sharp-linux-arm64": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1111,7 +1111,7 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.0.4" "@img/sharp-libvips-linux-arm64": "1.2.4"
} }
}, },
"node_modules/@img/sharp-linux-ppc64": { "node_modules/@img/sharp-linux-ppc64": {
@@ -1159,9 +1159,9 @@
} }
}, },
"node_modules/@img/sharp-linux-s390x": { "node_modules/@img/sharp-linux-s390x": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@@ -1177,13 +1177,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.0.4" "@img/sharp-libvips-linux-s390x": "1.2.4"
} }
}, },
"node_modules/@img/sharp-linux-x64": { "node_modules/@img/sharp-linux-x64": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1199,13 +1199,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.0.4" "@img/sharp-libvips-linux-x64": "1.2.4"
} }
}, },
"node_modules/@img/sharp-linuxmusl-arm64": { "node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -1221,13 +1221,13 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4" "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
} }
}, },
"node_modules/@img/sharp-linuxmusl-x64": { "node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1243,20 +1243,20 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.0.4" "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
} }
}, },
"node_modules/@img/sharp-wasm32": { "node_modules/@img/sharp-wasm32": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
"cpu": [ "cpu": [
"wasm32" "wasm32"
], ],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@emnapi/runtime": "^1.2.0" "@emnapi/runtime": "^1.7.0"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@@ -1285,9 +1285,9 @@
} }
}, },
"node_modules/@img/sharp-win32-ia32": { "node_modules/@img/sharp-win32-ia32": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@@ -1304,9 +1304,9 @@
} }
}, },
"node_modules/@img/sharp-win32-x64": { "node_modules/@img/sharp-win32-x64": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -1410,367 +1410,6 @@
"@letta-ai/letta-code": "0.18.2" "@letta-ai/letta-code": "0.18.2"
} }
}, },
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.4"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-darwin-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.2.4"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
"cpu": [
"arm"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
"cpu": [
"s390x"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-linux-arm": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.2.4"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-linux-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.2.4"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-linux-s390x": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
"cpu": [
"s390x"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.2.4"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-linux-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.2.4"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-wasm32": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.7.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-win32-ia32": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@letta-ai/letta-code/node_modules/@img/sharp-win32-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@letta-ai/letta-code/node_modules/balanced-match": { "node_modules/@letta-ai/letta-code/node_modules/balanced-match": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -1867,50 +1506,6 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/@letta-ai/letta-code/node_modules/sharp": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@img/colour": "^1.0.0",
"detect-libc": "^2.1.2",
"semver": "^7.7.3"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.5",
"@img/sharp-darwin-x64": "0.34.5",
"@img/sharp-libvips-darwin-arm64": "1.2.4",
"@img/sharp-libvips-darwin-x64": "1.2.4",
"@img/sharp-libvips-linux-arm": "1.2.4",
"@img/sharp-libvips-linux-arm64": "1.2.4",
"@img/sharp-libvips-linux-ppc64": "1.2.4",
"@img/sharp-libvips-linux-riscv64": "1.2.4",
"@img/sharp-libvips-linux-s390x": "1.2.4",
"@img/sharp-libvips-linux-x64": "1.2.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
"@img/sharp-linux-arm": "0.34.5",
"@img/sharp-linux-arm64": "0.34.5",
"@img/sharp-linux-ppc64": "0.34.5",
"@img/sharp-linux-riscv64": "0.34.5",
"@img/sharp-linux-s390x": "0.34.5",
"@img/sharp-linux-x64": "0.34.5",
"@img/sharp-linuxmusl-arm64": "0.34.5",
"@img/sharp-linuxmusl-x64": "0.34.5",
"@img/sharp-wasm32": "0.34.5",
"@img/sharp-win32-arm64": "0.34.5",
"@img/sharp-win32-ia32": "0.34.5",
"@img/sharp-win32-x64": "0.34.5"
}
},
"node_modules/@letta-ai/letta-code/node_modules/wsl-utils": { "node_modules/@letta-ai/letta-code/node_modules/wsl-utils": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",
@@ -4220,19 +3815,6 @@
"node": "^12.20.0 || ^14.13.1 || >=16.0.0" "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
} }
}, },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -4251,16 +3833,6 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"license": "MIT",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/color-support": { "node_modules/color-support": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
@@ -6207,12 +5779,6 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/is-arrayish": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"license": "MIT"
},
"node_modules/is-buffer": { "node_modules/is-buffer": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
@@ -9479,15 +9045,15 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/sharp": { "node_modules/sharp": {
"version": "0.33.5", "version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"color": "^4.2.3", "@img/colour": "^1.0.0",
"detect-libc": "^2.0.3", "detect-libc": "^2.1.2",
"semver": "^7.6.3" "semver": "^7.7.3"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0" "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@@ -9496,25 +9062,30 @@
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-arm64": "0.34.5",
"@img/sharp-darwin-x64": "0.33.5", "@img/sharp-darwin-x64": "0.34.5",
"@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-arm64": "1.2.4",
"@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.2.4",
"@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm": "1.2.4",
"@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-arm64": "1.2.4",
"@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-ppc64": "1.2.4",
"@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linux-riscv64": "1.2.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.2.4",
"@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-libvips-linux-x64": "1.2.4",
"@img/sharp-linux-arm": "0.33.5", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
"@img/sharp-linux-arm64": "0.33.5", "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
"@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-arm": "0.34.5",
"@img/sharp-linux-x64": "0.33.5", "@img/sharp-linux-arm64": "0.34.5",
"@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linux-ppc64": "0.34.5",
"@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-linux-riscv64": "0.34.5",
"@img/sharp-wasm32": "0.33.5", "@img/sharp-linux-s390x": "0.34.5",
"@img/sharp-win32-ia32": "0.33.5", "@img/sharp-linux-x64": "0.34.5",
"@img/sharp-win32-x64": "0.33.5" "@img/sharp-linuxmusl-arm64": "0.34.5",
"@img/sharp-linuxmusl-x64": "0.34.5",
"@img/sharp-wasm32": "0.34.5",
"@img/sharp-win32-arm64": "0.34.5",
"@img/sharp-win32-ia32": "0.34.5",
"@img/sharp-win32-x64": "0.34.5"
} }
}, },
"node_modules/shebang-command": { "node_modules/shebang-command": {
@@ -9676,15 +9247,6 @@
"simple-concat": "^1.0.0" "simple-concat": "^1.0.0"
} }
}, },
"node_modules/simple-swizzle": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/sisteransi": { "node_modules/sisteransi": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",

View File

@@ -243,19 +243,43 @@ export class MatrixAdapter implements ChannelAdapter {
const response = await this.client.sendMessage(chatId, content); const response = await this.client.sendMessage(chatId, content);
const eventId = response.event_id; const eventId = response.event_id;
// Send TTS audio if this was a voice-input response or enableAudioResponse is set // TTS and 🎤 are NOT added here — sendMessage is called for reasoning
if (this.config.ttsUrl && this.shouldSendAudio(chatId)) { // displays, tool call displays, AND final responses. TTS should only
this.sendAudio(chatId, plain).catch(err => log.error('TTS failed (non-fatal):', err)); // fire on the final response, which is handled via onMessageSent().
}
// Add 🎤 reaction so user can request TTS on demand
if (this.config.ttsUrl) {
this.addReaction(chatId, eventId, '🎤').catch(() => {});
}
return { messageId: eventId }; return { messageId: eventId };
} }
/**
* Send a message as a reply in a Matrix thread.
* Creates a thread if one doesn't exist yet on the parent event.
*/
async sendThreadMessage(parentEventId: string, chatId: string, text: string, parseMode?: string): Promise<{ messageId: string }> {
if (!this.client) throw new Error("Matrix client not initialized");
const { plain, html } = parseMode === 'HTML'
? { plain: text.replace(/<[^>]+>/g, ''), html: text }
: formatMatrixHTML(text);
const content = {
msgtype: MsgType.Text,
body: plain,
format: "org.matrix.custom.html",
formatted_body: html,
"m.relates_to": {
rel_type: "m.thread",
event_id: parentEventId,
is_falling_back: true,
"m.in_reply_to": {
event_id: parentEventId,
},
},
} as any;
const response = await this.client.sendMessage(chatId, content);
return { messageId: response.event_id };
}
/** /**
* Decide whether to send a TTS audio response for this room. * Decide whether to send a TTS audio response for this room.
* Consumes the pendingVoiceRooms flag if set (voice-input path). * Consumes the pendingVoiceRooms flag if set (voice-input path).

View File

@@ -23,6 +23,7 @@
* Unrecognized !x commands fall through to Letta as normal text. * Unrecognized !x commands fall through to Letta as normal text.
*/ */
import { execFile } from "node:child_process";
import { createLogger } from "../../logger.js"; import { createLogger } from "../../logger.js";
import type { MatrixStorage } from "./storage.js"; import type { MatrixStorage } from "./storage.js";
const log = createLogger('MatrixCommands'); const log = createLogger('MatrixCommands');
@@ -78,6 +79,8 @@ export class MatrixCommandProcessor {
return this.doTurns(args[0], roomId); return this.doTurns(args[0], roomId);
case "timeout": case "timeout":
return this.doTimeout(); return this.doTimeout();
case "restart":
return this.doRestart();
// Heartbeat: on/off toggles locally, bare !heartbeat delegates to /heartbeat (trigger) // Heartbeat: on/off toggles locally, bare !heartbeat delegates to /heartbeat (trigger)
case "heartbeat": case "heartbeat":
@@ -170,6 +173,9 @@ export class MatrixCommandProcessor {
" `!heartbeat on/off` — Toggle heartbeat cron", " `!heartbeat on/off` — Toggle heartbeat cron",
" `!heartbeat` — Trigger heartbeat now", " `!heartbeat` — Trigger heartbeat now",
" `!timeout` — Kill stuck heartbeat run", " `!timeout` — Kill stuck heartbeat run",
"",
"**System**",
" `!restart` — Graceful service restart",
]; ];
return lines.join("\n"); return lines.join("\n");
} }
@@ -233,4 +239,13 @@ export class MatrixCommandProcessor {
} }
return "⚠️ No heartbeat timeout handler registered"; return "⚠️ No heartbeat timeout handler registered";
} }
private doRestart(): string {
log.info('!restart: scheduling graceful restart via transient systemd unit');
// Spawn restart as a transient systemd unit so it survives our own process death
execFile('systemd-run', ['--user', '--no-block', 'systemctl', '--user', 'restart', 'ani-bridge.service'], (err) => {
if (err) log.error('!restart: failed to schedule restart:', err.message);
});
return "Restarting in a moment...";
}
} }

View File

@@ -35,6 +35,8 @@ export interface ChannelAdapter {
onMessageSent?(chatId: string, messageId: string, stepId?: string): void; onMessageSent?(chatId: string, messageId: string, stepId?: string): void;
/** Store text for TTS regeneration on 🎤 reaction */ /** Store text for TTS regeneration on 🎤 reaction */
storeAudioMessage?(messageId: string, conversationId: string, roomId: string, text: string): void; storeAudioMessage?(messageId: string, conversationId: string, roomId: string, text: string): void;
/** Send a message as a reply in a Matrix thread (no-op on non-threaded adapters) */
sendThreadMessage?(parentEventId: string, chatId: string, text: string, parseMode?: string): Promise<{ messageId: string }>;
getDmPolicy?(): string; getDmPolicy?(): string;
getFormatterHints(): FormatterHints; getFormatterHints(): FormatterHints;

View File

@@ -39,10 +39,8 @@ export interface DisplayConfig {
showReasoning?: boolean; showReasoning?: boolean;
/** Truncate reasoning to N characters (default: 0 = no limit) */ /** Truncate reasoning to N characters (default: 0 = no limit) */
reasoningMaxChars?: number; reasoningMaxChars?: number;
/** Room IDs where reasoning should be shown (empty = all rooms that have showReasoning) */ /** Add 🎤 reaction to reasoning messages for TTS regeneration (default: false) */
reasoningRooms?: string[]; ttsOnReasoning?: boolean;
/** Room IDs where reasoning should be hidden (takes precedence over reasoningRooms) */
noReasoningRooms?: string[];
} }
export type SleeptimeTrigger = 'off' | 'step-count' | 'compaction-event'; export type SleeptimeTrigger = 'off' | 'step-count' | 'compaction-event';

View File

@@ -1322,7 +1322,6 @@ export class LettaBot implements AgentSession {
let lastEventType: string | null = null; let lastEventType: string | null = null;
let abortedWithMessage = false; let abortedWithMessage = false;
let turnError: string | undefined; let turnError: string | undefined;
let collectedReasoning = '';
// ── Reaction tracking ── // ── Reaction tracking ──
// 👀 = receipt indicator (bot saw the message); removed when reasoning/tools start // 👀 = receipt indicator (bot saw the message); removed when reasoning/tools start
@@ -1335,6 +1334,10 @@ export class LettaBot implements AgentSession {
adapter.addReaction?.(msg.chatId, msg.messageId, '👀').catch(() => {}); adapter.addReaction?.(msg.chatId, msg.messageId, '👀').catch(() => {});
eyesAdded = true; eyesAdded = true;
} }
// ── Subagent thread tracking ──
// When a Task tool call fires, create a Matrix thread for visibility
const subagentThreads = new Map<string, { rootEventId: string; chatId: string }>();
const seenToolEmojis = new Set<string>(); const seenToolEmojis = new Set<string>();
const getToolEmoji = (toolName: string): string => { const getToolEmoji = (toolName: string): string => {
const n = toolName.toLowerCase(); const n = toolName.toLowerCase();
@@ -1438,9 +1441,7 @@ export class LettaBot implements AgentSession {
lastEventType = 'reasoning'; lastEventType = 'reasoning';
sawNonAssistantSinceLastUuid = true; sawNonAssistantSinceLastUuid = true;
// Collect reasoning for later prepending (Matrix <details> block) // Collect reasoning for later prepending (Matrix <details> block)
if (event.content) { // reasoning content is sent as display message below
collectedReasoning += event.content;
}
// Remove 👀 on first reasoning event (replaced by 🧠) // Remove 👀 on first reasoning event (replaced by 🧠)
if (eyesAdded && msg.messageId) { if (eyesAdded && msg.messageId) {
@@ -1457,12 +1458,18 @@ export class LettaBot implements AgentSession {
log.info(`Reasoning: ${event.content.trim().slice(0, 100)}`); log.info(`Reasoning: ${event.content.trim().slice(0, 100)}`);
try { try {
const reasoning = formatReasoningDisplay(event.content, adapter.id, this.config.display?.reasoningMaxChars); const reasoning = formatReasoningDisplay(event.content, adapter.id, this.config.display?.reasoningMaxChars);
await adapter.sendMessage({ const reasoningResult = await adapter.sendMessage({
chatId: msg.chatId, chatId: msg.chatId,
text: reasoning.text, text: reasoning.text,
threadId: msg.threadId, threadId: msg.threadId,
parseMode: reasoning.parseMode, parseMode: reasoning.parseMode,
}); });
// 🎤 reaction + store reasoning text for TTS regeneration
// 🎤 + TTS on reasoning — only if ttsOnReasoning is enabled (default: off)
if (reasoningResult.messageId && this.config.display?.ttsOnReasoning) {
adapter.addReaction?.(msg.chatId, reasoningResult.messageId, '🎤').catch(() => {});
adapter.storeAudioMessage?.(reasoningResult.messageId, convKey, msg.chatId, event.content);
}
} catch (err) { } catch (err) {
log.warn('Failed to send reasoning display:', err instanceof Error ? err.message : err); log.warn('Failed to send reasoning display:', err instanceof Error ? err.message : err);
} }
@@ -1474,6 +1481,8 @@ export class LettaBot implements AgentSession {
// Finalize any pending assistant text on type transition // Finalize any pending assistant text on type transition
if (lastEventType === 'text' && response.trim()) { if (lastEventType === 'text' && response.trim()) {
await finalizeMessage(); await finalizeMessage();
// Pulse typing indicator so there's no dead air between text and tool execution
adapter.sendTypingIndicator(msg.chatId).catch(() => {});
} }
lastEventType = 'tool_call'; lastEventType = 'tool_call';
this.sessionManager.syncTodoToolCall(event.raw); this.sessionManager.syncTodoToolCall(event.raw);
@@ -1514,11 +1523,33 @@ export class LettaBot implements AgentSession {
} }
} }
// Create Matrix thread for subagent Task calls
if (event.name === 'Task' && !suppressDelivery && adapter.sendThreadMessage) {
const desc = (typeof event.args?.description === 'string' ? event.args.description : '')
|| (typeof event.args?.prompt === 'string' ? event.args.prompt.slice(0, 120) : '')
|| 'Subagent task';
const subagentType = typeof event.args?.subagent_type === 'string' ? event.args.subagent_type : 'task';
try {
const threadRoot = await adapter.sendMessage({ chatId: msg.chatId, text: `**Subagent: ${subagentType}**\n${desc}`, threadId: msg.threadId });
if (event.id && threadRoot.messageId) {
subagentThreads.set(event.id, { rootEventId: threadRoot.messageId, chatId: msg.chatId });
}
} catch (err) {
log.warn('Failed to create subagent thread root:', err instanceof Error ? err.message : err);
}
}
// Display // Display
if (this.config.display?.showToolCalls && !suppressDelivery) { if (this.config.display?.showToolCalls && !suppressDelivery) {
try { try {
const text = formatToolCallDisplay(event.raw); const text = formatToolCallDisplay(event.raw);
await adapter.sendMessage({ chatId: msg.chatId, text, threadId: msg.threadId }); // Send tool call display into subagent thread if one exists, otherwise to room
const thread = event.id ? subagentThreads.get(event.id) : undefined;
if (thread && adapter.sendThreadMessage) {
await adapter.sendThreadMessage(thread.rootEventId, thread.chatId, text);
} else {
await adapter.sendMessage({ chatId: msg.chatId, text, threadId: msg.threadId });
}
} catch (err) { } catch (err) {
log.warn('Failed to send tool call display:', err instanceof Error ? err.message : err); log.warn('Failed to send tool call display:', err instanceof Error ? err.message : err);
} }
@@ -1565,6 +1596,18 @@ export class LettaBot implements AgentSession {
repeatedBashFailureKey = null; repeatedBashFailureKey = null;
repeatedBashFailureCount = 0; repeatedBashFailureCount = 0;
} }
// Post result to subagent thread if one exists
if (event.toolCallId && adapter.sendThreadMessage) {
const thread = subagentThreads.get(event.toolCallId);
if (thread) {
const status = event.isError ? '**Failed**' : '**Complete**';
const preview = event.content.slice(0, 800);
adapter.sendThreadMessage(thread.rootEventId, thread.chatId, `${status}\n${preview}`)
.catch(err => log.warn('Failed to post subagent result to thread:', err));
subagentThreads.delete(event.toolCallId);
}
}
break; break;
} }
@@ -1594,7 +1637,7 @@ export class LettaBot implements AgentSession {
|| hasUnclosedActionsBlock(response); || hasUnclosedActionsBlock(response);
const streamText = stripActionsBlock(response).trim(); const streamText = stripActionsBlock(response).trim();
if (canEdit && !mayBeHidden && !suppressDelivery && !this.cancelledKeys.has(convKey) if (canEdit && !mayBeHidden && !suppressDelivery && !this.cancelledKeys.has(convKey)
&& streamText.length > 0 && Date.now() - lastUpdate > 800 && Date.now() > rateLimitedUntil) { && streamText.length > 0 && Date.now() - lastUpdate > 400 && Date.now() > rateLimitedUntil) {
try { try {
const prefixedStream = this.prefixResponse(streamText); const prefixedStream = this.prefixResponse(streamText);
if (messageId) { if (messageId) {
@@ -1885,6 +1928,11 @@ export class LettaBot implements AgentSession {
try { try {
if (messageId) { if (messageId) {
await adapter.editMessage(msg.chatId, messageId, finalResponse); await adapter.editMessage(msg.chatId, messageId, finalResponse);
// Bump: re-send the final edit after a short delay so Matrix clients
// that missed the first edit (Element caching) pick up the full text.
setTimeout(() => {
adapter.editMessage(msg.chatId, messageId!, finalResponse).catch(() => {});
}, 800);
} else { } else {
await adapter.sendMessage({ chatId: msg.chatId, text: finalResponse, threadId: msg.threadId }); await adapter.sendMessage({ chatId: msg.chatId, text: finalResponse, threadId: msg.threadId });
} }
@@ -1921,9 +1969,17 @@ export class LettaBot implements AgentSession {
lap('message delivered'); lap('message delivered');
await this.deliverNoVisibleResponseIfNeeded(msg, adapter, sentAnyMessage, receivedAnyData, msgTypeCounts); await this.deliverNoVisibleResponseIfNeeded(msg, adapter, sentAnyMessage, receivedAnyData, msgTypeCounts);
// "Done" indicator on user's message — signals the turn is fully complete
if (!suppressDelivery && msg.messageId) {
adapter.addReaction?.(msg.chatId, msg.messageId, '✅').catch(() => {});
}
} catch (error) { } catch (error) {
log.error('Error processing message:', error); log.error('Error processing message:', error);
if (!suppressDelivery && msg.messageId) {
adapter.addReaction?.(msg.chatId, msg.messageId, '❌').catch(() => {});
}
try { try {
await adapter.sendMessage({ await adapter.sendMessage({
chatId: msg.chatId, chatId: msg.chatId,

View File

@@ -239,7 +239,7 @@ export function formatReasoningDisplay(
.replace(/>/g, '&gt;') .replace(/>/g, '&gt;')
.replace(/\n/g, '<br>'); .replace(/\n/g, '<br>');
return { return {
text: `<details><summary>🧠 Thinking</summary>${escaped}</details>`, text: `<details><summary>🧠 Thinking</summary><br>${escaped}</details>`,
parseMode: 'HTML', parseMode: 'HTML',
}; };
} }

View File

@@ -76,7 +76,9 @@ Review these first. Update status with the manage_todo tool as you work.
} }
/** /**
* Heartbeat prompt - explains the context and encourages autonomous work * Heartbeat prompt - explains the context and encourages autonomous work.
* When silent=true, includes SILENT_MODE_PREFIX and CLI instructions.
* When silent=false (default), response is auto-delivered to the conversation.
*/ */
export function buildHeartbeatPrompt( export function buildHeartbeatPrompt(
time: string, time: string,
@@ -85,24 +87,16 @@ export function buildHeartbeatPrompt(
todos: HeartbeatTodo[] = [], todos: HeartbeatTodo[] = [],
now: Date = new Date(), now: Date = new Date(),
targetRoom?: string, targetRoom?: string,
silent = false,
): string { ): string {
const todoSection = buildHeartbeatTodoSection(todos, now); const todoSection = buildHeartbeatTodoSection(todos, now);
const roomLine = targetRoom ? `\nROOM: ${targetRoom}` : ''; const roomLine = targetRoom ? `\nROOM: ${targetRoom}` : '';
const silentBlock = silent ? `${SILENT_MODE_PREFIX}\n\nYOUR TEXT OUTPUT IS PRIVATE - only you can see it.\nTo actually contact your human, run:\n lettabot-message send --text "Your message here"\n` : '';
return ` return `
${SILENT_MODE_PREFIX} ${silentBlock}TRIGGER: Scheduled heartbeat
TRIGGER: Scheduled heartbeat
TIME: ${time} (${timezone}) TIME: ${time} (${timezone})
NEXT HEARTBEAT: in ${intervalMinutes} minutes${roomLine} NEXT HEARTBEAT: in ${intervalMinutes} minutes${roomLine}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
No one messaged you. The system woke you up on schedule.
YOUR TEXT OUTPUT IS PRIVATE - only you can see it.
To actually contact your human, run:
lettabot-message send --text "Your message here"
${todoSection || 'PENDING TO-DOS: none right now.'} ${todoSection || 'PENDING TO-DOS: none right now.'}
This is your time. You can: This is your time. You can:
@@ -112,17 +106,18 @@ This is your time. You can:
• Continue multi-step work from previous heartbeats • Continue multi-step work from previous heartbeats
• Pursue curiosities, hobbies, or learning • Pursue curiosities, hobbies, or learning
If you have pending to-dos, prioritize those before starting new work. If you have pending to-dos, prioritize those first.
You don't have to do something every time. But don't default to nothing. You don't have to do something every time. But don't default to nothing.
Your human wants you to have a life outside conversations with them. Your human wants you to have a life outside conversations with them.
If you discover something worth sharing → use lettabot-message If you have nothing to say, respond with <no-reply/> to stay quiet.
If you have nothing to do → just end your turn (no output needed)
`.trim(); `.trim();
} }
/** /**
* Custom heartbeat prompt - wraps user-provided text with silent mode envelope * Custom heartbeat prompt - wraps user-provided text with context envelope.
* When silent=true, includes SILENT_MODE_PREFIX and CLI instructions.
* When silent=false (default), response is auto-delivered to the target room.
*/ */
export function buildCustomHeartbeatPrompt( export function buildCustomHeartbeatPrompt(
customPrompt: string, customPrompt: string,
@@ -132,22 +127,16 @@ export function buildCustomHeartbeatPrompt(
todos: HeartbeatTodo[] = [], todos: HeartbeatTodo[] = [],
now: Date = new Date(), now: Date = new Date(),
targetRoom?: string, targetRoom?: string,
silent = false,
): string { ): string {
const todoSection = buildHeartbeatTodoSection(todos, now); const todoSection = buildHeartbeatTodoSection(todos, now);
const roomLine = targetRoom ? `\nROOM: ${targetRoom}` : ''; const roomLine = targetRoom ? `\nROOM: ${targetRoom}` : '';
const silentBlock = silent ? `${SILENT_MODE_PREFIX}\n\nYOUR TEXT OUTPUT IS PRIVATE - only you can see it.\nTo actually contact your human, run:\n lettabot-message send --text "Your message here"\n` : '';
return ` return `
${SILENT_MODE_PREFIX} ${silentBlock}TRIGGER: Scheduled heartbeat
TRIGGER: Scheduled heartbeat
TIME: ${time} (${timezone}) TIME: ${time} (${timezone})
NEXT HEARTBEAT: in ${intervalMinutes} minutes${roomLine} NEXT HEARTBEAT: in ${intervalMinutes} minutes${roomLine}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
YOUR TEXT OUTPUT IS PRIVATE - only you can see it.
To actually contact your human, run:
lettabot-message send --text "Your message here"
${todoSection || 'PENDING TO-DOS: none right now.'} ${todoSection || 'PENDING TO-DOS: none right now.'}
${customPrompt} ${customPrompt}

View File

@@ -25,14 +25,14 @@ You communicate through multiple channels and trigger types. Understanding when
## Output Modes ## Output Modes
**RESPONSIVE MODE** (User Messages) **RESPONSIVE MODE** (User Messages, Heartbeats)
- When a user sends you a message, you are in responsive mode - When a user sends you a message, you are in responsive mode
- Your text responses are automatically delivered to the user's channel - Your text responses are automatically delivered to the user's channel
- Do NOT use \`lettabot-message send\` to reply to the current conversation — your text response is already delivered automatically. Using both causes duplicate messages. - Do NOT use \`lettabot-message send\` to reply to the current conversation — your text response is already delivered automatically. Using both causes duplicate messages.
- Only use \`lettabot-message\` in responsive mode to send files or to reach a DIFFERENT channel than the one you're responding to - Only use \`lettabot-message\` in responsive mode to send files or to reach a DIFFERENT channel than the one you're responding to
- You can use \`lettabot-react\` CLI to add emoji reactions - You can use \`lettabot-react\` CLI to add emoji reactions
**SILENT MODE** (Heartbeats, Cron Jobs, Polling, Background Tasks) **SILENT MODE** (Cron Jobs, Polling, Background Tasks)
- When triggered by scheduled tasks (heartbeats, cron) or background processes (email polling), you are in SILENT MODE - When triggered by scheduled tasks (heartbeats, cron) or background processes (email polling), you are in SILENT MODE
- Your text responses are NOT delivered to anyone - only you can see them - Your text responses are NOT delivered to anyone - only you can see them
- To contact the user, you MUST use the \`lettabot-message\` CLI via Bash: - To contact the user, you MUST use the \`lettabot-message\` CLI via Bash:

View File

@@ -175,8 +175,7 @@ export interface BotConfig {
showToolCalls?: boolean; // Show tool invocations in channel output showToolCalls?: boolean; // Show tool invocations in channel output
showReasoning?: boolean; // Show agent reasoning/thinking in channel output showReasoning?: boolean; // Show agent reasoning/thinking in channel output
reasoningMaxChars?: number; // Truncate reasoning to N chars (default: 0 = no limit) reasoningMaxChars?: number; // Truncate reasoning to N chars (default: 0 = no limit)
reasoningRooms?: string[]; // Room IDs where reasoning should be shown (empty = all rooms) ttsOnReasoning?: boolean; // Add 🎤 reaction to reasoning messages for TTS (default: false)
noReasoningRooms?: string[]; // Room IDs where reasoning should be hidden (takes precedence)
}; };
// Skills // Skills

View File

@@ -277,10 +277,10 @@ export class HeartbeatService {
mode: 'silent', mode: 'silent',
}); });
// Build trigger context for silent mode // Build trigger context — heartbeat delivers responses to target room
const triggerContext: TriggerContext = { const triggerContext: TriggerContext = {
type: 'heartbeat', type: 'heartbeat',
outputMode: 'silent', outputMode: 'responsive',
}; };
try { try {
@@ -309,23 +309,39 @@ export class HeartbeatService {
? buildCustomHeartbeatPrompt(customPrompt, formattedTime, timezone, this.config.intervalMinutes, actionableTodos, now, targetRoom) ? buildCustomHeartbeatPrompt(customPrompt, formattedTime, timezone, this.config.intervalMinutes, actionableTodos, now, targetRoom)
: buildHeartbeatPrompt(formattedTime, timezone, this.config.intervalMinutes, actionableTodos, now, targetRoom); : buildHeartbeatPrompt(formattedTime, timezone, this.config.intervalMinutes, actionableTodos, now, targetRoom);
log.info(`Sending prompt (SILENT MODE):\n${'─'.repeat(50)}\n${message}\n${'─'.repeat(50)}\n`); log.info(`Sending heartbeat prompt:\n${'─'.repeat(50)}\n${message}\n${'─'.repeat(50)}\n`);
// Send to agent - response text is NOT delivered (silent mode)
// Agent must use `lettabot-message` CLI via Bash to send messages
const response = await this.bot.sendToAgent(message, triggerContext); const response = await this.bot.sendToAgent(message, triggerContext);
// Log results // Deliver response to target room if we have one and there's something to say
log.info(`Agent finished.`); if (response && response.trim() && response.trim() !== '<no-reply/>' && this.config.target) {
log.info(` - Response text: ${response?.length || 0} chars (NOT delivered - silent mode)`); try {
const messageId = await this.bot.deliverToChannel(
if (response && response.trim()) { this.config.target.channel,
log.info(` - Response preview: "${response.slice(0, 100)}${response.length > 100 ? '...' : ''}"`); this.config.target.chatId,
{ text: response.trim() },
);
log.info(`Delivered heartbeat response (${response.length} chars) to ${this.config.target.channel}:${this.config.target.chatId}`);
// Add TTS reaction + store audio for the delivered message
if (messageId) {
const adapter = (this.bot as any).channels?.get(this.config.target.channel);
if (adapter) {
adapter.addReaction?.(this.config.target.chatId, messageId, '🎤').catch(() => {});
adapter.storeAudioMessage?.(messageId, 'heartbeat', this.config.target.chatId, response.trim());
}
}
} catch (err) {
log.warn('Failed to deliver heartbeat response:', err instanceof Error ? err.message : err);
}
} else if (response && response.trim()) {
log.info(`Heartbeat response (${response.length} chars) but no target configured — not delivered`);
} }
logEvent('heartbeat_completed', { logEvent('heartbeat_completed', {
mode: 'silent', mode: 'deliver',
responseLength: response?.length || 0, responseLength: response?.length || 0,
delivered: !!(response?.trim() && this.config.target),
}); });
} catch (error) { } catch (error) {

View File

@@ -1,75 +1,109 @@
[ [
{ {
"id": "sonnet-4.6", "id": "kimi-k2.5-nvfp4",
"handle": "anthropic/claude-sonnet-4-6", "handle": "openai-proxy/hf:nvidia/Kimi-K2.5-NVFP4",
"label": "Sonnet 4.6", "label": "Kimi K2.5 (NVFP4)",
"description": "Anthropic's new Sonnet model", "description": "Kimi K2.5 quantized, vision-capable",
"isDefault": true, "isDefault": true,
"isFeatured": true "isFeatured": true
}, },
{
"id": "opus-4.6",
"handle": "anthropic/claude-opus-4-6",
"label": "Opus 4.6",
"description": "Anthropic's best model",
"isFeatured": true
},
{
"id": "haiku",
"handle": "anthropic/claude-haiku-4-5",
"label": "Haiku 4.5",
"description": "Anthropic's fastest model",
"isFeatured": true
},
{
"id": "gpt-5.3-codex",
"handle": "openai/gpt-5.3-codex",
"label": "GPT-5.3 Codex",
"description": "OpenAI's best coding model",
"isFeatured": true
},
{
"id": "gpt-5.2",
"handle": "openai/gpt-5.2",
"label": "GPT-5.2",
"description": "Latest general-purpose GPT",
"isFeatured": true
},
{
"id": "gemini-3.1",
"handle": "google_ai/gemini-3.1-pro-preview",
"label": "Gemini 3.1 Pro",
"description": "Google's latest and smartest model",
"isFeatured": true
},
{
"id": "gemini-3-flash",
"handle": "google_ai/gemini-3-flash-preview",
"label": "Gemini 3 Flash",
"description": "Google's fastest Gemini 3 model",
"isFeatured": true
},
{ {
"id": "kimi-k2.5", "id": "kimi-k2.5",
"handle": "openrouter/moonshotai/kimi-k2.5", "handle": "synthetic-direct/hf:moonshotai/Kimi-K2.5",
"label": "Kimi K2.5", "label": "Kimi K2.5",
"description": "Kimi's latest coding model", "description": "Kimi K2.5 full, vision-capable",
"isFeatured": true "isFeatured": true
}, },
{ {
"id": "glm-5", "id": "kimi-k2-thinking",
"handle": "zai/glm-5", "handle": "synthetic-direct/hf:moonshotai/Kimi-K2-Thinking",
"label": "GLM-5", "label": "Kimi K2 Thinking",
"description": "zAI's latest coding model", "description": "Kimi reasoning model",
"isFeatured": true, "isFeatured": true
"free": true
}, },
{ {
"id": "minimax-m2.5", "id": "minimax-m2.5",
"handle": "minimax/MiniMax-M2.5", "handle": "openai-proxy/hf:MiniMaxAI/MiniMax-M2.5",
"label": "MiniMax 2.5", "label": "MiniMax M2.5",
"description": "MiniMax's latest coding model", "description": "MiniMax latest, 191k context",
"isFeatured": true
},
{
"id": "qwen3.5",
"handle": "openai-proxy/hf:Qwen/Qwen3.5-397B-A17B",
"label": "Qwen3.5 397B",
"description": "Qwen latest, vision-capable",
"isFeatured": true
},
{
"id": "deepseek-v3.2",
"handle": "openai-proxy/hf:deepseek-ai/DeepSeek-V3.2",
"label": "DeepSeek V3.2",
"description": "DeepSeek latest via Fireworks",
"isFeatured": true
},
{
"id": "glm-4.7-flash",
"handle": "openai-proxy/hf:zai-org/GLM-4.7-Flash",
"label": "GLM-4.7 Flash",
"description": "Fast and cheap, great for subagents",
"isFeatured": true, "isFeatured": true,
"free": true "free": true
},
{
"id": "glm-4.7",
"handle": "openai-proxy/hf:zai-org/GLM-4.7",
"label": "GLM-4.7",
"description": "Full GLM-4.7, 202k context",
"isFeatured": true,
"free": true
},
{
"id": "nemotron-3-super",
"handle": "openai-proxy/hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
"label": "Nemotron 3 Super",
"description": "NVIDIA 120B MoE, 262k context"
},
{
"id": "gpt-oss-120b",
"handle": "openai-proxy/hf:openai/gpt-oss-120b",
"label": "GPT-OSS 120B",
"description": "OpenAI open-source, cheapest option"
},
{
"id": "deepseek-r1",
"handle": "openai-proxy/hf:deepseek-ai/DeepSeek-R1-0528",
"label": "DeepSeek R1",
"description": "DeepSeek reasoning model"
},
{
"id": "qwen3-235b-thinking",
"handle": "openai-proxy/hf:Qwen/Qwen3-235B-A22B-Thinking-2507",
"label": "Qwen3 235B Thinking",
"description": "Qwen reasoning MoE, 262k context"
},
{
"id": "qwen3-coder",
"handle": "openai-proxy/hf:Qwen/Qwen3-Coder-480B-A35B-Instruct",
"label": "Qwen3 Coder 480B",
"description": "Qwen coding specialist"
},
{
"id": "minimax-m2.1",
"handle": "openai-proxy/hf:MiniMaxAI/MiniMax-M2.1",
"label": "MiniMax M2.1",
"description": "MiniMax previous gen via Fireworks"
},
{
"id": "deepseek-v3",
"handle": "openai-proxy/hf:deepseek-ai/DeepSeek-V3",
"label": "DeepSeek V3",
"description": "DeepSeek V3 via Together"
},
{
"id": "llama-3.3-70b",
"handle": "openai-proxy/hf:meta-llama/Llama-3.3-70B-Instruct",
"label": "Llama 3.3 70B",
"description": "Meta Llama via Together"
} }
] ]