openbot 0.5.7 → 0.5.8
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/dist/app/cli.js +1 -1
- package/dist/plugins/storage/service.js +39 -21
- package/package.json +2 -2
- package/src/app/cli.ts +1 -1
- package/src/plugins/storage/service.ts +60 -54
- package/src/services/plugins/domain.ts +7 -1
package/dist/app/cli.js
CHANGED
|
@@ -18,7 +18,7 @@ function checkNodeVersion() {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
checkNodeVersion();
|
|
21
|
-
program.name('openbot').description('OpenBot CLI').version('0.5.
|
|
21
|
+
program.name('openbot').description('OpenBot CLI').version('0.5.8');
|
|
22
22
|
program
|
|
23
23
|
.command('start')
|
|
24
24
|
.description('Start the OpenBot harness')
|
|
@@ -12,7 +12,7 @@ import { listBuiltInPlugins, parsePluginModule } from '../../services/plugins/re
|
|
|
12
12
|
import { enrichOpenbotPluginDescriptor, listRegistryModelOptions, } from '../../services/plugins/model-registry.js';
|
|
13
13
|
import { processService } from '../../services/process.js';
|
|
14
14
|
import { memoryService } from '../memory/service.js';
|
|
15
|
-
import { guessMimeType, resolveChannelFile, statChannelFile
|
|
15
|
+
import { guessMimeType, resolveChannelFile, statChannelFile } from './files.js';
|
|
16
16
|
const resolveBaseDir = () => getBaseDir();
|
|
17
17
|
const ENTITY_SVG_CANDIDATE_NAMES = ['avatar.svg', 'icon.svg', 'image.svg', 'logo.svg'];
|
|
18
18
|
const toSvgDataUrl = (svg) => `data:image/svg+xml;base64,${Buffer.from(svg, 'utf-8').toString('base64')}`;
|
|
@@ -115,10 +115,7 @@ function mergeCloudSystemPluginRefs(defaults, diskRefs) {
|
|
|
115
115
|
];
|
|
116
116
|
}
|
|
117
117
|
/** No `openbot` / `bash` — storage-side effects and infra plugins only. */
|
|
118
|
-
const STATE_DEFAULT_PLUGINS = [
|
|
119
|
-
{ id: 'storage' },
|
|
120
|
-
{ id: 'plugin-manager' },
|
|
121
|
-
];
|
|
118
|
+
const STATE_DEFAULT_PLUGINS = [{ id: 'storage' }, { id: 'plugin-manager' }];
|
|
122
119
|
const STATE_AGENT_INSTRUCTIONS = 'Built-in infra agent for deterministic state reads. No conversational model is attached; handle storage, approvals, memory, and plugin marketplace events.';
|
|
123
120
|
function getSystemAgentDetails(overrides) {
|
|
124
121
|
const defaultPlugins = isCloudMode() ? getCloudSystemDefaultPlugins() : SYSTEM_DEFAULT_PLUGINS;
|
|
@@ -289,12 +286,37 @@ const toVariablesRecord = (raw) => {
|
|
|
289
286
|
String(value ?? ''),
|
|
290
287
|
]));
|
|
291
288
|
};
|
|
289
|
+
let cachedRuntimeVersion;
|
|
290
|
+
const getRuntimeVersion = () => {
|
|
291
|
+
if (cachedRuntimeVersion !== undefined)
|
|
292
|
+
return cachedRuntimeVersion || undefined;
|
|
293
|
+
try {
|
|
294
|
+
const pkgPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../../package.json');
|
|
295
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
296
|
+
cachedRuntimeVersion = typeof pkg.version === 'string' ? pkg.version : '';
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
cachedRuntimeVersion = '';
|
|
300
|
+
}
|
|
301
|
+
return cachedRuntimeVersion || undefined;
|
|
302
|
+
};
|
|
303
|
+
const readPluginPackageVersion = async (pluginDir) => {
|
|
304
|
+
try {
|
|
305
|
+
const pkg = JSON.parse(await fs.readFile(path.join(pluginDir, 'package.json'), 'utf-8'));
|
|
306
|
+
return typeof pkg.version === 'string' ? pkg.version : undefined;
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
292
312
|
const listBuiltInPluginDescriptors = async () => {
|
|
313
|
+
const version = getRuntimeVersion();
|
|
293
314
|
return listBuiltInPlugins().map((plugin) => ({
|
|
294
315
|
id: plugin.id,
|
|
295
316
|
name: plugin.name,
|
|
296
317
|
description: plugin.description,
|
|
297
318
|
builtIn: true,
|
|
319
|
+
version,
|
|
298
320
|
image: plugin.image,
|
|
299
321
|
configSchema: plugin.configSchema,
|
|
300
322
|
createdAt: new Date(),
|
|
@@ -356,12 +378,16 @@ const listPluginsFromDisk = async () => {
|
|
|
356
378
|
const parsed = parsePluginModule(module);
|
|
357
379
|
if (!parsed)
|
|
358
380
|
return null;
|
|
359
|
-
const image = await
|
|
381
|
+
const [image, version] = await Promise.all([
|
|
382
|
+
resolveEntityImageDataUrl(pluginDir),
|
|
383
|
+
readPluginPackageVersion(pluginDir),
|
|
384
|
+
]);
|
|
360
385
|
return {
|
|
361
386
|
id,
|
|
362
387
|
name: parsed.name || id,
|
|
363
388
|
description: parsed.description || '',
|
|
364
389
|
builtIn: false,
|
|
390
|
+
version,
|
|
365
391
|
image: parsed.image || image,
|
|
366
392
|
configSchema: parsed.configSchema,
|
|
367
393
|
createdAt: new Date(),
|
|
@@ -480,7 +506,7 @@ export const storageService = {
|
|
|
480
506
|
channel.recentThreads = allThreads
|
|
481
507
|
.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime())
|
|
482
508
|
.slice(0, 5);
|
|
483
|
-
const threadsUnseen = allThreads.some(t => t.hasUnseenMessages);
|
|
509
|
+
const threadsUnseen = allThreads.some((t) => t.hasUnseenMessages);
|
|
484
510
|
channel.hasUnseenMessages = rootUnseen || threadsUnseen;
|
|
485
511
|
}
|
|
486
512
|
catch {
|
|
@@ -521,8 +547,7 @@ export const storageService = {
|
|
|
521
547
|
finalState.cwd = resolvedCwd;
|
|
522
548
|
await fs.mkdir(resolvedCwd, { recursive: true });
|
|
523
549
|
await fs.mkdir(channelDir, { recursive: true });
|
|
524
|
-
await fs.writeFile(specPath, spec?.trim() ||
|
|
525
|
-
`# ${normalizedChannelId}\n\n`);
|
|
550
|
+
await fs.writeFile(specPath, spec?.trim() || `# ${normalizedChannelId}\n\n`);
|
|
526
551
|
await writeJsonFileAtomically(statePath, finalState);
|
|
527
552
|
},
|
|
528
553
|
ensureChannel: async ({ channelId, spec, initialState, cwd, }) => {
|
|
@@ -684,9 +709,7 @@ export const storageService = {
|
|
|
684
709
|
console.error(`Failed to read thread state for channel ${channelId} thread ${threadId}`, error);
|
|
685
710
|
}
|
|
686
711
|
}
|
|
687
|
-
const threadName = isRecord(state) && typeof state.name === 'string'
|
|
688
|
-
? state.name.trim()
|
|
689
|
-
: '';
|
|
712
|
+
const threadName = isRecord(state) && typeof state.name === 'string' ? state.name.trim() : '';
|
|
690
713
|
return {
|
|
691
714
|
id: threadId,
|
|
692
715
|
name: threadName || threadId,
|
|
@@ -840,9 +863,7 @@ export const storageService = {
|
|
|
840
863
|
const discoveredImage = await resolveEntityImageDataUrl(agentDir);
|
|
841
864
|
const stats = await fs.stat(agentMdPath);
|
|
842
865
|
const pluginRefs = parsePluginRefs(data.plugins);
|
|
843
|
-
const frontmatterImage = typeof data.image === 'string' && data.image.trim() !== ''
|
|
844
|
-
? data.image.trim()
|
|
845
|
-
: undefined;
|
|
866
|
+
const frontmatterImage = typeof data.image === 'string' && data.image.trim() !== '' ? data.image.trim() : undefined;
|
|
846
867
|
diskDetails = {
|
|
847
868
|
id: agentId,
|
|
848
869
|
name: typeof data.name === 'string' ? data.name : agentId,
|
|
@@ -964,8 +985,7 @@ export const storageService = {
|
|
|
964
985
|
// Built-in agents merge disk overlays with code defaults on read; on write, partial
|
|
965
986
|
// updates (e.g. plugins-only) must still persist a complete AGENT.md.
|
|
966
987
|
if (isBuiltinOverlayAgentId(agentId)) {
|
|
967
|
-
const pluginRefs = plugins ??
|
|
968
|
-
(nextData.plugins !== undefined ? parsePluginRefs(nextData.plugins) : undefined);
|
|
988
|
+
const pluginRefs = plugins ?? (nextData.plugins !== undefined ? parsePluginRefs(nextData.plugins) : undefined);
|
|
969
989
|
const diskOverrides = {};
|
|
970
990
|
if (typeof nextData.name === 'string' && nextData.name.trim() !== '') {
|
|
971
991
|
diskOverrides.name = nextData.name;
|
|
@@ -1170,7 +1190,7 @@ export const storageService = {
|
|
|
1170
1190
|
typeof raw === 'object' &&
|
|
1171
1191
|
'variables' in raw &&
|
|
1172
1192
|
Array.isArray(raw.variables)) {
|
|
1173
|
-
const entries =
|
|
1193
|
+
const entries = raw.variables
|
|
1174
1194
|
.filter((v) => typeof v?.key === 'string')
|
|
1175
1195
|
.map((v) => [v.key, { value: String(v.value ?? ''), secret: !!v.secret }]);
|
|
1176
1196
|
return Object.fromEntries(entries);
|
|
@@ -1236,9 +1256,7 @@ export const storageService = {
|
|
|
1236
1256
|
if (!baseCwd) {
|
|
1237
1257
|
throw new Error('Channel has no CWD configured');
|
|
1238
1258
|
}
|
|
1239
|
-
const targetDir = subPath
|
|
1240
|
-
? resolveChannelFile(baseCwd, subPath)
|
|
1241
|
-
: resolvePath(baseCwd);
|
|
1259
|
+
const targetDir = subPath ? resolveChannelFile(baseCwd, subPath) : resolvePath(baseCwd);
|
|
1242
1260
|
const entries = await fs.readdir(targetDir, { withFileTypes: true });
|
|
1243
1261
|
return entries
|
|
1244
1262
|
.filter((e) => !e.name.startsWith('.'))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openbot",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"build": "tsc && mkdir -p dist/assets && cp src/assets/icon.svg dist/assets/icon.svg",
|
|
12
12
|
"start": "node dist/app/cli.js start",
|
|
13
13
|
"format": "prettier --write .",
|
|
14
|
-
"push": "fly deploy --build-only --push --config deploy/fly.toml -a openbot-runtime --image-label v0.5.
|
|
14
|
+
"push": "fly deploy --build-only --push --config deploy/fly.toml -a openbot-runtime --image-label v0.5.8"
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
17
|
"openbot": "./dist/app/cli.js"
|
package/src/app/cli.ts
CHANGED
|
@@ -40,11 +40,7 @@ import {
|
|
|
40
40
|
import { OpenBotEvent, OpenBotState } from '../../app/types.js';
|
|
41
41
|
import { processService } from '../../services/process.js';
|
|
42
42
|
import { memoryService } from '../memory/service.js';
|
|
43
|
-
import {
|
|
44
|
-
guessMimeType,
|
|
45
|
-
resolveChannelFile,
|
|
46
|
-
statChannelFile,
|
|
47
|
-
} from './files.js';
|
|
43
|
+
import { guessMimeType, resolveChannelFile, statChannelFile } from './files.js';
|
|
48
44
|
|
|
49
45
|
const resolveBaseDir = () => getBaseDir();
|
|
50
46
|
|
|
@@ -142,10 +138,7 @@ function getCloudSystemDefaultPlugins(): PluginRef[] {
|
|
|
142
138
|
});
|
|
143
139
|
}
|
|
144
140
|
|
|
145
|
-
function mergeCloudSystemPluginRefs(
|
|
146
|
-
defaults: PluginRef[],
|
|
147
|
-
diskRefs: PluginRef[],
|
|
148
|
-
): PluginRef[] {
|
|
141
|
+
function mergeCloudSystemPluginRefs(defaults: PluginRef[], diskRefs: PluginRef[]): PluginRef[] {
|
|
149
142
|
const defaultOpenbot = defaults.find((ref) => ref.id === OPENBOT_PLUGIN_ID);
|
|
150
143
|
const diskOpenbot = diskRefs.find((ref) => ref.id === OPENBOT_PLUGIN_ID);
|
|
151
144
|
|
|
@@ -163,10 +156,7 @@ function mergeCloudSystemPluginRefs(
|
|
|
163
156
|
}
|
|
164
157
|
|
|
165
158
|
/** No `openbot` / `bash` — storage-side effects and infra plugins only. */
|
|
166
|
-
const STATE_DEFAULT_PLUGINS: PluginRef[] = [
|
|
167
|
-
{ id: 'storage' },
|
|
168
|
-
{ id: 'plugin-manager' },
|
|
169
|
-
];
|
|
159
|
+
const STATE_DEFAULT_PLUGINS: PluginRef[] = [{ id: 'storage' }, { id: 'plugin-manager' }];
|
|
170
160
|
|
|
171
161
|
const STATE_AGENT_INSTRUCTIONS =
|
|
172
162
|
'Built-in infra agent for deterministic state reads. No conversational model is attached; handle storage, approvals, memory, and plugin marketplace events.';
|
|
@@ -177,8 +167,7 @@ function getSystemAgentDetails(overrides?: Partial<AgentDetails>): AgentDetails
|
|
|
177
167
|
id: SYSTEM_AGENT_ID,
|
|
178
168
|
name: 'OpenBot',
|
|
179
169
|
image: getBundledSystemAgentImage(),
|
|
180
|
-
description:
|
|
181
|
-
'First-party orchestration agent for OpenBot.',
|
|
170
|
+
description: 'First-party orchestration agent for OpenBot.',
|
|
182
171
|
instructions: '',
|
|
183
172
|
plugins: defaultPlugins.map((ref) => ref.id),
|
|
184
173
|
pluginRefs: defaultPlugins,
|
|
@@ -211,9 +200,10 @@ function getSystemAgentDetails(overrides?: Partial<AgentDetails>): AgentDetails
|
|
|
211
200
|
};
|
|
212
201
|
}
|
|
213
202
|
|
|
214
|
-
const refs =
|
|
215
|
-
|
|
216
|
-
|
|
203
|
+
const refs =
|
|
204
|
+
overrides.pluginRefs && overrides.pluginRefs.length > 0
|
|
205
|
+
? overrides.pluginRefs
|
|
206
|
+
: defaults.pluginRefs;
|
|
217
207
|
|
|
218
208
|
return {
|
|
219
209
|
...defaults,
|
|
@@ -243,9 +233,10 @@ function getStateAgentDetails(overrides?: Partial<AgentDetails>): AgentDetails {
|
|
|
243
233
|
|
|
244
234
|
if (!overrides) return defaults;
|
|
245
235
|
|
|
246
|
-
const refs =
|
|
247
|
-
|
|
248
|
-
|
|
236
|
+
const refs =
|
|
237
|
+
overrides.pluginRefs && overrides.pluginRefs.length > 0
|
|
238
|
+
? overrides.pluginRefs
|
|
239
|
+
: defaults.pluginRefs;
|
|
249
240
|
|
|
250
241
|
const diskInstructions = overrides.instructions?.trim();
|
|
251
242
|
const instructions =
|
|
@@ -374,12 +365,42 @@ const toVariablesRecord = (raw: unknown): Record<string, string> => {
|
|
|
374
365
|
);
|
|
375
366
|
};
|
|
376
367
|
|
|
368
|
+
let cachedRuntimeVersion: string | undefined;
|
|
369
|
+
|
|
370
|
+
const getRuntimeVersion = (): string | undefined => {
|
|
371
|
+
if (cachedRuntimeVersion !== undefined) return cachedRuntimeVersion || undefined;
|
|
372
|
+
try {
|
|
373
|
+
const pkgPath = path.join(
|
|
374
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
375
|
+
'../../../package.json',
|
|
376
|
+
);
|
|
377
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version?: unknown };
|
|
378
|
+
cachedRuntimeVersion = typeof pkg.version === 'string' ? pkg.version : '';
|
|
379
|
+
} catch {
|
|
380
|
+
cachedRuntimeVersion = '';
|
|
381
|
+
}
|
|
382
|
+
return cachedRuntimeVersion || undefined;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const readPluginPackageVersion = async (pluginDir: string): Promise<string | undefined> => {
|
|
386
|
+
try {
|
|
387
|
+
const pkg = JSON.parse(await fs.readFile(path.join(pluginDir, 'package.json'), 'utf-8')) as {
|
|
388
|
+
version?: unknown;
|
|
389
|
+
};
|
|
390
|
+
return typeof pkg.version === 'string' ? pkg.version : undefined;
|
|
391
|
+
} catch {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
377
396
|
const listBuiltInPluginDescriptors = async (): Promise<PluginDescriptor[]> => {
|
|
397
|
+
const version = getRuntimeVersion();
|
|
378
398
|
return listBuiltInPlugins().map((plugin) => ({
|
|
379
399
|
id: plugin.id,
|
|
380
400
|
name: plugin.name,
|
|
381
401
|
description: plugin.description,
|
|
382
402
|
builtIn: true,
|
|
403
|
+
version,
|
|
383
404
|
image: plugin.image,
|
|
384
405
|
configSchema: plugin.configSchema,
|
|
385
406
|
createdAt: new Date(),
|
|
@@ -443,12 +464,16 @@ const listPluginsFromDisk = async (): Promise<PluginDescriptor[]> => {
|
|
|
443
464
|
const module = await import(pathToFileURL(distPath).href);
|
|
444
465
|
const parsed = parsePluginModule(module as Record<string, unknown>);
|
|
445
466
|
if (!parsed) return null;
|
|
446
|
-
const image = await
|
|
467
|
+
const [image, version] = await Promise.all([
|
|
468
|
+
resolveEntityImageDataUrl(pluginDir),
|
|
469
|
+
readPluginPackageVersion(pluginDir),
|
|
470
|
+
]);
|
|
447
471
|
return {
|
|
448
472
|
id,
|
|
449
473
|
name: parsed.name || id,
|
|
450
474
|
description: parsed.description || '',
|
|
451
475
|
builtIn: false,
|
|
476
|
+
version,
|
|
452
477
|
image: parsed.image || image,
|
|
453
478
|
configSchema: parsed.configSchema,
|
|
454
479
|
createdAt: new Date(),
|
|
@@ -468,9 +493,7 @@ const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
|
468
493
|
!!value && typeof value === 'object' && !Array.isArray(value);
|
|
469
494
|
|
|
470
495
|
/** Display-oriented fields persisted in a channel's `state.json`. */
|
|
471
|
-
const readChannelStateFileFields = (
|
|
472
|
-
parsed: unknown,
|
|
473
|
-
): { name?: string; cwd?: string } => {
|
|
496
|
+
const readChannelStateFileFields = (parsed: unknown): { name?: string; cwd?: string } => {
|
|
474
497
|
if (!isRecord(parsed)) {
|
|
475
498
|
return {};
|
|
476
499
|
}
|
|
@@ -583,7 +606,7 @@ export const storageService = {
|
|
|
583
606
|
};
|
|
584
607
|
|
|
585
608
|
const channelLastRead = lastReadMap[name] || {};
|
|
586
|
-
|
|
609
|
+
|
|
587
610
|
try {
|
|
588
611
|
// Check root unread
|
|
589
612
|
const rootEvents = await storageService.getEvents({ channelId: name });
|
|
@@ -595,8 +618,8 @@ export const storageService = {
|
|
|
595
618
|
channel.recentThreads = allThreads
|
|
596
619
|
.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime())
|
|
597
620
|
.slice(0, 5);
|
|
598
|
-
|
|
599
|
-
const threadsUnseen = allThreads.some(t => t.hasUnseenMessages);
|
|
621
|
+
|
|
622
|
+
const threadsUnseen = allThreads.some((t) => t.hasUnseenMessages);
|
|
600
623
|
channel.hasUnseenMessages = rootUnseen || threadsUnseen;
|
|
601
624
|
} catch {
|
|
602
625
|
channel.hasUnseenMessages = false;
|
|
@@ -654,11 +677,7 @@ export const storageService = {
|
|
|
654
677
|
finalState.cwd = resolvedCwd;
|
|
655
678
|
await fs.mkdir(resolvedCwd, { recursive: true });
|
|
656
679
|
await fs.mkdir(channelDir, { recursive: true });
|
|
657
|
-
await fs.writeFile(
|
|
658
|
-
specPath,
|
|
659
|
-
spec?.trim() ||
|
|
660
|
-
`# ${normalizedChannelId}\n\n`,
|
|
661
|
-
);
|
|
680
|
+
await fs.writeFile(specPath, spec?.trim() || `# ${normalizedChannelId}\n\n`);
|
|
662
681
|
await writeJsonFileAtomically(statePath, finalState);
|
|
663
682
|
},
|
|
664
683
|
ensureChannel: async ({
|
|
@@ -708,10 +727,7 @@ export const storageService = {
|
|
|
708
727
|
await fs.access(specPath);
|
|
709
728
|
} catch (error: unknown) {
|
|
710
729
|
if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {
|
|
711
|
-
await fs.writeFile(
|
|
712
|
-
specPath,
|
|
713
|
-
spec?.trim() || `# ${normalizedChannelId}\n\n`,
|
|
714
|
-
);
|
|
730
|
+
await fs.writeFile(specPath, spec?.trim() || `# ${normalizedChannelId}\n\n`);
|
|
715
731
|
} else {
|
|
716
732
|
throw error;
|
|
717
733
|
}
|
|
@@ -818,8 +834,7 @@ export const storageService = {
|
|
|
818
834
|
|
|
819
835
|
try {
|
|
820
836
|
const threadState = await readJsonFile<Record<string, unknown>>(threadStatePath, {});
|
|
821
|
-
const threadName =
|
|
822
|
-
typeof threadState.name === 'string' ? threadState.name.trim() : '';
|
|
837
|
+
const threadName = typeof threadState.name === 'string' ? threadState.name.trim() : '';
|
|
823
838
|
if (threadName) {
|
|
824
839
|
threadDisplayName = threadName;
|
|
825
840
|
}
|
|
@@ -876,10 +891,7 @@ export const storageService = {
|
|
|
876
891
|
}
|
|
877
892
|
}
|
|
878
893
|
|
|
879
|
-
const threadName =
|
|
880
|
-
isRecord(state) && typeof state.name === 'string'
|
|
881
|
-
? state.name.trim()
|
|
882
|
-
: '';
|
|
894
|
+
const threadName = isRecord(state) && typeof state.name === 'string' ? state.name.trim() : '';
|
|
883
895
|
|
|
884
896
|
return {
|
|
885
897
|
id: threadId,
|
|
@@ -1078,9 +1090,7 @@ export const storageService = {
|
|
|
1078
1090
|
|
|
1079
1091
|
const pluginRefs = parsePluginRefs(data.plugins);
|
|
1080
1092
|
const frontmatterImage =
|
|
1081
|
-
typeof data.image === 'string' && data.image.trim() !== ''
|
|
1082
|
-
? data.image.trim()
|
|
1083
|
-
: undefined;
|
|
1093
|
+
typeof data.image === 'string' && data.image.trim() !== '' ? data.image.trim() : undefined;
|
|
1084
1094
|
|
|
1085
1095
|
diskDetails = {
|
|
1086
1096
|
id: agentId,
|
|
@@ -1237,8 +1247,7 @@ export const storageService = {
|
|
|
1237
1247
|
// updates (e.g. plugins-only) must still persist a complete AGENT.md.
|
|
1238
1248
|
if (isBuiltinOverlayAgentId(agentId)) {
|
|
1239
1249
|
const pluginRefs =
|
|
1240
|
-
plugins ??
|
|
1241
|
-
(nextData.plugins !== undefined ? parsePluginRefs(nextData.plugins) : undefined);
|
|
1250
|
+
plugins ?? (nextData.plugins !== undefined ? parsePluginRefs(nextData.plugins) : undefined);
|
|
1242
1251
|
const diskOverrides: Partial<AgentDetails> = {};
|
|
1243
1252
|
if (typeof nextData.name === 'string' && nextData.name.trim() !== '') {
|
|
1244
1253
|
diskOverrides.name = nextData.name;
|
|
@@ -1472,7 +1481,7 @@ export const storageService = {
|
|
|
1472
1481
|
'variables' in raw &&
|
|
1473
1482
|
Array.isArray((raw as { variables: unknown }).variables)
|
|
1474
1483
|
) {
|
|
1475
|
-
const entries = (
|
|
1484
|
+
const entries = (raw as { variables: StoredVariable[] }).variables
|
|
1476
1485
|
.filter((v) => typeof v?.key === 'string')
|
|
1477
1486
|
.map((v) => [v.key, { value: String(v.value ?? ''), secret: !!v.secret }] as const);
|
|
1478
1487
|
return Object.fromEntries(entries);
|
|
@@ -1574,9 +1583,7 @@ export const storageService = {
|
|
|
1574
1583
|
throw new Error('Channel has no CWD configured');
|
|
1575
1584
|
}
|
|
1576
1585
|
|
|
1577
|
-
const targetDir = subPath
|
|
1578
|
-
? resolveChannelFile(baseCwd, subPath)
|
|
1579
|
-
: resolvePath(baseCwd);
|
|
1586
|
+
const targetDir = subPath ? resolveChannelFile(baseCwd, subPath) : resolvePath(baseCwd);
|
|
1580
1587
|
|
|
1581
1588
|
const entries = await fs.readdir(targetDir, { withFileTypes: true });
|
|
1582
1589
|
return entries
|
|
@@ -1623,8 +1630,7 @@ export const storageService = {
|
|
|
1623
1630
|
|
|
1624
1631
|
const targetFile = resolveChannelFile(baseCwd, filePath);
|
|
1625
1632
|
const buf = await fs.readFile(targetFile);
|
|
1626
|
-
const content =
|
|
1627
|
-
encoding === 'base64' ? buf.toString('base64') : buf.toString('utf-8');
|
|
1633
|
+
const content = encoding === 'base64' ? buf.toString('base64') : buf.toString('utf-8');
|
|
1628
1634
|
|
|
1629
1635
|
return {
|
|
1630
1636
|
content,
|
|
@@ -35,6 +35,8 @@ export type PluginDescriptor = {
|
|
|
35
35
|
description: string;
|
|
36
36
|
/** True when bundled with the core server (`src/registry/plugins`); false for ~/.openbot/plugins installs. */
|
|
37
37
|
builtIn: boolean;
|
|
38
|
+
/** Installed npm semver for disk plugins; runtime package version for built-ins. */
|
|
39
|
+
version?: string;
|
|
38
40
|
image?: string;
|
|
39
41
|
configSchema?: ConfigSchema;
|
|
40
42
|
createdAt: Date;
|
|
@@ -136,7 +138,11 @@ export interface Storage {
|
|
|
136
138
|
}) => Promise<void>;
|
|
137
139
|
getThreads: (args: { channelId: string }) => Promise<Thread[]>;
|
|
138
140
|
getThreadDetails: (args: { channelId: string; threadId: string }) => Promise<ThreadDetails>;
|
|
139
|
-
setLastRead: (args: {
|
|
141
|
+
setLastRead: (args: {
|
|
142
|
+
channelId: string;
|
|
143
|
+
threadId?: string;
|
|
144
|
+
lastReadEventId: string;
|
|
145
|
+
}) => Promise<void>;
|
|
140
146
|
/** User-facing agent list; excludes agents with `hidden: true` (e.g. built-in `state`). */
|
|
141
147
|
getAgents: () => Promise<Agent[]>;
|
|
142
148
|
getPlugins: () => Promise<PluginDescriptor[]>;
|