@csmedeiros/codemax 1.0.3 → 1.0.7

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 (54) hide show
  1. package/README.md +4 -20
  2. package/launcher.js +28 -0
  3. package/package.json +15 -121
  4. package/LICENSE +0 -21
  5. package/dist/commands/cursorRules.d.ts +0 -15
  6. package/dist/commands/cursorRules.js +0 -118
  7. package/dist/commands/slashCommands.d.ts +0 -36
  8. package/dist/commands/slashCommands.js +0 -236
  9. package/dist/configuration/configManager.d.ts +0 -32
  10. package/dist/configuration/configManager.js +0 -71
  11. package/dist/configuration/modelContextWindows.d.ts +0 -3
  12. package/dist/configuration/modelContextWindows.js +0 -13
  13. package/dist/conversation/agentGraph.d.ts +0 -203
  14. package/dist/conversation/agentGraph.js +0 -433
  15. package/dist/conversation/agentTurn.d.ts +0 -40
  16. package/dist/conversation/agentTurn.js +0 -252
  17. package/dist/conversation/chatHistory.d.ts +0 -24
  18. package/dist/conversation/chatHistory.js +0 -251
  19. package/dist/conversation/compactionUtils.d.ts +0 -17
  20. package/dist/conversation/compactionUtils.js +0 -57
  21. package/dist/conversation/prompts/planPrompt.d.ts +0 -1
  22. package/dist/conversation/prompts/planPrompt.js +0 -12
  23. package/dist/conversation/prompts/systemPrompt.d.ts +0 -29
  24. package/dist/conversation/prompts/systemPrompt.js +0 -149
  25. package/dist/entry/cli.d.ts +0 -2
  26. package/dist/entry/cli.js +0 -24
  27. package/dist/observability/langfuseTracing.d.ts +0 -5
  28. package/dist/observability/langfuseTracing.js +0 -76
  29. package/dist/shared/types.d.ts +0 -25
  30. package/dist/shared/types.js +0 -1
  31. package/dist/terminal/app.d.ts +0 -2
  32. package/dist/terminal/app.js +0 -1236
  33. package/dist/terminal/components.d.ts +0 -18
  34. package/dist/terminal/components.js +0 -43
  35. package/dist/terminal/markdown.d.ts +0 -4
  36. package/dist/terminal/markdown.js +0 -47
  37. package/dist/terminal/screens/compactionSettings.d.ts +0 -6
  38. package/dist/terminal/screens/compactionSettings.js +0 -66
  39. package/dist/terminal/screens/modelSettings.d.ts +0 -10
  40. package/dist/terminal/screens/modelSettings.js +0 -76
  41. package/dist/terminal/textField.d.ts +0 -7
  42. package/dist/terminal/textField.js +0 -136
  43. package/dist/terminal/theme.d.ts +0 -23
  44. package/dist/terminal/theme.js +0 -23
  45. package/dist/tooling/mcpConfig.d.ts +0 -40
  46. package/dist/tooling/mcpConfig.js +0 -49
  47. package/dist/tooling/planControlChannel.d.ts +0 -2
  48. package/dist/tooling/planControlChannel.js +0 -21
  49. package/dist/tooling/toolConfig.d.ts +0 -42
  50. package/dist/tooling/toolConfig.js +0 -138
  51. package/dist/tooling/toolUiCallback.d.ts +0 -21
  52. package/dist/tooling/toolUiCallback.js +0 -268
  53. package/dist/tooling/tools.d.ts +0 -216
  54. package/dist/tooling/tools.js +0 -614
@@ -1,433 +0,0 @@
1
- /**
2
- * LangChain agent graph (StateGraph + tools).
3
- */
4
- import { END, MessagesZodState, START, StateGraph, interrupt, } from '@langchain/langgraph';
5
- import { SqliteSaver } from '@langchain/langgraph-checkpoint-sqlite';
6
- import Database from 'better-sqlite3';
7
- import os from 'os';
8
- import fs from 'fs';
9
- import path from 'path';
10
- import { HumanMessage, RemoveMessage, SystemMessage, ToolMessage, } from '@langchain/core/messages';
11
- import { z } from 'zod';
12
- import { getConfig, updateConfig } from '../configuration/configManager.js';
13
- import { TOOLS, PLAN_TOOLS } from '../tooling/tools.js';
14
- import { SYSTEM_PROMPT, COMPACT_SUMMARY_PROMPT, buildCodemaxTurnDatetimeUserContent, } from './prompts/systemPrompt.js';
15
- import { PLAN_MODE_PROMPT } from './prompts/planPrompt.js';
16
- import { loadProjectSkills } from '../commands/slashCommands.js';
17
- import { loadWorkspaceRules, matchRules } from '../commands/cursorRules.js';
18
- import { ChatOpenAI } from '@langchain/openai';
19
- import { getContextWindow } from '../configuration/modelContextWindows.js';
20
- import { estimateTokens, shouldCompactMessages, computeKeepStart, } from './compactionUtils.js';
21
- export { estimateTokens, shouldCompactMessages, computeKeepStart };
22
- export const COMPACTION_SUMMARY_SENTINEL = '[COMPACTION_SUMMARY]';
23
- let model;
24
- export function initModel() {
25
- const config = getConfig();
26
- if (!config.apiKey) {
27
- process.stderr.write('\x07'); // Alert beep
28
- console.error('\n[CodeMax] FATAL: API Key is not set in ~/.codemax/config.json or environment.\n');
29
- throw new Error('API_KEY missing');
30
- }
31
- model = new ChatOpenAI({
32
- model: config.modelName,
33
- apiKey: config.apiKey,
34
- temperature: 0.3,
35
- maxTokens: 16384,
36
- streaming: true,
37
- maxRetries: 0,
38
- configuration: {
39
- baseURL: config.baseURL || undefined,
40
- },
41
- modelKwargs: {
42
- chat_template_kwargs: { enable_thinking: true, clear_thinking: false },
43
- },
44
- });
45
- }
46
- initModel();
47
- export function updateModelConfig(cfg) {
48
- updateConfig(cfg);
49
- initModel();
50
- }
51
- export const ToolExecutionMode = z.enum(['auto', 'ask', 'edits', 'plan']);
52
- /** Same shape as `MessagesAnnotation` + extra channels — required for `toolsCondition` / `ToolNode`. */
53
- const StateDefinition = MessagesZodState.extend({
54
- files: z.record(z.string(), z.string()).default({}),
55
- toolExecutionMode: ToolExecutionMode.default('ask'),
56
- todos: z
57
- .array(z.object({
58
- id: z.string(),
59
- task: z.string(),
60
- status: z.enum(['pending', 'in_progress', 'completed']),
61
- }))
62
- .default([]),
63
- activeSkill: z.string().optional(),
64
- forceCompact: z.boolean().default(false),
65
- });
66
- async function callModel(state, config) {
67
- const datetimeMsg = new HumanMessage(buildCodemaxTurnDatetimeUserContent());
68
- const executionCwd = process.env['CODEMAX_SHELL_CWD'] ?? process.cwd();
69
- const skills = loadProjectSkills(executionCwd);
70
- const injected = [];
71
- if (skills.length > 0) {
72
- const descriptionsText = 'These are the available skills and its descriptions. You MUST read skills when the use case matches with the description.\n\n' +
73
- skills.map(s => `${s.name}:\n\n${s.description}`).join('\n\n\n');
74
- injected.push(new HumanMessage(descriptionsText));
75
- }
76
- if (state.activeSkill) {
77
- injected.push(new HumanMessage(state.activeSkill));
78
- }
79
- const workspaceRules = loadWorkspaceRules(executionCwd);
80
- const matchedRules = matchRules(workspaceRules, state.messages);
81
- if (workspaceRules.length > 0) {
82
- process.stderr.write(`[CodeMax:rules] loaded=${workspaceRules.length} matched=${matchedRules.length}` +
83
- (matchedRules.length > 0
84
- ? ` files=[${matchedRules.map(r => r.filename).join(', ')}]`
85
- : '') +
86
- '\n');
87
- }
88
- for (const rule of matchedRules) {
89
- injected.push(new HumanMessage(`[Rule: ${rule.description}]\n\n${rule.content}`));
90
- }
91
- if (state.toolExecutionMode === 'plan') {
92
- injected.unshift(new HumanMessage(PLAN_MODE_PROMPT));
93
- }
94
- const messages = [
95
- new SystemMessage(SYSTEM_PROMPT),
96
- ...injected,
97
- datetimeMsg,
98
- ...state.messages,
99
- ];
100
- const toolList = state.toolExecutionMode === 'plan' ? PLAN_TOOLS : TOOLS;
101
- const activeLlm = model.bindTools(toolList);
102
- const response = await activeLlm.invoke(messages, config);
103
- return { messages: [response], activeSkill: undefined };
104
- }
105
- async function toolNode(state, config) {
106
- const lastMessage = state.messages[state.messages.length - 1];
107
- const toolCalls = lastMessage?.tool_calls ?? [];
108
- const results = [];
109
- let currentTodos = [...(state.todos || [])];
110
- for (const toolCall of toolCalls) {
111
- const { name } = toolCall;
112
- const mode = state.toolExecutionMode;
113
- if (state.toolExecutionMode === 'plan' &&
114
- (name === 'writeFileTool' || name === 'editFileTool')) {
115
- results.push(new ToolMessage({
116
- tool_call_id: toolCall.id,
117
- content: 'writeFileTool and editFileTool are disabled in Plan Mode. Use writePlanTool to output your plan.',
118
- }));
119
- continue;
120
- }
121
- let shouldInterrupt = false;
122
- if (name === 'readFileTool') {
123
- shouldInterrupt = false; // Read-only is always safe
124
- }
125
- else if (name === 'todoTool') {
126
- shouldInterrupt = false; // Todo updates are safe
127
- }
128
- else if (mode === 'ask') {
129
- shouldInterrupt = true;
130
- }
131
- else if (mode === 'edits') {
132
- if (name === 'writeFileTool' || name === 'editFileTool') {
133
- shouldInterrupt = false; // Auto-accept edits
134
- }
135
- else {
136
- shouldInterrupt = true; // Ask for shellTool and others
137
- }
138
- }
139
- else if (mode === 'auto') {
140
- shouldInterrupt = false; // Auto-accept everything
141
- }
142
- if (shouldInterrupt) {
143
- const response = interrupt({
144
- type: 'tool_approval',
145
- toolCall,
146
- });
147
- if (response.action === 'approve_always') {
148
- state.toolExecutionMode = 'auto';
149
- }
150
- else if (response.action !== 'approve') {
151
- results.push(new ToolMessage({
152
- tool_call_id: toolCall.id,
153
- content: 'Tool execution denied by user.',
154
- }));
155
- continue;
156
- }
157
- }
158
- const activeToolList = state.toolExecutionMode === 'plan' ? PLAN_TOOLS : TOOLS;
159
- const tool = activeToolList.find(t => t.name === name);
160
- if (!tool) {
161
- results.push(new ToolMessage({
162
- tool_call_id: toolCall.id,
163
- content: `Tool ${name} not found.`,
164
- }));
165
- continue;
166
- }
167
- let output;
168
- try {
169
- if (name === 'todoTool') {
170
- const { action, id, task, status } = toolCall.args;
171
- if (action === 'add') {
172
- currentTodos.push({
173
- id: Math.random().toString(36).substring(7),
174
- task: task,
175
- status: status || 'pending',
176
- });
177
- }
178
- else if (action === 'update') {
179
- currentTodos = currentTodos.map(t => t.id === id
180
- ? { ...t, task: task || t.task, status: status || t.status }
181
- : t);
182
- }
183
- else if (action === 'remove') {
184
- currentTodos = currentTodos.filter(t => t.id !== id);
185
- }
186
- output = new ToolMessage({
187
- tool_call_id: toolCall.id,
188
- content: 'Todo list updated.',
189
- });
190
- }
191
- else {
192
- output = await tool.invoke(toolCall, config);
193
- }
194
- }
195
- catch (error) {
196
- output = new ToolMessage({
197
- tool_call_id: toolCall.id,
198
- content: `Error: ${error?.message || String(error)}`,
199
- });
200
- }
201
- results.push(output);
202
- }
203
- return {
204
- messages: results,
205
- toolExecutionMode: state.toolExecutionMode,
206
- todos: currentTodos,
207
- };
208
- }
209
- const memoryDir = path.join(os.homedir(), '.codemax', 'memory');
210
- fs.mkdirSync(memoryDir, { recursive: true });
211
- const memoryPath = path.join(memoryDir, 'MEMORY.sqlite');
212
- export const db = new Database(memoryPath);
213
- export { model };
214
- const checkpointer = new SqliteSaver(db);
215
- function shouldCompact(state) {
216
- if (state.forceCompact)
217
- return true;
218
- const cfg = getConfig();
219
- const window = getContextWindow(cfg.modelName);
220
- return shouldCompactMessages(state.messages, {
221
- threshold: cfg.compactionThreshold,
222
- contextWindow: window,
223
- });
224
- }
225
- let fullMessagesInitialized = false;
226
- function ensureFullMessagesTable() {
227
- if (fullMessagesInitialized)
228
- return;
229
- db.exec(`
230
- CREATE TABLE IF NOT EXISTS chat_messages_full (
231
- thread_id TEXT PRIMARY KEY,
232
- messages_json TEXT NOT NULL,
233
- updated_at INTEGER NOT NULL
234
- )
235
- `);
236
- fullMessagesInitialized = true;
237
- }
238
- function readStoredFullMessages(threadId) {
239
- ensureFullMessagesTable();
240
- try {
241
- const row = db
242
- .prepare(`SELECT messages_json FROM chat_messages_full WHERE thread_id = ?`)
243
- .get(threadId);
244
- if (!row)
245
- return null;
246
- return JSON.parse(row.messages_json);
247
- }
248
- catch {
249
- return null;
250
- }
251
- }
252
- function writeStoredFullMessages(threadId, raw) {
253
- ensureFullMessagesTable();
254
- try {
255
- db.prepare(`INSERT INTO chat_messages_full (thread_id, messages_json, updated_at) VALUES (?, ?, ?)
256
- ON CONFLICT(thread_id) DO UPDATE SET messages_json = excluded.messages_json, updated_at = excluded.updated_at`).run(threadId, JSON.stringify(raw), Date.now());
257
- }
258
- catch {
259
- // ignore
260
- }
261
- }
262
- function serializeMessageForStorage(m) {
263
- const type = m._getType();
264
- const base = { id: m.id, type, content: m.content };
265
- if (type === 'ai') {
266
- const ai = m;
267
- if (ai.tool_calls)
268
- base.tool_calls = ai.tool_calls;
269
- }
270
- if (type === 'tool') {
271
- const tm = m;
272
- base.tool_call_id = tm.tool_call_id;
273
- base.name = tm.name;
274
- }
275
- return base;
276
- }
277
- function persistFullHistorySnapshot(threadId, currentMessages) {
278
- const serialized = currentMessages
279
- .filter(m => {
280
- const c = m.content;
281
- const text = typeof c === 'string'
282
- ? c
283
- : Array.isArray(c)
284
- ? c
285
- .map((b) => (typeof b === 'string' ? b : b?.text ?? ''))
286
- .join('')
287
- : '';
288
- return !text.startsWith(COMPACTION_SUMMARY_SENTINEL);
289
- })
290
- .map(serializeMessageForStorage);
291
- const stored = readStoredFullMessages(threadId) ?? [];
292
- const seenIds = new Set();
293
- const merged = [];
294
- for (const m of stored) {
295
- const id = m?.id;
296
- if (id && seenIds.has(id))
297
- continue;
298
- if (id)
299
- seenIds.add(id);
300
- merged.push(m);
301
- }
302
- for (const m of serialized) {
303
- const id = m?.id;
304
- if (id && seenIds.has(id))
305
- continue;
306
- if (id)
307
- seenIds.add(id);
308
- merged.push(m);
309
- }
310
- writeStoredFullMessages(threadId, merged);
311
- }
312
- async function compactMessages(state, config) {
313
- const cfg = getConfig();
314
- const window = getContextWindow(cfg.modelName);
315
- const tokensToKeep = Math.max(1, Math.floor(0.15 * window));
316
- const msgs = state.messages;
317
- if (msgs.length === 0)
318
- return { forceCompact: false };
319
- let keepStart = computeKeepStart(msgs, tokensToKeep);
320
- if (keepStart >= msgs.length)
321
- keepStart = msgs.length - 1;
322
- if (keepStart <= 0)
323
- return { forceCompact: false };
324
- const olderMessages = msgs.slice(0, keepStart);
325
- const threadId = config?.configurable?.thread_id;
326
- if (threadId) {
327
- try {
328
- persistFullHistorySnapshot(threadId, msgs);
329
- }
330
- catch {
331
- // non-fatal
332
- }
333
- }
334
- let summaryText = '';
335
- try {
336
- const summarizer = new ChatOpenAI({
337
- model: cfg.modelName,
338
- apiKey: cfg.apiKey,
339
- temperature: 0.3,
340
- maxTokens: 8000,
341
- streaming: false,
342
- maxRetries: 0,
343
- configuration: { baseURL: cfg.baseURL || undefined },
344
- });
345
- const response = await summarizer.invoke([
346
- new SystemMessage(COMPACT_SUMMARY_PROMPT),
347
- ...olderMessages,
348
- new HumanMessage('Produce the detailed compaction summary now as instructed.'),
349
- ], config);
350
- const c = response.content;
351
- summaryText =
352
- typeof c === 'string'
353
- ? c
354
- : Array.isArray(c)
355
- ? c
356
- .map((b) => (typeof b === 'string' ? b : b?.text ?? ''))
357
- .join('')
358
- : '';
359
- }
360
- catch (e) {
361
- process.stderr.write(`[CodeMax:compact] summarization failed: ${e?.message || e}\n`);
362
- return { forceCompact: false };
363
- }
364
- if (!summaryText.trim())
365
- return { forceCompact: false };
366
- const removeOps = [];
367
- for (const m of olderMessages) {
368
- if (m.id)
369
- removeOps.push(new RemoveMessage({ id: m.id }));
370
- }
371
- const summaryMsg = new SystemMessage(`${COMPACTION_SUMMARY_SENTINEL}\n\n${summaryText.trim()}`);
372
- return {
373
- messages: [...removeOps, summaryMsg],
374
- forceCompact: false,
375
- };
376
- }
377
- function routeFromCallModel(state) {
378
- if (state.forceCompact)
379
- return 'compactMessages';
380
- const last = state.messages[state.messages.length - 1];
381
- const hasToolCalls = last && Array.isArray(last.tool_calls) && last.tool_calls.length > 0;
382
- if (hasToolCalls)
383
- return 'tools';
384
- if (shouldCompact(state))
385
- return 'compactMessages';
386
- return END;
387
- }
388
- function routeFromTools(state) {
389
- if (state.forceCompact || shouldCompact(state))
390
- return 'compactMessages';
391
- return 'callModel';
392
- }
393
- export const agent = new StateGraph(StateDefinition)
394
- .addNode('callModel', callModel, {
395
- retryPolicy: {
396
- maxAttempts: 3,
397
- initialInterval: 3000,
398
- backoffFactor: 2.0,
399
- retryOn: (error) => {
400
- const isRateLimit = error?.code === 'MODEL_RATE_LIMIT' ||
401
- error?.status === 429 ||
402
- error?.message?.includes('429') ||
403
- error?.name === 'RateLimitError';
404
- const isAbort = error?.name === 'AbortError';
405
- if (!isRateLimit && !isAbort) {
406
- console.error('[CodeMax] Critical model error (skipping retry):', error);
407
- }
408
- return isRateLimit;
409
- },
410
- },
411
- })
412
- .addNode('tools', toolNode)
413
- .addNode('compactMessages', compactMessages)
414
- .addEdge(START, 'callModel')
415
- .addConditionalEdges('callModel', routeFromCallModel, [
416
- 'tools',
417
- 'compactMessages',
418
- END,
419
- ])
420
- .addConditionalEdges('tools', routeFromTools, [
421
- 'compactMessages',
422
- 'callModel',
423
- ])
424
- .addEdge('compactMessages', 'callModel')
425
- .compile({ checkpointer });
426
- export async function triggerCompaction(threadId) {
427
- const config = { configurable: { thread_id: threadId } };
428
- await agent.updateState(config, { forceCompact: true });
429
- await agent.invoke(null, {
430
- ...config,
431
- recursionLimit: getConfig().recursionLimit,
432
- });
433
- }
@@ -1,40 +0,0 @@
1
- import { ToolExecutionMode } from './agentGraph.js';
2
- export type AgentTurnEvent = {
3
- type: 'on_tool_start';
4
- toolName?: string;
5
- input?: string;
6
- } | {
7
- type: 'on_tool_end';
8
- toolName?: string;
9
- output?: string;
10
- } | {
11
- type: 'on_tool_error';
12
- error?: string;
13
- } | {
14
- type: 'on_chat_start';
15
- } | {
16
- type: 'on_chat_delta';
17
- text: string;
18
- } | {
19
- type: 'on_chat_end';
20
- } | {
21
- type: 'on_interrupt';
22
- payload: any;
23
- } | {
24
- type: 'status';
25
- text: string;
26
- } | {
27
- type: 'on_state_update';
28
- todos: any[];
29
- };
30
- export type AgentTurnOptions = {
31
- prompt?: string;
32
- threadId: string;
33
- sessionId: string;
34
- tracingEnabled: boolean;
35
- signal?: AbortSignal;
36
- toolExecutionMode?: ToolExecutionMode;
37
- resumeResponse?: any;
38
- activeSkill?: string;
39
- };
40
- export declare function streamAgentTurn(options: AgentTurnOptions): AsyncGenerator<AgentTurnEvent>;