cmdr-agent 1.2.2 → 1.3.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 +19 -0
- package/dist/bin/cmdr.js +2 -1
- package/dist/bin/cmdr.js.map +1 -1
- package/dist/src/cli/args.d.ts +1 -0
- package/dist/src/cli/args.d.ts.map +1 -1
- package/dist/src/cli/args.js +4 -0
- package/dist/src/cli/args.js.map +1 -1
- package/dist/src/cli/repl.d.ts +1 -0
- package/dist/src/cli/repl.d.ts.map +1 -1
- package/dist/src/cli/repl.js +5 -1
- package/dist/src/cli/repl.js.map +1 -1
- package/dist/src/core/agent-runner.d.ts.map +1 -1
- package/dist/src/core/agent-runner.js +10 -1
- package/dist/src/core/agent-runner.js.map +1 -1
- package/dist/src/core/intent.d.ts +2 -0
- package/dist/src/core/intent.d.ts.map +1 -1
- package/dist/src/core/intent.js +12 -0
- package/dist/src/core/intent.js.map +1 -1
- package/dist/src/core/presets.d.ts.map +1 -1
- package/dist/src/core/presets.js +49 -15
- package/dist/src/core/presets.js.map +1 -1
- package/dist/src/core/types.d.ts +4 -0
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/llm/model-registry.d.ts.map +1 -1
- package/dist/src/llm/model-registry.js +9 -1
- package/dist/src/llm/model-registry.js.map +1 -1
- package/dist/src/llm/ollama.d.ts +8 -1
- package/dist/src/llm/ollama.d.ts.map +1 -1
- package/dist/src/llm/ollama.js +199 -94
- package/dist/src/llm/ollama.js.map +1 -1
- package/dist/src/session/compaction.js +4 -0
- package/dist/src/session/compaction.js.map +1 -1
- package/dist/src/session/prompt-builder.d.ts +28 -1
- package/dist/src/session/prompt-builder.d.ts.map +1 -1
- package/dist/src/session/prompt-builder.js +97 -23
- package/dist/src/session/prompt-builder.js.map +1 -1
- package/dist/src/session/session-manager.d.ts +10 -3
- package/dist/src/session/session-manager.d.ts.map +1 -1
- package/dist/src/session/session-manager.js +53 -16
- package/dist/src/session/session-manager.js.map +1 -1
- package/dist/src/session/session-persistence.d.ts +12 -0
- package/dist/src/session/session-persistence.d.ts.map +1 -1
- package/dist/src/session/session-persistence.js +94 -4
- package/dist/src/session/session-persistence.js.map +1 -1
- package/dist/src/tools/built-in/bash-security.d.ts +13 -0
- package/dist/src/tools/built-in/bash-security.d.ts.map +1 -0
- package/dist/src/tools/built-in/bash-security.js +53 -0
- package/dist/src/tools/built-in/bash-security.js.map +1 -0
- package/dist/src/tools/built-in/bash.d.ts.map +1 -1
- package/dist/src/tools/built-in/bash.js +7 -1
- package/dist/src/tools/built-in/bash.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,39 +1,113 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PromptBuilder —
|
|
2
|
+
* PromptBuilder — modular prompt construction pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Each module has a priority (lower = earlier) and a static flag.
|
|
5
|
+
* Static modules are identical across turns, enabling Ollama KV cache reuse.
|
|
3
6
|
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
/* ── Priority constants ────────────────────────────────────────────── */
|
|
8
|
+
export const PROMPT_PRIORITIES = {
|
|
9
|
+
ROLE: 10,
|
|
10
|
+
TOOL_POLICY: 20,
|
|
11
|
+
PROJECT_INSTRUCTIONS: 30,
|
|
12
|
+
SKILLS: 40,
|
|
13
|
+
PROJECT_CONTEXT: 50,
|
|
14
|
+
RUNTIME_CONTEXT: 60,
|
|
15
|
+
CONVERSATION_STATE: 70,
|
|
16
|
+
};
|
|
17
|
+
/* ── PromptBuilder class ───────────────────────────────────────────── */
|
|
18
|
+
export class PromptBuilder {
|
|
19
|
+
modules = new Map();
|
|
20
|
+
addModule(mod) {
|
|
21
|
+
this.modules.set(mod.id, mod);
|
|
11
22
|
}
|
|
12
|
-
|
|
13
|
-
|
|
23
|
+
updateModule(id, content) {
|
|
24
|
+
const existing = this.modules.get(id);
|
|
25
|
+
if (existing) {
|
|
26
|
+
existing.content = content;
|
|
27
|
+
}
|
|
14
28
|
}
|
|
15
|
-
|
|
16
|
-
|
|
29
|
+
removeModule(id) {
|
|
30
|
+
this.modules.delete(id);
|
|
17
31
|
}
|
|
18
|
-
|
|
19
|
-
|
|
32
|
+
build() {
|
|
33
|
+
return [...this.modules.values()]
|
|
34
|
+
.sort((a, b) => a.priority - b.priority)
|
|
35
|
+
.map(m => m.content)
|
|
36
|
+
.filter(c => c.length > 0)
|
|
37
|
+
.join('\n\n');
|
|
20
38
|
}
|
|
21
|
-
|
|
22
|
-
|
|
39
|
+
/** Returns only static modules — for KV cache prefix estimation. */
|
|
40
|
+
getStaticPrefix() {
|
|
41
|
+
return [...this.modules.values()]
|
|
42
|
+
.filter(m => m.isStatic)
|
|
43
|
+
.sort((a, b) => a.priority - b.priority)
|
|
44
|
+
.map(m => m.content)
|
|
45
|
+
.filter(c => c.length > 0)
|
|
46
|
+
.join('\n\n');
|
|
23
47
|
}
|
|
24
|
-
|
|
48
|
+
}
|
|
49
|
+
export function buildSystemPrompt(options) {
|
|
50
|
+
const builder = new PromptBuilder();
|
|
51
|
+
const ctx = options.projectContext;
|
|
52
|
+
// 10: Role — base system prompt (STATIC)
|
|
53
|
+
builder.addModule({
|
|
54
|
+
id: 'role',
|
|
55
|
+
content: options.basePrompt,
|
|
56
|
+
priority: PROMPT_PRIORITIES.ROLE,
|
|
57
|
+
isStatic: true,
|
|
58
|
+
});
|
|
59
|
+
// 30: Project instructions — CMDR.md (STATIC per session)
|
|
25
60
|
if (ctx.cmdrInstructions) {
|
|
26
|
-
|
|
61
|
+
builder.addModule({
|
|
62
|
+
id: 'project_instructions',
|
|
63
|
+
content: `<project_instructions>\nThe user has provided the following instructions for this project. Follow them unless they conflict with safety:\n\n${ctx.cmdrInstructions}\n</project_instructions>`,
|
|
64
|
+
priority: PROMPT_PRIORITIES.PROJECT_INSTRUCTIONS,
|
|
65
|
+
isStatic: true,
|
|
66
|
+
});
|
|
27
67
|
}
|
|
28
|
-
//
|
|
68
|
+
// 40: Skills (STATIC per turn)
|
|
29
69
|
if (ctx.activeSkills && ctx.activeSkills.length > 0) {
|
|
30
|
-
|
|
70
|
+
const skillBlocks = ctx.activeSkills.map(skill => {
|
|
31
71
|
const scriptNote = skill.scripts.length > 0
|
|
32
72
|
? `\n\nHelper scripts available at:\n${skill.scripts.map(s => ` - ${s}`).join('\n')}`
|
|
33
73
|
: '';
|
|
34
|
-
|
|
35
|
-
}
|
|
74
|
+
return `<skill name="${skill.name}">\n${skill.instructions}${scriptNote}\n</skill>`;
|
|
75
|
+
});
|
|
76
|
+
builder.addModule({
|
|
77
|
+
id: 'skills',
|
|
78
|
+
content: skillBlocks.join('\n\n'),
|
|
79
|
+
priority: PROMPT_PRIORITIES.SKILLS,
|
|
80
|
+
isStatic: true,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// 50: Project context (STATIC per session)
|
|
84
|
+
const contextParts = [];
|
|
85
|
+
if (ctx.language !== 'unknown')
|
|
86
|
+
contextParts.push(`Language: ${ctx.language}`);
|
|
87
|
+
if (ctx.framework)
|
|
88
|
+
contextParts.push(`Framework: ${ctx.framework}`);
|
|
89
|
+
if (ctx.packageManager)
|
|
90
|
+
contextParts.push(`Package manager: ${ctx.packageManager}`);
|
|
91
|
+
if (contextParts.length > 0) {
|
|
92
|
+
builder.addModule({
|
|
93
|
+
id: 'project_context',
|
|
94
|
+
content: `Project context:\n${contextParts.join('\n')}\nRoot: ${ctx.rootDir}`,
|
|
95
|
+
priority: PROMPT_PRIORITIES.PROJECT_CONTEXT,
|
|
96
|
+
isStatic: true,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// 60: Runtime context — dynamic per turn (git branch, etc.)
|
|
100
|
+
const runtimeParts = [];
|
|
101
|
+
if (ctx.gitBranch)
|
|
102
|
+
runtimeParts.push(`Git branch: ${ctx.gitBranch}`);
|
|
103
|
+
if (runtimeParts.length > 0) {
|
|
104
|
+
builder.addModule({
|
|
105
|
+
id: 'runtime_context',
|
|
106
|
+
content: `Runtime:\n${runtimeParts.join('\n')}`,
|
|
107
|
+
priority: PROMPT_PRIORITIES.RUNTIME_CONTEXT,
|
|
108
|
+
isStatic: false,
|
|
109
|
+
});
|
|
36
110
|
}
|
|
37
|
-
return
|
|
111
|
+
return builder.build();
|
|
38
112
|
}
|
|
39
113
|
//# sourceMappingURL=prompt-builder.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-builder.js","sourceRoot":"","sources":["../../../src/session/prompt-builder.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"prompt-builder.js","sourceRoot":"","sources":["../../../src/session/prompt-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SessionManager — conversation history with token counting and compaction.
|
|
2
|
+
* SessionManager — append-only conversation history with token counting and compaction.
|
|
3
|
+
*
|
|
4
|
+
* Messages are never deleted. On compaction, old messages are flagged as
|
|
5
|
+
* transcript-only and a compact summary is inserted as a boundary.
|
|
3
6
|
*/
|
|
4
7
|
import type { LLMMessage, LLMAdapter, SessionState, ProjectContext, TokenUsage } from '../core/types.js';
|
|
5
8
|
export declare class SessionManager {
|
|
6
9
|
private session;
|
|
7
10
|
private compactionConfig;
|
|
11
|
+
private consecutiveCompactFailures;
|
|
8
12
|
constructor(projectContext: ProjectContext, maxContextTokens?: number);
|
|
9
13
|
get id(): string;
|
|
10
|
-
get messages(): LLMMessage[];
|
|
11
14
|
get tokenCount(): number;
|
|
12
15
|
get projectContext(): ProjectContext;
|
|
16
|
+
/** All messages including transcript-only (full history). */
|
|
17
|
+
get messages(): LLMMessage[];
|
|
18
|
+
/** Messages that should be sent to the LLM API (excludes transcript-only and meta). */
|
|
19
|
+
getApiMessages(): LLMMessage[];
|
|
13
20
|
addMessage(message: LLMMessage): void;
|
|
14
21
|
addMessages(messages: LLMMessage[]): void;
|
|
15
22
|
shouldCompact(): boolean;
|
|
16
23
|
/**
|
|
17
24
|
* Multi-stage compaction: truncate tool results → LLM summary → hard truncation.
|
|
18
|
-
*
|
|
25
|
+
* Uses append-only pattern: old messages are flagged, not removed.
|
|
19
26
|
*/
|
|
20
27
|
compact(adapter: LLMAdapter, model: string): Promise<{
|
|
21
28
|
before: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../../src/session/session-manager.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../../src/session/session-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAWxG,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,gBAAgB,CAAkB;IAC1C,OAAO,CAAC,0BAA0B,CAAI;gBAE1B,cAAc,EAAE,cAAc,EAAE,gBAAgB,SAAQ;IAgBpE,IAAI,EAAE,IAAI,MAAM,CAA2B;IAC3C,IAAI,UAAU,IAAI,MAAM,CAAmC;IAC3D,IAAI,cAAc,IAAI,cAAc,CAAuC;IAE3E,6DAA6D;IAC7D,IAAI,QAAQ,IAAI,UAAU,EAAE,CAAiC;IAE7D,uFAAuF;IACvF,cAAc,IAAI,UAAU,EAAE;IAM9B,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI;IAMrC,WAAW,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,IAAI;IAQzC,aAAa,IAAI,OAAO;IAOxB;;;OAGG;IACG,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IA6ClH,KAAK,IAAI,IAAI;IAMb,mEAAmE;IACnE,mBAAmB,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI;IAQnD,aAAa,IAAI,UAAU;IAO3B,QAAQ,IAAI,YAAY;IAIxB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMnC,iEAAiE;IACjE,aAAa,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,IAAI;CAK5C"}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SessionManager — conversation history with token counting and compaction.
|
|
2
|
+
* SessionManager — append-only conversation history with token counting and compaction.
|
|
3
|
+
*
|
|
4
|
+
* Messages are never deleted. On compaction, old messages are flagged as
|
|
5
|
+
* transcript-only and a compact summary is inserted as a boundary.
|
|
3
6
|
*/
|
|
4
7
|
import { countMessageTokens } from '../llm/token-counter.js';
|
|
5
8
|
import { shouldCompact as checkCompact, compactHistory, DEFAULT_COMPACTION_CONFIG, } from './compaction.js';
|
|
9
|
+
const MAX_COMPACT_FAILURES = 3;
|
|
6
10
|
export class SessionManager {
|
|
7
11
|
session;
|
|
8
12
|
compactionConfig;
|
|
13
|
+
consecutiveCompactFailures = 0;
|
|
9
14
|
constructor(projectContext, maxContextTokens = 32768) {
|
|
10
15
|
this.session = {
|
|
11
16
|
id: `session_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -22,43 +27,75 @@ export class SessionManager {
|
|
|
22
27
|
};
|
|
23
28
|
}
|
|
24
29
|
get id() { return this.session.id; }
|
|
25
|
-
get messages() { return this.session.messages; }
|
|
26
30
|
get tokenCount() { return this.session.tokenCount; }
|
|
27
31
|
get projectContext() { return this.session.projectContext; }
|
|
32
|
+
/** All messages including transcript-only (full history). */
|
|
33
|
+
get messages() { return this.session.messages; }
|
|
34
|
+
/** Messages that should be sent to the LLM API (excludes transcript-only and meta). */
|
|
35
|
+
getApiMessages() {
|
|
36
|
+
return this.session.messages.filter(m => !m.isVisibleInTranscriptOnly && !m.isMeta);
|
|
37
|
+
}
|
|
28
38
|
addMessage(message) {
|
|
29
39
|
this.session.messages.push(message);
|
|
30
|
-
this.session.tokenCount = countMessageTokens(this.
|
|
40
|
+
this.session.tokenCount = countMessageTokens(this.getApiMessages());
|
|
31
41
|
this.session.lastActivity = new Date();
|
|
32
42
|
}
|
|
33
43
|
addMessages(messages) {
|
|
34
44
|
for (const msg of messages) {
|
|
35
45
|
this.session.messages.push(msg);
|
|
36
46
|
}
|
|
37
|
-
this.session.tokenCount = countMessageTokens(this.
|
|
47
|
+
this.session.tokenCount = countMessageTokens(this.getApiMessages());
|
|
38
48
|
this.session.lastActivity = new Date();
|
|
39
49
|
}
|
|
40
50
|
shouldCompact() {
|
|
41
|
-
|
|
51
|
+
if (this.consecutiveCompactFailures >= MAX_COMPACT_FAILURES) {
|
|
52
|
+
return false; // Circuit breaker: stop trying after repeated failures
|
|
53
|
+
}
|
|
54
|
+
return checkCompact(this.getApiMessages(), this.session.tokenCount, this.compactionConfig);
|
|
42
55
|
}
|
|
43
56
|
/**
|
|
44
57
|
* Multi-stage compaction: truncate tool results → LLM summary → hard truncation.
|
|
45
|
-
*
|
|
58
|
+
* Uses append-only pattern: old messages are flagged, not removed.
|
|
46
59
|
*/
|
|
47
60
|
async compact(adapter, model) {
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
const apiMessages = this.getApiMessages();
|
|
62
|
+
const before = apiMessages.length;
|
|
63
|
+
const beforeTokens = this.session.tokenCount;
|
|
64
|
+
try {
|
|
65
|
+
const result = await compactHistory(apiMessages, this.compactionConfig, adapter, model);
|
|
66
|
+
// Flag old API messages as transcript-only
|
|
67
|
+
for (const msg of this.session.messages) {
|
|
68
|
+
if (!msg.isVisibleInTranscriptOnly && !msg.isMeta) {
|
|
69
|
+
// If this message isn't in the compacted result, flag it
|
|
70
|
+
if (!result.messages.includes(msg)) {
|
|
71
|
+
msg.isVisibleInTranscriptOnly = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Insert new messages from compaction (summary boundary etc.)
|
|
76
|
+
for (const msg of result.messages) {
|
|
77
|
+
if (msg.isCompactSummary || msg.isCompactBoundary) {
|
|
78
|
+
// Find insertion point: after all existing messages
|
|
79
|
+
this.session.messages.push(msg);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
this.session.tokenCount = countMessageTokens(this.getApiMessages());
|
|
83
|
+
this.consecutiveCompactFailures = 0;
|
|
84
|
+
return {
|
|
85
|
+
before,
|
|
86
|
+
after: this.getApiMessages().length,
|
|
87
|
+
tokensSaved: beforeTokens - this.session.tokenCount,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
this.consecutiveCompactFailures++;
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
58
94
|
}
|
|
59
95
|
clear() {
|
|
60
96
|
this.session.messages = [];
|
|
61
97
|
this.session.tokenCount = 0;
|
|
98
|
+
this.consecutiveCompactFailures = 0;
|
|
62
99
|
}
|
|
63
100
|
/** Update the max context tokens (e.g. after switching models). */
|
|
64
101
|
updateContextLength(maxContextTokens) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../../../src/session/session-manager.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../../../src/session/session-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Session persistence — save/load conversation sessions to ~/.cmdr/sessions/.
|
|
3
|
+
*
|
|
4
|
+
* Uses append-only JSONL (one JSON object per line) for crash-safe writes.
|
|
5
|
+
* Each line is one of:
|
|
6
|
+
* {"type":"meta","sessionId":"...","model":"...","projectRoot":"...","createdAt":"..."}
|
|
7
|
+
* {"type":"message","role":"user","content":[...],"timestamp":"..."}
|
|
8
|
+
* {"type":"compact","boundaryIndex":42,"summary":"...","timestamp":"..."}
|
|
3
9
|
*/
|
|
4
10
|
import type { LLMMessage, SessionState } from '../core/types.js';
|
|
5
11
|
export interface SavedSession {
|
|
@@ -12,6 +18,12 @@ export interface SavedSession {
|
|
|
12
18
|
toolsUsed?: string[];
|
|
13
19
|
summary?: string;
|
|
14
20
|
}
|
|
21
|
+
/** Append a single message to the JSONL session file. */
|
|
22
|
+
export declare function appendSessionMessage(sessionId: string, msg: LLMMessage): Promise<void>;
|
|
23
|
+
/** Write session meta header (called once at session start). */
|
|
24
|
+
export declare function writeSessionMeta(sessionId: string, model: string, projectRoot: string): Promise<void>;
|
|
25
|
+
/** Append a compaction marker to the journal. */
|
|
26
|
+
export declare function appendCompactMarker(sessionId: string, boundaryIndex: number, summary: string): Promise<void>;
|
|
15
27
|
export declare function saveSession(sessionState: SessionState, model: string): Promise<string>;
|
|
16
28
|
export declare function loadSession(sessionId: string): Promise<SavedSession | null>;
|
|
17
29
|
/** Find the most recent session for a given project directory. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-persistence.d.ts","sourceRoot":"","sources":["../../../src/session/session-persistence.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"session-persistence.d.ts","sourceRoot":"","sources":["../../../src/session/session-persistence.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAkB,MAAM,kBAAkB,CAAA;AAKhF,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,UAAU,EAAE,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAwED,yDAAyD;AACzD,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,IAAI,CAAC,CAaf;AAED,gEAAgE;AAChE,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,iDAAiD;AACjD,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAQf;AAMD,wBAAsB,WAAW,CAC/B,YAAY,EAAE,YAAY,EAC1B,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CAkBjB;AAED,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAkDjF;AAED,kEAAkE;AAClE,wBAAsB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAKzF;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC;IAClD,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAC,CAAC,CAuCF;AAED,sCAAsC;AACtC,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAMD,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAA6C;IAC1D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;gBAEvB,UAAU,SAAO;IAI7B;;;OAGG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAYvC,+BAA+B;IAC/B,MAAM,IAAI,IAAI;IAOd,yCAAyC;IACnC,KAAK,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAIpD"}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Session persistence — save/load conversation sessions to ~/.cmdr/sessions/.
|
|
3
|
+
*
|
|
4
|
+
* Uses append-only JSONL (one JSON object per line) for crash-safe writes.
|
|
5
|
+
* Each line is one of:
|
|
6
|
+
* {"type":"meta","sessionId":"...","model":"...","projectRoot":"...","createdAt":"..."}
|
|
7
|
+
* {"type":"message","role":"user","content":[...],"timestamp":"..."}
|
|
8
|
+
* {"type":"compact","boundaryIndex":42,"summary":"...","timestamp":"..."}
|
|
3
9
|
*/
|
|
4
|
-
import { readFile, writeFile, mkdir, readdir } from 'fs/promises';
|
|
10
|
+
import { readFile, writeFile, mkdir, readdir, appendFile } from 'fs/promises';
|
|
11
|
+
import { existsSync } from 'fs';
|
|
5
12
|
import { join } from 'path';
|
|
6
13
|
import { homedir } from 'os';
|
|
7
14
|
const CMDR_DIR = join(homedir(), '.cmdr');
|
|
@@ -37,6 +44,49 @@ function extractSummary(messages) {
|
|
|
37
44
|
}
|
|
38
45
|
return '';
|
|
39
46
|
}
|
|
47
|
+
function journalPath(sessionId) {
|
|
48
|
+
return join(SESSIONS_DIR, `${sessionId}.jsonl`);
|
|
49
|
+
}
|
|
50
|
+
/** Append a single message to the JSONL session file. */
|
|
51
|
+
export async function appendSessionMessage(sessionId, msg) {
|
|
52
|
+
await ensureDir(SESSIONS_DIR);
|
|
53
|
+
const line = {
|
|
54
|
+
type: 'message',
|
|
55
|
+
role: msg.role,
|
|
56
|
+
content: msg.content,
|
|
57
|
+
timestamp: new Date().toISOString(),
|
|
58
|
+
...(msg.isCompactSummary ? { isCompactSummary: true } : {}),
|
|
59
|
+
...(msg.isCompactBoundary ? { isCompactBoundary: true } : {}),
|
|
60
|
+
...(msg.isVisibleInTranscriptOnly ? { isVisibleInTranscriptOnly: true } : {}),
|
|
61
|
+
...(msg.isMeta ? { isMeta: true } : {}),
|
|
62
|
+
};
|
|
63
|
+
await appendFile(journalPath(sessionId), JSON.stringify(line) + '\n', 'utf-8');
|
|
64
|
+
}
|
|
65
|
+
/** Write session meta header (called once at session start). */
|
|
66
|
+
export async function writeSessionMeta(sessionId, model, projectRoot) {
|
|
67
|
+
await ensureDir(SESSIONS_DIR);
|
|
68
|
+
const line = {
|
|
69
|
+
type: 'meta',
|
|
70
|
+
sessionId,
|
|
71
|
+
model,
|
|
72
|
+
projectRoot,
|
|
73
|
+
createdAt: new Date().toISOString(),
|
|
74
|
+
};
|
|
75
|
+
await appendFile(journalPath(sessionId), JSON.stringify(line) + '\n', 'utf-8');
|
|
76
|
+
}
|
|
77
|
+
/** Append a compaction marker to the journal. */
|
|
78
|
+
export async function appendCompactMarker(sessionId, boundaryIndex, summary) {
|
|
79
|
+
const line = {
|
|
80
|
+
type: 'compact',
|
|
81
|
+
boundaryIndex,
|
|
82
|
+
summary,
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
};
|
|
85
|
+
await appendFile(journalPath(sessionId), JSON.stringify(line) + '\n', 'utf-8');
|
|
86
|
+
}
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Save / Load
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
40
90
|
export async function saveSession(sessionState, model) {
|
|
41
91
|
await ensureDir(SESSIONS_DIR);
|
|
42
92
|
const saved = {
|
|
@@ -49,15 +99,55 @@ export async function saveSession(sessionState, model) {
|
|
|
49
99
|
toolsUsed: extractToolsUsed(sessionState.messages),
|
|
50
100
|
summary: extractSummary(sessionState.messages),
|
|
51
101
|
};
|
|
102
|
+
// Write atomic JSON snapshot (for listSessions/quick load)
|
|
52
103
|
const filePath = join(SESSIONS_DIR, `${sessionState.id}.json`);
|
|
53
104
|
await writeFile(filePath, JSON.stringify(saved, null, 2), 'utf-8');
|
|
54
105
|
return sessionState.id;
|
|
55
106
|
}
|
|
56
107
|
export async function loadSession(sessionId) {
|
|
57
108
|
try {
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
109
|
+
// Try JSON snapshot first (faster)
|
|
110
|
+
const jsonPath = join(SESSIONS_DIR, `${sessionId}.json`);
|
|
111
|
+
if (existsSync(jsonPath)) {
|
|
112
|
+
const data = await readFile(jsonPath, 'utf-8');
|
|
113
|
+
return JSON.parse(data);
|
|
114
|
+
}
|
|
115
|
+
// Fallback: reconstruct from JSONL journal
|
|
116
|
+
const jPath = journalPath(sessionId);
|
|
117
|
+
if (!existsSync(jPath))
|
|
118
|
+
return null;
|
|
119
|
+
const content = await readFile(jPath, 'utf-8');
|
|
120
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
121
|
+
let meta = null;
|
|
122
|
+
const messages = [];
|
|
123
|
+
for (const line of lines) {
|
|
124
|
+
const entry = JSON.parse(line);
|
|
125
|
+
if (entry.type === 'meta') {
|
|
126
|
+
meta = entry;
|
|
127
|
+
}
|
|
128
|
+
else if (entry.type === 'message') {
|
|
129
|
+
messages.push({
|
|
130
|
+
role: entry.role,
|
|
131
|
+
content: entry.content,
|
|
132
|
+
...(entry.isCompactSummary ? { isCompactSummary: true } : {}),
|
|
133
|
+
...(entry.isCompactBoundary ? { isCompactBoundary: true } : {}),
|
|
134
|
+
...(entry.isVisibleInTranscriptOnly ? { isVisibleInTranscriptOnly: true } : {}),
|
|
135
|
+
...(entry.isMeta ? { isMeta: true } : {}),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!meta)
|
|
140
|
+
return null;
|
|
141
|
+
return {
|
|
142
|
+
id: meta.sessionId,
|
|
143
|
+
messages,
|
|
144
|
+
projectRoot: meta.projectRoot,
|
|
145
|
+
model: meta.model,
|
|
146
|
+
createdAt: meta.createdAt,
|
|
147
|
+
lastActivity: new Date().toISOString(),
|
|
148
|
+
toolsUsed: extractToolsUsed(messages),
|
|
149
|
+
summary: extractSummary(messages),
|
|
150
|
+
};
|
|
61
151
|
}
|
|
62
152
|
catch {
|
|
63
153
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-persistence.js","sourceRoot":"","sources":["../../../src/session/session-persistence.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"session-persistence.js","sourceRoot":"","sources":["../../../src/session/session-persistence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bash command security checks.
|
|
3
|
+
*
|
|
4
|
+
* Derived from Claude Code's 23 security checks — blocks destructive ops,
|
|
5
|
+
* data exfiltration, and shell injection vectors.
|
|
6
|
+
*/
|
|
7
|
+
export interface SecurityResult {
|
|
8
|
+
safe: boolean;
|
|
9
|
+
reason?: string;
|
|
10
|
+
sanitized: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function sanitizeBashCommand(command: string): SecurityResult;
|
|
13
|
+
//# sourceMappingURL=bash-security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bash-security.d.ts","sourceRoot":"","sources":["../../../../src/tools/built-in/bash-security.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA+BH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA0BnE"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bash command security checks.
|
|
3
|
+
*
|
|
4
|
+
* Derived from Claude Code's 23 security checks — blocks destructive ops,
|
|
5
|
+
* data exfiltration, and shell injection vectors.
|
|
6
|
+
*/
|
|
7
|
+
// Destructive or dangerous command patterns
|
|
8
|
+
const BLOCKED_PATTERNS = [
|
|
9
|
+
// Destructive commands
|
|
10
|
+
{ pattern: /\brm\s+(-[a-zA-Z]*f[a-zA-Z]*\s+)?\/\s*$/, label: 'rm -rf /' },
|
|
11
|
+
{ pattern: /\bchmod\s+(-R\s+)?777\s+\//, label: 'chmod 777 /' },
|
|
12
|
+
{ pattern: /\bmkfs\b/, label: 'filesystem format' },
|
|
13
|
+
{ pattern: /\bdd\s+.*of=\/dev\//, label: 'dd to device' },
|
|
14
|
+
// Data exfiltration
|
|
15
|
+
{ pattern: /\bcurl\s+.*-d\s+.*@/, label: 'curl posting file contents' },
|
|
16
|
+
{ pattern: /\bwget\s+.*--post-file/, label: 'wget posting files' },
|
|
17
|
+
// Sensitive file access via subshell
|
|
18
|
+
{ pattern: /\$\(.*\bcat\b.*\/etc\/(passwd|shadow)/, label: 'reading sensitive files' },
|
|
19
|
+
];
|
|
20
|
+
// Zsh builtins that should never come from an LLM
|
|
21
|
+
const BLOCKED_ZSH_BUILTINS = new Set([
|
|
22
|
+
'bindkey', 'compdef', 'compadd', 'zmodload', 'autoload',
|
|
23
|
+
'zle', 'zstyle', 'typeset', 'setopt', 'unsetopt',
|
|
24
|
+
'functions', 'aliases', 'disable', 'enable', 'emulate',
|
|
25
|
+
]);
|
|
26
|
+
// Zero-width characters (from Claude Code HackerOne finding)
|
|
27
|
+
const ZERO_WIDTH_REGEX = /[\u200B\u200C\u200D\u2060\uFEFF]/g;
|
|
28
|
+
// Zsh equals expansion: =curl → /usr/bin/curl
|
|
29
|
+
const ZSH_EQUALS_REGEX = /(?:^|\s)=[a-zA-Z]/;
|
|
30
|
+
export function sanitizeBashCommand(command) {
|
|
31
|
+
// Strip zero-width characters
|
|
32
|
+
const cleaned = command.replace(ZERO_WIDTH_REGEX, '');
|
|
33
|
+
// Check zsh equals expansion
|
|
34
|
+
if (ZSH_EQUALS_REGEX.test(cleaned)) {
|
|
35
|
+
return { safe: false, reason: 'Zsh equals expansion detected', sanitized: cleaned };
|
|
36
|
+
}
|
|
37
|
+
// Check blocked patterns
|
|
38
|
+
for (const { pattern, label } of BLOCKED_PATTERNS) {
|
|
39
|
+
if (pattern.test(cleaned)) {
|
|
40
|
+
return { safe: false, reason: `Blocked: ${label}`, sanitized: cleaned };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Check blocked zsh builtins at start of each segment
|
|
44
|
+
const segments = cleaned.split(/[;|&]/);
|
|
45
|
+
for (const segment of segments) {
|
|
46
|
+
const firstWord = segment.trim().split(/\s/)[0];
|
|
47
|
+
if (BLOCKED_ZSH_BUILTINS.has(firstWord)) {
|
|
48
|
+
return { safe: false, reason: `Blocked shell builtin: ${firstWord}`, sanitized: cleaned };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { safe: true, sanitized: cleaned };
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=bash-security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bash-security.js","sourceRoot":"","sources":["../../../../src/tools/built-in/bash-security.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,4CAA4C;AAC5C,MAAM,gBAAgB,GAAyC;IAC7D,uBAAuB;IACvB,EAAE,OAAO,EAAE,yCAAyC,EAAE,KAAK,EAAE,UAAU,EAAE;IACzE,EAAE,OAAO,EAAE,4BAA4B,EAAE,KAAK,EAAE,aAAa,EAAE;IAC/D,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,mBAAmB,EAAE;IACnD,EAAE,OAAO,EAAE,qBAAqB,EAAE,KAAK,EAAE,cAAc,EAAE;IAEzD,oBAAoB;IACpB,EAAE,OAAO,EAAE,qBAAqB,EAAE,KAAK,EAAE,4BAA4B,EAAE;IACvE,EAAE,OAAO,EAAE,wBAAwB,EAAE,KAAK,EAAE,oBAAoB,EAAE;IAElE,qCAAqC;IACrC,EAAE,OAAO,EAAE,uCAAuC,EAAE,KAAK,EAAE,yBAAyB,EAAE;CACvF,CAAA;AAED,kDAAkD;AAClD,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IACnC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU;IACvD,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU;IAChD,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS;CACvD,CAAC,CAAA;AAEF,6DAA6D;AAC7D,MAAM,gBAAgB,GAAG,mCAAmC,CAAA;AAE5D,8CAA8C;AAC9C,MAAM,gBAAgB,GAAG,mBAAmB,CAAA;AAQ5C,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,8BAA8B;IAC9B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAA;IAErD,6BAA6B;IAC7B,IAAI,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACnC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,+BAA+B,EAAE,SAAS,EAAE,OAAO,EAAE,CAAA;IACrF,CAAC;IAED,yBAAyB;IACzB,KAAK,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,gBAAgB,EAAE,CAAC;QAClD,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAA;QACzE,CAAC;IACH,CAAC;IAED,sDAAsD;IACtD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACvC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;QAC/C,IAAI,oBAAoB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACxC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,0BAA0B,SAAS,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAA;QAC3F,CAAC;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAA;AAC3C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../../../src/tools/built-in/bash.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../../../src/tools/built-in/bash.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,eAAO,MAAM,QAAQ;;;;EAmCnB,CAAA"}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { spawn } from 'child_process';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { defineTool } from '../registry.js';
|
|
7
|
+
import { sanitizeBashCommand } from './bash-security.js';
|
|
7
8
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
8
9
|
export const bashTool = defineTool({
|
|
9
10
|
name: 'bash',
|
|
@@ -16,9 +17,14 @@ export const bashTool = defineTool({
|
|
|
16
17
|
cwd: z.string().optional().describe('Working directory for the command.'),
|
|
17
18
|
}),
|
|
18
19
|
execute: async (input, context) => {
|
|
20
|
+
// Security check
|
|
21
|
+
const check = sanitizeBashCommand(input.command);
|
|
22
|
+
if (!check.safe) {
|
|
23
|
+
return { data: `Command blocked: ${check.reason}`, isError: true };
|
|
24
|
+
}
|
|
19
25
|
const timeoutMs = input.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
20
26
|
const cwd = input.cwd ?? context.cwd ?? process.cwd();
|
|
21
|
-
const { stdout, stderr, exitCode } = await runCommand(
|
|
27
|
+
const { stdout, stderr, exitCode } = await runCommand(check.sanitized, cwd, timeoutMs, context.abortSignal);
|
|
22
28
|
const parts = [];
|
|
23
29
|
if (stdout)
|
|
24
30
|
parts.push(stdout);
|