@exreve/exk 1.0.22 → 1.0.24
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/dist/agentSession.js +6 -2
- package/dist/index.js +119 -36
- package/dist/moduleMcpServer.js +8 -3
- package/dist/ttc-cli.tar.gz +0 -0
- package/package.json +1 -1
package/dist/agentSession.js
CHANGED
|
@@ -427,11 +427,15 @@ export class AgentSessionManager {
|
|
|
427
427
|
const mcpServer = this.buildMcpServer(sessionId);
|
|
428
428
|
if (mcpServer) {
|
|
429
429
|
const toolHint = getModuleToolHint(session.enabledModules || []);
|
|
430
|
+
console.log(`[agentSession] MCP server created: name=${mcpServer.name}, hasHint=${!!toolHint}`);
|
|
431
|
+
console.log(`[agentSession] MCP server keys:`, Object.keys(mcpServer));
|
|
432
|
+
console.log(`[agentSession] MCP server type:`, mcpServer.type);
|
|
430
433
|
return {
|
|
431
|
-
mcpServers: [mcpServer],
|
|
432
|
-
...(toolHint ? { systemPrompt: toolHint } : {})
|
|
434
|
+
mcpServers: { [mcpServer.name]: mcpServer },
|
|
435
|
+
...(toolHint ? { systemPrompt: { type: 'preset', preset: 'claude_code', append: toolHint } } : {})
|
|
433
436
|
};
|
|
434
437
|
}
|
|
438
|
+
console.log(`[agentSession] No MCP server created (enabledModules=${JSON.stringify(session.enabledModules)})`);
|
|
435
439
|
return {};
|
|
436
440
|
})(),
|
|
437
441
|
...(pathToClaudeCodeExecutable ? { pathToClaudeCodeExecutable } : {}),
|
package/dist/index.js
CHANGED
|
@@ -202,8 +202,19 @@ function getCliVersion() {
|
|
|
202
202
|
}
|
|
203
203
|
const CURRENT_FILE = fileURLToPath(import.meta.url);
|
|
204
204
|
const __dirname = path.dirname(CURRENT_FILE);
|
|
205
|
+
let cachedCliHash = null;
|
|
205
206
|
function getCliHash() {
|
|
206
|
-
|
|
207
|
+
if (cachedCliHash)
|
|
208
|
+
return cachedCliHash;
|
|
209
|
+
try {
|
|
210
|
+
const hash = createHash('sha256').update(fsSync.readFileSync(CURRENT_FILE)).digest('hex');
|
|
211
|
+
cachedCliHash = hash;
|
|
212
|
+
return hash;
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
// File may be temporarily missing during update — return safe fallback
|
|
216
|
+
return 'updating';
|
|
217
|
+
}
|
|
207
218
|
}
|
|
208
219
|
async function checkForUpdate() {
|
|
209
220
|
try {
|
|
@@ -221,48 +232,108 @@ async function checkForUpdate() {
|
|
|
221
232
|
return null;
|
|
222
233
|
}
|
|
223
234
|
}
|
|
235
|
+
let isUpdating = false;
|
|
224
236
|
async function replaceSelf(tarballBuffer) {
|
|
237
|
+
isUpdating = true;
|
|
225
238
|
const extractDir = path.join(os.tmpdir(), `ttc-update-${Date.now()}`);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
239
|
+
const stagingDir = path.join(os.tmpdir(), `ttc-stage-${Date.now()}`);
|
|
240
|
+
try {
|
|
241
|
+
await fs.mkdir(extractDir, { recursive: true });
|
|
242
|
+
const tarPath = path.join(extractDir, 'ttc-cli.tar.gz');
|
|
243
|
+
await fs.writeFile(tarPath, tarballBuffer);
|
|
244
|
+
execSync(`tar -xzf "${tarPath}" -C "${extractDir}"`);
|
|
245
|
+
await fs.unlink(tarPath);
|
|
246
|
+
// Preserve user config/token files (never overwrite on update)
|
|
247
|
+
const preserveFiles = ['device-config.json', 'device-id.json', 'config.json'];
|
|
248
|
+
const preserved = {};
|
|
249
|
+
for (const f of preserveFiles) {
|
|
250
|
+
try {
|
|
251
|
+
preserved[f] = await fs.readFile(path.join(CONFIG_DIR, f));
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
/* file may not exist */
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// Build the complete new state in a staging directory first.
|
|
258
|
+
// This ensures we never delete the running app until the replacement is
|
|
259
|
+
// fully prepared and verified — no window where files are missing.
|
|
260
|
+
const cliStaging = path.join(stagingDir, 'cli');
|
|
261
|
+
const sharedStaging = path.join(stagingDir, 'shared');
|
|
262
|
+
await fs.cp(path.join(extractDir, 'cli'), cliStaging, { recursive: true });
|
|
263
|
+
await fs.cp(path.join(extractDir, 'shared'), sharedStaging, { recursive: true });
|
|
264
|
+
await fs.copyFile(path.join(extractDir, 'package.json'), path.join(stagingDir, 'package.json'));
|
|
265
|
+
// Restore preserved config into staging
|
|
266
|
+
for (const f of preserveFiles) {
|
|
267
|
+
if (preserved[f])
|
|
268
|
+
await fs.writeFile(path.join(stagingDir, f), preserved[f]);
|
|
269
|
+
}
|
|
270
|
+
// Sanity check: staging must contain a valid dist/index.js
|
|
271
|
+
const indexJs = path.join(cliStaging, 'dist', 'index.js');
|
|
272
|
+
try {
|
|
273
|
+
const stat = await fs.stat(indexJs);
|
|
274
|
+
if (stat.size === 0)
|
|
275
|
+
throw new Error('Empty index.js');
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
throw new Error(`Staged update is invalid (missing or empty dist/index.js): ${err.message}`);
|
|
279
|
+
}
|
|
280
|
+
// Atomic swap: rename staging dirs to final locations.
|
|
281
|
+
// rename() is atomic on the same filesystem. We move the old dirs out of
|
|
282
|
+
// the way first, then rename new ones in. If anything fails mid-swap we
|
|
283
|
+
// can attempt to roll back.
|
|
284
|
+
const cliDest = path.join(CONFIG_DIR, 'cli');
|
|
285
|
+
const sharedDest = path.join(CONFIG_DIR, 'shared');
|
|
286
|
+
const cliOld = cliDest + '.old';
|
|
287
|
+
const sharedOld = sharedDest + '.old';
|
|
288
|
+
// Move current dirs to .old (must exist for this to work)
|
|
289
|
+
await fs.rm(cliOld, { recursive: true, force: true });
|
|
290
|
+
await fs.rm(sharedOld, { recursive: true, force: true });
|
|
291
|
+
if (fsSync.existsSync(cliDest)) {
|
|
292
|
+
await fs.rename(cliDest, cliOld);
|
|
293
|
+
}
|
|
294
|
+
if (fsSync.existsSync(sharedDest)) {
|
|
295
|
+
await fs.rename(sharedDest, sharedOld);
|
|
296
|
+
}
|
|
297
|
+
// Move staged dirs into place
|
|
235
298
|
try {
|
|
236
|
-
|
|
299
|
+
await fs.rename(cliStaging, cliDest);
|
|
300
|
+
await fs.rename(sharedStaging, sharedDest);
|
|
301
|
+
await fs.copyFile(path.join(stagingDir, 'package.json'), path.join(CONFIG_DIR, 'package.json'));
|
|
302
|
+
}
|
|
303
|
+
catch (swapErr) {
|
|
304
|
+
// Rollback: restore old dirs
|
|
305
|
+
console.error(`⚠ Swap failed: ${swapErr.message}, rolling back...`);
|
|
306
|
+
try {
|
|
307
|
+
if (fsSync.existsSync(cliOld))
|
|
308
|
+
await fs.rename(cliOld, cliDest);
|
|
309
|
+
if (fsSync.existsSync(sharedOld))
|
|
310
|
+
await fs.rename(sharedOld, sharedDest);
|
|
311
|
+
}
|
|
312
|
+
catch (rollbackErr) {
|
|
313
|
+
console.error(`⚠ Rollback also failed: ${rollbackErr.message}`);
|
|
314
|
+
}
|
|
315
|
+
throw swapErr;
|
|
316
|
+
}
|
|
317
|
+
// Clean up old dirs and temp dirs
|
|
318
|
+
await fs.rm(cliOld, { recursive: true, force: true }).catch(() => { });
|
|
319
|
+
await fs.rm(sharedOld, { recursive: true, force: true }).catch(() => { });
|
|
320
|
+
await fs.rm(extractDir, { recursive: true, force: true });
|
|
321
|
+
await fs.rm(stagingDir, { recursive: true, force: true }).catch(() => { });
|
|
322
|
+
// Invalidate cached hash since we just replaced the binary
|
|
323
|
+
cachedCliHash = null;
|
|
324
|
+
console.log('✓ CLI updated');
|
|
325
|
+
const npmPaths = [path.join(os.homedir(), '.nvm/versions/node/v22/bin/npm'), path.join(os.homedir(), '.nvm/versions/node/v20/bin/npm')];
|
|
326
|
+
const npmBin = npmPaths.find(p => fsSync.existsSync(p)) || 'npm';
|
|
327
|
+
try {
|
|
328
|
+
execSync(`"${npmBin}" install --omit=dev`, { cwd: CONFIG_DIR, stdio: 'inherit' });
|
|
329
|
+
console.log('✓ Dependencies updated');
|
|
237
330
|
}
|
|
238
331
|
catch {
|
|
239
|
-
|
|
332
|
+
console.warn('⚠ npm install failed');
|
|
240
333
|
}
|
|
241
334
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const sharedDest = path.join(CONFIG_DIR, 'shared');
|
|
245
|
-
await fs.rm(cliDest, { recursive: true, force: true });
|
|
246
|
-
await fs.rm(sharedDest, { recursive: true, force: true });
|
|
247
|
-
await fs.cp(path.join(extractDir, 'cli'), cliDest, { recursive: true });
|
|
248
|
-
await fs.cp(path.join(extractDir, 'shared'), sharedDest, { recursive: true });
|
|
249
|
-
// Update package.json and npm install
|
|
250
|
-
await fs.copyFile(path.join(extractDir, 'package.json'), path.join(CONFIG_DIR, 'package.json'));
|
|
251
|
-
await fs.rm(extractDir, { recursive: true, force: true });
|
|
252
|
-
// Restore preserved config
|
|
253
|
-
for (const f of preserveFiles) {
|
|
254
|
-
if (preserved[f])
|
|
255
|
-
await fs.writeFile(path.join(CONFIG_DIR, f), preserved[f]);
|
|
256
|
-
}
|
|
257
|
-
console.log('✓ CLI updated');
|
|
258
|
-
const npmPaths = [path.join(os.homedir(), '.nvm/versions/node/v22/bin/npm'), path.join(os.homedir(), '.nvm/versions/node/v20/bin/npm')];
|
|
259
|
-
const npmBin = npmPaths.find(p => fsSync.existsSync(p)) || 'npm';
|
|
260
|
-
try {
|
|
261
|
-
execSync(`"${npmBin}" install --omit=dev`, { cwd: CONFIG_DIR, stdio: 'inherit' });
|
|
262
|
-
console.log('✓ Dependencies updated');
|
|
263
|
-
}
|
|
264
|
-
catch {
|
|
265
|
-
console.warn('⚠ npm install failed');
|
|
335
|
+
finally {
|
|
336
|
+
isUpdating = false;
|
|
266
337
|
}
|
|
267
338
|
}
|
|
268
339
|
async function selfUpdate(force = false) {
|
|
@@ -1716,6 +1787,10 @@ async function runDaemon(foreground = false, email) {
|
|
|
1716
1787
|
// ========== Version & Update Handlers ==========
|
|
1717
1788
|
// Respond with CLI version info
|
|
1718
1789
|
socket.on('version:info', (_data, callback) => {
|
|
1790
|
+
if (isUpdating) {
|
|
1791
|
+
callback({ success: false, error: 'Update in progress' });
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1719
1794
|
callback({
|
|
1720
1795
|
success: true,
|
|
1721
1796
|
version: getCliVersion(),
|
|
@@ -1728,6 +1803,10 @@ async function runDaemon(foreground = false, email) {
|
|
|
1728
1803
|
});
|
|
1729
1804
|
// Force update: npm update -g @exreve/exk then restart PM2
|
|
1730
1805
|
socket.on('force-update', (_data, callback) => {
|
|
1806
|
+
if (isUpdating) {
|
|
1807
|
+
callback?.({ success: false, error: 'Update already in progress' });
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1731
1810
|
if (foreground) {
|
|
1732
1811
|
console.log('[force-update] Received force update command from server');
|
|
1733
1812
|
}
|
|
@@ -1770,6 +1849,10 @@ async function runDaemon(foreground = false, email) {
|
|
|
1770
1849
|
});
|
|
1771
1850
|
// ========== update:start handler (legacy compatibility) ==========
|
|
1772
1851
|
socket.on('update:start', (_data, callback) => {
|
|
1852
|
+
if (isUpdating) {
|
|
1853
|
+
callback?.({ success: false, error: 'Update already in progress' });
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1773
1856
|
// Use npm-based self-update
|
|
1774
1857
|
if (foreground) {
|
|
1775
1858
|
console.log('[update:start] Starting npm self-update...');
|
package/dist/moduleMcpServer.js
CHANGED
|
@@ -15,7 +15,7 @@ function createUserChoiceTool(onChoiceRequest) {
|
|
|
15
15
|
options: z.array(z.object({ label: z.string(), value: z.string() })),
|
|
16
16
|
timeout: z.number().optional()
|
|
17
17
|
};
|
|
18
|
-
return tool('user_choice_request', '
|
|
18
|
+
return tool('user_choice_request', 'Present a choice modal to the user and wait for their selection. Use this whenever you need a decision, confirmation, or preference from the user. The AskUserQuestion tool is disabled — this is your only way to get interactive user input. Always use this before proceeding with ambiguous tasks or destructive actions.', schema, async (args, _extra) => {
|
|
19
19
|
if (!onChoiceRequest) {
|
|
20
20
|
return {
|
|
21
21
|
content: [
|
|
@@ -101,10 +101,15 @@ export function getModuleToolHint(enabledModules) {
|
|
|
101
101
|
return null;
|
|
102
102
|
const toolDescriptions = [];
|
|
103
103
|
if (enabledModules.includes('user-choice')) {
|
|
104
|
-
toolDescriptions.push(
|
|
104
|
+
toolDescriptions.push(`- user_choice_request(question: str, options: [{label: str, value: str}], timeout?: int): Present a modal to the remote user with a question and clickable options. Returns the user's selection.
|
|
105
|
+
IMPORTANT: The built-in AskUserQuestion tool is DISABLED. Whenever you need user input, decisions, or confirmations, you MUST use user_choice_request instead.
|
|
106
|
+
Always call this tool when:
|
|
107
|
+
- There are multiple valid approaches and you need the user to pick one
|
|
108
|
+
- You need explicit confirmation before a destructive or significant action
|
|
109
|
+
- The user's preference matters for how to proceed`);
|
|
105
110
|
}
|
|
106
111
|
// Add more module tool descriptions here as they are implemented
|
|
107
112
|
if (toolDescriptions.length === 0)
|
|
108
113
|
return null;
|
|
109
|
-
return `You have access to the following custom MCP tools:\n${toolDescriptions.join('\n')}\n\
|
|
114
|
+
return `You have access to the following custom MCP tools:\n${toolDescriptions.join('\n')}\n\nThese tools are your ONLY way to interact with the user (AskUserQuestion is disabled). Call them directly — do NOT ask the user to type responses in chat.`;
|
|
110
115
|
}
|
package/dist/ttc-cli.tar.gz
CHANGED
|
Binary file
|