openbot 0.2.3 → 0.2.6
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 +1 -1
- package/dist/agents/agent-creator.js +58 -19
- package/dist/agents/os-agent.js +1 -4
- package/dist/agents/planner-agent.js +32 -0
- package/dist/agents/topic-agent.js +1 -1
- package/dist/architecture/contracts.js +1 -0
- package/dist/architecture/execution-engine.js +151 -0
- package/dist/architecture/intent-classifier.js +26 -0
- package/dist/architecture/planner.js +106 -0
- package/dist/automation-worker.js +121 -0
- package/dist/automations.js +52 -0
- package/dist/cli.js +116 -146
- package/dist/config.js +20 -0
- package/dist/core/agents.js +41 -0
- package/dist/core/delegation.js +124 -0
- package/dist/core/manager.js +73 -0
- package/dist/core/plugins.js +77 -0
- package/dist/core/router.js +40 -0
- package/dist/installers.js +156 -0
- package/dist/marketplace.js +80 -0
- package/dist/open-bot.js +34 -157
- package/dist/orchestrator.js +247 -51
- package/dist/plugins/approval/index.js +107 -3
- package/dist/plugins/brain/index.js +17 -86
- package/dist/plugins/brain/memory.js +1 -1
- package/dist/plugins/brain/prompt.js +8 -13
- package/dist/plugins/brain/types.js +0 -15
- package/dist/plugins/file-system/index.js +8 -8
- package/dist/plugins/llm/context-shaping.js +177 -0
- package/dist/plugins/llm/index.js +223 -49
- package/dist/plugins/memory/index.js +220 -0
- package/dist/plugins/memory/memory.js +122 -0
- package/dist/plugins/memory/prompt.js +55 -0
- package/dist/plugins/memory/types.js +45 -0
- package/dist/plugins/shell/index.js +3 -3
- package/dist/plugins/skills/index.js +9 -9
- package/dist/registry/index.js +1 -4
- package/dist/registry/plugin-loader.js +361 -56
- package/dist/registry/plugin-registry.js +21 -4
- package/dist/registry/ts-agent-loader.js +4 -4
- package/dist/registry/yaml-agent-loader.js +78 -20
- package/dist/runtime/execution-trace.js +41 -0
- package/dist/runtime/intent-routing.js +26 -0
- package/dist/runtime/openbot-runtime.js +354 -0
- package/dist/server.js +513 -41
- package/dist/ui/widgets/approval-card.js +22 -2
- package/dist/ui/widgets/delegation.js +29 -0
- package/dist/version.js +62 -0
- package/package.json +4 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ui } from "@melony/ui-kit/server";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { createIdentityModule } from "./identity.js";
|
|
5
4
|
import { createMemoryModule } from "./memory.js";
|
|
6
5
|
import { buildBrainPrompt } from "./prompt.js";
|
|
7
6
|
import { statusWidget } from "../../ui/widgets/status.js";
|
|
@@ -16,44 +15,40 @@ function expandPath(p) {
|
|
|
16
15
|
}
|
|
17
16
|
/**
|
|
18
17
|
* Create a prompt-builder function bound to a baseDir.
|
|
19
|
-
* Returns the brain's portion of the system prompt (
|
|
18
|
+
* Returns the brain's portion of the system prompt (base context + memory).
|
|
20
19
|
*/
|
|
21
|
-
export function createBrainPromptBuilder(baseDir) {
|
|
20
|
+
export function createBrainPromptBuilder(baseDir, baseContext) {
|
|
22
21
|
const expandedBase = expandPath(baseDir);
|
|
23
22
|
const modules = {
|
|
24
|
-
identity: createIdentityModule(expandedBase),
|
|
25
23
|
memory: createMemoryModule(expandedBase),
|
|
26
24
|
};
|
|
27
|
-
return async (context) => buildBrainPrompt(expandedBase, modules, context);
|
|
25
|
+
return async (context) => buildBrainPrompt(expandedBase, modules, baseContext || "", context);
|
|
28
26
|
}
|
|
29
27
|
// --- Plugin ---
|
|
30
28
|
/**
|
|
31
29
|
* Brain Plugin for Melony
|
|
32
30
|
*
|
|
33
|
-
* Provides
|
|
31
|
+
* Provides memory capabilities (long-term memory + recall).
|
|
34
32
|
* Skills are managed by the separate skills plugin.
|
|
35
33
|
*
|
|
36
|
-
* Architecture: thin facade
|
|
37
|
-
* (identity, memory) — each independently testable and replaceable.
|
|
34
|
+
* Architecture: thin facade over memory module.
|
|
38
35
|
*/
|
|
39
36
|
export const brainPlugin = (options) => (builder) => {
|
|
40
37
|
const { baseDir } = options;
|
|
41
38
|
const expandedBase = expandPath(baseDir);
|
|
42
|
-
// Create sub-
|
|
43
|
-
const identity = createIdentityModule(expandedBase);
|
|
39
|
+
// Create sub-module
|
|
44
40
|
const memory = createMemoryModule(expandedBase);
|
|
45
41
|
// ─── Initialization ───────────────────────────────────────────────
|
|
46
|
-
builder.on("init", async function* (_event
|
|
42
|
+
builder.on("init", async function* (_event) {
|
|
47
43
|
yield {
|
|
48
44
|
type: "brain:status",
|
|
49
|
-
data: { message: "Initializing
|
|
45
|
+
data: { message: "Initializing memory..." },
|
|
50
46
|
};
|
|
51
47
|
await fs.mkdir(expandedBase, { recursive: true, mode: 0o700 });
|
|
52
|
-
await identity.initialize();
|
|
53
48
|
await memory.initialize();
|
|
54
49
|
yield {
|
|
55
50
|
type: "brain:status",
|
|
56
|
-
data: { message: "
|
|
51
|
+
data: { message: "Memory initialized", severity: "success" },
|
|
57
52
|
};
|
|
58
53
|
});
|
|
59
54
|
// ─── Memory: Remember ─────────────────────────────────────────────
|
|
@@ -66,7 +61,7 @@ export const brainPlugin = (options) => (builder) => {
|
|
|
66
61
|
data: { message: "Remembered", severity: "success" },
|
|
67
62
|
};
|
|
68
63
|
yield {
|
|
69
|
-
type: "action:
|
|
64
|
+
type: "action:result",
|
|
70
65
|
data: {
|
|
71
66
|
action: "remember",
|
|
72
67
|
toolCallId,
|
|
@@ -87,7 +82,7 @@ export const brainPlugin = (options) => (builder) => {
|
|
|
87
82
|
},
|
|
88
83
|
};
|
|
89
84
|
yield {
|
|
90
|
-
type: "action:
|
|
85
|
+
type: "action:result",
|
|
91
86
|
data: {
|
|
92
87
|
action: "remember",
|
|
93
88
|
toolCallId,
|
|
@@ -102,7 +97,7 @@ export const brainPlugin = (options) => (builder) => {
|
|
|
102
97
|
try {
|
|
103
98
|
const results = await memory.recall(query, { tags, limit });
|
|
104
99
|
yield {
|
|
105
|
-
type: "action:
|
|
100
|
+
type: "action:result",
|
|
106
101
|
data: {
|
|
107
102
|
action: "recall",
|
|
108
103
|
toolCallId,
|
|
@@ -120,7 +115,7 @@ export const brainPlugin = (options) => (builder) => {
|
|
|
120
115
|
}
|
|
121
116
|
catch (error) {
|
|
122
117
|
yield {
|
|
123
|
-
type: "action:
|
|
118
|
+
type: "action:result",
|
|
124
119
|
data: {
|
|
125
120
|
action: "recall",
|
|
126
121
|
toolCallId,
|
|
@@ -142,7 +137,7 @@ export const brainPlugin = (options) => (builder) => {
|
|
|
142
137
|
},
|
|
143
138
|
};
|
|
144
139
|
yield {
|
|
145
|
-
type: "action:
|
|
140
|
+
type: "action:result",
|
|
146
141
|
data: {
|
|
147
142
|
action: "forget",
|
|
148
143
|
toolCallId,
|
|
@@ -157,7 +152,7 @@ export const brainPlugin = (options) => (builder) => {
|
|
|
157
152
|
}
|
|
158
153
|
catch (error) {
|
|
159
154
|
yield {
|
|
160
|
-
type: "action:
|
|
155
|
+
type: "action:result",
|
|
161
156
|
data: {
|
|
162
157
|
action: "forget",
|
|
163
158
|
toolCallId,
|
|
@@ -176,7 +171,7 @@ export const brainPlugin = (options) => (builder) => {
|
|
|
176
171
|
data: { message: "Journal entry added", severity: "success" },
|
|
177
172
|
};
|
|
178
173
|
yield {
|
|
179
|
-
type: "action:
|
|
174
|
+
type: "action:result",
|
|
180
175
|
data: {
|
|
181
176
|
action: "journal",
|
|
182
177
|
toolCallId,
|
|
@@ -193,7 +188,7 @@ export const brainPlugin = (options) => (builder) => {
|
|
|
193
188
|
},
|
|
194
189
|
};
|
|
195
190
|
yield {
|
|
196
|
-
type: "action:
|
|
191
|
+
type: "action:result",
|
|
197
192
|
data: {
|
|
198
193
|
action: "journal",
|
|
199
194
|
toolCallId,
|
|
@@ -202,70 +197,6 @@ export const brainPlugin = (options) => (builder) => {
|
|
|
202
197
|
};
|
|
203
198
|
}
|
|
204
199
|
});
|
|
205
|
-
// ─── Identity: Update ──────────────────────────────────────────────
|
|
206
|
-
builder.on("action:updateIdentity", async function* (event) {
|
|
207
|
-
const { content, toolCallId } = event.data;
|
|
208
|
-
try {
|
|
209
|
-
await identity.updateIdentity(content);
|
|
210
|
-
yield {
|
|
211
|
-
type: "brain:status",
|
|
212
|
-
data: { message: "Identity updated", severity: "success" },
|
|
213
|
-
};
|
|
214
|
-
yield {
|
|
215
|
-
type: "action:taskResult",
|
|
216
|
-
data: {
|
|
217
|
-
action: "updateIdentity",
|
|
218
|
-
toolCallId,
|
|
219
|
-
result: {
|
|
220
|
-
success: true,
|
|
221
|
-
message: "Identity updated. Changes will take effect on next initialization.",
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
catch (error) {
|
|
227
|
-
yield {
|
|
228
|
-
type: "brain:status",
|
|
229
|
-
data: {
|
|
230
|
-
message: `Failed to update identity: ${error.message}`,
|
|
231
|
-
severity: "error",
|
|
232
|
-
},
|
|
233
|
-
};
|
|
234
|
-
yield {
|
|
235
|
-
type: "action:taskResult",
|
|
236
|
-
data: {
|
|
237
|
-
action: "updateIdentity",
|
|
238
|
-
toolCallId,
|
|
239
|
-
result: { error: error.message },
|
|
240
|
-
},
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
// ─── Identity: Read ────────────────────────────────────────────────
|
|
245
|
-
builder.on("action:readIdentity", async function* (event) {
|
|
246
|
-
const { file, toolCallId } = event.data;
|
|
247
|
-
try {
|
|
248
|
-
const content = await identity.readFile(file);
|
|
249
|
-
yield {
|
|
250
|
-
type: "action:taskResult",
|
|
251
|
-
data: {
|
|
252
|
-
action: "readIdentity",
|
|
253
|
-
toolCallId,
|
|
254
|
-
result: { file, content },
|
|
255
|
-
},
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
catch (error) {
|
|
259
|
-
yield {
|
|
260
|
-
type: "action:taskResult",
|
|
261
|
-
data: {
|
|
262
|
-
action: "readIdentity",
|
|
263
|
-
toolCallId,
|
|
264
|
-
result: { error: `Could not read ${file}: ${error.message}` },
|
|
265
|
-
},
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
200
|
builder.on("brain:status", async function* (event) {
|
|
270
201
|
yield ui.event(statusWidget(event.data.message, event.data.severity));
|
|
271
202
|
});
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Includes only what the brain owns:
|
|
6
6
|
* - Environment context
|
|
7
|
-
* -
|
|
7
|
+
* - Base context from settings (optional)
|
|
8
8
|
* - A handful of the most recent memories
|
|
9
|
-
* -
|
|
9
|
+
* - Memory capability instructions
|
|
10
10
|
*
|
|
11
11
|
* Skills are handled by the separate skills plugin and composed
|
|
12
12
|
* at the top level in open-bot.ts.
|
|
13
13
|
*/
|
|
14
|
-
export async function buildBrainPrompt(baseDir, modules, context) {
|
|
14
|
+
export async function buildBrainPrompt(baseDir, modules, baseContext, context) {
|
|
15
15
|
const parts = [];
|
|
16
16
|
const state = context?.state;
|
|
17
17
|
const currentCwd = state?.cwd || process.cwd();
|
|
@@ -22,13 +22,10 @@ export async function buildBrainPrompt(baseDir, modules, context) {
|
|
|
22
22
|
- CWD: ${currentCwd}
|
|
23
23
|
- Bot Home: ${baseDir}
|
|
24
24
|
</environment>`);
|
|
25
|
-
// 2.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const soul = await modules.identity.getSoul();
|
|
30
|
-
if (soul)
|
|
31
|
-
parts.push(`<soul>\n${soul}\n</soul>`);
|
|
25
|
+
// 2. Base context from settings (optional)
|
|
26
|
+
if (baseContext.trim()) {
|
|
27
|
+
parts.push(`<base_context>\n${baseContext.trim()}\n</base_context>`);
|
|
28
|
+
}
|
|
32
29
|
// 3. Recent memories (lean — just a few to keep context fresh)
|
|
33
30
|
const recentFacts = await modules.memory.getRecentFacts(5);
|
|
34
31
|
if (recentFacts.length > 0) {
|
|
@@ -37,15 +34,13 @@ export async function buildBrainPrompt(baseDir, modules, context) {
|
|
|
37
34
|
.join("\n");
|
|
38
35
|
parts.push(`<recent_memories>\n${factsList}\n</recent_memories>`);
|
|
39
36
|
}
|
|
40
|
-
// 4.
|
|
37
|
+
// 4. Memory capabilities
|
|
41
38
|
parts.push(`<brain_tools>
|
|
42
39
|
Use these to manage your persistent state:
|
|
43
40
|
- \`remember(content, tags)\`: Store facts/preferences
|
|
44
41
|
- \`recall(query, tags)\`: Search long-term memory
|
|
45
42
|
- \`forget(memoryId)\`: Remove outdated info
|
|
46
43
|
- \`journal(content)\`: Record session reflections
|
|
47
|
-
- \`updateIdentity(content)\`: Refine your persona
|
|
48
|
-
- \`readIdentity(file)\`: Inspect SOUL.md or IDENTITY.md
|
|
49
44
|
</brain_tools>`);
|
|
50
45
|
return `\n${parts.join("\n\n")}\n`;
|
|
51
46
|
}
|
|
@@ -42,19 +42,4 @@ export const brainToolDefinitions = {
|
|
|
42
42
|
content: z.string().describe("Journal entry content"),
|
|
43
43
|
}),
|
|
44
44
|
},
|
|
45
|
-
// Identity tools
|
|
46
|
-
updateIdentity: {
|
|
47
|
-
description: "Update your identity file to refine your personality and traits. Start it with # Identity.",
|
|
48
|
-
inputSchema: z.object({
|
|
49
|
-
content: z.string().describe("New content for IDENTITY.md"),
|
|
50
|
-
}),
|
|
51
|
-
},
|
|
52
|
-
readIdentity: {
|
|
53
|
-
description: "Read your current identity or soul configuration.",
|
|
54
|
-
inputSchema: z.object({
|
|
55
|
-
file: z
|
|
56
|
-
.enum(["IDENTITY.md", "SOUL.md"])
|
|
57
|
-
.describe("Which identity file to read"),
|
|
58
|
-
}),
|
|
59
|
-
},
|
|
60
45
|
};
|
|
@@ -56,7 +56,7 @@ export const fileSystemPlugin = (options = {}) => (builder) => {
|
|
|
56
56
|
try {
|
|
57
57
|
const content = await fs.readFile(resolvePath(filePath, state.cwd), "utf-8");
|
|
58
58
|
yield {
|
|
59
|
-
type: "action:
|
|
59
|
+
type: "action:result",
|
|
60
60
|
data: {
|
|
61
61
|
action: "readFile",
|
|
62
62
|
result: { content: truncate(content, maxFileReadLength) },
|
|
@@ -70,7 +70,7 @@ export const fileSystemPlugin = (options = {}) => (builder) => {
|
|
|
70
70
|
}
|
|
71
71
|
catch (error) {
|
|
72
72
|
yield {
|
|
73
|
-
type: "action:
|
|
73
|
+
type: "action:result",
|
|
74
74
|
data: { action: "readFile", result: { error: error.message }, toolCallId },
|
|
75
75
|
};
|
|
76
76
|
yield {
|
|
@@ -90,7 +90,7 @@ export const fileSystemPlugin = (options = {}) => (builder) => {
|
|
|
90
90
|
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
91
91
|
await fs.writeFile(fullPath, content, "utf-8");
|
|
92
92
|
yield {
|
|
93
|
-
type: "action:
|
|
93
|
+
type: "action:result",
|
|
94
94
|
data: { action: "writeFile", result: { success: true }, toolCallId },
|
|
95
95
|
};
|
|
96
96
|
yield {
|
|
@@ -100,7 +100,7 @@ export const fileSystemPlugin = (options = {}) => (builder) => {
|
|
|
100
100
|
}
|
|
101
101
|
catch (error) {
|
|
102
102
|
yield {
|
|
103
|
-
type: "action:
|
|
103
|
+
type: "action:result",
|
|
104
104
|
data: { action: "writeFile", result: { error: error.message }, toolCallId },
|
|
105
105
|
};
|
|
106
106
|
yield {
|
|
@@ -122,7 +122,7 @@ export const fileSystemPlugin = (options = {}) => (builder) => {
|
|
|
122
122
|
data: { message: `Files listed successfully`, severity: "success" }
|
|
123
123
|
};
|
|
124
124
|
yield {
|
|
125
|
-
type: "action:
|
|
125
|
+
type: "action:result",
|
|
126
126
|
data: { action: "listFiles", result: { files }, toolCallId },
|
|
127
127
|
};
|
|
128
128
|
}
|
|
@@ -132,7 +132,7 @@ export const fileSystemPlugin = (options = {}) => (builder) => {
|
|
|
132
132
|
data: { message: `Files listing failed: ${error.message}`, severity: "error" }
|
|
133
133
|
};
|
|
134
134
|
yield {
|
|
135
|
-
type: "action:
|
|
135
|
+
type: "action:result",
|
|
136
136
|
data: { action: "listFiles", result: { error: error.message }, toolCallId },
|
|
137
137
|
};
|
|
138
138
|
}
|
|
@@ -146,7 +146,7 @@ export const fileSystemPlugin = (options = {}) => (builder) => {
|
|
|
146
146
|
try {
|
|
147
147
|
await fs.unlink(resolvePath(filePath, state.cwd));
|
|
148
148
|
yield {
|
|
149
|
-
type: "action:
|
|
149
|
+
type: "action:result",
|
|
150
150
|
data: { action: "deleteFile", result: { success: true }, toolCallId },
|
|
151
151
|
};
|
|
152
152
|
yield {
|
|
@@ -156,7 +156,7 @@ export const fileSystemPlugin = (options = {}) => (builder) => {
|
|
|
156
156
|
}
|
|
157
157
|
catch (error) {
|
|
158
158
|
yield {
|
|
159
|
-
type: "action:
|
|
159
|
+
type: "action:result",
|
|
160
160
|
data: { action: "deleteFile", result: { error: error.message }, toolCallId },
|
|
161
161
|
};
|
|
162
162
|
yield {
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
const DEFAULT_OPTIONS = {
|
|
2
|
+
maxRecentRawMessages: 4,
|
|
3
|
+
maxRelevantMessages: 6,
|
|
4
|
+
maxContextChars: 12000,
|
|
5
|
+
maxTurnSummaries: 20,
|
|
6
|
+
maxConstraints: 8,
|
|
7
|
+
};
|
|
8
|
+
const STOP_WORDS = new Set([
|
|
9
|
+
"the",
|
|
10
|
+
"and",
|
|
11
|
+
"for",
|
|
12
|
+
"that",
|
|
13
|
+
"with",
|
|
14
|
+
"this",
|
|
15
|
+
"from",
|
|
16
|
+
"have",
|
|
17
|
+
"your",
|
|
18
|
+
"will",
|
|
19
|
+
"would",
|
|
20
|
+
"should",
|
|
21
|
+
"could",
|
|
22
|
+
"about",
|
|
23
|
+
"what",
|
|
24
|
+
"when",
|
|
25
|
+
"where",
|
|
26
|
+
"which",
|
|
27
|
+
"into",
|
|
28
|
+
"just",
|
|
29
|
+
"like",
|
|
30
|
+
"than",
|
|
31
|
+
"then",
|
|
32
|
+
"there",
|
|
33
|
+
"their",
|
|
34
|
+
"them",
|
|
35
|
+
"also",
|
|
36
|
+
"need",
|
|
37
|
+
"want",
|
|
38
|
+
"please",
|
|
39
|
+
]);
|
|
40
|
+
function mergeOptions(overrides) {
|
|
41
|
+
return {
|
|
42
|
+
...DEFAULT_OPTIONS,
|
|
43
|
+
...(overrides ?? {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function clip(text, maxChars) {
|
|
47
|
+
if (text.length <= maxChars)
|
|
48
|
+
return text;
|
|
49
|
+
return `${text.slice(0, maxChars - 3)}...`;
|
|
50
|
+
}
|
|
51
|
+
function toSearchText(message) {
|
|
52
|
+
return message.content.toLowerCase();
|
|
53
|
+
}
|
|
54
|
+
function tokenize(text) {
|
|
55
|
+
return text
|
|
56
|
+
.toLowerCase()
|
|
57
|
+
.replace(/[^a-z0-9\s]/g, " ")
|
|
58
|
+
.split(/\s+/)
|
|
59
|
+
.filter((token) => token.length > 2 && !STOP_WORDS.has(token));
|
|
60
|
+
}
|
|
61
|
+
function overlapScore(messageText, queryTerms) {
|
|
62
|
+
if (queryTerms.length === 0)
|
|
63
|
+
return 0;
|
|
64
|
+
let matched = 0;
|
|
65
|
+
for (const term of queryTerms) {
|
|
66
|
+
if (messageText.includes(term))
|
|
67
|
+
matched += 1;
|
|
68
|
+
}
|
|
69
|
+
return matched / queryTerms.length;
|
|
70
|
+
}
|
|
71
|
+
function recencyBoost(index, total) {
|
|
72
|
+
if (total <= 1)
|
|
73
|
+
return 0;
|
|
74
|
+
return index / (total - 1);
|
|
75
|
+
}
|
|
76
|
+
function estimateChars(messages) {
|
|
77
|
+
return messages.reduce((sum, m) => sum + (m.content?.length ?? 0), 0);
|
|
78
|
+
}
|
|
79
|
+
function buildContextBrief(state, maxChars) {
|
|
80
|
+
if (!state)
|
|
81
|
+
return "";
|
|
82
|
+
const lines = [];
|
|
83
|
+
if (state.currentGoal)
|
|
84
|
+
lines.push(`Current objective: ${state.currentGoal}`);
|
|
85
|
+
if (state.constraints && state.constraints.length > 0) {
|
|
86
|
+
lines.push(`Constraints: ${state.constraints.join(" | ")}`);
|
|
87
|
+
}
|
|
88
|
+
if (state.rollingSummary)
|
|
89
|
+
lines.push(`Recent summary: ${state.rollingSummary}`);
|
|
90
|
+
if (lines.length === 0)
|
|
91
|
+
return "";
|
|
92
|
+
return clip(`System context for this turn:\n${lines.map((line) => `- ${line}`).join("\n")}`, maxChars);
|
|
93
|
+
}
|
|
94
|
+
export function buildShapedContext(input) {
|
|
95
|
+
const options = mergeOptions(input.options);
|
|
96
|
+
const messages = input.messages;
|
|
97
|
+
if (messages.length <= options.maxRecentRawMessages) {
|
|
98
|
+
const brief = buildContextBrief(input.contextState, 1500);
|
|
99
|
+
return brief
|
|
100
|
+
? [{ role: "user", content: brief }, ...messages]
|
|
101
|
+
: messages;
|
|
102
|
+
}
|
|
103
|
+
const recentStart = Math.max(0, messages.length - options.maxRecentRawMessages);
|
|
104
|
+
const recent = messages.slice(recentStart);
|
|
105
|
+
const older = messages.slice(0, recentStart);
|
|
106
|
+
const latestUser = [...messages]
|
|
107
|
+
.reverse()
|
|
108
|
+
.find((message) => message.role === "user");
|
|
109
|
+
const queryTerms = latestUser ? tokenize(latestUser.content) : [];
|
|
110
|
+
const scored = older
|
|
111
|
+
.map((message, index) => {
|
|
112
|
+
const text = toSearchText(message);
|
|
113
|
+
const relevance = overlapScore(text, queryTerms);
|
|
114
|
+
const score = relevance * 0.8 + recencyBoost(index, older.length) * 0.2;
|
|
115
|
+
return { index, score };
|
|
116
|
+
})
|
|
117
|
+
.filter((row) => row.score > 0)
|
|
118
|
+
.sort((a, b) => b.score - a.score)
|
|
119
|
+
.slice(0, options.maxRelevantMessages)
|
|
120
|
+
.sort((a, b) => a.index - b.index);
|
|
121
|
+
const selectedOlder = scored.map((row) => older[row.index]);
|
|
122
|
+
const brief = buildContextBrief(input.contextState, 1500);
|
|
123
|
+
let selected = [
|
|
124
|
+
...(brief ? [{ role: "user", content: brief }] : []),
|
|
125
|
+
...selectedOlder,
|
|
126
|
+
...recent,
|
|
127
|
+
];
|
|
128
|
+
while (estimateChars(selected) > options.maxContextChars) {
|
|
129
|
+
const removableIndex = selected.findIndex((message, index) => {
|
|
130
|
+
if (brief && index === 0)
|
|
131
|
+
return false;
|
|
132
|
+
return index < selected.length - options.maxRecentRawMessages;
|
|
133
|
+
});
|
|
134
|
+
if (removableIndex === -1)
|
|
135
|
+
break;
|
|
136
|
+
selected.splice(removableIndex, 1);
|
|
137
|
+
}
|
|
138
|
+
return selected;
|
|
139
|
+
}
|
|
140
|
+
function extractConstraints(text, existing, maxConstraints) {
|
|
141
|
+
const hasConstraintLanguage = /\b(do not|don't|must|never|always|only)\b/i.test(text);
|
|
142
|
+
if (!hasConstraintLanguage)
|
|
143
|
+
return existing;
|
|
144
|
+
const normalized = clip(text.trim().replace(/\s+/g, " "), 180);
|
|
145
|
+
if (!normalized)
|
|
146
|
+
return existing;
|
|
147
|
+
if (existing.includes(normalized))
|
|
148
|
+
return existing;
|
|
149
|
+
const next = [...existing, normalized];
|
|
150
|
+
return next.slice(-maxConstraints);
|
|
151
|
+
}
|
|
152
|
+
export function updateContextState(input, optionsOverride) {
|
|
153
|
+
const options = mergeOptions(optionsOverride);
|
|
154
|
+
const existing = input.contextState ?? {};
|
|
155
|
+
const constraints = extractConstraints(input.latestUserMessage, existing.constraints ?? [], options.maxConstraints);
|
|
156
|
+
const compactUser = clip(input.latestUserMessage.trim().replace(/\s+/g, " "), 200);
|
|
157
|
+
const compactAssistant = clip(input.latestAssistantMessage.trim().replace(/\s+/g, " "), 240);
|
|
158
|
+
const turnSummary = compactUser || compactAssistant
|
|
159
|
+
? `U: ${compactUser || "-"} | A: ${compactAssistant || "-"}`
|
|
160
|
+
: "";
|
|
161
|
+
const turnSummaries = turnSummary
|
|
162
|
+
? [...(existing.turnSummaries ?? []), turnSummary].slice(-options.maxTurnSummaries)
|
|
163
|
+
: existing.turnSummaries ?? [];
|
|
164
|
+
const rollingSummary = turnSummaries.slice(-6).join(" || ");
|
|
165
|
+
const latestUserMessage = input.latestUserMessage.trim();
|
|
166
|
+
const isToolEcho = latestUserMessage.startsWith("System: Action ");
|
|
167
|
+
const currentGoal = !isToolEcho && latestUserMessage
|
|
168
|
+
? clip(latestUserMessage, 240)
|
|
169
|
+
: existing.currentGoal;
|
|
170
|
+
return {
|
|
171
|
+
currentGoal,
|
|
172
|
+
constraints,
|
|
173
|
+
turnSummaries,
|
|
174
|
+
rollingSummary,
|
|
175
|
+
updatedAt: new Date().toISOString(),
|
|
176
|
+
};
|
|
177
|
+
}
|