openbot 0.2.12 → 0.2.13

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.
Files changed (141) hide show
  1. package/.prettierrc +8 -0
  2. package/AGENTS.md +68 -0
  3. package/CONTRIBUTING.md +74 -0
  4. package/LICENSE +21 -0
  5. package/README.md +117 -14
  6. package/dist/agents/system.js +106 -0
  7. package/dist/app/cli.js +27 -0
  8. package/dist/app/config.js +64 -0
  9. package/dist/app/server.js +237 -0
  10. package/dist/app/utils.js +35 -0
  11. package/dist/harness/agent-harness.js +45 -0
  12. package/dist/harness/mcp.js +61 -0
  13. package/dist/harness/orchestrator.js +273 -0
  14. package/dist/harness/process.js +7 -0
  15. package/dist/plugins/ai-sdk.js +141 -0
  16. package/dist/plugins/delegation.js +52 -0
  17. package/dist/plugins/mcp.js +140 -0
  18. package/dist/plugins/storage.js +502 -0
  19. package/dist/plugins/ui.js +47 -0
  20. package/dist/registry/plugins.js +73 -0
  21. package/dist/services/storage.js +724 -0
  22. package/docs/README.md +7 -0
  23. package/docs/agents.md +83 -0
  24. package/docs/architecture.md +34 -0
  25. package/docs/plugins.md +77 -0
  26. package/logo-black.png +0 -0
  27. package/{dist/assets/logo.js → logo-black.svg} +24 -24
  28. package/{dist/ui/sidebar.js → logo-white.svg} +23 -88
  29. package/package.json +10 -9
  30. package/src/agents/system.ts +112 -0
  31. package/src/app/cli.ts +38 -0
  32. package/src/app/config.ts +104 -0
  33. package/src/app/server.ts +284 -0
  34. package/src/app/types.ts +476 -0
  35. package/src/app/utils.ts +43 -0
  36. package/src/assets/icon.svg +1 -0
  37. package/src/harness/agent-harness.ts +58 -0
  38. package/src/harness/mcp.ts +78 -0
  39. package/src/harness/orchestrator.ts +342 -0
  40. package/src/harness/process.ts +9 -0
  41. package/src/harness/types.ts +34 -0
  42. package/src/plugins/ai-sdk.ts +197 -0
  43. package/src/plugins/delegation.ts +60 -0
  44. package/src/plugins/mcp.ts +154 -0
  45. package/src/plugins/storage.ts +725 -0
  46. package/src/plugins/ui.ts +57 -0
  47. package/src/registry/plugins.ts +85 -0
  48. package/src/services/storage.ts +957 -0
  49. package/tsconfig.json +18 -0
  50. package/dist/agents/agent-creator.js +0 -74
  51. package/dist/agents/browser-agent.js +0 -31
  52. package/dist/agents/os-agent.js +0 -32
  53. package/dist/agents/planner-agent.js +0 -32
  54. package/dist/agents/topic-agent.js +0 -46
  55. package/dist/architecture/execution-engine.js +0 -151
  56. package/dist/architecture/intent-classifier.js +0 -26
  57. package/dist/architecture/planner.js +0 -106
  58. package/dist/automation-worker.js +0 -121
  59. package/dist/automations.js +0 -52
  60. package/dist/cli.js +0 -279
  61. package/dist/config.js +0 -53
  62. package/dist/core/agents.js +0 -41
  63. package/dist/core/delegation.js +0 -230
  64. package/dist/core/manager.js +0 -96
  65. package/dist/core/plugins.js +0 -74
  66. package/dist/core/router.js +0 -191
  67. package/dist/handlers/init.js +0 -29
  68. package/dist/handlers/session-change.js +0 -21
  69. package/dist/handlers/settings.js +0 -47
  70. package/dist/handlers/tab-change.js +0 -14
  71. package/dist/installers.js +0 -156
  72. package/dist/marketplace.js +0 -80
  73. package/dist/model-catalog.js +0 -132
  74. package/dist/model-defaults.js +0 -25
  75. package/dist/models.js +0 -47
  76. package/dist/open-bot.js +0 -51
  77. package/dist/orchestrator/direct-invocation.js +0 -13
  78. package/dist/orchestrator/events.js +0 -36
  79. package/dist/orchestrator/state.js +0 -54
  80. package/dist/orchestrator.js +0 -422
  81. package/dist/plugins/agent/index.js +0 -81
  82. package/dist/plugins/approval/index.js +0 -100
  83. package/dist/plugins/brain/identity.js +0 -77
  84. package/dist/plugins/brain/index.js +0 -204
  85. package/dist/plugins/brain/memory.js +0 -120
  86. package/dist/plugins/brain/prompt.js +0 -46
  87. package/dist/plugins/brain/types.js +0 -45
  88. package/dist/plugins/brain/ui.js +0 -7
  89. package/dist/plugins/browser/index.js +0 -629
  90. package/dist/plugins/browser/ui.js +0 -13
  91. package/dist/plugins/file-system/index.js +0 -171
  92. package/dist/plugins/file-system/ui.js +0 -6
  93. package/dist/plugins/llm/context-budget.js +0 -139
  94. package/dist/plugins/llm/context-shaping.js +0 -177
  95. package/dist/plugins/llm/index.js +0 -380
  96. package/dist/plugins/memory/index.js +0 -220
  97. package/dist/plugins/memory/memory.js +0 -122
  98. package/dist/plugins/memory/prompt.js +0 -55
  99. package/dist/plugins/memory/types.js +0 -45
  100. package/dist/plugins/meta-agent/index.js +0 -570
  101. package/dist/plugins/meta-agent/ui.js +0 -11
  102. package/dist/plugins/shell/index.js +0 -100
  103. package/dist/plugins/shell/ui.js +0 -6
  104. package/dist/plugins/skills/index.js +0 -286
  105. package/dist/plugins/skills/types.js +0 -50
  106. package/dist/plugins/skills/ui.js +0 -12
  107. package/dist/registry/agent-registry.js +0 -35
  108. package/dist/registry/index.js +0 -2
  109. package/dist/registry/plugin-loader.js +0 -499
  110. package/dist/registry/plugin-registry.js +0 -44
  111. package/dist/registry/ts-agent-loader.js +0 -82
  112. package/dist/registry/yaml-agent-loader.js +0 -246
  113. package/dist/runtime/execution-trace.js +0 -41
  114. package/dist/runtime/intent-routing.js +0 -26
  115. package/dist/runtime/openbot-runtime.js +0 -354
  116. package/dist/server.js +0 -890
  117. package/dist/session.js +0 -179
  118. package/dist/ui/block.js +0 -12
  119. package/dist/ui/header.js +0 -52
  120. package/dist/ui/layout.js +0 -26
  121. package/dist/ui/navigation.js +0 -15
  122. package/dist/ui/settings.js +0 -106
  123. package/dist/ui/skills.js +0 -7
  124. package/dist/ui/thread.js +0 -16
  125. package/dist/ui/widgets/action-list.js +0 -2
  126. package/dist/ui/widgets/approval-card.js +0 -9
  127. package/dist/ui/widgets/code-snippet.js +0 -2
  128. package/dist/ui/widgets/data-block.js +0 -2
  129. package/dist/ui/widgets/data-table.js +0 -2
  130. package/dist/ui/widgets/delegation.js +0 -29
  131. package/dist/ui/widgets/empty-state.js +0 -2
  132. package/dist/ui/widgets/index.js +0 -23
  133. package/dist/ui/widgets/inquiry.js +0 -7
  134. package/dist/ui/widgets/key-value.js +0 -2
  135. package/dist/ui/widgets/progress-step.js +0 -2
  136. package/dist/ui/widgets/resource-card.js +0 -2
  137. package/dist/ui/widgets/status.js +0 -2
  138. package/dist/ui/widgets/todo-list.js +0 -2
  139. package/dist/version.js +0 -62
  140. /package/dist/{types.js → app/types.js} +0 -0
  141. /package/dist/{architecture/contracts.js → harness/types.js} +0 -0
@@ -1,171 +0,0 @@
1
- import { uiEvent } from "../../ui/block.js";
2
- import { z } from "zod";
3
- import * as fs from "node:fs/promises";
4
- import * as path from "node:path";
5
- import { statusWidget } from "../../ui/widgets/status.js";
6
- export const fileSystemToolDefinitions = {
7
- readFile: {
8
- description: "Read the contents of a file. Path can be absolute or relative to the current working directory.",
9
- inputSchema: z.object({
10
- path: z.string().describe("The path to the file (e.g., 'src/main.ts' or '/etc/hosts')"),
11
- }),
12
- },
13
- writeFile: {
14
- description: "Write content to a file. Path can be absolute or relative to the current working directory.",
15
- inputSchema: z.object({
16
- path: z.string().describe("The path to the file (e.g., 'new-file.ts')"),
17
- content: z.string().describe("The content to write to the file"),
18
- }),
19
- },
20
- listFiles: {
21
- description: "List files in a directory. Path can be absolute or relative to the current working directory.",
22
- inputSchema: z.object({
23
- path: z.string().describe("The path to the directory (use '.' for current directory)"),
24
- }),
25
- },
26
- deleteFile: {
27
- description: "Delete a file. Path can be absolute or relative to the current working directory.",
28
- inputSchema: z.object({
29
- path: z.string().describe("The path to the file"),
30
- }),
31
- },
32
- };
33
- /**
34
- * Truncates a string by keeping the first and last N characters.
35
- */
36
- function truncate(str, maxChars) {
37
- if (!str || str.length <= maxChars)
38
- return str;
39
- const half = Math.floor(maxChars / 2);
40
- const truncatedCount = str.length - maxChars;
41
- return `${str.slice(0, half)}\n\n[... ${truncatedCount} characters truncated ...]\n\n${str.slice(-half)}`;
42
- }
43
- export const fileSystemPlugin = (options = {}) => (builder) => {
44
- const { baseDir = "/", maxFileReadLength = 10000 } = options;
45
- const resolvePath = (p, stateCwd) => {
46
- // If p is absolute, resolve from root. If relative, resolve from stateCwd or baseDir.
47
- const resolved = path.isAbsolute(p) ? p : path.resolve(stateCwd || baseDir, p);
48
- return resolved;
49
- };
50
- builder.on("action:readFile", async function* (event, { state }) {
51
- const { path: filePath, toolCallId } = event.data;
52
- yield {
53
- type: "file-system:status",
54
- data: { message: `Reading file: ${filePath} (relative to ${state.cwd || baseDir})` }
55
- };
56
- try {
57
- const content = await fs.readFile(resolvePath(filePath, state.cwd), "utf-8");
58
- yield {
59
- type: "action:result",
60
- data: {
61
- action: "readFile",
62
- result: { content: truncate(content, maxFileReadLength) },
63
- toolCallId
64
- },
65
- };
66
- yield {
67
- type: "file-system:status",
68
- data: { message: `File read successfully`, severity: "success" }
69
- };
70
- }
71
- catch (error) {
72
- yield {
73
- type: "action:result",
74
- data: { action: "readFile", result: { error: error.message }, toolCallId },
75
- };
76
- yield {
77
- type: "file-system:status",
78
- data: { message: `File read failed: ${error.message}`, severity: "error" }
79
- };
80
- }
81
- });
82
- builder.on("action:writeFile", async function* (event, { state }) {
83
- const { path: filePath, content, toolCallId } = event.data;
84
- yield {
85
- type: "file-system:status",
86
- data: { message: `Writing file: ${filePath}` }
87
- };
88
- try {
89
- const fullPath = resolvePath(filePath, state.cwd);
90
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
91
- await fs.writeFile(fullPath, content, "utf-8");
92
- yield {
93
- type: "action:result",
94
- data: { action: "writeFile", result: { success: true }, toolCallId },
95
- };
96
- yield {
97
- type: "file-system:status",
98
- data: { message: `File written successfully`, severity: "success" }
99
- };
100
- }
101
- catch (error) {
102
- yield {
103
- type: "action:result",
104
- data: { action: "writeFile", result: { error: error.message }, toolCallId },
105
- };
106
- yield {
107
- type: "file-system:status",
108
- data: { message: `File write failed: ${error.message}`, severity: "error" }
109
- };
110
- }
111
- });
112
- builder.on("action:listFiles", async function* (event, { state }) {
113
- const { path: dirPath, toolCallId } = event.data;
114
- yield {
115
- type: "file-system:status",
116
- data: { message: `Listing files in: ${dirPath}` }
117
- };
118
- try {
119
- const files = await fs.readdir(resolvePath(dirPath, state.cwd));
120
- yield {
121
- type: "file-system:status",
122
- data: { message: `Files listed successfully`, severity: "success" }
123
- };
124
- yield {
125
- type: "action:result",
126
- data: { action: "listFiles", result: { files }, toolCallId },
127
- };
128
- }
129
- catch (error) {
130
- yield {
131
- type: "file-system:status",
132
- data: { message: `Files listing failed: ${error.message}`, severity: "error" }
133
- };
134
- yield {
135
- type: "action:result",
136
- data: { action: "listFiles", result: { error: error.message }, toolCallId },
137
- };
138
- }
139
- });
140
- builder.on("action:deleteFile", async function* (event, { state }) {
141
- const { path: filePath, toolCallId } = event.data;
142
- yield {
143
- type: "file-system:status",
144
- data: { message: `Deleting file: ${filePath}` }
145
- };
146
- try {
147
- await fs.unlink(resolvePath(filePath, state.cwd));
148
- yield {
149
- type: "action:result",
150
- data: { action: "deleteFile", result: { success: true }, toolCallId },
151
- };
152
- yield {
153
- type: "file-system:status",
154
- data: { message: `File deleted successfully`, severity: "success" }
155
- };
156
- }
157
- catch (error) {
158
- yield {
159
- type: "action:result",
160
- data: { action: "deleteFile", result: { error: error.message }, toolCallId },
161
- };
162
- yield {
163
- type: "file-system:status",
164
- data: { message: `File deletion failed: ${error.message}`, severity: "error" }
165
- };
166
- }
167
- });
168
- builder.on("file-system:status", async function* (event) {
169
- yield uiEvent(statusWidget(event.data.message, event.data.severity));
170
- });
171
- };
@@ -1,6 +0,0 @@
1
- import { ui } from "@melony/ui-kit/server";
2
- export const fileSystemUIPlugin = () => (builder) => {
3
- builder.on("file-system:status", async function* (event) {
4
- yield ui.event(ui.status(event.data.message, event.data.severity));
5
- });
6
- };
@@ -1,139 +0,0 @@
1
- function truncateMiddle(value, maxChars) {
2
- if (value.length <= maxChars)
3
- return value;
4
- const half = Math.floor(maxChars / 2);
5
- const removed = value.length - maxChars;
6
- return `${value.slice(0, half)}\n\n[... ${removed} characters truncated ...]\n\n${value.slice(-half)}`;
7
- }
8
- function estimateStringTokens(value) {
9
- if (!value)
10
- return 0;
11
- return Math.max(1, Math.ceil(value.length / 4));
12
- }
13
- function estimateMessageTokens(message) {
14
- if (typeof message.content === "string") {
15
- return estimateStringTokens(message.content);
16
- }
17
- if (Array.isArray(message.content)) {
18
- let total = 0;
19
- for (const part of message.content) {
20
- if (typeof part === "string") {
21
- total += estimateStringTokens(part);
22
- }
23
- else if (part && typeof part === "object") {
24
- total += estimateStringTokens(JSON.stringify(part));
25
- }
26
- }
27
- return total;
28
- }
29
- return 0;
30
- }
31
- function cloneWithTrimmedSystemMessages(messages, maxSystemChars) {
32
- return messages.map((message) => {
33
- if (message.role !== "system" || typeof message.content !== "string") {
34
- return message;
35
- }
36
- return {
37
- ...message,
38
- content: truncateMiddle(message.content, maxSystemChars),
39
- };
40
- });
41
- }
42
- function getLeadingSystemCount(messages) {
43
- let count = 0;
44
- for (const message of messages) {
45
- if (message.role !== "system")
46
- break;
47
- count++;
48
- }
49
- return count;
50
- }
51
- function collectAssistantToolPairings(messages) {
52
- const toolCallToAssistant = new Map();
53
- const assistantToToolResults = new Map();
54
- messages.forEach((message, index) => {
55
- if (message.role !== "assistant" || !Array.isArray(message.content))
56
- return;
57
- for (const part of message.content) {
58
- if (part
59
- && typeof part === "object"
60
- && part.type === "tool-call"
61
- && typeof part.toolCallId === "string") {
62
- toolCallToAssistant.set(part.toolCallId, index);
63
- }
64
- }
65
- });
66
- messages.forEach((message, index) => {
67
- if (message.role !== "tool" || !Array.isArray(message.content))
68
- return;
69
- for (const part of message.content) {
70
- if (!part || typeof part !== "object")
71
- continue;
72
- const toolCallId = part.toolCallId;
73
- if (typeof toolCallId !== "string")
74
- continue;
75
- const assistantIndex = toolCallToAssistant.get(toolCallId);
76
- if (typeof assistantIndex !== "number")
77
- continue;
78
- const bucket = assistantToToolResults.get(assistantIndex) ?? new Set();
79
- bucket.add(index);
80
- assistantToToolResults.set(assistantIndex, bucket);
81
- }
82
- });
83
- return assistantToToolResults;
84
- }
85
- function buildAtomicGroupIndices(index, messages, assistantToToolResults) {
86
- const message = messages[index];
87
- if (!message)
88
- return [];
89
- if (message.role === "assistant") {
90
- const results = assistantToToolResults.get(index);
91
- if (!results || results.size === 0)
92
- return [index];
93
- return [index, ...Array.from(results)].sort((a, b) => a - b);
94
- }
95
- if (message.role === "tool") {
96
- for (const [assistantIndex, resultIndices] of assistantToToolResults.entries()) {
97
- if (resultIndices.has(index)) {
98
- return [assistantIndex, ...Array.from(resultIndices)].sort((a, b) => a - b);
99
- }
100
- }
101
- }
102
- return [index];
103
- }
104
- export function buildBudgetedMessages(messages, options) {
105
- if (messages.length === 0)
106
- return messages;
107
- const normalized = cloneWithTrimmedSystemMessages(messages, options.maxSystemChars);
108
- const systemCount = getLeadingSystemCount(normalized);
109
- const leadingSystem = normalized.slice(0, systemCount);
110
- const history = normalized.slice(systemCount);
111
- const usableBudget = Math.max(1, options.maxContextTokens - options.reserveOutputTokens);
112
- let used = leadingSystem.reduce((sum, message) => sum + estimateMessageTokens(message), 0);
113
- const requiredIndices = new Set();
114
- for (let i = history.length - 1; i >= 0; i--) {
115
- if (history[i].role === "user") {
116
- requiredIndices.add(i);
117
- break;
118
- }
119
- }
120
- const assistantToToolResults = collectAssistantToolPairings(history);
121
- const included = new Set();
122
- const visited = new Set();
123
- for (let i = history.length - 1; i >= 0; i--) {
124
- if (visited.has(i))
125
- continue;
126
- const group = buildAtomicGroupIndices(i, history, assistantToToolResults);
127
- for (const idx of group)
128
- visited.add(idx);
129
- const groupTokens = group.reduce((sum, idx) => sum + estimateMessageTokens(history[idx]), 0);
130
- const isRequired = group.some((idx) => requiredIndices.has(idx));
131
- if (isRequired || used + groupTokens <= usableBudget) {
132
- for (const idx of group)
133
- included.add(idx);
134
- used += groupTokens;
135
- }
136
- }
137
- const selectedHistory = history.filter((_, index) => included.has(index));
138
- return [...leadingSystem, ...selectedHistory];
139
- }
@@ -1,177 +0,0 @@
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
- }