codeep 2.18.1 → 2.20.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 +53 -16
- package/dist/acp/commands.js +11 -55
- package/dist/acp/protocol.d.ts +34 -0
- package/dist/acp/server.d.ts +6 -1
- package/dist/acp/server.js +97 -2
- package/dist/api/index.js +9 -0
- package/dist/commands/core/index.d.ts +19 -0
- package/dist/commands/core/index.js +28 -0
- package/dist/commands/core/keysync.d.ts +2 -0
- package/dist/commands/core/keysync.js +34 -0
- package/dist/commands/core/telemetry.d.ts +2 -0
- package/dist/commands/core/telemetry.js +34 -0
- package/dist/config/index.js +2 -2
- package/dist/config/providers.js +7 -4
- package/dist/renderer/App.d.ts +9 -48
- package/dist/renderer/App.js +113 -338
- package/dist/renderer/Screen.d.ts +13 -0
- package/dist/renderer/Screen.js +22 -0
- package/dist/renderer/commands/registry.js +3 -3
- package/dist/renderer/commands.js +19 -51
- package/dist/renderer/components/CommandAutocomplete.d.ts +46 -0
- package/dist/renderer/components/CommandAutocomplete.js +103 -0
- package/dist/renderer/components/HunkPicker.d.ts +48 -0
- package/dist/renderer/components/HunkPicker.js +140 -0
- package/dist/renderer/components/MentionPicker.d.ts +60 -0
- package/dist/renderer/components/MentionPicker.js +111 -0
- package/dist/renderer/components/PasteDialog.d.ts +43 -0
- package/dist/renderer/components/PasteDialog.js +70 -0
- package/dist/renderer/layout.js +1 -0
- package/dist/renderer/main.js +15 -39
- package/dist/utils/agent.js +121 -26
- package/dist/utils/agentChat.d.ts +11 -4
- package/dist/utils/agentChat.js +53 -25
- package/dist/utils/codeepCloud.d.ts +3 -0
- package/dist/utils/codeepCloud.js +62 -7
- package/dist/utils/personalities.d.ts +63 -5
- package/dist/utils/personalities.js +583 -31
- package/dist/utils/shell.d.ts +11 -1
- package/dist/utils/shell.js +169 -82
- package/dist/utils/ssrfGuard.d.ts +18 -0
- package/dist/utils/ssrfGuard.js +83 -0
- package/dist/utils/taskPlanner.d.ts +7 -1
- package/dist/utils/taskPlanner.js +16 -7
- package/dist/utils/tokenTracker.js +5 -3
- package/dist/utils/toolExecution.d.ts +1 -0
- package/dist/utils/toolExecution.js +48 -88
- package/dist/utils/tools.d.ts +3 -3
- package/dist/utils/tools.js +18 -13
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/utils/agentChat.js
CHANGED
|
@@ -23,6 +23,8 @@ import { recordTokenUsage, extractOpenAIUsage, extractAnthropicUsage } from './t
|
|
|
23
23
|
import { parseOpenAIToolCalls, parseAnthropicToolCalls, parseToolCalls } from './toolParsing.js';
|
|
24
24
|
import { formatToolDefinitions, getOpenAITools, getAnthropicTools } from './tools.js';
|
|
25
25
|
import { readOpenRouterPreferences } from './openrouterPrefs.js';
|
|
26
|
+
import { checkApiRateLimit } from './ratelimit.js';
|
|
27
|
+
import { ApiError } from '../api/index.js';
|
|
26
28
|
import { handleStream, handleOpenAIAgentStream, handleAnthropicAgentStream } from './agentStream.js';
|
|
27
29
|
import { logger } from './logger.js';
|
|
28
30
|
const debug = (...args) => {
|
|
@@ -282,21 +284,24 @@ export async function summarizeEarlierHistory(history, maxChars = 16000) {
|
|
|
282
284
|
return ''; // graceful — recent verbatim history still gets injected
|
|
283
285
|
}
|
|
284
286
|
}
|
|
285
|
-
export function getAgentSystemPrompt(projectContext) {
|
|
287
|
+
export function getAgentSystemPrompt(projectContext, runtime) {
|
|
286
288
|
const root = projectContext.root || process.cwd();
|
|
287
289
|
// State the real underlying model/provider so "which model are you"
|
|
288
290
|
// gets a truthful answer instead of a hallucinated one.
|
|
289
|
-
const model = String(config.get('model') || '');
|
|
290
|
-
const providerId = String(config.get('provider') || '');
|
|
291
|
+
const model = String(runtime?.model || config.get('model') || '');
|
|
292
|
+
const providerId = String(runtime?.providerId || config.get('provider') || '');
|
|
291
293
|
const identity = model
|
|
292
294
|
? `You are Codeep, an autonomous AI coding agent operating inside this project. The underlying model is \`${model}\` (via ${providerId}). If asked which model or provider you are, answer truthfully with these details. Never claim to be Claude or any other model unless that is genuinely the configured model.`
|
|
293
295
|
: `You are Codeep, an autonomous AI coding agent operating inside this project. Never refer to yourself as Claude or any other AI unless that is genuinely the configured model.`;
|
|
296
|
+
const toolInstructions = runtime?.allowedToolNames !== undefined
|
|
297
|
+
? `This run has an enforced tool allowlist. Only these tools may be used: ${runtime.allowedToolNames.length ? runtime.allowedToolNames.slice(0, 50).join(', ') : '(none)'}. Do not request or claim access to any other tool.`
|
|
298
|
+
: `- read_file / write_file / edit_file / delete_file — file ops (prefer edit_file for modifications to keep surrounding content intact)
|
|
299
|
+
- create_directory / list_files / search_code — project navigation
|
|
300
|
+
- execute_command — ONLY for package managers & version control: npm, yarn, pnpm, bun, git, composer, pip, cargo, go, make. Never for ls/cat/grep/mkdir/rm/cp/mv/touch — use the dedicated tools.`;
|
|
294
301
|
return `${identity}
|
|
295
302
|
|
|
296
303
|
## Tools
|
|
297
|
-
|
|
298
|
-
- create_directory / list_files / search_code — project navigation
|
|
299
|
-
- execute_command — ONLY for package managers & version control: npm, yarn, pnpm, bun, git, composer, pip, cargo, go, make. Never for ls/cat/grep/mkdir/rm/cp/mv/touch — use the dedicated tools.
|
|
304
|
+
${toolInstructions}
|
|
300
305
|
|
|
301
306
|
## Behavior
|
|
302
307
|
- Do what the user asked — in whatever language they wrote. Tool names stay English.
|
|
@@ -332,8 +337,9 @@ ${projectContext.structure ? `\n## Project Structure\n${projectContext.structure
|
|
|
332
337
|
return intelligence ? `\n\n${generateContextFromIntelligence(intelligence)}` : '';
|
|
333
338
|
})()}`;
|
|
334
339
|
}
|
|
335
|
-
export function getFallbackSystemPrompt(projectContext, additionalTools) {
|
|
336
|
-
|
|
340
|
+
export function getFallbackSystemPrompt(projectContext, additionalTools, runtime) {
|
|
341
|
+
const allowed = runtime?.allowedToolNames ? new Set(runtime.allowedToolNames) : undefined;
|
|
342
|
+
return getAgentSystemPrompt(projectContext, runtime) + '\n\n' + formatToolDefinitions(additionalTools, allowed);
|
|
337
343
|
}
|
|
338
344
|
/**
|
|
339
345
|
* Make a chat API call for agent mode with native tool support.
|
|
@@ -346,17 +352,29 @@ export async function agentChat(messages, systemPrompt, onChunk, abortSignal, dy
|
|
|
346
352
|
* invoke). Optional — built-in tools work the same whether this is
|
|
347
353
|
* omitted or an empty array.
|
|
348
354
|
*/
|
|
349
|
-
additionalTools) {
|
|
350
|
-
const protocol = config.get('protocol');
|
|
351
|
-
const model = config.get('model');
|
|
352
|
-
const providerId = config.get('provider');
|
|
353
|
-
const apiKey = getApiKey() || (isNoApiKeyProvider(providerId) ? 'ollama' : null);
|
|
355
|
+
additionalTools, runtime) {
|
|
356
|
+
const protocol = runtime?.protocol ?? config.get('protocol');
|
|
357
|
+
const model = runtime?.model ?? config.get('model');
|
|
358
|
+
const providerId = runtime?.providerId ?? config.get('provider');
|
|
359
|
+
const apiKey = getApiKey(providerId) || (isNoApiKeyProvider(providerId) ? 'ollama' : null);
|
|
360
|
+
const allowedTools = runtime?.allowedToolNames ? new Set(runtime.allowedToolNames) : undefined;
|
|
354
361
|
let baseUrl = resolveBaseUrl(providerId, protocol);
|
|
355
362
|
const authHeader = getProviderAuthHeader(providerId, protocol);
|
|
356
363
|
if (!baseUrl)
|
|
357
364
|
throw new Error(`Provider ${providerId} does not support ${protocol} protocol`);
|
|
365
|
+
// Global API throttle — same choke point as api/chat(). Checked here so
|
|
366
|
+
// the agent loop (which can run up to agentMaxIterations iterations, each
|
|
367
|
+
// with its own API call) is rate-limited even when it never routes
|
|
368
|
+
// through api/chat(). Bypassed for no-key local providers (Ollama) —
|
|
369
|
+
// there is no quota to protect on localhost.
|
|
370
|
+
if (!isNoApiKeyProvider(providerId)) {
|
|
371
|
+
const rateCheck = checkApiRateLimit();
|
|
372
|
+
if (!rateCheck.allowed) {
|
|
373
|
+
throw new ApiError(rateCheck.message || 'API rate limit exceeded', 429);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
358
376
|
if (!supportsNativeTools(providerId, protocol)) {
|
|
359
|
-
return await agentChatFallback(messages, systemPrompt, onChunk, abortSignal);
|
|
377
|
+
return await agentChatFallback(messages, systemPrompt, onChunk, abortSignal, dynamicTimeout, additionalTools, runtime);
|
|
360
378
|
}
|
|
361
379
|
const controller = new AbortController();
|
|
362
380
|
const timeoutMs = dynamicTimeout || config.get('apiTimeout');
|
|
@@ -427,7 +445,7 @@ additionalTools) {
|
|
|
427
445
|
model,
|
|
428
446
|
messages: [],
|
|
429
447
|
rawMessages: [{ role: 'system', content: systemPrompt }, ...messages],
|
|
430
|
-
tools: getOpenAITools(additionalTools),
|
|
448
|
+
tools: getOpenAITools(additionalTools, allowedTools),
|
|
431
449
|
numCtx,
|
|
432
450
|
keepAlive: config.get('ollamaKeepAlive') || undefined,
|
|
433
451
|
temperature: requiresDefaultTemperature(providerId) ? undefined : Number(config.get('temperature')),
|
|
@@ -447,7 +465,7 @@ additionalTools) {
|
|
|
447
465
|
}
|
|
448
466
|
body = {
|
|
449
467
|
model, messages: [{ role: 'system', content: systemPrompt }, ...messages],
|
|
450
|
-
tools: getOpenAITools(additionalTools), tool_choice: 'auto', stream: useStreaming,
|
|
468
|
+
tools: getOpenAITools(additionalTools, allowedTools), tool_choice: 'auto', stream: useStreaming,
|
|
451
469
|
...tempParam, ...tokParam, ...reasoningParam,
|
|
452
470
|
// Ask ALL OpenAI-compatible providers to emit a usage block in the
|
|
453
471
|
// stream — without this most (DeepSeek/Kimi/Grok/Qwen/GLM/…) send no
|
|
@@ -466,7 +484,7 @@ additionalTools) {
|
|
|
466
484
|
// Cache hits cost 0.1× input. Misses ("cache creation") cost 1.25×.
|
|
467
485
|
// Net win after the 2nd same-shape request. Below 1024 input tokens
|
|
468
486
|
// Anthropic silently skips caching — no error path to handle.
|
|
469
|
-
const anthropicTools = getAnthropicTools(additionalTools);
|
|
487
|
+
const anthropicTools = getAnthropicTools(additionalTools, allowedTools);
|
|
470
488
|
const cachedTools = anthropicTools.length > 0
|
|
471
489
|
? [
|
|
472
490
|
...anthropicTools.slice(0, -1),
|
|
@@ -487,7 +505,7 @@ additionalTools) {
|
|
|
487
505
|
if (!response.ok) {
|
|
488
506
|
const errorText = await response.text();
|
|
489
507
|
if (errorText.includes('tools') || errorText.includes('function') || response.status === 400) {
|
|
490
|
-
return await agentChatFallback(messages, systemPrompt, onChunk, abortSignal);
|
|
508
|
+
return await agentChatFallback(messages, systemPrompt, onChunk, abortSignal, dynamicTimeout, additionalTools, runtime);
|
|
491
509
|
}
|
|
492
510
|
throw new Error(`API error: ${response.status} - ${errorText}`);
|
|
493
511
|
}
|
|
@@ -546,7 +564,7 @@ additionalTools) {
|
|
|
546
564
|
throw error;
|
|
547
565
|
}
|
|
548
566
|
if (err.message.includes('tools') || err.message.includes('function')) {
|
|
549
|
-
return await agentChatFallback(messages, systemPrompt, onChunk, abortSignal);
|
|
567
|
+
return await agentChatFallback(messages, systemPrompt, onChunk, abortSignal, dynamicTimeout, additionalTools, runtime);
|
|
550
568
|
}
|
|
551
569
|
throw error;
|
|
552
570
|
}
|
|
@@ -559,15 +577,25 @@ additionalTools) {
|
|
|
559
577
|
/**
|
|
560
578
|
* Fallback chat without native tools (text-based tool format)
|
|
561
579
|
*/
|
|
562
|
-
export async function agentChatFallback(messages, systemPrompt, onChunk, abortSignal, dynamicTimeout) {
|
|
563
|
-
const protocol = config.get('protocol');
|
|
564
|
-
const model = config.get('model');
|
|
565
|
-
const providerId = config.get('provider');
|
|
566
|
-
const apiKey = getApiKey() || (isNoApiKeyProvider(providerId) ? 'ollama' : null);
|
|
580
|
+
export async function agentChatFallback(messages, systemPrompt, onChunk, abortSignal, dynamicTimeout, additionalTools, runtime) {
|
|
581
|
+
const protocol = runtime?.protocol ?? config.get('protocol');
|
|
582
|
+
const model = runtime?.model ?? config.get('model');
|
|
583
|
+
const providerId = runtime?.providerId ?? config.get('provider');
|
|
584
|
+
const apiKey = getApiKey(providerId) || (isNoApiKeyProvider(providerId) ? 'ollama' : null);
|
|
585
|
+
const allowedTools = runtime?.allowedToolNames ? new Set(runtime.allowedToolNames) : undefined;
|
|
567
586
|
let baseUrl = resolveBaseUrl(providerId, protocol);
|
|
568
587
|
const authHeader = getProviderAuthHeader(providerId, protocol);
|
|
569
588
|
if (!baseUrl)
|
|
570
589
|
throw new Error(`Provider ${providerId} does not support ${protocol} protocol`);
|
|
590
|
+
// See rate-limit note in agentChat above — same choke point, same local-
|
|
591
|
+
// provider bypass. This path runs when the provider has no native tool
|
|
592
|
+
// support, so it's reached directly (not via agentChat's early return).
|
|
593
|
+
if (!isNoApiKeyProvider(providerId)) {
|
|
594
|
+
const rateCheck = checkApiRateLimit();
|
|
595
|
+
if (!rateCheck.allowed) {
|
|
596
|
+
throw new ApiError(rateCheck.message || 'API rate limit exceeded', 429);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
571
599
|
const controller = new AbortController();
|
|
572
600
|
const timeoutMs = dynamicTimeout || config.get('apiTimeout');
|
|
573
601
|
let isTimeout = false;
|
|
@@ -589,7 +617,7 @@ export async function agentChatFallback(messages, systemPrompt, onChunk, abortSi
|
|
|
589
617
|
headers['anthropic-version'] = '2023-06-01';
|
|
590
618
|
const fallbackPrompt = systemPrompt.includes('## Available Tools')
|
|
591
619
|
? systemPrompt
|
|
592
|
-
: systemPrompt + '\n\n' + formatToolDefinitions();
|
|
620
|
+
: systemPrompt + '\n\n' + formatToolDefinitions(additionalTools, allowedTools);
|
|
593
621
|
try {
|
|
594
622
|
let endpoint;
|
|
595
623
|
let body;
|
|
@@ -84,7 +84,9 @@ declare function readFileBundle(kind: 'personalities' | 'commands'): Record<stri
|
|
|
84
84
|
* files. Only writes files that don't already exist (additive merge —
|
|
85
85
|
* never clobber local edits). Returns the count of newly written files. */
|
|
86
86
|
declare function writeFileBundle(kind: 'personalities' | 'commands', items: Record<string, string>): number;
|
|
87
|
+
declare function writePulledPersonalityBundle(items: Record<string, string>): number;
|
|
87
88
|
export declare const pullPersonalities: () => Promise<number | null>;
|
|
89
|
+
export declare const getLastPersonalityPullBackupCount: () => number;
|
|
88
90
|
export declare const pushPersonalities: () => Promise<number | null>;
|
|
89
91
|
export declare const pullCommands: () => Promise<number | null>;
|
|
90
92
|
export declare const pushCommands: () => Promise<number | null>;
|
|
@@ -181,4 +183,5 @@ export declare function syncMemoryNotes(projectName: string, notes: string[]): P
|
|
|
181
183
|
export declare const _globalDirForTest: typeof globalDir;
|
|
182
184
|
export declare const _readFileBundleForTest: typeof readFileBundle;
|
|
183
185
|
export declare const _writeFileBundleForTest: typeof writeFileBundle;
|
|
186
|
+
export declare const _writePulledPersonalityBundleForTest: typeof writePulledPersonalityBundle;
|
|
184
187
|
export {};
|
|
@@ -241,11 +241,11 @@ export async function purgeKeys() {
|
|
|
241
241
|
//
|
|
242
242
|
// Both are name → raw-.md-body bundles stored in a global dir
|
|
243
243
|
// (~/.codeep/personalities, ~/.codeep/commands). The sync is bidirectional
|
|
244
|
-
// and merge-based
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
import { readFileSync, readdirSync, existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
244
|
+
// and merge-based. Personality pulls are cloud-authoritative because the web
|
|
245
|
+
// builder edits the cloud copy: changed local files are backed up before the
|
|
246
|
+
// remote body replaces them. Commands retain the older additive-only merge.
|
|
247
|
+
// Pull never deletes a local file that disappeared from the server.
|
|
248
|
+
import { readFileSync, readdirSync, existsSync, mkdirSync, writeFileSync, renameSync, unlinkSync } from 'fs';
|
|
249
249
|
import { join } from 'path';
|
|
250
250
|
import { homedir } from 'os';
|
|
251
251
|
function globalDir(kind) {
|
|
@@ -284,7 +284,7 @@ function writeFileBundle(kind, items) {
|
|
|
284
284
|
mkdirSync(dir, { recursive: true });
|
|
285
285
|
let written = 0;
|
|
286
286
|
for (const [name, body] of Object.entries(items)) {
|
|
287
|
-
if (!/^[a-z0-9][a-z0-9-]*$/.test(name) || name.length > 64 || typeof body !== 'string' || !body)
|
|
287
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(name) || name.length > 64 || typeof body !== 'string' || !body || body.length > 64 * 1024)
|
|
288
288
|
continue;
|
|
289
289
|
const filePath = join(dir, `${name}.md`);
|
|
290
290
|
if (existsSync(filePath))
|
|
@@ -297,6 +297,57 @@ function writeFileBundle(kind, items) {
|
|
|
297
297
|
}
|
|
298
298
|
return written;
|
|
299
299
|
}
|
|
300
|
+
/** Apply a cloud personality bundle. Updated bodies replace the active local
|
|
301
|
+
* file so web edits actually take effect, but every divergent local body is
|
|
302
|
+
* first copied to ~/.codeep/backups/personalities/. */
|
|
303
|
+
let lastPersonalityPullBackupCount = 0;
|
|
304
|
+
function writePulledPersonalityBundle(items) {
|
|
305
|
+
lastPersonalityPullBackupCount = 0;
|
|
306
|
+
const dir = globalDir('personalities');
|
|
307
|
+
if (!existsSync(dir))
|
|
308
|
+
mkdirSync(dir, { recursive: true });
|
|
309
|
+
let written = 0;
|
|
310
|
+
for (const [name, body] of Object.entries(items)) {
|
|
311
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(name) || name.length > 64 || typeof body !== 'string' || !body || body.length > 64 * 1024)
|
|
312
|
+
continue;
|
|
313
|
+
const filePath = join(dir, `${name}.md`);
|
|
314
|
+
let tempPath = '';
|
|
315
|
+
try {
|
|
316
|
+
if (existsSync(filePath)) {
|
|
317
|
+
const local = readFileSync(filePath, 'utf8');
|
|
318
|
+
if (local === body)
|
|
319
|
+
continue;
|
|
320
|
+
const backupDir = join(homedir(), '.codeep', 'backups', 'personalities');
|
|
321
|
+
if (!existsSync(backupDir))
|
|
322
|
+
mkdirSync(backupDir, { recursive: true });
|
|
323
|
+
let suffix = new Date().toISOString().replace(/[:.]/g, '-');
|
|
324
|
+
let backupPath = join(backupDir, `${name}-${suffix}.md`);
|
|
325
|
+
let collision = 1;
|
|
326
|
+
while (existsSync(backupPath)) {
|
|
327
|
+
backupPath = join(backupDir, `${name}-${suffix}-${collision++}.md`);
|
|
328
|
+
}
|
|
329
|
+
writeFileSync(backupPath, local);
|
|
330
|
+
lastPersonalityPullBackupCount++;
|
|
331
|
+
}
|
|
332
|
+
// Same-directory rename is atomic on supported local filesystems: a
|
|
333
|
+
// crash cannot leave a half-written active personality.
|
|
334
|
+
tempPath = join(dir, `.${name}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`);
|
|
335
|
+
writeFileSync(tempPath, body);
|
|
336
|
+
renameSync(tempPath, filePath);
|
|
337
|
+
written++;
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// A backup/write failure leaves the existing active file untouched.
|
|
341
|
+
if (tempPath && existsSync(tempPath)) {
|
|
342
|
+
try {
|
|
343
|
+
unlinkSync(tempPath);
|
|
344
|
+
}
|
|
345
|
+
catch { /* best effort temp cleanup */ }
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return written;
|
|
350
|
+
}
|
|
300
351
|
async function pullBundle(kind) {
|
|
301
352
|
const syncToken = getSyncToken();
|
|
302
353
|
if (!syncToken)
|
|
@@ -308,7 +359,9 @@ async function pullBundle(kind) {
|
|
|
308
359
|
const data = await res.json();
|
|
309
360
|
if (!data.ok)
|
|
310
361
|
return null;
|
|
311
|
-
return
|
|
362
|
+
return kind === 'personalities'
|
|
363
|
+
? writePulledPersonalityBundle(data.items ?? {})
|
|
364
|
+
: writeFileBundle(kind, data.items ?? {});
|
|
312
365
|
}
|
|
313
366
|
catch {
|
|
314
367
|
return null;
|
|
@@ -330,6 +383,7 @@ async function pushBundle(kind) {
|
|
|
330
383
|
return res?.ok ? count : null;
|
|
331
384
|
}
|
|
332
385
|
export const pullPersonalities = () => pullBundle('personalities');
|
|
386
|
+
export const getLastPersonalityPullBackupCount = () => lastPersonalityPullBackupCount;
|
|
333
387
|
export const pushPersonalities = () => pushBundle('personalities');
|
|
334
388
|
export const pullCommands = () => pullBundle('commands');
|
|
335
389
|
export const pushCommands = () => pushBundle('commands');
|
|
@@ -644,3 +698,4 @@ async function fetchWithRetry(url, options, maxRetries = 2) {
|
|
|
644
698
|
export const _globalDirForTest = globalDir;
|
|
645
699
|
export const _readFileBundleForTest = readFileBundle;
|
|
646
700
|
export const _writeFileBundleForTest = writeFileBundle;
|
|
701
|
+
export const _writePulledPersonalityBundleForTest = writePulledPersonalityBundle;
|
|
@@ -10,13 +10,21 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Project shadows global shadows built-in, by name.
|
|
12
12
|
*
|
|
13
|
-
* File format (project / global):
|
|
13
|
+
* File format (project / global): legacy prompt-only Markdown remains valid;
|
|
14
|
+
* structured custom bots add a small versioned frontmatter block:
|
|
14
15
|
* ```
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* ---
|
|
17
|
+
* codeep: custom-bot/v1
|
|
18
|
+
* model: automatic
|
|
19
|
+
* tools: [files, tests, git]
|
|
20
|
+
* scope: all
|
|
21
|
+
* projects: []
|
|
22
|
+
* ---
|
|
23
|
+
* # Concise Reviewer
|
|
24
|
+
* <behavior sections — appended to the system prompt>
|
|
17
25
|
* ```
|
|
18
|
-
*
|
|
19
|
-
*
|
|
26
|
+
* The first H1 is the display name. Tools/model/scope are enforced only for
|
|
27
|
+
* structured files; old files stay unrestricted.
|
|
20
28
|
*
|
|
21
29
|
* Activation:
|
|
22
30
|
* - `config.activePersonality` holds the active name (or null/undefined
|
|
@@ -26,7 +34,15 @@
|
|
|
26
34
|
* personality is active.
|
|
27
35
|
* - Persists across sessions until cleared with `/personality off`.
|
|
28
36
|
*/
|
|
37
|
+
import type { ToolCall } from './tools.js';
|
|
29
38
|
export type PersonalityScope = 'builtin' | 'project' | 'global';
|
|
39
|
+
export type PersonalityCapability = 'files' | 'terminal' | 'tests' | 'git' | 'web' | 'mcp';
|
|
40
|
+
export type PersonalityProjectScope = 'all' | 'selected' | 'personal' | 'unspecified';
|
|
41
|
+
export interface PersonalityRuntimeModel {
|
|
42
|
+
providerId: string;
|
|
43
|
+
model: string;
|
|
44
|
+
protocol: 'openai' | 'anthropic';
|
|
45
|
+
}
|
|
30
46
|
export interface Personality {
|
|
31
47
|
/** Slug (filename without .md, or built-in id). Lowercase, hyphens. */
|
|
32
48
|
name: string;
|
|
@@ -37,9 +53,50 @@ export interface Personality {
|
|
|
37
53
|
/** Markdown body appended to the system prompt when active. */
|
|
38
54
|
prompt: string;
|
|
39
55
|
scope: PersonalityScope;
|
|
56
|
+
/** True for `custom-bot/v1` files and the previous web section format. */
|
|
57
|
+
structured?: boolean;
|
|
58
|
+
/** False when a frontmatter `codeep` schema marker is present but unsupported. */
|
|
59
|
+
schemaValid?: boolean;
|
|
60
|
+
/** `automatic` inherits the user's current provider/model for this run. */
|
|
61
|
+
modelPreference?: string;
|
|
62
|
+
/** Normalised high-level capabilities selected in the builder. */
|
|
63
|
+
tools?: PersonalityCapability[];
|
|
64
|
+
/** Original declared tool values, retained for diagnostics/UI. */
|
|
65
|
+
declaredTools?: string[];
|
|
66
|
+
/** True when a structured file explicitly declares Tools, including `[]`. */
|
|
67
|
+
restrictTools?: boolean;
|
|
68
|
+
projectScope?: PersonalityProjectScope;
|
|
69
|
+
/** False when versioned metadata explicitly declares an unknown scope. */
|
|
70
|
+
scopeValid?: boolean;
|
|
71
|
+
projects?: string[];
|
|
72
|
+
responsibility?: string;
|
|
73
|
+
responseStyle?: string;
|
|
74
|
+
always?: string[];
|
|
75
|
+
never?: string[];
|
|
76
|
+
advancedInstructions?: string;
|
|
40
77
|
}
|
|
78
|
+
/** Whether a structured bot's model field satisfies the portable v1 contract. */
|
|
79
|
+
export declare function isPersonalityModelPreferenceValid(personality: Personality): boolean;
|
|
80
|
+
/** Parse both custom-bot/v1 and the original web builder's heading format. */
|
|
81
|
+
export declare function parsePersonalityMarkdown(raw: string, name: string, scope: PersonalityScope): Personality;
|
|
82
|
+
/**
|
|
83
|
+
* Scope is enforced against the workspace basename only. This intentionally
|
|
84
|
+
* avoids accepting arbitrary paths from cloud-authored metadata.
|
|
85
|
+
*/
|
|
86
|
+
export declare function isPersonalityAvailable(personality: Personality, workspaceRoot?: string): boolean;
|
|
87
|
+
/** Concrete tool names that may be advertised for a structured custom bot. */
|
|
88
|
+
export declare function getPersonalityToolAllowlist(personality: Personality, registeredMcpToolNames?: ReadonlySet<string>): string[] | undefined;
|
|
89
|
+
/** Runtime gate. It is deliberately stricter than the prompt/tool catalog. */
|
|
90
|
+
export declare function isPersonalityToolCallAllowed(personality: Personality, toolCall: ToolCall, registeredMcpToolNames?: ReadonlySet<string>): boolean;
|
|
91
|
+
/** Resolve an exact provider/model preference without mutating global config. */
|
|
92
|
+
export declare function resolvePersonalityRuntimeModel(personality: Personality, current: {
|
|
93
|
+
providerId: string;
|
|
94
|
+
model: string;
|
|
95
|
+
protocol: 'openai' | 'anthropic';
|
|
96
|
+
}): PersonalityRuntimeModel | null;
|
|
41
97
|
export declare function loadAllPersonalities(workspaceRoot?: string): Personality[];
|
|
42
98
|
export declare function findPersonality(name: string, workspaceRoot?: string): Personality | null;
|
|
99
|
+
export declare function getActivePersonality(workspaceRoot?: string): Personality | null;
|
|
43
100
|
/**
|
|
44
101
|
* Returns the prompt addendum for the currently active personality, or
|
|
45
102
|
* '' when none is set. Called from agent.ts after the base system prompt
|
|
@@ -47,4 +104,5 @@ export declare function findPersonality(name: string, workspaceRoot?: string): P
|
|
|
47
104
|
* project rules conflict.
|
|
48
105
|
*/
|
|
49
106
|
export declare function getActivePersonalityPrompt(workspaceRoot?: string): string;
|
|
107
|
+
export declare function formatPersonalityActivation(personality: Personality): string;
|
|
50
108
|
export declare function formatPersonalityList(workspaceRoot?: string): string;
|