obol-ai 0.2.26 → 0.2.27
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/package.json +1 -1
- package/src/clean.js +5 -3
- package/src/telegram/commands/admin.js +29 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obol-ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.27",
|
|
4
4
|
"description": "Self-evolving AI assistant that learns, remembers, and acts on its own. Persistent vector memory, self-rewriting personality, proactive heartbeats.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
package/src/clean.js
CHANGED
|
@@ -10,6 +10,7 @@ const ALLOWED_ROOT_FILES = new Set([
|
|
|
10
10
|
'.first-run-done',
|
|
11
11
|
'.post-setup-done',
|
|
12
12
|
]);
|
|
13
|
+
const TEMP_DOTDIRS = new Set(['.typst', '.cache', '.tmp']);
|
|
13
14
|
|
|
14
15
|
// Extensions that belong in scripts/
|
|
15
16
|
const SCRIPT_EXTS = new Set(['.js', '.ts', '.sh', '.py', '.rb', '.php', '.go', '.rs', '.pl', '.lua']);
|
|
@@ -56,8 +57,9 @@ function scanWorkspace(userDir) {
|
|
|
56
57
|
|
|
57
58
|
for (const entry of entries) {
|
|
58
59
|
if (entry.isDirectory()) {
|
|
59
|
-
if (
|
|
60
|
-
|
|
60
|
+
if (TEMP_DOTDIRS.has(entry.name)) {
|
|
61
|
+
issues.push({ type: 'dir', name: entry.name, dest: null, children: [] });
|
|
62
|
+
} else if (!ALLOWED_DIRS.has(entry.name) && !entry.name.startsWith('.')) {
|
|
61
63
|
issues.push({ type: 'dir', name: entry.name, dest: 'apps', children: safeReaddirAll(path.join(userDir, entry.name)) });
|
|
62
64
|
}
|
|
63
65
|
} else if (entry.isFile()) {
|
|
@@ -96,7 +98,7 @@ function applyIssues(baseDir, issues) {
|
|
|
96
98
|
for (const item of issues) {
|
|
97
99
|
if (item.type === 'dir') {
|
|
98
100
|
const src = path.join(baseDir, item.name);
|
|
99
|
-
if (item.children.length === 0) {
|
|
101
|
+
if (!item.dest || item.children.length === 0) {
|
|
100
102
|
try {
|
|
101
103
|
fs.rmSync(src, { recursive: true, force: true });
|
|
102
104
|
applied.push({ path: item.name + '/', action: 'deleted (empty dir)' });
|
|
@@ -78,9 +78,7 @@ Read every script in ${plan.baseDir}/scripts/. If any script has hardcoded API k
|
|
|
78
78
|
\`\`\`
|
|
79
79
|
3. Never leave plaintext secrets in script files
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
Check the tests/ folder at ${plan.baseDir}/tests/. If it has test files, run them — fix any failures and re-run until all pass. If tests/ is empty or missing, read the scripts in ${plan.baseDir}/scripts/ and write a test file for each script, then run them all.
|
|
83
|
-
Summarize what was cleaned, secrets migrated, and final test results.`);
|
|
81
|
+
Summarize what was cleaned and secrets migrated.`);
|
|
84
82
|
|
|
85
83
|
const taskPrompt = promptParts.join('\n\n');
|
|
86
84
|
|
|
@@ -109,6 +107,33 @@ Summarize what was cleaned, secrets migrated, and final test results.`);
|
|
|
109
107
|
stopTyping();
|
|
110
108
|
await ctx.reply(`⚠️ Clean failed: ${e.message}`).catch(() => {});
|
|
111
109
|
}
|
|
110
|
+
|
|
111
|
+
// Phase 2: write and run tests (separate chat so it can't be skipped)
|
|
112
|
+
const testsAfter = fs.existsSync(testsDir) && fs.readdirSync(testsDir).filter(f => !f.startsWith('.')).length > 0;
|
|
113
|
+
if (!testsAfter && hasScripts) {
|
|
114
|
+
const testPrompt = `Read every script in ${plan.baseDir}/scripts/. For each script, write a corresponding test file in ${plan.baseDir}/tests/. Name each test file test-<script-name> (e.g. scripts/gmail-send.py → tests/test-gmail-send.py). After writing all tests, run them and fix any failures until they all pass. Summarize the test results.`;
|
|
115
|
+
const testStatus = createStatusTracker(ctx);
|
|
116
|
+
const testCtx = createChatContext(ctx, tenant, config, { allowedUsers: new Set(), bot, createAsk });
|
|
117
|
+
testCtx._model = 'claude-sonnet-4-6';
|
|
118
|
+
testCtx._onRouteDecision = (info) => { testStatus.setRouteInfo(info); testStatus.start(); };
|
|
119
|
+
testCtx._onToolStart = () => { testStatus.setStatusText('Writing tests'); testStatus.start(); };
|
|
120
|
+
const testTyping = startTyping(ctx);
|
|
121
|
+
try {
|
|
122
|
+
const { text: testResponse } = await tenant.claude.chat(testPrompt, testCtx);
|
|
123
|
+
testStatus.stopTimer();
|
|
124
|
+
testStatus.updateFormatting();
|
|
125
|
+
testTyping();
|
|
126
|
+
testStatus.deleteMsg();
|
|
127
|
+
if (testResponse?.trim()) {
|
|
128
|
+
const chunks = splitMessage(testResponse, 4096);
|
|
129
|
+
for (const chunk of chunks) await sendHtml(ctx, chunk).catch(() => {});
|
|
130
|
+
}
|
|
131
|
+
} catch (e) {
|
|
132
|
+
testStatus.clear();
|
|
133
|
+
testTyping();
|
|
134
|
+
await ctx.reply(`⚠️ Test writing failed: ${e.message}`).catch(() => {});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
112
137
|
} catch (e) {
|
|
113
138
|
await ctx.reply(`⚠️ Clean failed: ${e.message}`);
|
|
114
139
|
}
|
|
@@ -136,7 +161,7 @@ Summarize what was cleaned, secrets migrated, and final test results.`);
|
|
|
136
161
|
const { OBOL_DIR } = require('../../config');
|
|
137
162
|
const notifyPath = path.join(OBOL_DIR, '.upgrade-notify.json');
|
|
138
163
|
fs.writeFileSync(notifyPath, JSON.stringify({ chatId: ctx.chat.id, version: latest }));
|
|
139
|
-
execSync('pm2 restart obol', { encoding: 'utf-8', timeout: 15000 });
|
|
164
|
+
try { execSync('pm2 restart obol', { encoding: 'utf-8', timeout: 15000 }); } catch {}
|
|
140
165
|
} catch (e) {
|
|
141
166
|
await ctx.reply(`Upgrade failed: ${e.message.substring(0, 200)}`);
|
|
142
167
|
}
|