openbot 0.3.6 → 0.4.0
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/README.md +15 -16
- package/dist/app/agent-ids.js +4 -0
- package/dist/app/cli.js +1 -1
- package/dist/app/config.js +0 -19
- package/dist/app/server.js +8 -14
- package/dist/bus/services.js +34 -124
- package/dist/harness/agent-invoke-run.js +44 -0
- package/dist/harness/agent-turn.js +99 -0
- package/dist/harness/channel-participants.js +40 -0
- package/dist/harness/constants.js +2 -0
- package/dist/harness/context-meter.js +97 -0
- package/dist/harness/context.js +95 -47
- package/dist/harness/dispatch.js +144 -0
- package/dist/harness/dispatcher.js +45 -156
- package/dist/harness/history.js +177 -0
- package/dist/harness/index.js +91 -0
- package/dist/harness/orchestration.js +88 -0
- package/dist/harness/participants.js +22 -0
- package/dist/harness/run-harness.js +154 -0
- package/dist/harness/run.js +98 -0
- package/dist/harness/runtime-factory.js +0 -34
- package/dist/harness/runtime.js +57 -0
- package/dist/harness/todo-dispatch.js +51 -0
- package/dist/harness/todos.js +5 -0
- package/dist/harness/turn.js +79 -0
- package/dist/plugins/approval/index.js +105 -149
- package/dist/plugins/delegation/index.js +119 -32
- package/dist/plugins/memory/index.js +103 -14
- package/dist/plugins/memory/service.js +152 -0
- package/dist/plugins/openbot/context.js +80 -0
- package/dist/plugins/openbot/history.js +98 -0
- package/dist/plugins/openbot/index.js +31 -0
- package/dist/plugins/openbot/runtime.js +317 -0
- package/dist/plugins/openbot/system-prompt.js +5 -0
- package/dist/plugins/plugin-manager/index.js +105 -0
- package/dist/plugins/storage/index.js +573 -0
- package/dist/plugins/storage/service.js +1159 -0
- package/dist/plugins/storage-tools/index.js +2 -2
- package/dist/plugins/thread-namer/index.js +72 -0
- package/dist/plugins/thread-naming/generate-title.js +44 -0
- package/dist/plugins/thread-naming/index.js +103 -0
- package/dist/plugins/threads/index.js +114 -0
- package/dist/plugins/todo/index.js +24 -25
- package/dist/plugins/ui/index.js +2 -32
- package/dist/registry/plugins.js +3 -9
- package/dist/services/plugins/domain.js +1 -0
- package/dist/services/plugins/plugin-cache.js +9 -0
- package/dist/services/plugins/registry.js +110 -0
- package/dist/services/plugins/service.js +177 -0
- package/dist/services/plugins/types.js +1 -0
- package/dist/services/process.js +29 -0
- package/dist/services/storage.js +11 -10
- package/dist/services/thread-naming.js +81 -0
- package/docs/agents.md +16 -10
- package/docs/architecture.md +2 -2
- package/docs/plugins.md +6 -15
- package/docs/templates/AGENT.example.md +7 -13
- package/package.json +1 -2
- package/src/app/agent-ids.ts +5 -0
- package/src/app/cli.ts +1 -1
- package/src/app/config.ts +1 -31
- package/src/app/server.ts +8 -16
- package/src/app/types.ts +63 -189
- package/src/harness/index.ts +145 -0
- package/src/plugins/approval/index.ts +91 -189
- package/src/plugins/delegation/index.ts +136 -39
- package/src/plugins/memory/index.ts +112 -15
- package/src/{services/memory.ts → plugins/memory/service.ts} +1 -1
- package/src/plugins/openbot/context.ts +91 -0
- package/src/plugins/openbot/history.ts +107 -0
- package/src/plugins/openbot/index.ts +37 -0
- package/src/plugins/openbot/runtime.ts +384 -0
- package/src/plugins/openbot/system-prompt.ts +7 -0
- package/src/plugins/plugin-manager/index.ts +122 -0
- package/src/plugins/shell/index.ts +1 -1
- package/src/plugins/storage/index.ts +633 -0
- package/src/{services/storage.ts → plugins/storage/service.ts} +224 -67
- package/src/{bus/types.ts → services/plugins/domain.ts} +16 -7
- package/src/services/plugins/plugin-cache.ts +13 -0
- package/src/{registry/plugins.ts → services/plugins/registry.ts} +25 -27
- package/src/services/{plugins.ts → plugins/service.ts} +96 -2
- package/src/{bus/plugin.ts → services/plugins/types.ts} +3 -3
- package/src/bus/services.ts +0 -954
- package/src/harness/context.ts +0 -365
- package/src/harness/dispatcher.ts +0 -379
- package/src/harness/mcp.ts +0 -78
- package/src/harness/runtime-factory.ts +0 -129
- package/src/harness/todo-advance.ts +0 -128
- package/src/plugins/ai-sdk/index.ts +0 -41
- package/src/plugins/ai-sdk/runtime.ts +0 -468
- package/src/plugins/ai-sdk/system-prompt.ts +0 -18
- package/src/plugins/mcp/index.ts +0 -128
- package/src/plugins/storage-tools/index.ts +0 -90
- package/src/plugins/todo/index.ts +0 -64
- package/src/plugins/ui/index.ts +0 -227
- /package/src/{harness → services}/process.ts +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ORCHESTRATOR_AGENT_ID, STATE_AGENT_ID } from '../../app/agent-ids.js';
|
|
1
2
|
import {
|
|
2
3
|
DEFAULT_PLUGINS_DIR,
|
|
3
4
|
DEFAULT_AGENTS_DIR,
|
|
@@ -7,7 +8,7 @@ import {
|
|
|
7
8
|
resolvePath,
|
|
8
9
|
StoredVariable,
|
|
9
10
|
VARIABLES_FILE,
|
|
10
|
-
} from '
|
|
11
|
+
} from '../../app/config.js';
|
|
11
12
|
import fs from 'node:fs/promises';
|
|
12
13
|
import { readFileSync } from 'node:fs';
|
|
13
14
|
import path from 'node:path';
|
|
@@ -22,14 +23,14 @@ import {
|
|
|
22
23
|
PluginDescriptor,
|
|
23
24
|
Thread,
|
|
24
25
|
ThreadDetails,
|
|
25
|
-
} from '
|
|
26
|
-
import type { PluginRef } from '
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import { listBuiltInPlugins, parsePluginModule } from '
|
|
30
|
-
import { OpenBotEvent, OpenBotState } from '
|
|
31
|
-
import { processService } from '
|
|
32
|
-
import { memoryService } from '
|
|
26
|
+
} from '../../services/plugins/domain.js';
|
|
27
|
+
import type { PluginRef } from '../../services/plugins/types.js';
|
|
28
|
+
import { openbotPlugin } from '../openbot/index.js';
|
|
29
|
+
import { OPENBOT_SYSTEM_PROMPT } from '../openbot/system-prompt.js';
|
|
30
|
+
import { listBuiltInPlugins, parsePluginModule } from '../../services/plugins/registry.js';
|
|
31
|
+
import { OpenBotEvent, OpenBotState } from '../../app/types.js';
|
|
32
|
+
import { processService } from '../../services/process.js';
|
|
33
|
+
import { memoryService } from '../memory/service.js';
|
|
33
34
|
|
|
34
35
|
const resolveBaseDir = () => {
|
|
35
36
|
const config = loadConfig();
|
|
@@ -49,7 +50,10 @@ function getBundledSystemAgentImage(): string | undefined {
|
|
|
49
50
|
if (bundledSystemAgentImageLoaded) return bundledSystemAgentImage;
|
|
50
51
|
bundledSystemAgentImageLoaded = true;
|
|
51
52
|
try {
|
|
52
|
-
const iconPath = path.join(
|
|
53
|
+
const iconPath = path.join(
|
|
54
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
55
|
+
'../../assets/icon.svg',
|
|
56
|
+
);
|
|
53
57
|
const trimmed = readFileSync(iconPath, 'utf-8').trim();
|
|
54
58
|
if (!trimmed.startsWith('<svg')) return undefined;
|
|
55
59
|
bundledSystemAgentImage = toSvgDataUrl(trimmed);
|
|
@@ -103,27 +107,34 @@ const getConversationDir = (channelId: string, threadId?: string) => {
|
|
|
103
107
|
};
|
|
104
108
|
|
|
105
109
|
/** Built-in orchestrator agent id. Not creatable as a normal disk agent. */
|
|
106
|
-
const SYSTEM_AGENT_ID =
|
|
110
|
+
const SYSTEM_AGENT_ID = ORCHESTRATOR_AGENT_ID;
|
|
107
111
|
|
|
108
112
|
const SYSTEM_DEFAULT_PLUGINS: PluginRef[] = [
|
|
109
|
-
{ id: '
|
|
110
|
-
{ id: 'storage-tools' },
|
|
111
|
-
// { id: 'mcp' },
|
|
113
|
+
{ id: 'openbot', config: { model: 'openai/gpt-5.4-mini' } },
|
|
112
114
|
{ id: 'shell' },
|
|
113
|
-
{ id: 'todo' },
|
|
114
|
-
// { id: 'ui' },
|
|
115
115
|
{ id: 'approval' },
|
|
116
116
|
{ id: 'memory' },
|
|
117
|
+
{ id: 'delegation' },
|
|
118
|
+
{ id: 'storage' },
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
/** No `openbot` / `shell` — storage-side effects and infra plugins only. */
|
|
122
|
+
const STATE_DEFAULT_PLUGINS: PluginRef[] = [
|
|
123
|
+
{ id: 'storage' },
|
|
124
|
+
{ id: 'plugin-manager' },
|
|
117
125
|
];
|
|
118
126
|
|
|
127
|
+
const STATE_AGENT_INSTRUCTIONS =
|
|
128
|
+
'Built-in infra agent for deterministic state reads. No conversational model is attached; handle storage, approvals, memory, and plugin marketplace events.';
|
|
129
|
+
|
|
119
130
|
function getSystemAgentDetails(overrides?: Partial<AgentDetails>): AgentDetails {
|
|
120
131
|
const defaults: AgentDetails = {
|
|
121
132
|
id: SYSTEM_AGENT_ID,
|
|
122
133
|
name: 'OpenBot',
|
|
123
134
|
image: getBundledSystemAgentImage(),
|
|
124
135
|
description:
|
|
125
|
-
'First-party orchestration agent for OpenBot.
|
|
126
|
-
instructions:
|
|
136
|
+
'First-party orchestration agent for OpenBot.',
|
|
137
|
+
instructions: OPENBOT_SYSTEM_PROMPT,
|
|
127
138
|
plugins: SYSTEM_DEFAULT_PLUGINS.map((ref) => ref.id),
|
|
128
139
|
pluginRefs: SYSTEM_DEFAULT_PLUGINS,
|
|
129
140
|
createdAt: new Date(),
|
|
@@ -136,10 +147,15 @@ function getSystemAgentDetails(overrides?: Partial<AgentDetails>): AgentDetails
|
|
|
136
147
|
? overrides.pluginRefs
|
|
137
148
|
: defaults.pluginRefs;
|
|
138
149
|
|
|
150
|
+
const diskInstructions = overrides.instructions?.trim();
|
|
151
|
+
const instructions =
|
|
152
|
+
diskInstructions && diskInstructions.length > 0 ? diskInstructions : defaults.instructions;
|
|
153
|
+
|
|
139
154
|
return {
|
|
140
155
|
...defaults,
|
|
141
156
|
...overrides,
|
|
142
157
|
id: SYSTEM_AGENT_ID,
|
|
158
|
+
instructions,
|
|
143
159
|
image: overrides.image || defaults.image,
|
|
144
160
|
plugins: refs.map((ref) => ref.id),
|
|
145
161
|
pluginRefs: refs,
|
|
@@ -147,18 +163,65 @@ function getSystemAgentDetails(overrides?: Partial<AgentDetails>): AgentDetails
|
|
|
147
163
|
};
|
|
148
164
|
}
|
|
149
165
|
|
|
150
|
-
|
|
151
|
-
|
|
166
|
+
function getStateAgentDetails(overrides?: Partial<AgentDetails>): AgentDetails {
|
|
167
|
+
const defaults: AgentDetails = {
|
|
168
|
+
id: STATE_AGENT_ID,
|
|
169
|
+
name: 'State',
|
|
170
|
+
image: getBundledSystemAgentImage(),
|
|
171
|
+
description: 'Infrastructure agent for OpenBot — storage and hooks without an LLM.',
|
|
172
|
+
instructions: STATE_AGENT_INSTRUCTIONS,
|
|
173
|
+
plugins: STATE_DEFAULT_PLUGINS.map((ref) => ref.id),
|
|
174
|
+
pluginRefs: STATE_DEFAULT_PLUGINS,
|
|
175
|
+
hidden: true,
|
|
176
|
+
createdAt: new Date(),
|
|
177
|
+
updatedAt: new Date(),
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
if (!overrides) return defaults;
|
|
181
|
+
|
|
182
|
+
const refs = overrides.pluginRefs && overrides.pluginRefs.length > 0
|
|
183
|
+
? overrides.pluginRefs
|
|
184
|
+
: defaults.pluginRefs;
|
|
152
185
|
|
|
153
|
-
const
|
|
186
|
+
const diskInstructions = overrides.instructions?.trim();
|
|
187
|
+
const instructions =
|
|
188
|
+
diskInstructions && diskInstructions.length > 0 ? diskInstructions : defaults.instructions;
|
|
154
189
|
|
|
155
|
-
|
|
190
|
+
return {
|
|
191
|
+
...defaults,
|
|
192
|
+
...overrides,
|
|
193
|
+
id: STATE_AGENT_ID,
|
|
194
|
+
instructions,
|
|
195
|
+
image: overrides.image || defaults.image,
|
|
196
|
+
hidden: overrides.hidden !== undefined ? overrides.hidden : defaults.hidden,
|
|
197
|
+
plugins: refs.map((ref) => ref.id),
|
|
198
|
+
pluginRefs: refs,
|
|
199
|
+
updatedAt: new Date(),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const agentSummaryFromDetails = (details: AgentDetails): Agent => ({
|
|
204
|
+
id: details.id,
|
|
205
|
+
name: details.name || details.id,
|
|
206
|
+
description: details.description || '',
|
|
207
|
+
image: details.image,
|
|
208
|
+
plugins: details.plugins,
|
|
209
|
+
hidden: details.hidden,
|
|
210
|
+
createdAt: details.createdAt,
|
|
211
|
+
updatedAt: details.updatedAt,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Suppress unused warning until system agent customization re-uses openbotPlugin metadata.
|
|
215
|
+
void openbotPlugin;
|
|
216
|
+
|
|
217
|
+
/** Built-in agents may persist optional `agents/<id>/AGENT.md` overlays; read path merges them with defaults. */
|
|
218
|
+
const isBuiltinOverlayAgentId = (agentId: string): boolean =>
|
|
219
|
+
agentId === SYSTEM_AGENT_ID || agentId === STATE_AGENT_ID;
|
|
220
|
+
|
|
221
|
+
const assertAgentIdFormat = (agentId: string): void => {
|
|
156
222
|
if (!agentId || typeof agentId !== 'string') {
|
|
157
223
|
throw new Error('agentId is required');
|
|
158
224
|
}
|
|
159
|
-
if (RESERVED_DISK_AGENT_IDS.has(agentId)) {
|
|
160
|
-
throw new Error(`Agent id "${agentId}" is reserved`);
|
|
161
|
-
}
|
|
162
225
|
if (!/^[a-zA-Z0-9_-]+$/.test(agentId)) {
|
|
163
226
|
throw new Error('agentId must contain only letters, digits, underscores, and hyphens');
|
|
164
227
|
}
|
|
@@ -174,9 +237,7 @@ const THREAD_TITLE_MAX_LENGTH = 80;
|
|
|
174
237
|
const buildThreadTitleFromEvent = (event: OpenBotEvent): string | undefined => {
|
|
175
238
|
let rawContent = '';
|
|
176
239
|
|
|
177
|
-
if (
|
|
178
|
-
rawContent = event.data.content;
|
|
179
|
-
} else if (
|
|
240
|
+
if (
|
|
180
241
|
event.type === 'agent:invoke' &&
|
|
181
242
|
event.data?.role === 'user' &&
|
|
182
243
|
typeof event.data.content === 'string'
|
|
@@ -339,6 +400,12 @@ const readChannelStateFileFields = (
|
|
|
339
400
|
* Parse the `plugins:` array from AGENT.md frontmatter. Each entry must have an
|
|
340
401
|
* `id`; `config` is optional. Strings are accepted as a shorthand for `{ id }`.
|
|
341
402
|
*/
|
|
403
|
+
const parseHiddenFlag = (raw: unknown): boolean | undefined => {
|
|
404
|
+
if (raw === true) return true;
|
|
405
|
+
if (raw === false) return false;
|
|
406
|
+
return undefined;
|
|
407
|
+
};
|
|
408
|
+
|
|
342
409
|
const parsePluginRefs = (raw: unknown): PluginRef[] => {
|
|
343
410
|
if (!Array.isArray(raw)) return [];
|
|
344
411
|
const refs: PluginRef[] = [];
|
|
@@ -522,7 +589,7 @@ export const storageService = {
|
|
|
522
589
|
|
|
523
590
|
const baseState: Record<string, unknown> = { ...(initialState || {}) };
|
|
524
591
|
if (threadTitle?.trim()) {
|
|
525
|
-
baseState.
|
|
592
|
+
baseState.name = threadTitle.trim();
|
|
526
593
|
}
|
|
527
594
|
|
|
528
595
|
await fs.mkdir(threadDir, { recursive: true });
|
|
@@ -550,10 +617,10 @@ export const storageService = {
|
|
|
550
617
|
try {
|
|
551
618
|
const threadStateRaw = await fs.readFile(threadStatePath, 'utf-8');
|
|
552
619
|
const threadState = JSON.parse(threadStateRaw) as Record<string, unknown>;
|
|
553
|
-
const
|
|
554
|
-
typeof threadState.
|
|
555
|
-
if (
|
|
556
|
-
threadDisplayName =
|
|
620
|
+
const threadName =
|
|
621
|
+
typeof threadState.name === 'string' ? threadState.name.trim() : '';
|
|
622
|
+
if (threadName) {
|
|
623
|
+
threadDisplayName = threadName;
|
|
557
624
|
}
|
|
558
625
|
} catch (error: unknown) {
|
|
559
626
|
if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') {
|
|
@@ -599,14 +666,14 @@ export const storageService = {
|
|
|
599
666
|
}
|
|
600
667
|
}
|
|
601
668
|
|
|
602
|
-
const
|
|
603
|
-
isRecord(state) && typeof state.
|
|
604
|
-
? state.
|
|
669
|
+
const threadName =
|
|
670
|
+
isRecord(state) && typeof state.name === 'string'
|
|
671
|
+
? state.name.trim()
|
|
605
672
|
: '';
|
|
606
673
|
|
|
607
674
|
return {
|
|
608
675
|
id: threadId,
|
|
609
|
-
name:
|
|
676
|
+
name: threadName || threadId,
|
|
610
677
|
channelId,
|
|
611
678
|
state,
|
|
612
679
|
};
|
|
@@ -741,15 +808,7 @@ export const storageService = {
|
|
|
741
808
|
agentIds.map(async (id) => {
|
|
742
809
|
try {
|
|
743
810
|
const details = await storageService.getAgentDetails({ agentId: id });
|
|
744
|
-
return
|
|
745
|
-
id,
|
|
746
|
-
name: details.name || id,
|
|
747
|
-
description: details.description || '',
|
|
748
|
-
image: details.image,
|
|
749
|
-
plugins: details.plugins,
|
|
750
|
-
createdAt: details.createdAt,
|
|
751
|
-
updatedAt: details.updatedAt,
|
|
752
|
-
} satisfies Agent;
|
|
811
|
+
return agentSummaryFromDetails(details);
|
|
753
812
|
} catch {
|
|
754
813
|
return {
|
|
755
814
|
id,
|
|
@@ -764,23 +823,19 @@ export const storageService = {
|
|
|
764
823
|
);
|
|
765
824
|
|
|
766
825
|
const system = await storageService.getAgentDetails({ agentId: SYSTEM_AGENT_ID });
|
|
767
|
-
const builtInSystemAgent
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
image: system.image,
|
|
772
|
-
plugins: system.plugins,
|
|
773
|
-
createdAt: system.createdAt,
|
|
774
|
-
updatedAt: system.updatedAt,
|
|
775
|
-
};
|
|
826
|
+
const builtInSystemAgent = agentSummaryFromDetails(system);
|
|
827
|
+
|
|
828
|
+
const builtInStateRow = await storageService.getAgentDetails({ agentId: STATE_AGENT_ID });
|
|
829
|
+
const builtInStateAgent = agentSummaryFromDetails(builtInStateRow);
|
|
776
830
|
|
|
777
831
|
const deduped = new Map<string, Agent>();
|
|
778
832
|
deduped.set(builtInSystemAgent.id, builtInSystemAgent);
|
|
833
|
+
deduped.set(builtInStateAgent.id, builtInStateAgent);
|
|
779
834
|
for (const agent of agents) {
|
|
780
835
|
if (!deduped.has(agent.id)) deduped.set(agent.id, agent);
|
|
781
836
|
}
|
|
782
837
|
|
|
783
|
-
return Array.from(deduped.values());
|
|
838
|
+
return Array.from(deduped.values()).filter((agent) => !agent.hidden);
|
|
784
839
|
},
|
|
785
840
|
getPlugins: async (): Promise<PluginDescriptor[]> => {
|
|
786
841
|
const [builtIn, fromDisk] = await Promise.all([
|
|
@@ -824,16 +879,17 @@ export const storageService = {
|
|
|
824
879
|
pluginRefs,
|
|
825
880
|
description: typeof data.description === 'string' ? data.description : '',
|
|
826
881
|
image: frontmatterImage || discoveredImage || undefined,
|
|
882
|
+
hidden: parseHiddenFlag(data.hidden),
|
|
827
883
|
createdAt: stats.birthtime,
|
|
828
884
|
updatedAt: stats.mtime,
|
|
829
885
|
};
|
|
830
886
|
} catch (error) {
|
|
831
|
-
if (agentId !== SYSTEM_AGENT_ID) {
|
|
887
|
+
if (agentId !== SYSTEM_AGENT_ID && agentId !== STATE_AGENT_ID) {
|
|
832
888
|
const err = new Error(`Agent "${agentId}" does not exist.`);
|
|
833
889
|
(err as Error & { code?: string }).code = 'AGENT_NOT_FOUND';
|
|
834
890
|
throw err;
|
|
835
891
|
}
|
|
836
|
-
// swallow:
|
|
892
|
+
// swallow: built-in agents have optional `agents/<id>/AGENT.md` overrides
|
|
837
893
|
void error;
|
|
838
894
|
}
|
|
839
895
|
|
|
@@ -841,6 +897,10 @@ export const storageService = {
|
|
|
841
897
|
return getSystemAgentDetails(diskDetails);
|
|
842
898
|
}
|
|
843
899
|
|
|
900
|
+
if (agentId === STATE_AGENT_ID) {
|
|
901
|
+
return getStateAgentDetails(diskDetails);
|
|
902
|
+
}
|
|
903
|
+
|
|
844
904
|
if (!diskDetails) {
|
|
845
905
|
const error = new Error(`Agent "${agentId}" does not exist.`);
|
|
846
906
|
(error as Error & { code?: string }).code = 'AGENT_NOT_FOUND';
|
|
@@ -854,6 +914,7 @@ export const storageService = {
|
|
|
854
914
|
name,
|
|
855
915
|
description = '',
|
|
856
916
|
image,
|
|
917
|
+
hidden,
|
|
857
918
|
instructions,
|
|
858
919
|
plugins,
|
|
859
920
|
}: {
|
|
@@ -861,10 +922,11 @@ export const storageService = {
|
|
|
861
922
|
name: string;
|
|
862
923
|
description?: string;
|
|
863
924
|
image?: string;
|
|
925
|
+
hidden?: boolean;
|
|
864
926
|
instructions: string;
|
|
865
927
|
plugins: PluginRef[];
|
|
866
928
|
}): Promise<void> => {
|
|
867
|
-
|
|
929
|
+
assertAgentIdFormat(agentId);
|
|
868
930
|
const agentDir = resolvePath(path.join(getAgentsRootDir(), agentId));
|
|
869
931
|
const agentMdPath = path.join(agentDir, 'AGENT.md');
|
|
870
932
|
|
|
@@ -892,6 +954,9 @@ export const storageService = {
|
|
|
892
954
|
if (typeof image === 'string' && image.trim() !== '') {
|
|
893
955
|
data.image = image.trim();
|
|
894
956
|
}
|
|
957
|
+
if (hidden === true) {
|
|
958
|
+
data.hidden = true;
|
|
959
|
+
}
|
|
895
960
|
|
|
896
961
|
const body = matter.stringify(`${instructions.trim()}\n`, data);
|
|
897
962
|
await fs.writeFile(agentMdPath, body, 'utf-8');
|
|
@@ -901,6 +966,7 @@ export const storageService = {
|
|
|
901
966
|
name,
|
|
902
967
|
description,
|
|
903
968
|
image,
|
|
969
|
+
hidden,
|
|
904
970
|
instructions,
|
|
905
971
|
plugins,
|
|
906
972
|
}: {
|
|
@@ -908,10 +974,11 @@ export const storageService = {
|
|
|
908
974
|
name?: string;
|
|
909
975
|
description?: string;
|
|
910
976
|
image?: string;
|
|
977
|
+
hidden?: boolean;
|
|
911
978
|
instructions?: string;
|
|
912
979
|
plugins?: PluginRef[];
|
|
913
980
|
}): Promise<void> => {
|
|
914
|
-
|
|
981
|
+
assertAgentIdFormat(agentId);
|
|
915
982
|
const agentDir = resolvePath(path.join(getAgentsRootDir(), agentId));
|
|
916
983
|
const agentMdPath = path.join(agentDir, 'AGENT.md');
|
|
917
984
|
|
|
@@ -920,14 +987,18 @@ export const storageService = {
|
|
|
920
987
|
raw = await fs.readFile(agentMdPath, 'utf-8');
|
|
921
988
|
} catch (error: unknown) {
|
|
922
989
|
if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
990
|
+
if (!isBuiltinOverlayAgentId(agentId)) {
|
|
991
|
+
const err = new Error(`Agent "${agentId}" does not exist.`);
|
|
992
|
+
(err as Error & { code?: string }).code = 'AGENT_NOT_FOUND';
|
|
993
|
+
throw err;
|
|
994
|
+
}
|
|
995
|
+
raw = '';
|
|
996
|
+
} else {
|
|
997
|
+
throw error;
|
|
926
998
|
}
|
|
927
|
-
throw error;
|
|
928
999
|
}
|
|
929
1000
|
|
|
930
|
-
const parsed = matter(raw);
|
|
1001
|
+
const parsed = raw === '' ? { data: {}, content: '' } : matter(raw);
|
|
931
1002
|
const nextData: Record<string, unknown> = { ...parsed.data };
|
|
932
1003
|
if (name !== undefined) nextData.name = name;
|
|
933
1004
|
if (description !== undefined) nextData.description = description;
|
|
@@ -939,17 +1010,84 @@ export const storageService = {
|
|
|
939
1010
|
delete nextData.image;
|
|
940
1011
|
}
|
|
941
1012
|
}
|
|
1013
|
+
if (hidden !== undefined) {
|
|
1014
|
+
if (hidden) {
|
|
1015
|
+
nextData.hidden = true;
|
|
1016
|
+
} else {
|
|
1017
|
+
delete nextData.hidden;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
let nextContent = instructions !== undefined ? instructions : parsed.content;
|
|
1022
|
+
|
|
1023
|
+
// Built-in agents merge disk overlays with code defaults on read; on write, partial
|
|
1024
|
+
// updates (e.g. plugins-only) must still persist a complete AGENT.md.
|
|
1025
|
+
if (isBuiltinOverlayAgentId(agentId)) {
|
|
1026
|
+
const pluginRefs =
|
|
1027
|
+
plugins ??
|
|
1028
|
+
(nextData.plugins !== undefined ? parsePluginRefs(nextData.plugins) : undefined);
|
|
1029
|
+
const diskOverrides: Partial<AgentDetails> = {};
|
|
1030
|
+
if (typeof nextData.name === 'string' && nextData.name.trim() !== '') {
|
|
1031
|
+
diskOverrides.name = nextData.name;
|
|
1032
|
+
}
|
|
1033
|
+
if (typeof nextData.description === 'string') {
|
|
1034
|
+
diskOverrides.description = nextData.description;
|
|
1035
|
+
}
|
|
1036
|
+
const trimmedContent = String(nextContent).trim();
|
|
1037
|
+
if (trimmedContent) {
|
|
1038
|
+
diskOverrides.instructions = trimmedContent;
|
|
1039
|
+
}
|
|
1040
|
+
if (pluginRefs && pluginRefs.length > 0) {
|
|
1041
|
+
diskOverrides.pluginRefs = pluginRefs;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const effective =
|
|
1045
|
+
agentId === SYSTEM_AGENT_ID
|
|
1046
|
+
? getSystemAgentDetails(diskOverrides)
|
|
1047
|
+
: getStateAgentDetails(diskOverrides);
|
|
1048
|
+
|
|
1049
|
+
if (name === undefined) nextData.name = effective.name;
|
|
1050
|
+
if (description === undefined) nextData.description = effective.description;
|
|
1051
|
+
if (instructions === undefined && !String(nextContent).trim()) {
|
|
1052
|
+
nextContent = effective.instructions;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
942
1055
|
|
|
943
|
-
const nextContent = instructions !== undefined ? instructions : parsed.content;
|
|
944
1056
|
const body = matter.stringify(`${String(nextContent).trim()}\n`, nextData);
|
|
1057
|
+
await fs.mkdir(path.dirname(agentMdPath), { recursive: true });
|
|
945
1058
|
await fs.writeFile(agentMdPath, body, 'utf-8');
|
|
946
1059
|
},
|
|
947
1060
|
deleteAgent: async ({ agentId }: { agentId: string }): Promise<void> => {
|
|
948
|
-
|
|
1061
|
+
assertAgentIdFormat(agentId);
|
|
949
1062
|
const agentDir = resolvePath(path.join(getAgentsRootDir(), agentId));
|
|
950
1063
|
const agentMdPath = path.join(agentDir, 'AGENT.md');
|
|
951
1064
|
const packageJsonPath = path.join(agentDir, 'package.json');
|
|
952
1065
|
|
|
1066
|
+
if (isBuiltinOverlayAgentId(agentId)) {
|
|
1067
|
+
try {
|
|
1068
|
+
await fs.access(agentMdPath);
|
|
1069
|
+
} catch (error: unknown) {
|
|
1070
|
+
if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {
|
|
1071
|
+
const err = new Error(
|
|
1072
|
+
`Agent "${agentId}" has no AGENT.md on disk; nothing to remove (defaults already apply).`,
|
|
1073
|
+
);
|
|
1074
|
+
(err as Error & { code?: string }).code = 'AGENT_NOT_FOUND';
|
|
1075
|
+
throw err;
|
|
1076
|
+
}
|
|
1077
|
+
throw error;
|
|
1078
|
+
}
|
|
1079
|
+
await fs.unlink(agentMdPath);
|
|
1080
|
+
try {
|
|
1081
|
+
const remaining = await fs.readdir(agentDir);
|
|
1082
|
+
if (remaining.length === 0) {
|
|
1083
|
+
await fs.rmdir(agentDir);
|
|
1084
|
+
}
|
|
1085
|
+
} catch {
|
|
1086
|
+
// ignore cleanup failures
|
|
1087
|
+
}
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
953
1091
|
try {
|
|
954
1092
|
await fs.access(agentDir);
|
|
955
1093
|
} catch (error: unknown) {
|
|
@@ -1060,8 +1198,10 @@ export const storageService = {
|
|
|
1060
1198
|
try {
|
|
1061
1199
|
const threadDir = getConversationDir(channelId, threadId);
|
|
1062
1200
|
if (threadId) {
|
|
1201
|
+
let exists = false;
|
|
1063
1202
|
try {
|
|
1064
1203
|
await fs.access(threadDir);
|
|
1204
|
+
exists = true;
|
|
1065
1205
|
} catch (error: unknown) {
|
|
1066
1206
|
if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {
|
|
1067
1207
|
const threadTitle = buildThreadTitleFromEvent(event);
|
|
@@ -1074,6 +1214,23 @@ export const storageService = {
|
|
|
1074
1214
|
throw error;
|
|
1075
1215
|
}
|
|
1076
1216
|
}
|
|
1217
|
+
|
|
1218
|
+
if (exists) {
|
|
1219
|
+
// If the thread already exists, check if it has a name.
|
|
1220
|
+
// This handles threads created via action:create_thread without a title.
|
|
1221
|
+
const threadDetails = await storageService.getThreadDetails({ channelId, threadId });
|
|
1222
|
+
const currentState = (threadDetails.state as Record<string, unknown>) || {};
|
|
1223
|
+
if (!currentState.name) {
|
|
1224
|
+
const threadTitle = buildThreadTitleFromEvent(event);
|
|
1225
|
+
if (threadTitle) {
|
|
1226
|
+
await storageService.patchThreadState({
|
|
1227
|
+
channelId,
|
|
1228
|
+
threadId,
|
|
1229
|
+
state: { name: threadTitle },
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1077
1234
|
} else {
|
|
1078
1235
|
await fs.mkdir(threadDir, { recursive: true });
|
|
1079
1236
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type { OpenBotEvent } from '
|
|
2
|
-
import type { PluginRef } from './
|
|
3
|
-
import type { MemoryRecord, ListMemoriesArgs } from '
|
|
1
|
+
import type { OpenBotEvent } from '../../app/types.js';
|
|
2
|
+
import type { PluginRef } from './types.js';
|
|
3
|
+
import type { MemoryRecord, ListMemoriesArgs } from '../../plugins/memory/service.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Public data types exposed by the OpenBot
|
|
6
|
+
* Public data types exposed by the OpenBot platform.
|
|
7
7
|
*
|
|
8
|
-
* The
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* The platform layer owns channels, threads, the agent registry, and the event
|
|
9
|
+
* stream. Agents are composed entirely of Plugins (see `./types.ts`); their
|
|
10
|
+
* internal implementation is opaque to the platform.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
export type Agent = {
|
|
@@ -17,6 +17,8 @@ export type Agent = {
|
|
|
17
17
|
image?: string;
|
|
18
18
|
/** Plugin ids that compose this agent (mirrors AGENT.md `plugins[].id`). */
|
|
19
19
|
plugins: string[];
|
|
20
|
+
/** When true, omitted from `action:storage:get-agents` (still available via get-agent-details). */
|
|
21
|
+
hidden?: boolean;
|
|
20
22
|
createdAt: Date;
|
|
21
23
|
updatedAt: Date;
|
|
22
24
|
};
|
|
@@ -110,27 +112,34 @@ export interface Storage {
|
|
|
110
112
|
}) => Promise<void>;
|
|
111
113
|
getThreads: (args: { channelId: string }) => Promise<Thread[]>;
|
|
112
114
|
getThreadDetails: (args: { channelId: string; threadId: string }) => Promise<ThreadDetails>;
|
|
115
|
+
/** User-facing agent list; excludes agents with `hidden: true` (e.g. built-in `state`). */
|
|
113
116
|
getAgents: () => Promise<Agent[]>;
|
|
114
117
|
getPlugins: () => Promise<PluginDescriptor[]>;
|
|
115
118
|
getAgentDetails: (args: { agentId: string }) => Promise<AgentDetails>;
|
|
119
|
+
/** Includes built-in `system` / `state` agents as optional AGENT.md overlays. */
|
|
116
120
|
createAgent: (args: {
|
|
117
121
|
agentId: string;
|
|
118
122
|
name: string;
|
|
119
123
|
description?: string;
|
|
120
124
|
/** Avatar/logo URL or data URI; persisted in AGENT.md frontmatter. */
|
|
121
125
|
image?: string;
|
|
126
|
+
/** When true, agent is omitted from `getAgents` / `action:storage:get-agents`. */
|
|
127
|
+
hidden?: boolean;
|
|
122
128
|
instructions: string;
|
|
123
129
|
plugins: PluginRef[];
|
|
124
130
|
}) => Promise<void>;
|
|
131
|
+
/** Partial update; for `system` / `state`, creates overlay file if missing. */
|
|
125
132
|
updateAgent: (args: {
|
|
126
133
|
agentId: string;
|
|
127
134
|
name?: string;
|
|
128
135
|
description?: string;
|
|
129
136
|
/** Omit to leave unchanged; empty string removes stored image. */
|
|
130
137
|
image?: string;
|
|
138
|
+
hidden?: boolean;
|
|
131
139
|
instructions?: string;
|
|
132
140
|
plugins?: PluginRef[];
|
|
133
141
|
}) => Promise<void>;
|
|
142
|
+
/** For `system` / `state`, removes only `AGENT.md` (reverts to code defaults). */
|
|
134
143
|
deleteAgent: (args: { agentId: string }) => Promise<void>;
|
|
135
144
|
getEvents: (args: { channelId: string; threadId?: string }) => Promise<OpenBotEvent[]>;
|
|
136
145
|
getChannelDetails: (args: { channelId: string }) => Promise<ChannelDetails>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Plugin } from './types.js';
|
|
2
|
+
|
|
3
|
+
/** Resolved plugins (built-in + community); shared with registry resolution. */
|
|
4
|
+
export const resolvedPluginCache = new Map<string, Plugin>();
|
|
5
|
+
|
|
6
|
+
/** Community plugin ids that have already been logged as loaded once. */
|
|
7
|
+
export const loadedCommunityPlugins = new Set<string>();
|
|
8
|
+
|
|
9
|
+
/** Drop a single id from the in-memory resolver cache (e.g. after install/uninstall). */
|
|
10
|
+
export function invalidatePlugin(id: string): void {
|
|
11
|
+
resolvedPluginCache.delete(id);
|
|
12
|
+
loadedCommunityPlugins.delete(id);
|
|
13
|
+
}
|