codeep 3.3.3 → 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/dist/acp/commands.d.ts +50 -1
- package/dist/acp/commands.js +545 -109
- package/dist/acp/protocol.d.ts +14 -5
- package/dist/acp/server.d.ts +36 -1
- package/dist/acp/server.js +581 -155
- package/dist/acp/serverHandlers.d.ts +2 -1
- package/dist/acp/serverHandlers.js +3 -0
- package/dist/acp/session.d.ts +28 -2
- package/dist/acp/session.js +25 -6
- package/dist/acp/transport.d.ts +40 -4
- package/dist/acp/transport.js +218 -25
- package/dist/acp/turns.d.ts +20 -0
- package/dist/acp/turns.js +30 -0
- package/dist/api/index.js +2 -0
- package/dist/api/ollamaNative.d.ts +3 -0
- package/dist/api/ollamaNative.js +35 -3
- package/dist/config/index.d.ts +21 -4
- package/dist/config/index.js +178 -123
- package/dist/renderer/agentExecution.d.ts +30 -2
- package/dist/renderer/agentExecution.js +248 -92
- package/dist/renderer/commands/helpers.d.ts +18 -2
- package/dist/renderer/commands/helpers.js +28 -5
- package/dist/renderer/commands.d.ts +2 -0
- package/dist/renderer/commands.js +180 -64
- package/dist/renderer/main.d.ts +41 -0
- package/dist/renderer/main.js +181 -80
- package/dist/utils/agent.d.ts +69 -4
- package/dist/utils/agent.js +416 -248
- package/dist/utils/agentChat.js +82 -10
- package/dist/utils/agents.d.ts +2 -1
- package/dist/utils/agents.js +100 -29
- package/dist/utils/auditLog.d.ts +4 -3
- package/dist/utils/auditLog.js +92 -9
- package/dist/utils/checkpoints.js +11 -6
- package/dist/utils/codeReview.js +28 -23
- package/dist/utils/codeepCloud.d.ts +14 -2
- package/dist/utils/codeepCloud.js +56 -20
- package/dist/utils/customCommands.js +7 -2
- package/dist/utils/git.d.ts +262 -4
- package/dist/utils/git.js +1928 -61
- package/dist/utils/gitHookInstaller.d.ts +32 -1
- package/dist/utils/gitHookInstaller.js +76 -8
- package/dist/utils/gitignore.d.ts +8 -0
- package/dist/utils/gitignore.js +41 -10
- package/dist/utils/headlessReview.d.ts +11 -0
- package/dist/utils/headlessReview.js +33 -5
- package/dist/utils/history.d.ts +22 -6
- package/dist/utils/history.js +140 -26
- package/dist/utils/logger.js +6 -7
- package/dist/utils/mcpConfig.d.ts +24 -0
- package/dist/utils/mcpConfig.js +36 -5
- package/dist/utils/mentions.d.ts +28 -5
- package/dist/utils/mentions.js +253 -45
- package/dist/utils/personalities.js +16 -6
- package/dist/utils/planMode.d.ts +13 -7
- package/dist/utils/planMode.js +32 -12
- package/dist/utils/projectIntelligence.d.ts +2 -0
- package/dist/utils/projectIntelligence.js +27 -8
- package/dist/utils/projectPaths.d.ts +53 -0
- package/dist/utils/projectPaths.js +146 -0
- package/dist/utils/shell.d.ts +119 -0
- package/dist/utils/shell.js +417 -45
- package/dist/utils/skillBundles.js +17 -7
- package/dist/utils/skillBundlesCloud.js +20 -3
- package/dist/utils/skills.d.ts +24 -2
- package/dist/utils/skills.js +235 -43
- package/dist/utils/smartContext.js +97 -23
- package/dist/utils/telegramApproval.d.ts +10 -2
- package/dist/utils/telegramApproval.js +22 -4
- package/dist/utils/toolExecution.d.ts +50 -2
- package/dist/utils/toolExecution.js +418 -16
- package/dist/utils/toolParsing.d.ts +7 -1
- package/dist/utils/toolParsing.js +12 -3
- package/dist/utils/userProfile.js +58 -16
- package/dist/utils/verify.d.ts +25 -4
- package/dist/utils/verify.js +259 -74
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
* decoupled from global state. Import-heavy commands use dynamic imports
|
|
6
6
|
* to keep startup time low.
|
|
7
7
|
*/
|
|
8
|
-
import { config, getCurrentProvider, getModelsForCurrentProvider, PROTOCOLS, LANGUAGES, setProvider, setApiKey, clearApiKey, getApiKey, saveSession, startNewSession, loadSession, listSessionsWithInfo, deleteSession, renameSession, setProjectPermission, saveProfile, loadProfile, applyProfile, listProfiles, deleteProfile, initializeAsProject, isManuallyInitializedProject, } from '../config/index.js';
|
|
8
|
+
import { config, getCurrentProvider, getModelsForCurrentProvider, PROTOCOLS, LANGUAGES, setProvider, setApiKey, clearApiKey, getApiKey, saveSession, startNewSession, loadSession, listSessionsWithInfo, deleteSession, renameSession, sessionNameProblem, setProjectPermission, saveProfile, loadProfile, applyProfile, listProfiles, deleteProfile, initializeAsProject, isManuallyInitializedProject, } from '../config/index.js';
|
|
9
9
|
import { setTelegramToken, clearTelegramToken, hasTelegramToken } from '../utils/telegramCredentials.js';
|
|
10
10
|
import { getProjectContext } from '../utils/project.js';
|
|
11
11
|
import { getCurrentVersion } from '../utils/update.js';
|
|
12
12
|
import { getProviderList, getProvider, modelSupportsReasoningEffort, reasoningParamsFor, availableReasoningTiers, resolveReasoningTier, REASONING_TIERS } from '../config/providers.js';
|
|
13
13
|
import { setProjectContext } from '../api/index.js';
|
|
14
14
|
import { runSkill, runCommandChain } from './agentExecution.js';
|
|
15
|
-
import { loadProjectIntelligence, saveProjectIntelligence } from '../utils/projectIntelligence.js';
|
|
15
|
+
import { loadProjectIntelligence, saveProjectIntelligence, INTELLIGENCE_NOT_SAVED } from '../utils/projectIntelligence.js';
|
|
16
16
|
import { ollamaModelHint } from './ollamaHint.js';
|
|
17
|
-
import { buildSearchSnippets, parseKeepRecent, joinSessionName, parseTaskAddArgs, formatTaskList, formatProfileList, formatMemoryList, formatStatsReport, extractCodeBlocks, resolveBlockIndex, extractFileChanges, formatApplyDiffLine, parsePromptArgs, formatMcpReloadReport, formatMcpResourcesList, formatMcpResourceRead, formatMcpPromptsList, formatMcpPromptResult, formatMcpServerList, parseInsightsDays, formatCloudSessionLabel, formatMeSyncReport, formatMeLearnResult, formatMeInitResult, formatSkillsShow, formatSkillsBrowseEmpty, formatSkillsPublishResult } from './commands/helpers.js';
|
|
17
|
+
import { buildSearchSnippets, parseKeepRecent, joinSessionName, parseTaskAddArgs, formatTaskList, formatProfileList, formatMemoryList, formatStatsReport, extractCodeBlocks, resolveBlockIndex, extractFileChanges, formatApplyDiffLine, parsePromptArgs, formatMcpReloadReport, formatMcpResourcesList, formatMcpResourceRead, formatMcpPromptsList, formatMcpPromptResult, formatMcpServerList, parseInsightsDays, formatCloudSessionLabel, formatMeSyncReport, formatUndoAllReport, formatMeLearnResult, formatMeInitResult, formatSkillsShow, formatSkillsBrowseEmpty, formatSkillsPublishResult } from './commands/helpers.js';
|
|
18
18
|
import { resolveCommand } from './commands/registry.js';
|
|
19
19
|
import { telemetryCommand } from '../commands/core/telemetry.js';
|
|
20
20
|
import { keysyncCommand } from '../commands/core/keysync.js';
|
|
@@ -523,11 +523,13 @@ export async function handleCommand(command, args, ctx) {
|
|
|
523
523
|
ctx.app.notify('Not linked to codeep.dev. Run: codeep account');
|
|
524
524
|
break;
|
|
525
525
|
}
|
|
526
|
-
const {
|
|
526
|
+
const { pushUserProfileResult, pullUserProfileResult, describeSyncFailure } = await import('../utils/codeepCloud.js');
|
|
527
527
|
ctx.app.notify('Syncing your profile with codeep.dev…');
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
528
|
+
// Push first: the pull may create the local file, and pushing that
|
|
529
|
+
// back would be pointless.
|
|
530
|
+
const pushed = await pushUserProfileResult();
|
|
531
|
+
const pulled = await pullUserProfileResult();
|
|
532
|
+
ctx.app.addMessage({ role: 'system', content: formatMeSyncReport(pushed, pulled, describeSyncFailure) });
|
|
531
533
|
break;
|
|
532
534
|
}
|
|
533
535
|
if (sub === 'init') {
|
|
@@ -601,10 +603,17 @@ export async function handleCommand(command, args, ctx) {
|
|
|
601
603
|
return;
|
|
602
604
|
}
|
|
603
605
|
const prompt = composeExecutionPrompt(cur);
|
|
604
|
-
|
|
605
|
-
ctx.app.notify(`Executing plan for: ${cur.task.slice(0, 80)}${cur.task.length > 80 ? '…' : ''}`);
|
|
606
|
+
ctx.app.notify(`Executing plan for: ${cur.task.slice(0, 80)}${cur.task.length > 80 ? '…' : ''} — it stays pending until it has run successfully`);
|
|
606
607
|
const { runAgentTask } = await import('./agentExecution.js');
|
|
607
|
-
|
|
608
|
+
// The plan stays pending until it has run: a failed, stopped or
|
|
609
|
+
// declined run can be started again with /go.
|
|
610
|
+
runAgentTask(prompt, false, ctx, () => null, () => { }, {
|
|
611
|
+
onFinished: (outcome) => {
|
|
612
|
+
// A /plan issued meanwhile replaced it; that one has not run.
|
|
613
|
+
if (outcome === 'success' && getPendingPlan() === cur)
|
|
614
|
+
clearPendingPlan();
|
|
615
|
+
},
|
|
616
|
+
});
|
|
608
617
|
break;
|
|
609
618
|
}
|
|
610
619
|
case 'stop': {
|
|
@@ -759,7 +768,6 @@ export async function handleCommand(command, args, ctx) {
|
|
|
759
768
|
if (local && local.length > 0) {
|
|
760
769
|
ctx.app.setMessages(local);
|
|
761
770
|
ctx.setSessionId(localName);
|
|
762
|
-
config.set('currentSessionId', localName);
|
|
763
771
|
ctx.setSessionDisplayName?.(selected.sessionName ?? null);
|
|
764
772
|
ctx.app.notify('Local copy is newer than the cloud record — loaded the local session instead.');
|
|
765
773
|
return;
|
|
@@ -770,11 +778,11 @@ export async function handleCommand(command, args, ctx) {
|
|
|
770
778
|
saveSession(localName, history, ctx.projectPath);
|
|
771
779
|
ctx.app.setMessages(history);
|
|
772
780
|
// Keep ALL session-identity state in step, not just the renderer's
|
|
773
|
-
// copy: autosave + agent-mode sync read config.currentSessionId
|
|
774
|
-
// the next syncSession reads the display
|
|
775
|
-
// writes/renames the pulled history under
|
|
781
|
+
// copy: autosave + agent-mode sync read config.currentSessionId (which
|
|
782
|
+
// setSessionId updates), and the next syncSession reads the display
|
|
783
|
+
// name — leaving either stale writes/renames the pulled history under
|
|
784
|
+
// the PREVIOUS session.
|
|
776
785
|
ctx.setSessionId(localName);
|
|
777
|
-
config.set('currentSessionId', localName);
|
|
778
786
|
ctx.setSessionDisplayName?.(selected.sessionName ?? null);
|
|
779
787
|
ctx.app.notify(`Resumed from cloud: ${selected.sessionName || localName}`);
|
|
780
788
|
};
|
|
@@ -819,15 +827,21 @@ export async function handleCommand(command, args, ctx) {
|
|
|
819
827
|
}
|
|
820
828
|
case 'undo': {
|
|
821
829
|
import('../utils/agent.js').then(({ undoLastAction }) => {
|
|
822
|
-
|
|
830
|
+
// Only a run in this workspace; the agent records runs under this root.
|
|
831
|
+
const result = undoLastAction(ctx.projectContext?.root || ctx.projectPath);
|
|
823
832
|
ctx.app.notify(result.success ? `Undo: ${result.message}` : `Cannot undo: ${result.message}`);
|
|
824
833
|
});
|
|
825
834
|
break;
|
|
826
835
|
}
|
|
827
836
|
case 'undo-all': {
|
|
828
837
|
import('../utils/agent.js').then(({ undoAllActions }) => {
|
|
829
|
-
const result = undoAllActions();
|
|
830
|
-
|
|
838
|
+
const result = undoAllActions(ctx.projectContext?.root || ctx.projectPath);
|
|
839
|
+
if (!result.success && result.results.length <= 1) {
|
|
840
|
+
ctx.app.notify(result.results[0] ?? 'Nothing to undo');
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
ctx.app.addMessage({ role: 'system', content: formatUndoAllReport(result) });
|
|
844
|
+
}
|
|
831
845
|
});
|
|
832
846
|
break;
|
|
833
847
|
}
|
|
@@ -851,9 +865,12 @@ export async function handleCommand(command, args, ctx) {
|
|
|
851
865
|
return;
|
|
852
866
|
}
|
|
853
867
|
ctx.app.notify('Scanning project...');
|
|
854
|
-
import('../utils/projectIntelligence.js').then(({ scanProject, saveProjectIntelligence, generateContextFromIntelligence }) => {
|
|
868
|
+
import('../utils/projectIntelligence.js').then(({ scanProject, saveProjectIntelligence, generateContextFromIntelligence, INTELLIGENCE_NOT_SAVED }) => {
|
|
855
869
|
scanProject(ctx.projectContext.root).then(intelligence => {
|
|
856
|
-
saveProjectIntelligence(ctx.projectContext.root, intelligence)
|
|
870
|
+
if (!saveProjectIntelligence(ctx.projectContext.root, intelligence)) {
|
|
871
|
+
ctx.app.notify(INTELLIGENCE_NOT_SAVED);
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
857
874
|
const context = generateContextFromIntelligence(intelligence);
|
|
858
875
|
ctx.app.addMessage({ role: 'assistant', content: `# Project Scan Complete\n\n${context}` });
|
|
859
876
|
ctx.app.notify(`Scanned: ${intelligence.structure.totalFiles} files`);
|
|
@@ -935,6 +952,11 @@ Format: use headers per category, only include categories where you found issues
|
|
|
935
952
|
return;
|
|
936
953
|
}
|
|
937
954
|
const newName = joinSessionName(args);
|
|
955
|
+
const nameProblem = sessionNameProblem(newName);
|
|
956
|
+
if (nameProblem) {
|
|
957
|
+
ctx.app.notify(nameProblem);
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
938
960
|
const messages = ctx.app.getMessages();
|
|
939
961
|
if (messages.length === 0) {
|
|
940
962
|
ctx.app.notify('No messages to save. Start a conversation first.');
|
|
@@ -959,7 +981,14 @@ Format: use headers per category, only include categories where you found issues
|
|
|
959
981
|
}).catch(() => { });
|
|
960
982
|
}
|
|
961
983
|
else {
|
|
962
|
-
|
|
984
|
+
// renameSession refuses to replace another saved conversation. Looked
|
|
985
|
+
// up only now: a case-only rename finds this same file and succeeds.
|
|
986
|
+
const { existsSync } = await import('fs');
|
|
987
|
+
const { join } = await import('path');
|
|
988
|
+
const { getSessionsDir } = await import('../config/index.js');
|
|
989
|
+
ctx.app.notify(existsSync(join(getSessionsDir(ctx.projectPath), `${newName}.json`))
|
|
990
|
+
? `A session named "${newName}" already exists — pick another name`
|
|
991
|
+
: 'Failed to rename session');
|
|
963
992
|
}
|
|
964
993
|
break;
|
|
965
994
|
}
|
|
@@ -1186,9 +1215,25 @@ Format: use headers per category, only include categories where you found issues
|
|
|
1186
1215
|
ctx.app.notify('Usage: /git-commit <message>');
|
|
1187
1216
|
return;
|
|
1188
1217
|
}
|
|
1189
|
-
// Use execFile to avoid shell injection — pass commit message as a direct argument
|
|
1190
|
-
|
|
1191
|
-
|
|
1218
|
+
// Use execFile to avoid shell injection — pass commit message as a direct argument.
|
|
1219
|
+
// hardenedGitEnv() on top of that: the repo's own .git/config can name programs
|
|
1220
|
+
// git would run for this commit (core.fsmonitor, core.hooksPath, …).
|
|
1221
|
+
Promise.all([import('child_process'), import('../utils/git.js')]).then(([{ execFile }, { hardenedGitEnv }]) => {
|
|
1222
|
+
// The SAME cwd the commit runs in. Called with no argument, the scan
|
|
1223
|
+
// read process.cwd() instead — so a project opened anywhere other
|
|
1224
|
+
// than the directory Codeep was launched from was "hardened" against
|
|
1225
|
+
// a different repository's config entirely.
|
|
1226
|
+
let env;
|
|
1227
|
+
try {
|
|
1228
|
+
env = hardenedGitEnv({ cwd: ctx.projectPath });
|
|
1229
|
+
}
|
|
1230
|
+
catch (error) {
|
|
1231
|
+
// It refuses rather than hand git a half-scanned environment, and
|
|
1232
|
+
// its message names the key and what to do about it.
|
|
1233
|
+
ctx.app.notify(error instanceof Error ? error.message : 'Commit failed');
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
execFile('git', ['commit', '-m', message], { cwd: ctx.projectPath, encoding: 'utf-8', env }, (err) => {
|
|
1192
1237
|
if (err) {
|
|
1193
1238
|
ctx.app.notify(`Commit failed: ${err.message}`);
|
|
1194
1239
|
}
|
|
@@ -1510,7 +1555,8 @@ Format: use headers per category, only include categories where you found issues
|
|
|
1510
1555
|
}
|
|
1511
1556
|
case 'changes': {
|
|
1512
1557
|
import('../utils/agent.js').then(({ getCurrentSessionActions }) => {
|
|
1513
|
-
|
|
1558
|
+
// Changes of the run /undo acts on, in this project.
|
|
1559
|
+
const actions = getCurrentSessionActions(ctx.projectContext?.root || ctx.projectPath);
|
|
1514
1560
|
if (actions.length === 0) {
|
|
1515
1561
|
ctx.app.notify('No changes in current session');
|
|
1516
1562
|
return;
|
|
@@ -1572,18 +1618,25 @@ Format: use headers per category, only include categories where you found issues
|
|
|
1572
1618
|
}
|
|
1573
1619
|
const name = args.join(' ').trim() || undefined;
|
|
1574
1620
|
const provider = getCurrentProvider();
|
|
1575
|
-
const filesTouched = Array.from(new Set(getCurrentSessionActions()
|
|
1576
|
-
.filter(a => a.target && (a.type === 'write' || a.type === 'edit' || a.type === 'delete' || a.type === 'mkdir'))
|
|
1621
|
+
const filesTouched = Array.from(new Set(getCurrentSessionActions(ctx.projectContext?.root || ctx.projectPath)
|
|
1622
|
+
.filter(a => a.target && a.result !== 'undone' && (a.type === 'write' || a.type === 'edit' || a.type === 'delete' || a.type === 'mkdir'))
|
|
1577
1623
|
.map(a => a.target)));
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1624
|
+
let cp;
|
|
1625
|
+
try {
|
|
1626
|
+
cp = createCheckpoint({
|
|
1627
|
+
workspaceRoot: ctx.projectPath,
|
|
1628
|
+
sessionId: ctx.sessionId,
|
|
1629
|
+
provider: provider.id,
|
|
1630
|
+
model: config.get('model'),
|
|
1631
|
+
messages: ctx.app.getMessages(),
|
|
1632
|
+
filesTouched,
|
|
1633
|
+
name,
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
catch (err) {
|
|
1637
|
+
ctx.app.notify(`Could not save the checkpoint: ${err.message}`);
|
|
1638
|
+
break;
|
|
1639
|
+
}
|
|
1587
1640
|
ctx.app.addMessage({
|
|
1588
1641
|
role: 'system',
|
|
1589
1642
|
content: `# Checkpoint created\n\n\`${cp.id}\`${cp.name ? ` — **${cp.name}**` : ''}\n\nCaptured ${cp.messages.length} message${cp.messages.length === 1 ? '' : 's'}, ${cp.filesTouched.length} file${cp.filesTouched.length === 1 ? '' : 's'} touched${cp.gitHead ? `, git \`${cp.gitHead}\`` : ''}.\n\nUse \`/rewind ${cp.id}\` to restore.`,
|
|
@@ -1731,7 +1784,7 @@ Format: use headers per category, only include categories where you found issues
|
|
|
1731
1784
|
}
|
|
1732
1785
|
if (args[0] === 'rule' && args.length > 1) {
|
|
1733
1786
|
import('../utils/learning.js').then(({ addCustomRule }) => {
|
|
1734
|
-
addCustomRule(
|
|
1787
|
+
addCustomRule(args.slice(1).join(' '), ctx.projectPath);
|
|
1735
1788
|
ctx.app.notify('Custom rule added');
|
|
1736
1789
|
}).catch(() => ctx.app.notify('Learning module not available'));
|
|
1737
1790
|
return;
|
|
@@ -1962,7 +2015,7 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
1962
2015
|
break;
|
|
1963
2016
|
}
|
|
1964
2017
|
case 'skill': {
|
|
1965
|
-
import('../utils/skills.js').then(({ findSkill, formatSkillHelp, createSkillTemplate, saveCustomSkill, deleteCustomSkill, }) => {
|
|
2018
|
+
import('../utils/skills.js').then(({ findSkill, formatSkillHelp, createSkillTemplate, saveCustomSkill, deleteCustomSkill, customSkillFileExists, getSkippedCustomSkills, formatSkippedCustomSkills, }) => {
|
|
1966
2019
|
const subCommand = args[0]?.toLowerCase();
|
|
1967
2020
|
const skillName = args[1];
|
|
1968
2021
|
if (!subCommand) {
|
|
@@ -1992,6 +2045,17 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
1992
2045
|
ctx.app.notify(`Skill "${skillName}" already exists`);
|
|
1993
2046
|
return;
|
|
1994
2047
|
}
|
|
2048
|
+
// A file that does not load is not found above, and is still the
|
|
2049
|
+
// user's work.
|
|
2050
|
+
if (customSkillFileExists(skillName)) {
|
|
2051
|
+
const skipped = getSkippedCustomSkills().filter(f => f.file === `${skillName}.json`);
|
|
2052
|
+
ctx.app.addMessage({
|
|
2053
|
+
role: 'system',
|
|
2054
|
+
content: `~/.codeep/skills/${skillName}.json already exists — not replaced.`
|
|
2055
|
+
+ (skipped.length ? `\n\n${formatSkippedCustomSkills(skipped)}` : ''),
|
|
2056
|
+
});
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
1995
2059
|
const template = createSkillTemplate(skillName);
|
|
1996
2060
|
saveCustomSkill(template);
|
|
1997
2061
|
ctx.app.addMessage({
|
|
@@ -2300,10 +2364,18 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
2300
2364
|
// Sync the hand-written user profile (~/.codeep/profile.md). Push sends
|
|
2301
2365
|
// the local file; pull is additive (writes only if no local profile).
|
|
2302
2366
|
if (subCmd === 'all' || subCmd === 'profile') {
|
|
2303
|
-
const {
|
|
2304
|
-
|
|
2367
|
+
const { pushUserProfileResult, pullUserProfileResult, describeSyncFailure } = await import('../utils/codeepCloud.js');
|
|
2368
|
+
// Without a local profile there is nothing to push, which is not a
|
|
2369
|
+
// failure. Pushed before the pull, which may create the file.
|
|
2370
|
+
const pushedProfile = await pushUserProfileResult();
|
|
2371
|
+
if (!pushedProfile.ok)
|
|
2372
|
+
results.push(`✗ Failed to push your profile (about you) — ${describeSyncFailure(pushedProfile.reason)}`);
|
|
2373
|
+
else if (pushedProfile.count > 0)
|
|
2305
2374
|
results.push('✓ Your profile (about you) pushed');
|
|
2306
|
-
|
|
2375
|
+
const pulledProfile = await pullUserProfileResult();
|
|
2376
|
+
if (!pulledProfile.ok)
|
|
2377
|
+
results.push(`✗ Failed to pull your profile (about you) — ${describeSyncFailure(pulledProfile.reason)}`);
|
|
2378
|
+
else if (pulledProfile.count > 0)
|
|
2307
2379
|
results.push('✓ Your profile pulled to this machine');
|
|
2308
2380
|
}
|
|
2309
2381
|
ctx.app.addMessage({
|
|
@@ -2358,7 +2430,10 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
2358
2430
|
break;
|
|
2359
2431
|
}
|
|
2360
2432
|
const removed = intelligence.notes.splice(idx - 1, 1)[0];
|
|
2361
|
-
saveProjectIntelligence(projectRoot, intelligence)
|
|
2433
|
+
if (!saveProjectIntelligence(projectRoot, intelligence)) {
|
|
2434
|
+
ctx.app.notify(INTELLIGENCE_NOT_SAVED);
|
|
2435
|
+
break;
|
|
2436
|
+
}
|
|
2362
2437
|
import('../utils/codeepCloud.js').then(({ syncMemoryNotes }) => syncMemoryNotes(projectCtx?.name || '', intelligence.notes)).catch(() => { });
|
|
2363
2438
|
ctx.app.notify(`Removed: "${removed}"`);
|
|
2364
2439
|
break;
|
|
@@ -2366,7 +2441,10 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
2366
2441
|
if (sub === 'clear') {
|
|
2367
2442
|
const count = intelligence.notes.length;
|
|
2368
2443
|
intelligence.notes = [];
|
|
2369
|
-
saveProjectIntelligence(projectRoot, intelligence)
|
|
2444
|
+
if (!saveProjectIntelligence(projectRoot, intelligence)) {
|
|
2445
|
+
ctx.app.notify(INTELLIGENCE_NOT_SAVED);
|
|
2446
|
+
break;
|
|
2447
|
+
}
|
|
2370
2448
|
import('../utils/codeepCloud.js').then(({ syncMemoryNotes }) => syncMemoryNotes(projectCtx?.name || '', [])).catch(() => { });
|
|
2371
2449
|
ctx.app.notify(`Cleared ${count} memory note${count !== 1 ? 's' : ''}.`);
|
|
2372
2450
|
break;
|
|
@@ -2378,7 +2456,10 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
2378
2456
|
break;
|
|
2379
2457
|
}
|
|
2380
2458
|
intelligence.notes.push(note);
|
|
2381
|
-
saveProjectIntelligence(projectRoot, intelligence)
|
|
2459
|
+
if (!saveProjectIntelligence(projectRoot, intelligence)) {
|
|
2460
|
+
ctx.app.notify(INTELLIGENCE_NOT_SAVED);
|
|
2461
|
+
break;
|
|
2462
|
+
}
|
|
2382
2463
|
import('../utils/codeepCloud.js').then(({ syncMemoryNotes }) => syncMemoryNotes(projectCtx?.name || '', intelligence.notes)).catch(() => { });
|
|
2383
2464
|
ctx.app.notify(`Memory saved (${intelligence.notes.length} total): "${note}"`);
|
|
2384
2465
|
break;
|
|
@@ -2428,8 +2509,32 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
2428
2509
|
}
|
|
2429
2510
|
return true;
|
|
2430
2511
|
};
|
|
2431
|
-
const { addProjectMcpServer, removeProjectMcpServer,
|
|
2512
|
+
const { addProjectMcpServer, removeProjectMcpServer, loadMcpServerConfigSplit, selectSessionMcpServers, isWorkspaceMcpTrusted, trustWorkspaceMcp, untrustWorkspaceMcp } = await import('../utils/mcpConfig.js');
|
|
2432
2513
|
const { registerSessionServers } = await import('../utils/mcpRegistry.js');
|
|
2514
|
+
// The servers this session may run, chosen by the same rule startup and
|
|
2515
|
+
// ACP use: workspace entries arrive with the repo and start only once the
|
|
2516
|
+
// workspace is trusted. `addedHere` is an entry the user just typed into
|
|
2517
|
+
// /mcp add or /mcp install; starting that one needs no further consent.
|
|
2518
|
+
// registerSessionServers replaces the whole session, so everything that
|
|
2519
|
+
// should keep running has to be in the list.
|
|
2520
|
+
const serversToStart = (addedHere) => selectSessionMcpServers(projectPath, { userAdded: addedHere ? [addedHere] : [] });
|
|
2521
|
+
const untrustedNote = ({ servers, skipped }) => {
|
|
2522
|
+
if (skipped.length === 0)
|
|
2523
|
+
return '';
|
|
2524
|
+
const n = skipped.length;
|
|
2525
|
+
let note = `\n\n${n} workspace MCP server${n === 1 ? '' : 's'} not started — this workspace isn't trusted. Run \`/mcp trust\` to start ${n === 1 ? 'it' : 'them'}.`;
|
|
2526
|
+
// A repo entry that shares a name with one of the user's own servers
|
|
2527
|
+
// can keep that one from starting too; say so rather than let it look
|
|
2528
|
+
// like the user's server just vanished.
|
|
2529
|
+
const running = new Set(servers.map(s => s.name));
|
|
2530
|
+
const globalNames = new Set(loadMcpServerConfigSplit(undefined).global.map(s => s.name));
|
|
2531
|
+
const shadowed = skipped.map(s => s.name).filter(name => globalNames.has(name) && !running.has(name));
|
|
2532
|
+
if (shadowed.length > 0) {
|
|
2533
|
+
const list = shadowed.map(name => `\`${name}\``).join(', ');
|
|
2534
|
+
note += `\n\nYour own server${shadowed.length === 1 ? '' : 's'} ${list} ${shadowed.length === 1 ? 'is' : 'are'} not running either: this workspace defines a server with the same name, which takes its place once trusted.`;
|
|
2535
|
+
}
|
|
2536
|
+
return note;
|
|
2537
|
+
};
|
|
2433
2538
|
if (sub === 'trust') {
|
|
2434
2539
|
if (!requireProject())
|
|
2435
2540
|
break;
|
|
@@ -2443,8 +2548,8 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
2443
2548
|
ctx.app.notify('Workspace trusted — no workspace MCP servers defined yet.');
|
|
2444
2549
|
break;
|
|
2445
2550
|
}
|
|
2446
|
-
ctx.app.notify(`Workspace trusted.
|
|
2447
|
-
const { registered, errors } = await registerSessionServers(TUI_SESSION,
|
|
2551
|
+
ctx.app.notify(`Workspace trusted. Restarting MCP servers with ${workspace.length} from this workspace…`);
|
|
2552
|
+
const { registered, errors } = await registerSessionServers(TUI_SESSION, serversToStart().servers, { workspaceRoot: projectPath });
|
|
2448
2553
|
if (registered.length > 0)
|
|
2449
2554
|
ctx.app.notify(`MCP: ${registered.length} tool(s) ready. Type /mcp.`);
|
|
2450
2555
|
for (const e of errors)
|
|
@@ -2455,7 +2560,7 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
2455
2560
|
if (!requireProject())
|
|
2456
2561
|
break;
|
|
2457
2562
|
untrustWorkspaceMcp(projectPath);
|
|
2458
|
-
ctx.app.notify('Workspace MCP trust revoked — workspace servers won\'t
|
|
2563
|
+
ctx.app.notify('Workspace MCP trust revoked — workspace servers won\'t start again. Those running now stop at the next /mcp reload, add or remove, or when you exit.');
|
|
2459
2564
|
break;
|
|
2460
2565
|
}
|
|
2461
2566
|
if (sub === 'add') {
|
|
@@ -2470,15 +2575,15 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
2470
2575
|
const extraArgs = args.slice(3);
|
|
2471
2576
|
addProjectMcpServer(projectPath, { name, command, args: extraArgs });
|
|
2472
2577
|
ctx.app.notify(`Saved MCP server ${name} to .codeep/mcp_servers.json. Spawning…`);
|
|
2473
|
-
const
|
|
2474
|
-
const { registered, errors } = await registerSessionServers(TUI_SESSION,
|
|
2578
|
+
const selection = serversToStart(name);
|
|
2579
|
+
const { registered, errors } = await registerSessionServers(TUI_SESSION, selection.servers, { workspaceRoot: projectPath });
|
|
2475
2580
|
const ok = registered.filter(t => t.serverName === name);
|
|
2476
2581
|
const failed = errors.find(e => e.server === name);
|
|
2477
2582
|
ctx.app.addMessage({
|
|
2478
2583
|
role: 'system',
|
|
2479
|
-
content: failed
|
|
2584
|
+
content: (failed
|
|
2480
2585
|
? `Saved \`${name}\` but spawn failed: \`${failed.error}\``
|
|
2481
|
-
: `Added \`${name}\` (${ok.length} tool${ok.length === 1 ? '' : 's'} available)
|
|
2586
|
+
: `Added \`${name}\` (${ok.length} tool${ok.length === 1 ? '' : 's'} available).`) + untrustedNote(selection),
|
|
2482
2587
|
});
|
|
2483
2588
|
break;
|
|
2484
2589
|
}
|
|
@@ -2495,9 +2600,9 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
2495
2600
|
ctx.app.addMessage({ role: 'system', content: `No project-scoped MCP server named \`${name}\`.` });
|
|
2496
2601
|
break;
|
|
2497
2602
|
}
|
|
2498
|
-
const
|
|
2499
|
-
await registerSessionServers(TUI_SESSION,
|
|
2500
|
-
ctx.app.addMessage({ role: 'system', content: `Removed \`${name}\` from project config and stopped its process.` });
|
|
2603
|
+
const selection = serversToStart();
|
|
2604
|
+
await registerSessionServers(TUI_SESSION, selection.servers, { workspaceRoot: projectPath });
|
|
2605
|
+
ctx.app.addMessage({ role: 'system', content: `Removed \`${name}\` from project config and stopped its process.` + untrustedNote(selection) });
|
|
2501
2606
|
break;
|
|
2502
2607
|
}
|
|
2503
2608
|
if (sub === 'browse') {
|
|
@@ -2542,8 +2647,8 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
2542
2647
|
headers: entry.server.headers,
|
|
2543
2648
|
});
|
|
2544
2649
|
ctx.app.notify(`Saved ${entry.id} to project config. Spawning…`);
|
|
2545
|
-
const
|
|
2546
|
-
const { registered, errors } = await registerSessionServers(TUI_SESSION,
|
|
2650
|
+
const selection = serversToStart(entry.id);
|
|
2651
|
+
const { registered, errors } = await registerSessionServers(TUI_SESSION, selection.servers, { workspaceRoot: projectPath });
|
|
2547
2652
|
const failed = errors.find(e => e.server === entry.id);
|
|
2548
2653
|
const lines = [];
|
|
2549
2654
|
if (failed) {
|
|
@@ -2560,16 +2665,16 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
2560
2665
|
lines.push(`- \`${e.name}\`${req} — ${e.description}`);
|
|
2561
2666
|
}
|
|
2562
2667
|
}
|
|
2563
|
-
ctx.app.addMessage({ role: 'system', content: lines.join('\n') });
|
|
2668
|
+
ctx.app.addMessage({ role: 'system', content: lines.join('\n') + untrustedNote(selection) });
|
|
2564
2669
|
break;
|
|
2565
2670
|
}
|
|
2566
2671
|
if (sub === 'reload') {
|
|
2567
2672
|
if (!requireProject())
|
|
2568
2673
|
break;
|
|
2569
2674
|
ctx.app.notify('Reloading MCP server config…');
|
|
2570
|
-
const
|
|
2571
|
-
const { registered, errors } = await registerSessionServers(TUI_SESSION,
|
|
2572
|
-
ctx.app.addMessage({ role: 'system', content: formatMcpReloadReport(registered.length,
|
|
2675
|
+
const selection = serversToStart();
|
|
2676
|
+
const { registered, errors } = await registerSessionServers(TUI_SESSION, selection.servers, { workspaceRoot: projectPath });
|
|
2677
|
+
ctx.app.addMessage({ role: 'system', content: formatMcpReloadReport(registered.length, selection.servers.length, errors) + untrustedNote(selection) });
|
|
2573
2678
|
break;
|
|
2574
2679
|
}
|
|
2575
2680
|
if (sub === 'resources') {
|
|
@@ -2641,9 +2746,20 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
2641
2746
|
break;
|
|
2642
2747
|
}
|
|
2643
2748
|
// 2. Fall through to skill registry.
|
|
2644
|
-
runSkill(command, args, ctx).then(handled => {
|
|
2645
|
-
if (
|
|
2646
|
-
|
|
2749
|
+
runSkill(command, args, ctx).then(async (handled) => {
|
|
2750
|
+
if (handled)
|
|
2751
|
+
return;
|
|
2752
|
+
ctx.app.notify(`Unknown command: /${command}`);
|
|
2753
|
+
// The command may be a custom skill whose file does not load. Scanned
|
|
2754
|
+
// afresh here rather than trusting whatever the lookup left behind.
|
|
2755
|
+
const { loadCustomSkills, getSkippedCustomSkills, formatSkippedCustomSkills } = await import('../utils/skills.js');
|
|
2756
|
+
loadCustomSkills();
|
|
2757
|
+
const skipped = getSkippedCustomSkills().filter(f => f.file.toLowerCase() === `${command.toLowerCase()}.json`);
|
|
2758
|
+
if (skipped.length > 0) {
|
|
2759
|
+
ctx.app.addMessage({ role: 'system', content: `Unknown command: /${command}\n\n${formatSkippedCustomSkills(skipped)}` });
|
|
2760
|
+
}
|
|
2761
|
+
}).catch(err => {
|
|
2762
|
+
ctx.app.notify(`Skill error: ${err.message}`);
|
|
2647
2763
|
});
|
|
2648
2764
|
}
|
|
2649
2765
|
}
|
package/dist/renderer/main.d.ts
CHANGED
|
@@ -5,5 +5,46 @@
|
|
|
5
5
|
* This file contains only startup/init logic. Command dispatch lives in
|
|
6
6
|
* commands.ts and agent execution in agentExecution.ts.
|
|
7
7
|
*/
|
|
8
|
+
import { App } from './App';
|
|
9
|
+
import { type GitStatus } from '../utils/git';
|
|
10
|
+
import type { McpServer } from '../acp/protocol';
|
|
11
|
+
/**
|
|
12
|
+
* What to tell the user when git would not run in this project, or null when
|
|
13
|
+
* there is nothing to tell them.
|
|
14
|
+
*
|
|
15
|
+
* Without it the only symptom is the branch quietly missing from the header,
|
|
16
|
+
* which reads as "not a repository" — so a repository whose own `.git/config`
|
|
17
|
+
* names a program git would run looks like an ordinary folder, and the one
|
|
18
|
+
* thing the user has to do (remove that key) is never said anywhere. The
|
|
19
|
+
* refusal names the key and the `git config --unset` that clears it, so it is
|
|
20
|
+
* passed through verbatim rather than summarised into "git failed".
|
|
21
|
+
*
|
|
22
|
+
* Read off `refusal` and NOT off `error`, which is the field an earlier cut
|
|
23
|
+
* of this used. `error` is every way git can fail in a repository, and the
|
|
24
|
+
* commonest of them is a brand-new `git init` with no commit yet: `git
|
|
25
|
+
* rev-parse --abbrev-ref HEAD` answers `fatal: ambiguous argument 'HEAD'`
|
|
26
|
+
* there (git 2.54), so the first thing a user does in a new project met a
|
|
27
|
+
* warning made of git internals telling them to remove a config key that does
|
|
28
|
+
* not exist. `refusal` is filled on the hardening path and nowhere else, so
|
|
29
|
+
* an ordinary git failure stays as silent as it was before this notice
|
|
30
|
+
* existed — the branch is simply missing from the header, which is what it
|
|
31
|
+
* has always done.
|
|
32
|
+
*/
|
|
33
|
+
export declare function gitRefusalNotice(status: GitStatus): string | null;
|
|
8
34
|
/** Derive a short display name from a user message (first ~5 words, max 48 chars). */
|
|
9
35
|
export declare function deriveSessionName(message: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Start the MCP servers this terminal may run, under the fixed session id
|
|
38
|
+
* `codeep-tui`: the global servers (~/.codeep, the user's own) plus the
|
|
39
|
+
* workspace ones once the workspace is trusted. Registering replaces the
|
|
40
|
+
* session's whole set, so they go in one call; starting the global list and
|
|
41
|
+
* then the workspace list stopped the global servers. Returns the workspace
|
|
42
|
+
* servers left out because the workspace is not trusted yet.
|
|
43
|
+
*/
|
|
44
|
+
export declare function startTuiMcpServers(root: string, ui: Pick<App, 'notify' | 'notifyWarn'>): Promise<McpServer[]>;
|
|
45
|
+
/** Start the servers of a workspace the user has just trusted. A failure is
|
|
46
|
+
* shown, not swallowed: the user asked for these servers. */
|
|
47
|
+
export declare function startTrustedWorkspaceMcp(root: string, ui: Pick<App, 'notify' | 'notifyWarn'>): Promise<void>;
|
|
48
|
+
/** The CLI entry point. Exported so tests can drive a command without the
|
|
49
|
+
* module starting the app on import. */
|
|
50
|
+
export declare function main(): Promise<void>;
|