@kaitranntt/ccs 3.3.0 → 3.4.1
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 +66 -7
- package/VERSION +1 -1
- package/bin/{auth-commands.js → auth/auth-commands.js} +3 -3
- package/bin/ccs.js +38 -19
- package/bin/glmt/budget-calculator.js +114 -0
- package/bin/glmt/delta-accumulator.js +261 -0
- package/bin/glmt/glmt-proxy.js +488 -0
- package/bin/glmt/glmt-transformer.js +919 -0
- package/bin/glmt/locale-enforcer.js +80 -0
- package/bin/glmt/sse-parser.js +96 -0
- package/bin/glmt/task-classifier.js +162 -0
- package/bin/{doctor.js → management/doctor.js} +2 -2
- package/lib/ccs +1 -1
- package/lib/ccs.ps1 +1 -1
- package/package.json +1 -1
- package/scripts/dev-install.sh +35 -0
- package/bin/glmt-proxy.js +0 -307
- package/bin/glmt-transformer.js +0 -437
- /package/bin/{profile-detector.js → auth/profile-detector.js} +0 -0
- /package/bin/{profile-registry.js → auth/profile-registry.js} +0 -0
- /package/bin/{instance-manager.js → management/instance-manager.js} +0 -0
- /package/bin/{recovery-manager.js → management/recovery-manager.js} +0 -0
- /package/bin/{shared-manager.js → management/shared-manager.js} +0 -0
- /package/bin/{claude-detector.js → utils/claude-detector.js} +0 -0
- /package/bin/{config-manager.js → utils/config-manager.js} +0 -0
- /package/bin/{error-manager.js → utils/error-manager.js} +0 -0
- /package/bin/{helpers.js → utils/helpers.js} +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* LocaleEnforcer - Force English output from GLM models
|
|
6
|
+
*
|
|
7
|
+
* Purpose: GLM models default to Chinese when prompts are ambiguous or contain Chinese context.
|
|
8
|
+
* This module injects "MUST respond in English" instruction into system prompt or first user message.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const enforcer = new LocaleEnforcer({ forceEnglish: true });
|
|
12
|
+
* const modifiedMessages = enforcer.injectInstruction(messages);
|
|
13
|
+
*
|
|
14
|
+
* Configuration:
|
|
15
|
+
* CCS_GLMT_FORCE_ENGLISH=false - Disable locale enforcement (allow multilingual)
|
|
16
|
+
*
|
|
17
|
+
* Strategy:
|
|
18
|
+
* 1. If system prompt exists: Prepend instruction
|
|
19
|
+
* 2. If no system prompt: Prepend to first user message
|
|
20
|
+
* 3. Preserve message structure (string vs array content)
|
|
21
|
+
*/
|
|
22
|
+
class LocaleEnforcer {
|
|
23
|
+
constructor(options = {}) {
|
|
24
|
+
this.forceEnglish = options.forceEnglish ?? true;
|
|
25
|
+
this.instruction = "CRITICAL: You MUST respond in English only, regardless of the input language or context. This is a strict requirement.";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Inject English instruction into messages
|
|
30
|
+
* @param {Array} messages - Messages array to modify
|
|
31
|
+
* @returns {Array} Modified messages array
|
|
32
|
+
*/
|
|
33
|
+
injectInstruction(messages) {
|
|
34
|
+
if (!this.forceEnglish) {
|
|
35
|
+
return messages;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Clone messages to avoid mutation
|
|
39
|
+
const modifiedMessages = JSON.parse(JSON.stringify(messages));
|
|
40
|
+
|
|
41
|
+
// Strategy 1: Inject into system prompt (preferred)
|
|
42
|
+
const systemIndex = modifiedMessages.findIndex(m => m.role === 'system');
|
|
43
|
+
if (systemIndex >= 0) {
|
|
44
|
+
const systemMsg = modifiedMessages[systemIndex];
|
|
45
|
+
|
|
46
|
+
if (typeof systemMsg.content === 'string') {
|
|
47
|
+
systemMsg.content = `${this.instruction}\n\n${systemMsg.content}`;
|
|
48
|
+
} else if (Array.isArray(systemMsg.content)) {
|
|
49
|
+
systemMsg.content.unshift({
|
|
50
|
+
type: 'text',
|
|
51
|
+
text: this.instruction
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return modifiedMessages;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Strategy 2: Prepend to first user message
|
|
59
|
+
const userIndex = modifiedMessages.findIndex(m => m.role === 'user');
|
|
60
|
+
if (userIndex >= 0) {
|
|
61
|
+
const userMsg = modifiedMessages[userIndex];
|
|
62
|
+
|
|
63
|
+
if (typeof userMsg.content === 'string') {
|
|
64
|
+
userMsg.content = `${this.instruction}\n\n${userMsg.content}`;
|
|
65
|
+
} else if (Array.isArray(userMsg.content)) {
|
|
66
|
+
userMsg.content.unshift({
|
|
67
|
+
type: 'text',
|
|
68
|
+
text: this.instruction
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return modifiedMessages;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// No system or user messages found (edge case)
|
|
76
|
+
return modifiedMessages;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = LocaleEnforcer;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SSEParser - Parse Server-Sent Events (SSE) stream
|
|
6
|
+
*
|
|
7
|
+
* Handles:
|
|
8
|
+
* - Incomplete events across chunks
|
|
9
|
+
* - Multiple events in single chunk
|
|
10
|
+
* - Malformed data (skip gracefully)
|
|
11
|
+
* - [DONE] marker
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* const parser = new SSEParser();
|
|
15
|
+
* stream.on('data', chunk => {
|
|
16
|
+
* const events = parser.parse(chunk);
|
|
17
|
+
* events.forEach(event => { ... });
|
|
18
|
+
* });
|
|
19
|
+
*/
|
|
20
|
+
class SSEParser {
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.buffer = '';
|
|
23
|
+
this.eventCount = 0;
|
|
24
|
+
this.maxBufferSize = options.maxBufferSize || 1024 * 1024; // 1MB default
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse chunk and extract SSE events
|
|
29
|
+
* @param {Buffer|string} chunk - Data chunk from stream
|
|
30
|
+
* @returns {Array<Object>} Array of parsed events
|
|
31
|
+
*/
|
|
32
|
+
parse(chunk) {
|
|
33
|
+
this.buffer += chunk.toString();
|
|
34
|
+
|
|
35
|
+
// C-01 Fix: Prevent unbounded buffer growth (DoS protection)
|
|
36
|
+
if (this.buffer.length > this.maxBufferSize) {
|
|
37
|
+
throw new Error(`SSE buffer exceeded ${this.maxBufferSize} bytes (DoS protection)`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const lines = this.buffer.split('\n');
|
|
41
|
+
|
|
42
|
+
// Keep incomplete line in buffer
|
|
43
|
+
this.buffer = lines.pop() || '';
|
|
44
|
+
|
|
45
|
+
const events = [];
|
|
46
|
+
let currentEvent = { event: 'message', data: '' };
|
|
47
|
+
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
if (line.startsWith('event: ')) {
|
|
50
|
+
currentEvent.event = line.substring(7).trim();
|
|
51
|
+
} else if (line.startsWith('data: ')) {
|
|
52
|
+
const data = line.substring(6);
|
|
53
|
+
|
|
54
|
+
if (data === '[DONE]') {
|
|
55
|
+
this.eventCount++;
|
|
56
|
+
events.push({
|
|
57
|
+
event: 'done',
|
|
58
|
+
data: null,
|
|
59
|
+
index: this.eventCount
|
|
60
|
+
});
|
|
61
|
+
currentEvent = { event: 'message', data: '' };
|
|
62
|
+
} else {
|
|
63
|
+
try {
|
|
64
|
+
currentEvent.data = JSON.parse(data);
|
|
65
|
+
this.eventCount++;
|
|
66
|
+
currentEvent.index = this.eventCount;
|
|
67
|
+
events.push(currentEvent);
|
|
68
|
+
currentEvent = { event: 'message', data: '' };
|
|
69
|
+
} catch (e) {
|
|
70
|
+
// H-01 Fix: Log parse errors for debugging
|
|
71
|
+
if (typeof console !== 'undefined' && console.error) {
|
|
72
|
+
console.error('[SSEParser] Malformed JSON event:', e.message, 'Data:', data.substring(0, 100));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} else if (line.startsWith('id: ')) {
|
|
77
|
+
currentEvent.id = line.substring(4).trim();
|
|
78
|
+
} else if (line.startsWith('retry: ')) {
|
|
79
|
+
currentEvent.retry = parseInt(line.substring(7), 10);
|
|
80
|
+
}
|
|
81
|
+
// Empty lines separate events (already handled by JSON parsing)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return events;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Reset parser state (for reuse)
|
|
89
|
+
*/
|
|
90
|
+
reset() {
|
|
91
|
+
this.buffer = '';
|
|
92
|
+
this.eventCount = 0;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = SSEParser;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* TaskClassifier - Classify user prompts as reasoning, execution, or mixed tasks
|
|
6
|
+
*
|
|
7
|
+
* Purpose: Determine task type to inform thinking enable/disable decision.
|
|
8
|
+
* Uses keyword-based matching for fast, deterministic classification.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const classifier = new TaskClassifier();
|
|
12
|
+
* const taskType = classifier.classify(messages);
|
|
13
|
+
*
|
|
14
|
+
* Task types:
|
|
15
|
+
* - reasoning: Planning, design, analysis (enable thinking)
|
|
16
|
+
* - execution: Implementation, fixes, debugging (disable thinking for speed)
|
|
17
|
+
* - mixed: Ambiguous or both (default to safe thinking mode)
|
|
18
|
+
*
|
|
19
|
+
* Classification strategy:
|
|
20
|
+
* 1. Extract text from all user messages
|
|
21
|
+
* 2. Score against reasoning and execution keyword lists
|
|
22
|
+
* 3. Return type with highest score (or 'mixed' if tied/no matches)
|
|
23
|
+
*/
|
|
24
|
+
class TaskClassifier {
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
this.keywords = {
|
|
27
|
+
reasoning: [
|
|
28
|
+
'plan', 'design', 'analyze', 'architecture', 'strategy',
|
|
29
|
+
'approach', 'consider', 'evaluate', 'research', 'explore',
|
|
30
|
+
'brainstorm', 'think about', 'pros and cons', 'alternatives',
|
|
31
|
+
'compare', 'recommend', 'assess', 'review', 'investigate'
|
|
32
|
+
],
|
|
33
|
+
execution: [
|
|
34
|
+
'fix', 'implement', 'debug', 'refactor', 'optimize',
|
|
35
|
+
'add', 'remove', 'update', 'create', 'delete',
|
|
36
|
+
'change', 'modify', 'replace', 'move', 'rename',
|
|
37
|
+
'test', 'run', 'execute', 'deploy', 'build'
|
|
38
|
+
]
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Allow custom keywords via options
|
|
42
|
+
if (options.customKeywords) {
|
|
43
|
+
this.keywords = { ...this.keywords, ...options.customKeywords };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Classify messages as reasoning, execution, or mixed
|
|
49
|
+
* @param {Array} messages - Messages array
|
|
50
|
+
* @returns {string} 'reasoning', 'execution', or 'mixed'
|
|
51
|
+
*/
|
|
52
|
+
classify(messages) {
|
|
53
|
+
if (!messages || messages.length === 0) {
|
|
54
|
+
return 'mixed'; // Default to safe mode
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Extract text from all user messages
|
|
58
|
+
const text = messages
|
|
59
|
+
.filter(m => m.role === 'user')
|
|
60
|
+
.map(m => this._extractText(m.content))
|
|
61
|
+
.join(' ')
|
|
62
|
+
.toLowerCase();
|
|
63
|
+
|
|
64
|
+
if (!text.trim()) {
|
|
65
|
+
return 'mixed'; // No text found
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Score against keyword lists
|
|
69
|
+
const reasoningScore = this._matchScore(text, this.keywords.reasoning);
|
|
70
|
+
const executionScore = this._matchScore(text, this.keywords.execution);
|
|
71
|
+
|
|
72
|
+
// Classify based on scores
|
|
73
|
+
if (reasoningScore > executionScore) {
|
|
74
|
+
return 'reasoning';
|
|
75
|
+
} else if (executionScore > reasoningScore) {
|
|
76
|
+
return 'execution';
|
|
77
|
+
} else {
|
|
78
|
+
return 'mixed'; // Tied or no matches
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Extract text from message content
|
|
84
|
+
* @param {string|Array} content - Message content
|
|
85
|
+
* @returns {string} Extracted text
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
_extractText(content) {
|
|
89
|
+
if (typeof content === 'string') {
|
|
90
|
+
return content;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (Array.isArray(content)) {
|
|
94
|
+
return content
|
|
95
|
+
.filter(block => block.type === 'text')
|
|
96
|
+
.map(block => block.text || '')
|
|
97
|
+
.join(' ');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return '';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Calculate keyword match score
|
|
105
|
+
* @param {string} text - Text to search
|
|
106
|
+
* @param {Array} keywords - Keywords to match
|
|
107
|
+
* @returns {number} Number of matches
|
|
108
|
+
* @private
|
|
109
|
+
*/
|
|
110
|
+
_matchScore(text, keywords) {
|
|
111
|
+
return keywords.reduce((score, keyword) => {
|
|
112
|
+
// Support both exact match and word boundary match
|
|
113
|
+
const regex = new RegExp(`\\b${this._escapeRegex(keyword)}\\b`, 'i');
|
|
114
|
+
return score + (regex.test(text) ? 1 : 0);
|
|
115
|
+
}, 0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Escape special regex characters
|
|
120
|
+
* @param {string} str - String to escape
|
|
121
|
+
* @returns {string} Escaped string
|
|
122
|
+
* @private
|
|
123
|
+
*/
|
|
124
|
+
_escapeRegex(str) {
|
|
125
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get classification details (for debugging)
|
|
130
|
+
* @param {Array} messages - Messages array
|
|
131
|
+
* @returns {Object} { type, reasoningScore, executionScore, text }
|
|
132
|
+
*/
|
|
133
|
+
classifyWithDetails(messages) {
|
|
134
|
+
const text = messages
|
|
135
|
+
.filter(m => m.role === 'user')
|
|
136
|
+
.map(m => this._extractText(m.content))
|
|
137
|
+
.join(' ')
|
|
138
|
+
.toLowerCase();
|
|
139
|
+
|
|
140
|
+
const reasoningScore = this._matchScore(text, this.keywords.reasoning);
|
|
141
|
+
const executionScore = this._matchScore(text, this.keywords.execution);
|
|
142
|
+
|
|
143
|
+
let type;
|
|
144
|
+
if (reasoningScore > executionScore) {
|
|
145
|
+
type = 'reasoning';
|
|
146
|
+
} else if (executionScore > reasoningScore) {
|
|
147
|
+
type = 'execution';
|
|
148
|
+
} else {
|
|
149
|
+
type = 'mixed';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
type,
|
|
154
|
+
reasoningScore,
|
|
155
|
+
executionScore,
|
|
156
|
+
textLength: text.length,
|
|
157
|
+
textPreview: text.substring(0, 100) + (text.length > 100 ? '...' : '')
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = TaskClassifier;
|
|
@@ -4,8 +4,8 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const { spawn } = require('child_process');
|
|
7
|
-
const { colored } = require('
|
|
8
|
-
const { detectClaudeCli } = require('
|
|
7
|
+
const { colored } = require('../utils/helpers');
|
|
8
|
+
const { detectClaudeCli } = require('../utils/claude-detector');
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Health check results
|
package/lib/ccs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
# Version (updated by scripts/bump-version.sh)
|
|
5
|
-
CCS_VERSION="3.
|
|
5
|
+
CCS_VERSION="3.4.1"
|
|
6
6
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
7
|
readonly CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs/config.json}"
|
|
8
8
|
readonly PROFILES_JSON="$HOME/.ccs/profiles.json"
|
package/lib/ccs.ps1
CHANGED
|
@@ -12,7 +12,7 @@ param(
|
|
|
12
12
|
$ErrorActionPreference = "Stop"
|
|
13
13
|
|
|
14
14
|
# Version (updated by scripts/bump-version.sh)
|
|
15
|
-
$CcsVersion = "3.
|
|
15
|
+
$CcsVersion = "3.4.1"
|
|
16
16
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
17
17
|
$ConfigFile = if ($env:CCS_CONFIG) { $env:CCS_CONFIG } else { "$env:USERPROFILE\.ccs\config.json" }
|
|
18
18
|
$ProfilesJson = "$env:USERPROFILE\.ccs\profiles.json"
|
package/package.json
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Auto-install CCS locally for testing changes
|
|
3
|
+
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
echo "[CCS Dev Install] Starting..."
|
|
7
|
+
|
|
8
|
+
# Get to the right directory
|
|
9
|
+
cd "$(dirname "$0")/.."
|
|
10
|
+
|
|
11
|
+
# Pack the npm package
|
|
12
|
+
echo "[CCS Dev Install] Creating package..."
|
|
13
|
+
npm pack
|
|
14
|
+
|
|
15
|
+
# Find the tarball
|
|
16
|
+
TARBALL=$(ls -t kaitranntt-ccs-*.tgz | head -1)
|
|
17
|
+
|
|
18
|
+
if [ -z "$TARBALL" ]; then
|
|
19
|
+
echo "[CCS Dev Install] ERROR: No tarball found"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
echo "[CCS Dev Install] Found tarball: $TARBALL"
|
|
24
|
+
|
|
25
|
+
# Install globally
|
|
26
|
+
echo "[CCS Dev Install] Installing globally..."
|
|
27
|
+
npm install -g "$TARBALL"
|
|
28
|
+
|
|
29
|
+
# Clean up
|
|
30
|
+
echo "[CCS Dev Install] Cleaning up..."
|
|
31
|
+
rm "$TARBALL"
|
|
32
|
+
|
|
33
|
+
echo "[CCS Dev Install] ✓ Complete! CCS is now updated."
|
|
34
|
+
echo ""
|
|
35
|
+
echo "Test with: ccs glmt --version"
|